Merge "Ensure lockdown fan runs at 23KHz."
diff --git a/Makefile b/Makefile
index 921debb..1852f66 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@
 BUILD_SSDP?=y
 BUILD_DNSSD?=y
 BUILD_LOGUPLOAD?=   # default off: needs libgtest
-BUILD_IBEACON?=     # default off: needs bluetooth.h
+BUILD_BLUETOOTH?=     # default off: needs bluetooth.h
 BUILD_WAVEGUIDE?=y
 BUILD_DVBUTILS?=y
 BUILD_SYSMGR?=y
@@ -17,7 +17,7 @@
 BUILD_JSONPOLL?=n
 BUILD_PRESTERASTATS?=n
 export BUILD_HNVRAM BUILD_SSDP BUILD_DNSSD BUILD_LOGUPLOAD \
-	BUILD_IBEACON BUILD_WAVEGUIDE BUILD_DVBUTILS BUILD_SYSMGR \
+	BUILD_BLUETOOTH BUILD_WAVEGUIDE BUILD_DVBUTILS BUILD_SYSMGR \
 	BUILD_STATUTILS BUILD_CRYPTDEV BUILD_SIGNING BUILD_JSONPOLL \
 	BUILD_PRESTERASTATS BUILD_CACHE_WARMING
 
@@ -81,6 +81,10 @@
 DIRS+=diags
 endif
 
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gflt110)
+DIRS+=diags
+endif
+
 ifeq ($(BUILD_CONMAN),y)
 DIRS+=conman
 endif
@@ -93,6 +97,10 @@
 DIRS+=ledpattern
 endif
 
+ifeq ($(BUILD_BLUETOOTH),y)
+DIRS+=rcu_audio
+endif
+
 PREFIX=/usr
 BINDIR=$(DESTDIR)$(PREFIX)/bin
 LIBDIR=$(DESTDIR)$(PREFIX)/lib
diff --git a/cmds/Makefile b/cmds/Makefile
index 24bc8a1..6c19ab8 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -76,7 +76,7 @@
 SCRIPT_TARGETS += castcheck
 endif
 
-ifeq ($(BUILD_IBEACON),y)
+ifeq ($(BUILD_BLUETOOTH),y)
 ARCH_TARGETS += ibeacon eddystone
 endif
 
diff --git a/cmds/device_stats.proto b/cmds/device_stats.proto
index 4f47b5e..fd05a68 100644
--- a/cmds/device_stats.proto
+++ b/cmds/device_stats.proto
@@ -20,5 +20,11 @@
 
   // Public ipv6 address of onu
   optional string ipv6 = 6;
+
+  // Which channel fiber jack is supposed use
+  optional int64 requested_channel = 7;
+
+  // Which channel fiber jack is currently using
+  optional int64 current_channel = 8;
 };
 
diff --git a/cmds/statcatcher.cc b/cmds/statcatcher.cc
index bfd7033..a9ac9f0 100644
--- a/cmds/statcatcher.cc
+++ b/cmds/statcatcher.cc
@@ -143,7 +143,9 @@
 "onu_acs_contact_time": "%lld",
 "onu_uptime": %lld,
 "onu_serial": "%s",
-"onu_ipv6": "%s"
+"onu_ipv6": "%s",
+"onu_requested_channel": %lld,
+"onu_current_channel": %lld
 })";
     FILE *f = fopen(tmp_file.c_str(), "w");
     if (!f) {
@@ -157,7 +159,9 @@
             status.acs_contact_time(),
             status.uptime(),
             status.serial().c_str(),
-            status.ipv6().c_str());
+            status.ipv6().c_str(),
+            status.requested_channel(),
+            status.current_channel());
     fclose(f);
 
     if (rename(tmp_file.c_str(), stat_file.c_str()) != 0) {
diff --git a/cmds/statpitcher.cc b/cmds/statpitcher.cc
index 990666d..0a19443 100644
--- a/cmds/statpitcher.cc
+++ b/cmds/statpitcher.cc
@@ -158,6 +158,27 @@
   return result;
 }
 
+int64_t RequestedONUChannel() {
+  int64_t ret = -1;
+  std::string req_channel = "";
+  ReadFile("/sys/devices/platform/gpon/misc/laserChannel", &req_channel);
+  std::istringstream(req_channel) >> ret;
+  return ret;
+}
+
+int64_t CurrentONUChannel() {
+  int64_t ret = -1;
+  // Read current channel from I2C byte
+  std::shared_ptr<FILE> pipe(popen("i2cget -y 0 0x51 0x91", "r"), pclose);
+  if (pipe) {
+    char buffer[128];
+    if (fgets(buffer, 128, pipe.get()) != NULL) {
+      std::istringstream(buffer) >> ret;
+    }
+  }
+  return ret;
+}
+
 void MakePacket(std::vector<uint8_t>* pkt) {
   devstatus::Status status;
 
@@ -169,6 +190,8 @@
   status.set_uptime(Uptime());
   status.set_serial(serial_number);
   status.set_ipv6(IPAddress());
+  status.set_requested_channel(RequestedONUChannel());
+  status.set_current_channel(CurrentONUChannel());
 
   pkt->resize(status.ByteSize());
   status.SerializeToArray(&(*pkt)[0], status.ByteSize());
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index 040e812..374d495 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -79,7 +79,7 @@
         '-s': 'ssid',
         '--ssid': 'ssid',
         '-S': 'interface_suffix',
-        '--interface_suffix': 'interface_suffix',
+        '--interface-suffix': 'interface_suffix',
     }
     attr = None
     for line in self.command:
@@ -91,7 +91,7 @@
       attr = binwifi_option_attrs.get(line, None)
 
       if line.startswith('WIFI_PSK='):
-        self.passphrase = line.split('WIFI_PSK=')[-1]
+        self.passphrase = line[len('WIFI_PSK='):]
 
     if self.ssid is None:
       raise ValueError('Command file does not specify SSID')
@@ -170,6 +170,7 @@
       subprocess.check_output(command, stderr=subprocess.STDOUT, env=env)
     except subprocess.CalledProcessError as e:
       logging.error('Failed to start wifi client: %s', e.output)
+      self._status.wlan_failed = True
       return False
 
     return True
@@ -212,6 +213,7 @@
   COMMAND_FILE_REGEXP = WLAN_FILE_REGEXP_FMT % COMMAND_FILE_PREFIX
   ACCESS_POINT_FILE_REGEXP = WLAN_FILE_REGEXP_FMT % ACCESS_POINT_FILE_PREFIX
   GATEWAY_FILE_PREFIX = 'gateway.'
+  SUBNET_FILE_PREFIX = 'subnet.'
   MOCA_NODE_FILE_PREFIX = 'node'
   WIFI_SETCLIENT = ['wifi', 'setclient']
   IFUP = ['ifup']
@@ -295,6 +297,7 @@
               self._wpa_control_interface)
 
     for path, prefix in ((self._tmp_dir, self.GATEWAY_FILE_PREFIX),
+                         (self._tmp_dir, self.SUBNET_FILE_PREFIX),
                          (self._interface_status_dir, ''),
                          (self._moca_tmp_dir, self.MOCA_NODE_FILE_PREFIX),
                          (self._config_dir, self.COMMAND_FILE_PREFIX)):
@@ -583,7 +586,7 @@
     """Update the contents of /tmp/hosts."""
     lowest_metric_interface = None
     for ifc in [self.bridge] + self.wifi:
-      route = ifc.current_route()
+      route = ifc.current_routes().get('default', None)
       if route:
         metric = route.get('metric', 0)
         # Skip temporary connection_check routes.
@@ -598,6 +601,8 @@
     if lowest_metric_interface:
       ip = lowest_metric_interface[1].get_ip_address()
       ip_line = '%s %s\n' % (ip, HOSTNAME) if ip else ''
+      logging.info('Lowest metric default route is on dev %r',
+                   lowest_metric_interface[1].name)
 
     new_tmp_hosts = '%s127.0.0.1 localhost' % ip_line
 
@@ -689,6 +694,14 @@
           logging.info('Received gateway %r for interface %s', contents,
                        ifc.name)
 
+      if filename.startswith(self.SUBNET_FILE_PREFIX):
+        interface_name = filename.split(self.SUBNET_FILE_PREFIX)[-1]
+        ifc = self.interface_by_name(interface_name)
+        if ifc:
+          ifc.set_subnet(contents)
+          logging.info('Received subnet %r for interface %s', contents,
+                       ifc.name)
+
     elif path == self._moca_tmp_dir:
       match = re.match(r'^%s\d+$' % self.MOCA_NODE_FILE_PREFIX, filename)
       if match:
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 1f90f96..7cf1785 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -208,11 +208,12 @@
       #
       # 1)  Write an interface status file.
       # 2)  Call run-dhclient, which would call dhclient-script, which would
-      #     write a gateway file.
+      #     call ipapply, which would write gateway and subnet files.
       #
       # Fake both of these things instead.
       self.write_interface_status_file('1')
       self.write_gateway_file()
+      self.write_subnet_file()
 
   def stop_client(self):
     client_was_up = self.client_up
@@ -233,6 +234,13 @@
       # This value doesn't matter to conman, so it's fine to hard code it here.
       f.write('192.168.1.1')
 
+  def write_subnet_file(self):
+    subnet_file = os.path.join(self.tmp_dir,
+                               self.subnet_file_prefix + self.wifi.name)
+    with open(subnet_file, 'w') as f:
+      # This value doesn't matter to conman, so it's fine to hard code it here.
+      f.write('192.168.1.0/24')
+
   def write_interface_status_file(self, value):
     status_file = os.path.join(self.interface_status_dir, self.wifi.name)
     with open(status_file, 'w') as f:
@@ -240,6 +248,21 @@
       f.write(value)
 
 
+@wvtest.wvtest
+def WLANConfigurationParseTest():  # pylint: disable=invalid-name
+  """Test WLANConfiguration parsing."""
+  cmd = '\n'.join([
+      'WIFI_PSK=abcdWIFI_PSK=qwer', 'wifi', 'set', '-P', '-b', '5',
+      '--bridge=br0', '-s', 'my ssid=1', '--interface-suffix', '_suffix',
+  ])
+  config = WLANConfiguration('5', interface_test.Wifi('wcli0', 20), cmd, None,
+                             None)
+
+  wvtest.WVPASSEQ('my ssid=1', config.ssid)
+  wvtest.WVPASSEQ('abcdWIFI_PSK=qwer', config.passphrase)
+  wvtest.WVPASSEQ('_suffix', config.interface_suffix)
+
+
 class Wifi(interface_test.Wifi):
 
   def __init__(self, *args, **kwargs):
@@ -374,6 +397,7 @@
     wlan_configuration.tmp_dir = self._tmp_dir
     wlan_configuration.interface_status_dir = self._interface_status_dir
     wlan_configuration.gateway_file_prefix = self.GATEWAY_FILE_PREFIX
+    wlan_configuration.subnet_file_prefix = self.SUBNET_FILE_PREFIX
 
     super(ConnectionManager, self)._update_wlan_configuration(
         wlan_configuration)
@@ -394,6 +418,8 @@
     if up and not dhcp_failure:
       self.write_gateway_file('br0' if interface_name in ('eth0', 'moca0')
                               else interface_name)
+      self.write_subnet_file('br0' if interface_name in ('eth0', 'moca0')
+                             else interface_name)
 
   def _binwifi(self, *command):
     super(ConnectionManager, self)._binwifi(*command)
@@ -438,6 +464,13 @@
       # This value doesn't matter to conman, so it's fine to hard code it here.
       f.write('192.168.1.1')
 
+  def write_subnet_file(self, interface_name):
+    subnet_file = os.path.join(self._tmp_dir,
+                               self.SUBNET_FILE_PREFIX + interface_name)
+    with open(subnet_file, 'w') as f:
+      # This value doesn't matter to conman, so it's fine to hard code it here.
+      f.write('192.168.1.0/24')
+
   def write_interface_status_file(self, interface_name, value):
     status_file = os.path.join(self._interface_status_dir, interface_name)
     with open(status_file, 'w') as f:
@@ -616,10 +649,10 @@
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
   wvtest.WVPASS(os.path.exists(acs_autoprov_filepath))
   for wifi in c.wifi:
-    wvtest.WVFAIL(wifi.current_route())
+    wvtest.WVFAIL(wifi.current_routes_normal_testonly())
   wvtest.WVFAIL(c.has_status_files([status.P.CONNECTED_TO_WLAN,
                                     status.P.HAVE_CONFIG]))
 
@@ -628,7 +661,7 @@
   c.run_once()
   wvtest.WVFAIL(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVFAIL(c.bridge.current_route())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
   wvtest.WVFAIL(os.path.exists(acs_autoprov_filepath))
   wvtest.WVFAIL(c.has_status_files([status.P.CAN_REACH_ACS,
                                     status.P.CAN_REACH_INTERNET]))
@@ -638,35 +671,35 @@
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Bring up ethernet, access via both moca and ethernet.
   c.set_ethernet(True)
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Bring down moca, still have access via ethernet.
   c.set_moca(False)
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # The bridge interfaces are up, but they can't reach anything.
   c.bridge.set_connection_check_result('fail')
   c.run_until_interface_update()
   wvtest.WVFAIL(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVFAIL(c.bridge.current_route())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
 
   # Now c connects to a restricted network.
   c.bridge.set_connection_check_result('restricted')
   c.run_until_interface_update()
   wvtest.WVPASS(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Now the wired connection goes away.
   c.set_ethernet(False)
@@ -674,7 +707,9 @@
   c.run_once()
   wvtest.WVFAIL(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVFAIL(c.bridge.current_route())
+  # We have no links, so we should have no routes (not even low priority ones),
+  # and /tmp/hosts should only contain a line for localhost.
+  wvtest.WVFAIL(c.bridge.current_routes())
   check_tmp_hosts('127.0.0.1 localhost')
 
   # Now there are some scan results.
@@ -696,7 +731,7 @@
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
   wvtest.WVPASSEQ(c.log_upload_count, 1)
   # Disable scan results again.
   c.interface_with_scan_results = None
@@ -710,7 +745,7 @@
   c.disable_access_point(band)
   c.run_once()
   wvtest.WVPASS(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
   wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_WLAN]))
 
   # Kill wpa_supplicant.  conman should restart it.
@@ -751,7 +786,7 @@
   c.disable_access_point(band)
   c.run_once()
   wvtest.WVPASS(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
   wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_WLAN]))
 
   # Now enable the AP.  Since we have no wired connection, this should have no
@@ -759,8 +794,8 @@
   c.enable_access_point(band)
   c.run_once()
   wvtest.WVPASS(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
-  wvtest.WVFAIL(c.bridge.current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
   c.run_until_interface_update()
   check_tmp_hosts('192.168.1.100 %s\n127.0.0.1 localhost' % hostname)
 
@@ -772,8 +807,8 @@
   c.run_until_interface_update()
   wvtest.WVPASS(c.access_point_up(band))
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVFAIL(c.wifi_for_band(band).current_route())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVFAIL(c.wifi_for_band(band).current_routes())
+  wvtest.WVPASS(c.bridge.current_routes())
   check_tmp_hosts('192.168.1.101 %s\n127.0.0.1 localhost' % hostname)
 
   # Now move (rather than delete) the configuration file.  The AP should go
@@ -785,8 +820,8 @@
   c.run_once()
   wvtest.WVFAIL(c.access_point_up(band))
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVFAIL(c.wifi_for_band(band).current_route())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVFAIL(c.wifi_for_band(band).current_routes_normal_testonly())
+  wvtest.WVPASS(c.bridge.current_routes())
   wvtest.WVFAIL(c.has_status_files([status.P.HAVE_CONFIG]))
 
   # Now move it back, and the AP should come back.
@@ -794,8 +829,8 @@
   c.run_once()
   wvtest.WVPASS(c.access_point_up(band))
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVFAIL(c.wifi_for_band(band).current_route())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVFAIL(c.wifi_for_band(band).current_routes_normal_testonly())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Now delete the config and bring down the bridge and make sure we reprovision
   # via the last working BSS.
@@ -805,7 +840,9 @@
   c.run_until_interface_update()
   wvtest.WVFAIL(c.acs())
   wvtest.WVFAIL(c.internet())
-  check_tmp_hosts('127.0.0.1 localhost')
+  # We still have a link and might be wrong about the connection_check, so
+  # /tmp/hosts should still contain a line for this hostname.
+  check_tmp_hosts('192.168.1.101 %s\n127.0.0.1 localhost' % hostname)
   # s3 is not what the cycler would suggest trying next.
   wvtest.WVPASSNE('s3', c.wifi_for_band(band).cycler.peek())
   # Run only once, so that only one BSS can be tried.  It should be the s3 one,
@@ -979,11 +1016,11 @@
   c.run_once()
   wvtest.WVPASS(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
   wvtest.WVFAIL(c.client_up('2.4'))
   wvtest.WVFAIL(c.client_up('5'))
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # Disable the 2.4 GHz AP, make sure the 5 GHz AP stays up.  2.4 GHz should
   # join the WLAN.
@@ -992,9 +1029,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVPASS(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # Delete the 2.4 GHz WLAN configuration; it should leave the WLAN but nothing
   # else should change.
@@ -1003,9 +1040,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVFAIL(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # Disable the wired connection and remove the WLAN configurations.  Both
   # radios should scan.  Wait for 5 GHz to scan, then enable scan results for
@@ -1014,18 +1051,18 @@
   c.set_ethernet(False)
   c.run_once()
   wvtest.WVFAIL(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # The 5 GHz scan has no results.
   c.run_until_scan('5')
   c.run_once()
   c.run_until_interface_update()
   wvtest.WVFAIL(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # The next 2.4 GHz scan will have results.
   c.interface_with_scan_results = c.wifi_for_band('2.4').name
@@ -1035,9 +1072,9 @@
     c.run_once()
   c.run_until_interface_update()
   wvtest.WVPASS(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
   c.run_once()
   wvtest.WVPASSEQ(c.log_upload_count, 1)
 
@@ -1085,9 +1122,9 @@
   c.run_once()
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # Disable the 2.4 GHz AP; nothing should change.  The 2.4 GHz client should
   # not be up because the same radio is being used to run a 5 GHz AP.
@@ -1096,9 +1133,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVFAIL(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # Delete the 2.4 GHz WLAN configuration; nothing should change.
   c.delete_wlan_config('2.4')
@@ -1106,9 +1143,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVFAIL(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # Disable the wired connection and remove the WLAN configurations.  There
   # should be a single scan that leads to ACS access.  (It doesn't matter which
@@ -1118,9 +1155,9 @@
   c.set_ethernet(False)
   c.run_once()
   wvtest.WVFAIL(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes_normal_testonly())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes_normal_testonly())
 
   # The scan will have results that will lead to ACS access.
   c.interface_with_scan_results = c.wifi_for_band('2.4').name
@@ -1129,9 +1166,9 @@
     c.run_once()
   c.run_until_interface_update()
   wvtest.WVPASS(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
-  wvtest.WVPASS(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes_normal_testonly())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVPASS(c.wifi_for_band('5').current_routes())
   c.run_once()
   wvtest.WVPASSEQ(c.log_upload_count, 1)
 
@@ -1176,7 +1213,7 @@
   c.run_once()
   wvtest.WVPASS(c.wifi_for_band('2.4').acs())
   wvtest.WVPASS(c.wifi_for_band('2.4').internet())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
 
 
 @wvtest.wvtest
@@ -1189,7 +1226,7 @@
     c:  The ConnectionManager set up by @connection_manager_test.
   """
   wvtest.WVPASS(c._connected_to_wlan(c.wifi_for_band('2.4')))
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route)
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
 
 
 @wvtest.wvtest
diff --git a/conman/interface.py b/conman/interface.py
index e172a82..2d0fbbe 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -7,13 +7,18 @@
 import re
 import subprocess
 
+# This has to be called before another module calls it with a higher log level.
+# pylint: disable=g-import-not-at-top
+logging.basicConfig(level=logging.DEBUG)
+
 import experiment
 import wpactrl
 
 METRIC_5GHZ = 20
 METRIC_24GHZ_5GHZ = 21
 METRIC_24GHZ = 22
-METRIC_TEMPORARY_CONNECTION_CHECK = 99
+
+RFC2385_MULTICAST_ROUTE = '239.0.0.0/8'
 
 experiment.register('WifiSimulateWireless')
 CWMP_PATH = '/tmp/cwmp'
@@ -30,7 +35,7 @@
   IP_ROUTE = ['ip', 'route']
   IP_ADDR_SHOW = ['ip', 'addr', 'show', 'dev']
 
-  def __init__(self, name, metric):
+  def __init__(self, name, base_metric):
     self.name = name
 
     # Currently connected links for this interface, e.g. ethernet.
@@ -40,13 +45,18 @@
     self._has_acs = None
     self._has_internet = None
 
-    # The gateway IP for this interface.
+    self._subnet = None
     self._gateway_ip = None
-    self.metric = metric
+    self.base_metric = base_metric
+    self.metric_offset = 0
 
     # Until this is set True, the routing table will not be touched.
     self._initialized = False
 
+  @property
+  def metric(self):
+    return str(int(self.base_metric) + self.metric_offset)
+
   def _connection_check(self, check_acs):
     """Check this interface's connection status.
 
@@ -72,18 +82,7 @@
                    ' (ACS)' if check_acs else '', self.name)
       return False
 
-    # Temporarily add a route to make sure the connection check can be run.
-    # Give it a high metric so that it won't interfere with normal default
-    # routes.
-    added_temporary_route = False
-    if not self.current_route():
-      logging.debug('Adding temporary connection check route for dev %s',
-                    self.name)
-      self._ip_route('add', 'default',
-                     'via', self._gateway_ip,
-                     'dev', self.name,
-                     'metric', str(METRIC_TEMPORARY_CONNECTION_CHECK))
-      added_temporary_route = True
+    self.add_routes()
 
     cmd = [self.CONNECTION_CHECK, '-I', self.name]
     if check_acs:
@@ -96,14 +95,6 @@
                    self.name,
                    'passed' if result else 'failed')
 
-    # Delete the temporary route.
-    if added_temporary_route:
-      logging.debug('Deleting temporary connection check route for dev %s',
-                    self.name)
-      self._ip_route('del', 'default',
-                     'dev', self.name,
-                     'metric', str(METRIC_TEMPORARY_CONNECTION_CHECK))
-
     return result
 
   def gateway(self):
@@ -121,59 +112,110 @@
 
     return self._has_internet
 
-  def add_route(self):
-    """Adds a default route for this interface.
+  def add_routes(self):
+    """Update default routes for this interface.
 
-    First, checks whether an equivalent route already exists, and if so,
-    returns.
+    Remove any stale routes and add any missing desired routes.
     """
     if self.metric is None:
       logging.info('Cannot add route for %s without a metric.', self.name)
       return
 
-    if self._gateway_ip is None:
-      logging.info('Cannot add route for %s without a gateway IP.', self.name)
-      return
-
-    # If the current default route is the same, there is nothing to do.  If it
+    # If the current routes are the same, there is nothing to do.  If either
     # exists but is different, delete it before adding an updated one.
-    current = self.current_route()
-    if current:
-      if (current.get('via', None) == self._gateway_ip and
-          current.get('metric', None) == str(self.metric)):
-        return
-      else:
-        self.delete_route()
+    current = self.current_routes()
 
-    logging.debug('Adding default route for dev %s', self.name)
-    self._ip_route('add', 'default',
-                   'via', self._gateway_ip,
-                   'dev', self.name,
-                   'metric', str(self.metric))
+    to_add = []
 
-  def delete_route(self):
-    while self.current_route():
-      logging.debug('Deleting default route for dev %s', self.name)
-      self._ip_route('del', 'default',
-                     'dev', self.name)
+    subnet = current.get('subnet', {})
+    if self._subnet:
+      if ((subnet.get('route', None), subnet.get('metric', None)) !=
+          (self._subnet, str(self.metric))):
+        logging.debug('Adding subnet route for dev %s', self.name)
+        to_add.append(('subnet', ('add', self._subnet, 'dev', self.name,
+                                  'metric', str(self.metric))))
+        subnet = self._subnet
+    else:
+      subnet = None
+      self.delete_route('default', 'subnet')
 
-  def current_route(self):
-    """Read the current default route for this interface.
+    default = current.get('default', {})
+    if self._gateway_ip:
+      if (subnet and
+          (default.get('via', None), default.get('metric', None)) !=
+          (self._gateway_ip, str(self.metric))):
+        logging.debug('Adding default route for dev %s', self.name)
+        to_add.append(('default',
+                       ('add', 'default', 'via', self._gateway_ip,
+                        'dev', self.name, 'metric', str(self.metric))))
+    else:
+      self.delete_route('default')
+
+    # RFC2365 multicast route.
+    if current.get('multicast', {}).get('metric', None) != str(self.metric):
+      logging.debug('Adding multicast route for dev %s', self.name)
+      to_add.append(('multicast', ('add', RFC2385_MULTICAST_ROUTE,
+                                   'dev', self.name,
+                                   'metric', str(self.metric))))
+
+    for route_type, _ in to_add[::-1]:
+      self.delete_route(route_type)
+
+    for _, cmd in to_add:
+      self._ip_route(*cmd)
+
+  def delete_route(self, *args):
+    """Delete default and/or subnet routes for this interface.
+
+    Args:
+      *args:  Which routes to delete.  Must be at least one of 'default',
+          'subnet', 'multicast'.
+
+    Raises:
+      ValueError:  If neither default nor subnet is True.
+    """
+    args = set(args)
+    args &= set(('default', 'subnet', 'multicast'))
+    if not args:
+      raise ValueError(
+          'Must specify at least one of default, subnet, multicast to delete.')
+
+    # Use a sorted list to ensure that default comes before subnet.
+    for route_type in sorted(list(args)):
+      while route_type in self.current_routes():
+        logging.debug('Deleting %s route for dev %s', route_type, self.name)
+        self._ip_route('del', self.current_routes()[route_type]['route'],
+                       'dev', self.name)
+
+  def current_routes(self):
+    """Read the current routes for this interface.
 
     Returns:
-      A dict containing the gateway [and metric] of the route, or an empty dict
-      if there is currently no default route for this interface.
+      A dict mapping 'default' and/or 'subnet' to a dict containing the gateway
+      [and metric] of the route.  Only contains keys for routes that are
+      present.
     """
     result = {}
     for line in self._ip_route().splitlines():
-      if line.startswith('default') and 'dev %s' % self.name in line:
-        key = None
+      if 'dev %s' % self.name in line:
+        if line.startswith('default'):
+          route_type = 'default'
+        elif re.search(r'/\d{1,2}$', line.split()[0]):
+          route_type = 'subnet'
+        else:
+          continue
+        route = {}
+        key = 'route'
         for token in line.split():
           if token in ['via', 'metric']:
             key = token
           elif key:
-            result[key] = token
+            if key == 'route' and token == RFC2385_MULTICAST_ROUTE:
+              route_type = 'multicast'
+            route[key] = token
             key = None
+        if route:
+          result[route_type] = route
 
     return result
 
@@ -183,17 +225,17 @@
                    ' '.join(self.IP_ROUTE), ' '.join(args))
       return ''
 
-    return self._really_ip_route(*args)
-
-  def _really_ip_route(self, *args):
     try:
       logging.debug('%s calling ip route %s', self.name, ' '.join(args))
-      return subprocess.check_output(self.IP_ROUTE + list(args))
+      return self._really_ip_route(*args)
     except subprocess.CalledProcessError as e:
       logging.error('Failed to call "ip route" with args %r: %s', args,
                     e.message)
       return ''
 
+  def _really_ip_route(self, *args):
+    return subprocess.check_output(self.IP_ROUTE + list(args))
+
   def _ip_addr_show(self):
     try:
       return subprocess.check_output(self.IP_ADDR_SHOW + [self.name])
@@ -209,7 +251,12 @@
   def set_gateway_ip(self, gateway_ip):
     logging.info('New gateway IP %s for %s', gateway_ip, self.name)
     self._gateway_ip = gateway_ip
-    self.update_routes()
+    self.update_routes(expire_cache=True)
+
+  def set_subnet(self, subnet):
+    logging.info('New subnet %s for %s', subnet, self.name)
+    self._subnet = subnet
+    self.update_routes(expire_cache=True)
 
   def _set_link_status(self, link, is_up):
     """Set whether a link is up or not."""
@@ -243,36 +290,51 @@
   def update_routes(self, expire_cache=True):
     """Update this interface's routes.
 
-    If the interface has gained ACS or internet access, add a route.  If it had
-    either and now has neither, delete the route.
+    If the interface has ACS or internet access, prioritize its routes.  If it
+    doesn't but has a link, deprioritize the routes.  If it has no links, delete
+    the routes.
 
     Args:
       expire_cache:  If true, force a recheck of connection status before
-      deciding whether to add or remove routes.
+      deciding how to prioritize routes.
     """
     logging.debug('Updating routes for %s', self.name)
-    maybe_had_acs = self._has_acs
-    maybe_had_internet = self._has_internet
-
     if expire_cache:
       self.expire_connection_status_cache()
 
-    has_acs = self.acs()
-    has_internet = self.internet()
+    if self.acs() or self.internet():
+      self.prioritize_routes()
+    else:
+      # If we still have a link, just deprioritize the routes, in case we're
+      # wrong about the connection check.  If there's no actual link, then
+      # really delete the routes.
+      if self.links:
+        self.deprioritize_routes()
+      else:
+        self.delete_route('default', 'subnet', 'multicast')
 
-    # This is a little confusing:  We want to try adding a route if we _may_
-    # have gone from no access to some access, and we want to try deleting the
-    # route if we _may_ have lost *all* access. So the first condition checks
-    # for truthiness but the elif checks for explicit Falsity (i.e. excluding
-    # the None/unknown case).
-    had_access = maybe_had_acs or maybe_had_internet
-    # pylint: disable=g-explicit-bool-comparison
-    maybe_had_access = maybe_had_acs != False or maybe_had_internet != False
-    has_access = has_acs or has_internet
-    if not had_access and has_access:
-      self.add_route()
-    elif maybe_had_access and not has_access:
-      self.delete_route()
+  def prioritize_routes(self):
+    """When connection check succeeds, route priority (metric) should be normal.
+
+    This is the inverse of deprioritize_routes.
+    """
+    if not self._initialized:
+      return
+    logging.info('%s routes have normal priority', self.name)
+    self.metric_offset = 0
+    self.add_routes()
+
+  def deprioritize_routes(self):
+    """When connection check fails, deprioritize routes by increasing metric.
+
+    This is conservative alternative to deleting routes, in case we are mistaken
+    about route not providing a useful connection.
+    """
+    if not self._initialized:
+      return
+    logging.info('%s routes have low priority', self.name)
+    self.metric_offset = 50
+    self.add_routes()
 
   def initialize(self):
     """Tell the interface it has its initial state.
@@ -319,16 +381,22 @@
       self._moca_stations.remove(node_id)
       self.moca = bool(self._moca_stations)
 
-  def add_route(self):
+  def prioritize_routes(self):
     """We only want ACS autoprovisioning when we're using a wired route."""
-    super(Bridge, self).add_route()
+    super(Bridge, self).prioritize_routes()
     open(self._acs_autoprovisioning_filepath, 'w')
 
-  def delete_route(self):
+  def deprioritize_routes(self, *args, **kwargs):
     """We only want ACS autoprovisioning when we're using a wired route."""
     if os.path.exists(self._acs_autoprovisioning_filepath):
       os.unlink(self._acs_autoprovisioning_filepath)
-    super(Bridge, self).delete_route()
+    super(Bridge, self).deprioritize_routes(*args, **kwargs)
+
+  def delete_route(self, *args, **kwargs):
+    """We only want ACS autoprovisioning when we're using a wired route."""
+    if os.path.exists(self._acs_autoprovisioning_filepath):
+      os.unlink(self._acs_autoprovisioning_filepath)
+    super(Bridge, self).delete_route(*args, **kwargs)
 
   def _connection_check(self, check_acs):
     """Support for WifiSimulateWireless."""
@@ -528,7 +596,8 @@
       client_mode = self._qcsapi('get_mode', 'wifi0') == 'Station'
       ssid = self._qcsapi('get_ssid', 'wifi0')
       status = self._qcsapi('get_status', 'wifi0')
-      security = self._qcsapi('ssid_get_authentication_mode', 'wifi0', ssid)
+      security = (self._qcsapi('ssid_get_authentication_mode', 'wifi0', ssid)
+                  if ssid else None)
     except subprocess.CalledProcessError:
       # If QCSAPI failed, skip update.
       return
diff --git a/conman/interface_test.py b/conman/interface_test.py
index 13dcf14..79c58ae 100755
--- a/conman/interface_test.py
+++ b/conman/interface_test.py
@@ -5,6 +5,9 @@
 import logging
 import os
 import shutil
+import socket
+import struct
+import subprocess
 import tempfile
 import time
 
@@ -38,6 +41,13 @@
     self.routing_table = {}
     self.ip_testonly = None
 
+  def _connection_check(self, *args, **kwargs):
+    result = super(FakeInterfaceMixin, self)._connection_check(*args, **kwargs)
+    if (self.current_routes().get('default', {}).get('via', None) !=
+        self._gateway_ip):
+      return False
+    return result
+
   def set_connection_check_result(self, result):
     if result in ['succeed', 'fail', 'restricted']:
       # pylint: disable=invalid-name
@@ -46,27 +56,58 @@
       raise ValueError('Invalid fake connection_check script.')
 
   def _really_ip_route(self, *args):
+    def can_add_route():
+      def ip_to_int(ip):
+        return struct.unpack('!I', socket.inet_pton(socket.AF_INET, ip))[0]
+
+      if args[1] != 'default':
+        return True
+
+      via = ip_to_int(args[args.index('via') + 1])
+      for (ifc, route, _), _ in self.routing_table.iteritems():
+        if ifc != self.name:
+          continue
+
+        netmask = 0
+        if '/' in route:
+          route, netmask = route.split('/')
+          netmask = 32 - int(netmask)
+        route = ip_to_int(route)
+
+        if (route >> netmask) == (via >> netmask):
+          return True
+
+      return False
+
     if not args:
       return '\n'.join(self.routing_table.values() +
-                       ['1.2.3.4/24 dev %s proto kernel scope link' % self.name,
+                       ['1.2.3.4/24 dev fake0 proto kernel scope link',
+                        # Non-subnet route, e.g. to NFS host.
+                        '1.2.3.1 dev %s proto kernel scope link' % self.name,
                         'default via 1.2.3.4 dev fake0',
                         'random junk'])
 
     metric = None
     if 'metric' in args:
       metric = args[args.index('metric') + 1]
-    key = (self.name, metric)
+    if args[0] in ('add', 'del'):
+      route = args[1]
+    key = (self.name, route, metric)
     if args[0] == 'add' and key not in self.routing_table:
+      if not can_add_route():
+        raise subprocess.CalledProcessError(
+            'Tried to add default route without subnet route: %r',
+            self.routing_table)
       logging.debug('Adding route for %r', key)
       self.routing_table[key] = ' '.join(args[1:])
     elif args[0] == 'del':
       if key in self.routing_table:
         logging.debug('Deleting route for %r', key)
         del self.routing_table[key]
-      elif key[1] is None:
+      elif key[2] is None:
         # pylint: disable=g-builtin-op
         for k in self.routing_table.keys():
-          if k[0] == key[0]:
+          if k[:-1] == key[:-1]:
             logging.debug('Deleting route for %r (generalized from %s)', k, key)
             del self.routing_table[k]
             break
@@ -77,6 +118,10 @@
 
     return ''
 
+  def current_routes_normal_testonly(self):
+    result = self.current_routes()
+    return {k: v for k, v in result.iteritems() if int(v.get('metric', 0)) < 50}
+
 
 class Bridge(FakeInterfaceMixin, interface.Bridge):
   pass
@@ -86,8 +131,8 @@
   """Fake wpactrl.WPACtrl."""
 
   # pylint: disable=unused-argument
-  def __init__(self, socket):
-    self._socket = socket
+  def __init__(self, wpa_socket):
+    self._socket = wpa_socket
     self.events = []
     self.attached = False
     self.connected = False
@@ -330,59 +375,106 @@
 
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    wvtest.WVFAIL(b.current_routes())
+    wvtest.WVFAIL(b.current_routes_normal_testonly())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
 
     b.add_moca_station(0)
+    wvtest.WVFAIL(os.path.exists(autoprov_filepath))
+    b.set_subnet('192.168.1.0/24')
     b.set_gateway_ip('192.168.1.1')
+    wvtest.WVFAIL(os.path.exists(autoprov_filepath))
     # Everything should fail because the interface is not initialized.
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    wvtest.WVFAIL(b.current_routes_normal_testonly())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
     b.initialize()
     wvtest.WVPASS(b.acs())
     wvtest.WVPASS(b.internet())
-    wvtest.WVPASS(b.current_route())
+    current_routes = b.current_routes()
+    wvtest.WVPASSEQ(len(current_routes), 3)
+    wvtest.WVPASS('default' in current_routes)
+    wvtest.WVPASS('subnet' in current_routes)
+    wvtest.WVPASS('multicast' in current_routes)
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     b.add_moca_station(1)
     wvtest.WVPASS(b.acs())
     wvtest.WVPASS(b.internet())
-    wvtest.WVPASS(b.current_route())
+    wvtest.WVPASSEQ(len(b.current_routes()), 3)
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     b.remove_moca_station(0)
     b.remove_moca_station(1)
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    # We have no links, so should have no routes.
+    wvtest.WVFAIL(b.current_routes())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
 
     b.add_moca_station(2)
     wvtest.WVPASS(b.acs())
     wvtest.WVPASS(b.internet())
-    wvtest.WVPASS(b.current_route())
+    wvtest.WVPASSEQ(len(b.current_routes()), 3)
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     b.set_connection_check_result('fail')
     b.update_routes()
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    # We have links but the connection check failed, so we should only have a
+    # low priority route, i.e. metric at least 50.
+    wvtest.WVPASSEQ(len(b.current_routes()), 3)
+    wvtest.WVFAIL(b.current_routes_normal_testonly())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
 
     b.set_connection_check_result('restricted')
     b.update_routes()
     wvtest.WVPASS(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVPASS(b.current_route())
+    wvtest.WVPASSEQ(len(b.current_routes()), 3)
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     wvtest.WVFAIL(b.get_ip_address())
     b.ip_testonly = '192.168.1.100'
     wvtest.WVPASSEQ(b.get_ip_address(), '192.168.1.100')
 
+    # Get a new gateway/subnet (e.g. due to joining a new network).
+    # Not on the subnet; adding IP should fail.
+    b.set_gateway_ip('192.168.2.1')
+    wvtest.WVFAIL('default' in b.current_routes())
+    wvtest.WVPASS('subnet' in b.current_routes())
+    # Without a default route, the connection check should fail.
+    wvtest.WVFAIL(b.acs())
+
+    # Now we get the subnet and should add updated subnet and gateway routes.
+    b.set_subnet('192.168.2.0/24')
+    wvtest.WVPASSEQ(b.current_routes()['default']['via'], '192.168.2.1')
+    wvtest.WVPASSLE(int(b.current_routes()['default']['metric']), 50)
+    wvtest.WVPASSEQ(b.current_routes()['subnet']['route'], '192.168.2.0/24')
+    wvtest.WVPASSLE(int(b.current_routes()['subnet']['metric']), 50)
+    wvtest.WVPASS(b.acs())
+
+    # If we have no subnet, make sure that both subnet and default routes are
+    # removed.
+    b.set_subnet(None)
+    wvtest.WVFAIL('subnet' in b.current_routes())
+    wvtest.WVFAIL('default' in b.current_routes())
+
+    # Now repeat the new-network test, but with a faulty connection.  Make sure
+    # the metrics are set appropriately.
+    b.set_connection_check_result('fail')
+    b.set_subnet('192.168.3.0/24')
+    b.set_gateway_ip('192.168.3.1')
+    wvtest.WVPASSGE(int(b.current_routes()['default']['metric']), 50)
+    wvtest.WVPASSGE(int(b.current_routes()['subnet']['metric']), 50)
+
+    # Now test deleting only the gateway IP.
+    b.set_gateway_ip(None)
+    wvtest.WVPASS('subnet' in b.current_routes())
+    wvtest.WVFAIL('default' in b.current_routes())
+
   finally:
     shutil.rmtree(tmp_dir)
 
@@ -485,6 +577,7 @@
     b = Bridge('br0', '10', acs_autoprovisioning_filepath=autoprov_filepath)
     b.add_moca_station(0)
     b.set_gateway_ip('192.168.1.1')
+    b.set_subnet('192.168.1.0/24')
     b.set_connection_check_result('succeed')
     b.initialize()
 
diff --git a/conman/status.py b/conman/status.py
index e21dc01..7f75682 100644
--- a/conman/status.py
+++ b/conman/status.py
@@ -20,6 +20,7 @@
 
   TRYING_OPEN = 'TRYING_OPEN'
   TRYING_WLAN = 'TRYING_WLAN'
+  WLAN_FAILED = 'WLAN_FAILED'
   CONNECTED_TO_OPEN = 'CONNECTED_TO_OPEN'
   CONNECTED_TO_WLAN = 'CONNECTED_TO_WLAN'
   HAVE_CONFIG = 'HAVE_CONFIG'
@@ -43,6 +44,10 @@
         (),
         (P.TRYING_OPEN, P.CONNECTED_TO_OPEN, P.CONNECTED_TO_WLAN)
     ),
+    P.WLAN_FAILED: (
+        (),
+        (P.TRYING_WLAN, P.CONNECTED_TO_WLAN)
+    ),
     P.CONNECTED_TO_OPEN: (
         (),
         (P.CONNECTED_TO_WLAN, P.TRYING_OPEN, P.TRYING_WLAN)
diff --git a/diags/Makefile b/diags/Makefile
index 76855ce..b0fcdb9 100644
--- a/diags/Makefile
+++ b/diags/Makefile
@@ -7,6 +7,9 @@
 ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfsc100)
  DIRS += spacecast
 endif
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gflt110)
+ DIRS += chameleon
+endif
 
 PYTHON?=python
 
@@ -21,7 +24,7 @@
 clean:   $(addsuffix /clean,$(DIRS))
 install-libs: $(addsuffix /install-libs,$(DIRS))
 
-windcharger/all spacecast/all:	common/all
+windcharger/all spacecast/all chameleon/all:	common/all
 
 # The install targets in the recursive call use setuptools to build the python
 # packages. These cannot be run in parallel, as they appear to race with each
diff --git a/diags/chameleon/Makefile b/diags/chameleon/Makefile
new file mode 100644
index 0000000..e6af150
--- /dev/null
+++ b/diags/chameleon/Makefile
@@ -0,0 +1,42 @@
+default:	all
+
+BINARY = diags
+
+TARGETS=$(BINARY)
+INSTALL=install
+PREFIX=$(DESTDIR)/usr
+BINDIR=$(PREFIX)/sbin
+LIBDIR=$(PREFIX)/lib
+INCLUDEDIR=$(PREFIX)/include
+
+CC=$(CROSS_COMPILE)gcc
+CXX=$(CROSS_COMPILE)g++
+RM=rm -f
+
+CFLAGS = -Wall -Werror -Wimplicit -Wno-unknown-pragmas -D_GNU_SOURC -g
+CFLAGS += $(EXTRA_CFLAGS)
+LDFLAGS += $(EXTRA_LDFLAGS)
+
+IFLAGS += $(patsubst %,-I%,$(INC_DIRS))
+CFILES = $(wildcard ../common/*.c *.c)
+OFILES = $(patsubst %.c,%.o,$(CFILES))
+
+all:	$(TARGETS)
+
+install:
+	$(INSTALL) -m 0755 $(BINARY) $(BINDIR)/
+
+install-libs:
+	@:
+
+test:
+	@:
+
+$(BINARY):	$(OFILES)
+	$(CC) $^ $(LDFLAGS) -o $@
+
+%.o:	%.c
+	$(CC) $(CFLAGS) $(IFLAGS) -c $^ -c
+
+clean:
+	$(RM) $(OFILES) $(BINARY)
diff --git a/diags/chameleon/common.h b/diags/chameleon/common.h
new file mode 100644
index 0000000..f790704
--- /dev/null
+++ b/diags/chameleon/common.h
@@ -0,0 +1,14 @@
+/*
+ * (C) Copyright 2015 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#ifndef VENDOR_GOOGLE_DIAGS_LOCKDOWN_COMMON_H_
+#define VENDOR_GOOGLE_DIAGS_LOCKDOWN_COMMON_H_
+
+#define MAX_PKT_SIZE 4096
+#define FAIL_TEXT "FAILED:"
+#define PASS_TEXT "PASSED:"
+
+#endif  // VENDOR_GOOGLE_DIAGS_LOCKDOWN_COMMON_H_
diff --git a/diags/chameleon/datapath.c b/diags/chameleon/datapath.c
new file mode 100644
index 0000000..d5da3c4
--- /dev/null
+++ b/diags/chameleon/datapath.c
@@ -0,0 +1,248 @@
+/*
+ * (C) Copyright 2015 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "../common/io.h"
+#include "../common/util.h"
+
+#define AVANTA_BASE_ADDR 0xF1000000
+#define GPON_RECV_STATUS_FEC0 (AVANTA_BASE_ADDR + 0x000AC80C)
+#define GPON_RECV_STATUS_FEC1 (AVANTA_BASE_ADDR + 0x000AC810)
+#define GPON_RECV_STATUS_FEC2 (AVANTA_BASE_ADDR + 0x000AC814)
+#define GPON_RECV_STATUS_SUPER_FRAME_CNT (AVANTA_BASE_ADDR + 0x000AC818)
+#define PON_PHY_TEST_PRBS_COUNTER_0 (AVANTA_BASE_ADDR + 0x000A2E70)
+#define PON_PHY_TEST_PRBS_COUNTER_1 (AVANTA_BASE_ADDR + 0x000A2E74)
+#define PON_PHY_TEST_PRBS_COUNTER_2 (AVANTA_BASE_ADDR + 0x000A2E78)
+#define PON_PHY_TEST_PRBS_ERROR_COUNTER_0 (AVANTA_BASE_ADDR + 0x000A2E7C)
+#define PON_PHY_TEST_PRBS_ERROR_COUNTER_1 (AVANTA_BASE_ADDR + 0x000A2E80)
+#define PON_PHY_CTRL0 (AVANTA_BASE_ADDR + 0x000184F4)
+#define PON_PHY_RESET_BIT 0x8
+
+static int set_pon_phy_out_of_reset() {
+  unsigned int phy_ctrl0;
+
+  if (read_physical_addr(PON_PHY_CTRL0, &phy_ctrl0) != 0) {
+    printf("Read address 0x%x failed\n", PON_PHY_CTRL0);
+    return -1;
+  }
+  if (phy_ctrl0 & PON_PHY_RESET_BIT) {
+    phy_ctrl0 &= ~PON_PHY_RESET_BIT;
+    if (write_physical_addr(PON_PHY_CTRL0, phy_ctrl0) != 0) {
+      printf("Write address 0x%x value 0x%x failed\n", PON_PHY_CTRL0,
+             phy_ctrl0);
+      return -1;
+    }
+  }
+  return 0;
+}
+
+static void soc_reg_read_usage(void) {
+  printf("soc_reg_read <addr>\n");
+  printf("read Marvell 88F6601 registers\n");
+  printf("Example:\n");
+  printf("soc_reg_read 0x00018810\n");
+}
+
+int soc_reg_read(int argc, char *argv[]) {
+  unsigned int reg_addr, addr, value;
+
+  if (argc != 2) {
+    soc_reg_read_usage();
+    return -1;
+  }
+
+  reg_addr = strtoul(argv[1], NULL, 16);
+  addr = reg_addr + AVANTA_BASE_ADDR;
+  if (read_physical_addr(addr, &value) != 0) {
+    printf("Read address 0x%x failed\n", addr);
+    return -1;
+  }
+  printf("0x%x = 0x%x\n", reg_addr, value);
+
+  return 0;
+}
+
+static void soc_reg_write_usage(void) {
+  printf("soc_reg_write <addr> <data>\n");
+  printf("write Marvell 88F6601 registers\n");
+  printf("Example:\n");
+  printf("soc_reg_write 0x0007241C 0x0\n");
+}
+
+int soc_reg_write(int argc, char *argv[]) {
+  unsigned int reg_addr, addr, value;
+
+  if (argc != 3) {
+    soc_reg_write_usage();
+    return -1;
+  }
+
+  reg_addr = strtoul(argv[1], NULL, 16);
+  value = strtoul(argv[2], NULL, 16);
+  addr = reg_addr + AVANTA_BASE_ADDR;
+  if (write_physical_addr(addr, value) != 0) {
+    printf("Write address 0x%x value 0x%x failed\n", addr, value);
+    return -1;
+  }
+  printf("0x%x set to 0x%x\n", reg_addr, value);
+
+  return 0;
+}
+
+static void gpon_rx_status_usage(void) {
+  printf("gpon_rx_status\n");
+  printf("read Marvell 88F6601 GPON RX status registers\n");
+  printf("Example:\n");
+  printf("gpon_rx_status\n");
+}
+
+int gpon_rx_status(int argc, char *argv[]) {
+  unsigned int fec0, fec1, fec2, frame_cnt;
+
+  if (argc != 1 || argv == NULL) {
+    gpon_rx_status_usage();
+    return -1;
+  }
+
+  if (read_physical_addr(GPON_RECV_STATUS_FEC0, &fec0) != 0) {
+    printf("Read address 0x%x failed\n", GPON_RECV_STATUS_FEC0);
+    return -1;
+  }
+  if (read_physical_addr(GPON_RECV_STATUS_FEC1, &fec1) != 0) {
+    printf("Read address 0x%x failed\n", GPON_RECV_STATUS_FEC1);
+    return -1;
+  }
+  if (read_physical_addr(GPON_RECV_STATUS_FEC2, &fec2) != 0) {
+    printf("Read address 0x%x failed\n", GPON_RECV_STATUS_FEC2);
+    return -1;
+  }
+  if (read_physical_addr(GPON_RECV_STATUS_SUPER_FRAME_CNT, &frame_cnt) != 0) {
+    printf("Read address 0x%x failed\n", GPON_RECV_STATUS_SUPER_FRAME_CNT);
+    return -1;
+  }
+  printf(
+      "Bytes Received: 0x%x COR: 0x%x RX words Received: 0x%x Frame CNT: "
+      "0x%x\n",
+      fec0, fec1, fec2, frame_cnt);
+
+  return 0;
+}
+
+static void rx_prbs_cnt_usage(void) {
+  printf("rx_prbs_cnt\n");
+  printf("read Marvell 88F6601 RX PRBS coutner registers\n");
+  printf("Example:\n");
+  printf("rx_prbs_cnt\n");
+}
+
+int rx_prbs_cnt(int argc, char *argv[]) {
+  unsigned int cnt0, cnt1, cnt2;
+
+  if (argc != 1 || argv == NULL) {
+    rx_prbs_cnt_usage();
+    return -1;
+  }
+
+  if (set_pon_phy_out_of_reset() != 0) {
+    printf("Failed to take PHY out of reset\n");
+    return -1;
+  }
+  if (read_physical_addr(PON_PHY_TEST_PRBS_COUNTER_0, &cnt0) != 0) {
+    printf("Read address 0x%x failed\n", PON_PHY_TEST_PRBS_COUNTER_0);
+    return -1;
+  }
+  if (read_physical_addr(PON_PHY_TEST_PRBS_COUNTER_1, &cnt1) != 0) {
+    printf("Read address 0x%x failed\n", PON_PHY_TEST_PRBS_COUNTER_0);
+    return -1;
+  }
+  if (read_physical_addr(PON_PHY_TEST_PRBS_COUNTER_2, &cnt2) != 0) {
+    printf("Read address 0x%x failed\n", PON_PHY_TEST_PRBS_COUNTER_0);
+    return -1;
+  }
+  printf("RX PRBS count: 0x%x%04x%04x\n", cnt0, cnt1, cnt2);
+
+  return 0;
+}
+
+static void rx_prbs_err_cnt_usage(void) {
+  printf("rx_prbs_err_cnt\n");
+  printf("read Marvell 88F6601 RX PRBS error coutner registers\n");
+  printf("Example:\n");
+  printf("rx_prbs_err_cnt\n");
+}
+
+int rx_prbs_err_cnt(int argc, char *argv[]) {
+  unsigned int cnt0, cnt1;
+
+  if (argc != 1 || argv == NULL) {
+    rx_prbs_err_cnt_usage();
+    return -1;
+  }
+
+  if (set_pon_phy_out_of_reset() != 0) {
+    printf("Failed to take PHY out of reset\n");
+    return -1;
+  }
+  if (read_physical_addr(PON_PHY_TEST_PRBS_ERROR_COUNTER_0, &cnt0) != 0) {
+    printf("Read address 0x%x failed\n", PON_PHY_TEST_PRBS_ERROR_COUNTER_0);
+    return -1;
+  }
+  if (read_physical_addr(PON_PHY_TEST_PRBS_ERROR_COUNTER_0, &cnt1) != 0) {
+    printf("Read address 0x%x failed\n", PON_PHY_TEST_PRBS_ERROR_COUNTER_0);
+    return -1;
+  }
+  printf("RX PRBS error count: 0x%x%04x\n", cnt0, cnt1);
+
+  return 0;
+}
+
+static void gpon_cnts_usage(void) {
+  printf("gpon_cnts\n");
+  printf("dump all of Marvell 88F6601 GPON related error coutner registers\n");
+  printf("Example:\n");
+  printf("gpon_cnts\n");
+}
+
+int gpon_cnts(int argc, char *argv[]) {
+  if (argc != 1 || argv == NULL) {
+    gpon_cnts_usage();
+    return -1;
+  }
+
+  system_cmd("cat /sys/devices/platform/gpon/pm/bwMapCnt");
+  system_cmd("cat /sys/devices/platform/gpon/pm/fecCnt");
+  system_cmd("cat /sys/devices/platform/gpon/pm/gemCnt");
+  system_cmd("cat /sys/devices/platform/gpon/pm/rxPloamCnt");
+  system_cmd("cat /sys/devices/platform/gpon/pm/stdCnt");
+  system_cmd("cat /sys/devices/platform/gpon/pm/txPktCnt");
+  system_cmd("cat /sys/devices/platform/gpon/pm/txPloamCnt");
+
+  return 0;
+}
+
+static void gpon_alarms_usage(void) {
+  printf("gpon_alarms\n");
+  printf("Show 88F6601 GPON alarms\n");
+  printf("Example:\n");
+  printf("gpon_alarms\n");
+}
+
+int gpon_alarms(int argc, char *argv[]) {
+  if (argc != 1 || argv == NULL) {
+    gpon_alarms_usage();
+    return -1;
+  }
+
+  system_cmd("cat /sys/devices/platform/gpon/info/alarmGpon");
+
+  return 0;
+}
diff --git a/diags/chameleon/devmem3 b/diags/chameleon/devmem3
new file mode 100644
index 0000000..119b8b7
--- /dev/null
+++ b/diags/chameleon/devmem3
Binary files differ
diff --git a/diags/chameleon/diagutil.c b/diags/chameleon/diagutil.c
new file mode 100644
index 0000000..b2768d4
--- /dev/null
+++ b/diags/chameleon/diagutil.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2015 Google Inc.
+ * All rights reserved.
+ * diagutil -- Linux-based Hardware Diagnostic Utilities
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DIAGS_VERSION "1.1"
+
+/* External Functions */
+int ioread(int argc, char **argv);
+int iowrite(int argc, char **argv);
+int iowrite_only(int argc, char *argv[]);
+int i2cread(int argc, char *argv[]);
+int i2cwrite(int argc, char *argv[]);
+int i2cprobe(int argc, char *argv[]);
+int gpio_stat(int argc, char *argv[]);
+int gpio_set_dir(int argc, char *argv[]);
+int gpio_set_out_val(int argc, char *argv[]);
+int gpio_set_tx_enable(int argc, char *argv[]);
+int gpio_mailbox(int argc, char *argv[]);
+int get_temp(int argc, char *argv[]);
+int set_leds(int argc, char *argv[]);
+int get_leds(int argc, char *argv[]);
+int phy_read(int argc, char *argv[]);
+int phy_write(int argc, char *argv[]);
+int loopback_test(int argc, char *argv[]);
+int phy_read(int argc, char *argv[]);
+int phy_write(int argc, char *argv[]);
+int soc_reg_read(int argc, char *argv[]);
+int soc_reg_write(int argc, char *argv[]);
+int gpon_rx_status(int argc, char *argv[]);
+int rx_prbs_cnt(int argc, char *argv[]);
+int rx_prbs_err_cnt(int argc, char *argv[]);
+int gpon_cnts(int argc, char *argv[]);
+int gpon_alarms(int argc, char *argv[]);
+int sfp_reg_read(int argc, char *argv[]);
+int sfp_reg_write(int argc, char *argv[]);
+int sfp_diags_reg_read(int argc, char *argv[]);
+int sfp_diags_reg_write(int argc, char *argv[]);
+int sfp_info(int argc, char *argv[]);
+int sfp_vendor(int argc, char *argv[]);
+int sfp_pn(int argc, char *argv[]);
+int sfp_wavelength(int argc, char *argv[]);
+int sfp_set_wavelength(int argc, char *argv[]);
+int sfp_set_pw(int argc, char *argv[]);
+
+/* Define the command structure */
+typedef struct {
+  const char *name;
+  int (*funcp)(int, char **);
+} tCOMMAND;
+
+void printVersion() { printf("%s\n", DIAGS_VERSION); }
+
+int version(int argc, char *argv[]) {
+  // This is to avoid unused params warning
+  if ((argc != 1) || (argv[0] == NULL)) {
+    printf("Invalid command parameter\n");
+  }
+  printVersion();
+  return 0;
+}
+
+/* Table of supported commands */
+tCOMMAND command_list[] = {
+    {"ioread", ioread},
+    {"iowrite", iowrite},
+    {"iowrite_only", iowrite_only},
+    {"", NULL},
+    {"i2cread", i2cread},
+    {"i2cwrite", i2cwrite},
+    {"i2cprobe", i2cprobe},
+    {"", NULL},
+    {"gpio_stat", gpio_stat},
+    {"gpio_set_dir", gpio_set_dir},
+    {"gpio_set_out_val", gpio_set_out_val},
+    {"gpio_set_tx_enable", gpio_set_tx_enable},
+    {"gpio_mailbox", gpio_mailbox},
+    {"get_temp", get_temp},
+    {"set_leds", set_leds},
+    {"get_leds", get_leds},
+    {"", NULL},
+    {"phy_read", phy_read},
+    {"phy_write", phy_write},
+    {"loopback_test", loopback_test},
+    {"", NULL},
+    {"soc_reg_read", soc_reg_read},
+    {"soc_reg_write", soc_reg_write},
+    {"gpon_rx_status", gpon_rx_status},
+    {"rx_prbs_cnt", rx_prbs_cnt},
+    {"rx_prbs_err_cnt", rx_prbs_err_cnt},
+    {"gpon_cnts", gpon_cnts},
+    {"gpon_alarms", gpon_alarms},
+    {"", NULL},
+    {"sfp_reg_read", sfp_reg_read},
+    {"sfp_reg_write", sfp_reg_write},
+    {"sfp_diags_reg_read", sfp_diags_reg_read},
+    {"sfp_diags_reg_write", sfp_diags_reg_write},
+    {"sfp_info", sfp_info},
+    {"sfp_vendor", sfp_vendor},
+    {"sfp_pn", sfp_pn},
+    {"sfp_wavelength", sfp_wavelength},
+    {"sfp_set_wavelength", sfp_set_wavelength},
+    {"sfp_set_pw", sfp_set_pw},
+    {"", NULL},
+    {"version", version},
+    {"", NULL},
+    {NULL, NULL},
+};
+
+static void usage(void) {
+  int i;
+
+  printf("Supported commands:\n");
+
+  for (i = 0; command_list[i].name != NULL; ++i) {
+    printf("\t%s\n", command_list[i].name);
+  }
+
+  return;
+}
+
+int main(int argc, char *argv[]) {
+  int i;
+
+  if (argc > 1) {
+    /* Search the command list for a match */
+    for (i = 0; command_list[i].name != NULL; ++i) {
+      if (strcmp(argv[1], command_list[i].name) == 0) {
+        return command_list[i].funcp(argc - 1, &argv[1]);
+      }
+    }
+  }
+
+  /* no command or bad command */
+  usage();
+
+  return 0;
+}
diff --git a/diags/chameleon/eth_test.c b/diags/chameleon/eth_test.c
new file mode 100644
index 0000000..734fc94
--- /dev/null
+++ b/diags/chameleon/eth_test.c
@@ -0,0 +1,538 @@
+/*
+ * (C) Copyright 2015 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <linux/if_packet.h>
+#include <linux/types.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "../common/io.h"
+#include "../common/util.h"
+#include "common.h"
+#include "mdio.h"
+
+#define ETH_PORT_NAME "eth0"
+#define MAX_NET_IF 2
+#define BUF_SIZ 1536
+#define ETH_TEST_MAX_CMD 4096
+#define ETH_TEST_MAX_RSP 4096
+#define ETH_TRAFFIC_PORT "wan0"
+#define ETH_TRAFFIC_REPORT_PERIOD 60
+#define ETH_TRAFFIC_MAX_REPORT_PERIOD 300
+#define ETH_TRAFFIC_TEST_PERIOD_SYMBOL "-p"
+// 100 Mb/s
+#define ETH_TRAFFIC_PER_PERIOD_MAX \
+  (((unsigned int)ETH_TRAFFIC_MAX_REPORT_PERIOD) * ((unsigned int)13107200))
+
+#define SERVER_PORT 8888
+#define MAX_CMD_SIZE 256
+#define SCAN_CMD_FORMAT "%256s"
+#define MAX_INT 0x7FFFFFFF
+
+#define ETH_SEND_DELAY_IN_USEC 1000
+#define ETH_MAX_LAN_PORTS 2
+#define ETH_WAIT_AFTER_LOOPBACK_SET 5
+#define ETH_PKTS_SENT_BEFORE_WAIT 0xFF
+#define ETH_PKTS_LEN_DEFAULT 128
+#define ETH_BUFFER_SIZE (ETH_PKTS_SENT_BEFORE_WAIT * ETH_PKTS_LEN_DEFAULT)
+#define ETH_LOOPBACK_PASS_FACTOR 0.8  // 80%
+#define ETH_TEST_FLUSH_NUM 5
+
+#define ETH_RX_NAME "RX"
+#define ETH_TX_NAME "TX"
+#define ETH_PACKETS_NAME "packets:"
+#define ETH_ERRORS_NAME "errors:"
+#define ETH_BYTES_NAME "bytes:"
+#define ONE_MEG (1024 * 1024)
+
+#define ETH_DEBUG_PORT_ADDR_REG 0x1D
+#define ETH_DEBUG_PORT_DATA_REG 0x1E
+#define ETH_EXT_LPBK_PORT_ADDR_OFFSET 0xB
+#define ETH_EXT_LPBK_PORT_SET_DATA 0x3C40
+#define ETH_EXT_LPBK_PORT_CLEAR_DATA 0xBC00
+#define ETH_STAT_CLEAR_CMD "ifstat > /dev/null"
+#define ETH_STAT_CMD "ifstat %s | sed '1,3d;5d'"
+#define ETH_STAT_RX_POS 5
+#define ETH_STAT_TX_POS 7
+#define ETH_STAT_WAIT_PERIOD 1  // sec
+#define ETH_STAT_PERCENT_MARGIN 95
+
+#define ETH0_SMI_REG 0xF1072004
+
+void send_mac_pkt(char *if_name, char *out_name, unsigned int xfer_len,
+                  unsigned int xfer_wait, int n,
+                  const unsigned char *dst_mac1) {
+  int sockfd, i;
+  struct ifreq if_idx;
+  struct ifreq if_mac, out_mac;
+  int tx_len = 0;
+  char sendbuf[BUF_SIZ];
+  struct ether_header *eh = (struct ether_header *)sendbuf;
+  struct sockaddr_ll socket_address;
+  unsigned char dst_mac[6] = {0, 0, 0, 0, 0, 0};
+
+  /* Open RAW socket to send on */
+  if ((sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) == -1) {
+    perror("socket");
+  }
+
+  /* Get the index of the interface to send on */
+  memset(&if_idx, 0, sizeof(if_idx));
+  safe_strncpy(if_idx.ifr_name, if_name, IFNAMSIZ - 1);
+  if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
+    perror("SIOCGIFINDEX");
+  }
+  /* Get the MAC address of the interface to send on */
+  memset(&out_mac, 0, sizeof(out_mac));
+  if (out_name != NULL) {
+    safe_strncpy(out_mac.ifr_name, out_name, IFNAMSIZ - 1);
+    if (ioctl(sockfd, SIOCGIFHWADDR, &out_mac) < 0) {
+      perror("out SIOCGIFHWADDR");
+    }
+  }
+  memset(&if_mac, 0, sizeof(if_mac));
+  safe_strncpy(if_mac.ifr_name, if_name, IFNAMSIZ - 1);
+  if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0) {
+    perror("SIOCGIFHWADDR");
+  }
+  if (out_name != NULL) {
+    dst_mac[0] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[0];
+    dst_mac[1] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[1];
+    dst_mac[2] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[2];
+    dst_mac[3] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[3];
+    dst_mac[4] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[4];
+    dst_mac[5] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[5];
+  } else if (dst_mac1 != NULL) {
+    dst_mac[0] = dst_mac1[0];
+    dst_mac[1] = dst_mac1[1];
+    dst_mac[2] = dst_mac1[2];
+    dst_mac[3] = dst_mac1[3];
+    dst_mac[4] = dst_mac1[4];
+    dst_mac[5] = dst_mac1[5];
+  } else {
+    printf("Invalid out_name and dst_mac.\n");
+    return;
+  }
+
+  /* Construct the Ethernet header */
+  // memset(sendbuf, 0, BUF_SIZ);
+  for (i = 0; i < BUF_SIZ; ++i) {
+    sendbuf[i] = 0xA5;
+  }
+  /* Ethernet header */
+  eh->ether_shost[0] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[0];
+  eh->ether_shost[1] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[1];
+  eh->ether_shost[2] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[2];
+  eh->ether_shost[3] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[3];
+  eh->ether_shost[4] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[4];
+  eh->ether_shost[5] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[5];
+  eh->ether_dhost[0] = dst_mac[0];
+  eh->ether_dhost[1] = dst_mac[1];
+  eh->ether_dhost[2] = dst_mac[2];
+  eh->ether_dhost[3] = dst_mac[3];
+  eh->ether_dhost[4] = dst_mac[4];
+  eh->ether_dhost[5] = dst_mac[5];
+  /* Ethertype field */
+  eh->ether_type = htons(ETH_P_IP);
+  tx_len += sizeof(struct ether_header);
+
+  /* Packet data */
+  sendbuf[tx_len++] = 0xde;
+  sendbuf[tx_len++] = 0xad;
+  sendbuf[tx_len++] = 0xbe;
+  sendbuf[tx_len++] = 0xef;
+
+  /* Index of the network device */
+  socket_address.sll_ifindex = if_idx.ifr_ifindex;
+  /* Address length*/
+  socket_address.sll_halen = ETH_ALEN;
+  /* Destination MAC */
+  socket_address.sll_addr[0] = dst_mac[0];
+  socket_address.sll_addr[1] = dst_mac[1];
+  socket_address.sll_addr[2] = dst_mac[2];
+  socket_address.sll_addr[3] = dst_mac[3];
+  socket_address.sll_addr[4] = dst_mac[4];
+  socket_address.sll_addr[5] = dst_mac[5];
+
+  /* Send packet */
+  if (n < 0) {
+    while (1) {
+      if (sendto(sockfd, sendbuf, xfer_len, 0,
+                 (struct sockaddr *)&socket_address,
+                 sizeof(struct sockaddr_ll)) < 0) {
+        printf("Send failed at msg %d\n", i);
+        break;
+      }
+      if (xfer_wait > 0) {
+        if ((i & ETH_PKTS_SENT_BEFORE_WAIT) == 0) {
+          usleep(xfer_wait);
+        }
+      }
+    }
+  } else {
+    for (i = 0; i < n; ++i) {
+      if (sendto(sockfd, sendbuf, xfer_len, 0,
+                 (struct sockaddr *)&socket_address,
+                 sizeof(struct sockaddr_ll)) < 0) {
+        printf("Send failed at msg %d\n", i);
+        break;
+      }
+      if (xfer_wait > 0) {
+        if ((i & ETH_PKTS_SENT_BEFORE_WAIT) == 0) {
+          usleep(xfer_wait);
+        }
+      }
+    }
+  }
+  close(sockfd);
+}
+
+/* If extra is not NULL, rsp is returned as the string followed extra */
+int scan_command(char *command, char *rsp, char *extra) {
+  FILE *fp;
+  fp = popen(command, "r");
+  if (fp != NULL) {
+    if (extra != NULL) {
+      while (fscanf(fp, "%s", rsp) != EOF) {
+        if (!strcmp(rsp, extra)) {
+          if (fscanf(fp, "%s", rsp) <= 0)
+            return -1;
+          else
+            return 0;
+        }
+      }
+    } else {
+      fscanf(fp, SCAN_CMD_FORMAT, rsp);
+    }
+    pclose(fp);
+  } else {
+    return -1;
+  }
+  return 0;
+}
+
+int net_stat(unsigned int *rx_bytes, unsigned int *tx_bytes, char *name) {
+  static unsigned int tx_stat = 0;
+  static unsigned int rx_stat = 0;
+  char command[MAX_CMD_SIZE], rsp[MAX_CMD_SIZE];
+  unsigned int tmp;
+
+  if (strcmp(name, ETH_PORT_NAME) != 0) {
+    return -1;
+  }
+
+  snprintf(command, sizeof(command),
+           "cat /sys/class/net/%s/statistics/tx_bytes", name);
+  if (scan_command(command, rsp, NULL) == 0) *tx_bytes = strtoul(rsp, NULL, 10);
+
+  snprintf(command, sizeof(command),
+           "cat /sys/class/net/%s/statistics/rx_bytes", name);
+  if (scan_command(command, rsp, NULL) == 0) *rx_bytes = strtoul(rsp, NULL, 10);
+
+  if (*tx_bytes >= tx_stat) {
+    *tx_bytes -= tx_stat;
+    tx_stat += *tx_bytes;
+  } else {
+    tmp = *tx_bytes;
+    // tx_bytes is uint. It will continue to increment till wrap around
+    // When it wraps around, the current value will be less than the
+    // previous one. That is why this logic kicked in.
+    *tx_bytes += (0xffffffff - tx_stat);
+    tx_stat = tmp;
+  }
+
+  if (*rx_bytes >= rx_stat) {
+    *rx_bytes -= rx_stat;
+    rx_stat += *rx_bytes;
+  } else {
+    tmp = *rx_bytes;
+    // rx_bytes is uint. It will continue to increment till wrap around
+    // When it wraps around, the current value will be less than the
+    // previous one. That is why this logic kicked in.
+    *rx_bytes += (0xffffffff - rx_stat);
+    rx_stat = tmp;
+  }
+  return 0;
+}
+
+// Return 0 if lost carrier. Otherwise, 1
+int get_carrier_state(char *name) {
+  char command[MAX_CMD_SIZE], rsp[MAX_CMD_SIZE];
+
+  snprintf(command, sizeof(command), "cat /sys/class/net/%s/carrier", name);
+  if (scan_command(command, rsp, NULL) == 0) {
+    if (strcmp(rsp, "0") != 0) return 1;
+  }
+  return 0;
+}
+
+// This is the same as sleep but monitor the link carrier every second
+// Return true if the carrier is good every second. Otherwise false
+bool sleep_and_check_carrier(int duration, char *if_name) {
+  bool good_carrier = true;
+  int i;
+  for (i = 0; i < duration; ++i) {
+    if (get_carrier_state(if_name) == 0) good_carrier = false;
+    sleep(1);
+  }
+  return good_carrier;
+}
+
+int get_if_ip(char *name, unsigned int *ip) {
+  char command[ETH_TEST_MAX_CMD], rsp[ETH_TEST_MAX_RSP];
+  bool found = false;
+
+  snprintf(command, sizeof(command), "ip addr show %s", name);
+  if (scan_command(command, rsp, "inet") == 0) {
+    if (sscanf(rsp, "%u.%u.%u.%u", ip, (ip + 1), (ip + 2), (ip + 3)) <= 0) {
+      return -1;
+    }
+    found = true;
+  }
+
+  if (!found) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static void phy_read_usage(void) {
+  printf("phy_read <ifname> <reg>\n");
+  printf("Example:\n");
+  printf("phy_read %s 2\n", ETH_PORT_NAME);
+}
+
+int phy_read(int argc, char *argv[]) {
+  int rc = 0;
+  unsigned int tmp, reg, val = 0;
+
+  if (argc != 3) {
+    phy_read_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], ETH_PORT_NAME) != 0) {
+    printf("Currently support only port %s\n", ETH_PORT_NAME);
+    return -1;
+  }
+
+  reg = strtol(argv[2], NULL, 0);
+  tmp = reg;
+  reg = (reg << 21) + (1 << 26);
+  rc = write_physical_addr(ETH0_SMI_REG, reg);
+  rc += read_physical_addr(ETH0_SMI_REG, &val);
+  val &= 0xFFFF;
+  printf("PHY %s Reg 0x%x is 0x%x\n", argv[1], tmp, val);
+  return 0;
+}
+
+static void phy_write_usage(void) {
+  printf("phy_write <ifname> <reg> <val>\n");
+  printf("Example:\n");
+  printf("phy_write i%s 22 0x6\n", ETH_PORT_NAME);
+}
+
+int phy_write(int argc, char *argv[]) {
+  int rc = 0;
+  int tmp, reg, val;
+
+  if (argc != 4) {
+    phy_write_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], ETH_PORT_NAME) != 0) {
+    printf("Currently support only port %s\n", ETH_PORT_NAME);
+    return -1;
+  }
+
+  reg = strtol(argv[2], NULL, 0);
+  tmp = reg;
+  val = strtol(argv[3], NULL, 16);
+  reg = (reg << 21) + (0 << 26);
+  val &= 0xFFFF;
+  rc = write_physical_addr(ETH0_SMI_REG, reg);
+  rc += write_physical_addr(ETH0_SMI_REG, (reg + val));
+  printf("PHY %s Reg 0x%x = 0x%x\n", argv[1], tmp, val);
+  return 0;
+}
+
+static void send_if_usage(void) {
+  printf("send_if <source if> <num> [-t <delay between pkts send>]\n");
+  printf("Example:\n");
+  printf("send_if lan0 100\n");
+  printf("send 100 msg out of lan0\n");
+}
+
+int send_if(int argc, char *argv[]) {
+  int n;
+  char if_name[IFNAMSIZ];
+  unsigned int xfer_wait = ETH_SEND_DELAY_IN_USEC;
+  unsigned char dst_mac[6] = {0, 0, 0, 0, 0, 0};
+
+  /* Get interface name */
+  if (argc == 5) {
+    if (strcmp(argv[3], "-t") == 0) {
+      xfer_wait = strtoul(argv[4], NULL, 10);
+    } else {
+      send_if_usage();
+      return -1;
+    }
+  } else if (argc != 3) {
+    send_if_usage();
+    return -1;
+  }
+
+  strcpy(if_name, argv[1]);
+  n = strtol(argv[2], NULL, 10);
+
+  send_mac_pkt(if_name, NULL, BUF_SIZ, xfer_wait, n, dst_mac);
+
+  printf("Sent %d pkt of size %d from %s to %s\n", n, BUF_SIZ, argv[1],
+         argv[2]);
+
+  return 0;
+}
+
+static void loopback_test_usage(void) {
+  printf(
+      "loopback_test <interface> <duration in secs> "
+      "[<%s print-period in secs>]\n",
+      ETH_TRAFFIC_TEST_PERIOD_SYMBOL);
+  printf("- duration >=1 or -1 (forever)\n");
+  printf("- print-period >= 0 and <= %d\n", ETH_TRAFFIC_MAX_REPORT_PERIOD);
+  printf("- print-period > 0 if duration > 0\n");
+  printf("- print-period = 0 prints only the summary\n");
+}
+
+int loopback_test(int argc, char *argv[]) {
+  int duration, num = -1, collected_count = 0;
+  int pid, pid1, print_period = ETH_TRAFFIC_REPORT_PERIOD;
+  unsigned int pkt_len = ETH_PKTS_LEN_DEFAULT, rx_bytes, tx_bytes;
+  bool print_every_period = true, traffic_problem = false, problem = false;
+  float average_throughput = 0.0, throughput;
+  unsigned char dst_mac[6] = {0, 0, 0, 0, 0, 0};
+
+  if ((argc != 3) && (argc != 5)) {
+    loopback_test_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], ETH_PORT_NAME) != 0) {
+    printf("Invalid Ethernet Interface %s\n", argv[1]);
+    return -1;
+  }
+
+  duration = strtol(argv[2], NULL, 0);
+  if ((duration < -1) || (duration == 0)) {
+    loopback_test_usage();
+    return -1;
+  }
+
+  if (argc == 5) {
+    if (strcmp(argv[3], ETH_TRAFFIC_TEST_PERIOD_SYMBOL) != 0) {
+      loopback_test_usage();
+      return -1;
+    }
+
+    print_period = strtoul(argv[4], NULL, 0);
+    if (((print_period == 0) && (duration < 0)) || (print_period < 0) ||
+        (print_period > ETH_TRAFFIC_MAX_REPORT_PERIOD)) {
+      loopback_test_usage();
+      return -1;
+    }
+    if (print_period == 0) {
+      print_every_period = false;
+      print_period = ETH_TRAFFIC_REPORT_PERIOD;
+    }
+  }
+
+  // eth_external_loopback(argv[1], true);
+  system_cmd("ethtool -s " ETH_PORT_NAME " autoneg off duplex full speed 100");
+  sleep(2);
+
+  net_stat(&rx_bytes, &tx_bytes, argv[1]);
+
+  pid = fork();
+  if (pid < 0) {
+    printf("Server fork error %d, errno %d\n", pid, errno);
+    return -1;
+  }
+  if (pid == 0) {
+    // Child process
+    send_mac_pkt(argv[1], NULL, pkt_len, 0, num, dst_mac);
+    exit(0);
+  }
+  // Parent process
+  pid1 = pid;
+
+  while (duration != 0) {
+    if (duration >= 0) {
+      if (duration <= print_period) {
+        problem = !sleep_and_check_carrier(duration, argv[1]);
+        print_period = duration;
+        duration = 0;
+        kill(pid1, SIGKILL);
+        // printf("Killed processes %d and %d\n", pid1, pid2);
+      } else {
+        duration -= print_period;
+        problem = !sleep_and_check_carrier(print_period, argv[1]);
+      }
+    } else {
+      problem = !sleep_and_check_carrier(print_period, argv[1]);
+    }
+
+    if (duration > 0) kill(pid1, SIGSTOP);
+    sleep(ETH_STAT_WAIT_PERIOD);
+    net_stat(&rx_bytes, &tx_bytes, argv[1]);
+    if (duration > 0) kill(pid1, SIGCONT);
+    ++collected_count;
+    // Give 1% margin
+    if ((rx_bytes == 0) ||
+        (((tx_bytes / 100) * ETH_STAT_PERCENT_MARGIN) > rx_bytes)) {
+      problem = true;
+    }
+    if ((rx_bytes > ETH_TRAFFIC_PER_PERIOD_MAX) ||
+        (tx_bytes > ETH_TRAFFIC_PER_PERIOD_MAX)) {
+      problem = true;
+    }
+    traffic_problem |= problem;
+    if (!problem) {
+      throughput = (((float)rx_bytes) * 8) / (float)(print_period * ONE_MEG);
+      average_throughput += throughput;
+    } else {
+      throughput = 0.0;
+    }
+    if (print_every_period) {
+      printf("%s %s: %3.3f Mb/s (%d:%d)\n", (problem) ? FAIL_TEXT : PASS_TEXT,
+             argv[1], throughput, tx_bytes, rx_bytes);
+    }
+    problem = false;
+  }
+
+  // eth_external_loopback(argv[1], false);
+  system_cmd("ethtool -s " ETH_PORT_NAME " autoneg on");
+
+  average_throughput /= ((float)collected_count);
+  printf("%s overall %s: %3.3f Mb/s\n",
+         (traffic_problem) ? FAIL_TEXT : PASS_TEXT, argv[1],
+         average_throughput);
+
+  return 0;
+}
diff --git a/diags/chameleon/gpio.c b/diags/chameleon/gpio.c
new file mode 100644
index 0000000..0ec2bd2
--- /dev/null
+++ b/diags/chameleon/gpio.c
@@ -0,0 +1,407 @@
+/*
+ * (C) Copyright 2016 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "../common/io.h"
+#include "../common/util.h"
+#include "i2c.h"
+
+#define STOP_STR "stop"
+#define START_STR "start"
+#define LINE_MAX 128
+#define GET_TEMP "cat /sys/devices/platform/KW2Thermal.0/temp1_input"
+#define RED_LED "red"
+#define RED_LED_BRIGHTNESS "/sys/class/leds/sys-red/brightness"
+#define BLUE_LED "blue"
+#define BLUE_LED_BRIGHTNESS "/sys/class/leds/sys-blue/brightness"
+#define MPP_CONTROL_REG 0x00018000
+#define GPIO_DATA_OUT_REG 0x00018100
+#define GPIO_DATA_OUT_EN_REG 0x00018104
+#define GPIO_DATA_IN_REG 0x00018110
+#define GPIO_HIGH_DATA_OUT_REG 0x00018124
+#define GPIO_HIGH_DATA_OUT_EN_REG 0x00018128
+#define GPIO_HIGH_DATA_IN_REG 0x00018134
+#define GPIO_HIGH_PIN_START_NUM 32
+#define GPIO_MAX_PIN_NUM 37
+#define GPIO_DIR_IN_STR "in"
+#define GPIO_DIR_OUT_STR "out"
+#define GPIO_21_PON_TX_DIS 21
+#define AVANTA_BASE_ADDR 0xF1000000
+
+#define BOARD_TEMP_BUS 0
+#define BOARD_TEMP_ADDR 0x48
+#define BOARD_TEMP_ADDR_LEN 1
+#define BOARD_TEMP_REG 0
+#define BOARD_TEMP_LEN 2
+
+static void gpio_set_tx_enable_usage(void) {
+  printf("gpio_set_tx_enable <0 | 1>\n");
+  printf("Example:\n");
+  printf("gpio_set_tx_enable 0\n");
+  printf("set TX_ENABLE pin to low\n");
+}
+
+int gpio_set_tx_enable(int argc, char *argv[]) {
+  unsigned int tx_en_val, out_en, out, mask = 1 << GPIO_21_PON_TX_DIS;
+  unsigned int out_en_reg = AVANTA_BASE_ADDR + GPIO_DATA_OUT_EN_REG;
+  unsigned int out_reg = AVANTA_BASE_ADDR + GPIO_DATA_OUT_REG;
+  unsigned int mpp_ctrl =
+      AVANTA_BASE_ADDR + MPP_CONTROL_REG + (4 * (GPIO_21_PON_TX_DIS / 8));
+
+  if (argc != 2) {
+    gpio_set_tx_enable_usage();
+    return -1;
+  }
+  tx_en_val = get_num(argv[1]);
+  if (tx_en_val != 0 && tx_en_val != 1) {
+    printf("Invalid TX_ENABLE value %d\n", tx_en_val);
+    gpio_set_tx_enable_usage();
+    return -1;
+  }
+  if (read_physical_addr(out_en_reg, &out_en) != 0) {
+    printf("Read address 0x%x failed\n", out_en_reg);
+    return -1;
+  }
+  if ((out_en & mask) != 0) {
+    out_en &= ~mask;
+    if (write_physical_addr(out_en_reg, out_en) != 0) {
+      printf("Write address 0x%x of value 0x%x failed\n", out_en_reg, out_en);
+      return -1;
+    }
+  }
+  if (read_physical_addr(out_reg, &out) != 0) {
+    printf("Read address 0x%x failed\n", out_reg);
+    return -1;
+  }
+  if ((out & mask) != (tx_en_val << GPIO_21_PON_TX_DIS)) {
+    out = (out & (~mask)) | (tx_en_val << GPIO_21_PON_TX_DIS);
+    if (write_physical_addr(out_reg, out) != 0) {
+      printf("Write address 0x%x of value 0x%x failed\n", out_reg, out);
+      return -1;
+    }
+  }
+  // set MPP function to gpio
+  if (read_physical_addr(mpp_ctrl, &out) != 0) {
+    printf("Read address 0x%x failed\n", mpp_ctrl);
+    return -1;
+  }
+  out &= ~(0xFF << (4 * (GPIO_21_PON_TX_DIS % 8)));
+  if (write_physical_addr(mpp_ctrl, out) != 0) {
+    printf("Write address 0x%x of value 0x%x failed\n", mpp_ctrl, out);
+    return -1;
+  }
+  printf("Set TX_ENABLE to %d\n", tx_en_val);
+  return 0;
+}
+
+static void gpio_stat_usage(void) {
+  printf("gpio_stat <GPIO pin num (0 to %d)>\n", GPIO_MAX_PIN_NUM);
+  printf("Example:\n");
+  printf("gpio_stat 21\n");
+  printf("Display the status of the specified GPIO pin\n");
+}
+
+int gpio_stat(int argc, char *argv[]) {
+  unsigned int pin_num, pin_mask, in, out, out_en;
+  unsigned int out_reg, out_en_reg, in_reg;
+
+  if (argc != 2) {
+    gpio_stat_usage();
+    return -1;
+  }
+
+  pin_num = get_num(argv[1]);
+
+  if (pin_num > GPIO_MAX_PIN_NUM) {
+    printf("Invalid GPIO pin number %d\n", pin_num);
+    gpio_stat_usage();
+    return -1;
+  }
+  if (pin_num >= GPIO_HIGH_PIN_START_NUM) {
+    out_reg = AVANTA_BASE_ADDR + GPIO_HIGH_DATA_OUT_REG;
+    out_en_reg = AVANTA_BASE_ADDR + GPIO_HIGH_DATA_OUT_EN_REG;
+    in_reg = AVANTA_BASE_ADDR + GPIO_HIGH_DATA_IN_REG;
+    pin_mask = 1 << (pin_num - GPIO_HIGH_PIN_START_NUM);
+  } else {
+    out_reg = AVANTA_BASE_ADDR + GPIO_DATA_OUT_REG;
+    out_en_reg = AVANTA_BASE_ADDR + GPIO_DATA_OUT_EN_REG;
+    in_reg = AVANTA_BASE_ADDR + GPIO_DATA_IN_REG;
+    pin_mask = 1 << pin_num;
+  }
+  if (read_physical_addr(out_reg, &out) != 0) {
+    printf("Read address 0x%x failed\n", out_reg);
+    return -1;
+  }
+  if (read_physical_addr(out_en_reg, &out_en) != 0) {
+    printf("Read address 0x%x failed\n", out_en_reg);
+    return -1;
+  }
+  if (read_physical_addr(in_reg, &in) != 0) {
+    printf("Read address 0x%x failed\n", in_reg);
+    return -1;
+  }
+  printf("GPIO pin %d: DIR: %s IN: 0x%x OUT: 0x%x\n", pin_num,
+         (out_en & pin_mask) ? "in" : "out", (in & pin_mask) ? 1 : 0,
+         (out & pin_mask) ? 1 : 0);
+  printf("GPIO regs: EN 0x%08x OUT 0x%08x IN 0x%08x MASK 0x%08x\n", out_en, out,
+         in, pin_mask);
+
+  return 0;
+}
+
+static void gpio_set_dir_usage(void) {
+  printf("gpio_set_dir <GPIO pin num (0 to %d)> <%s | %s>\n", GPIO_MAX_PIN_NUM,
+         GPIO_DIR_IN_STR, GPIO_DIR_OUT_STR);
+  printf("Example:\n");
+  printf("gpio_set_dir 21 %s\n", GPIO_DIR_OUT_STR);
+  printf("set the specified GPIO pin to input or output\n");
+}
+
+int gpio_set_dir(int argc, char *argv[]) {
+  unsigned int pin_num, pin_mask, out_en, out_en_reg;
+  bool is_output = true;
+
+  if (argc != 3) {
+    gpio_set_dir_usage();
+    return -1;
+  }
+
+  pin_num = get_num(argv[1]);
+
+  if (pin_num > GPIO_MAX_PIN_NUM) {
+    printf("Invalid GPIO pin number %d\n", pin_num);
+    gpio_set_dir_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[2], GPIO_DIR_IN_STR) == 0)
+    is_output = false;
+  else if (strcmp(argv[2], GPIO_DIR_OUT_STR) == 0)
+    is_output = true;
+  else {
+    printf("Invalid GPIO pin direction %s\n", argv[2]);
+    gpio_set_dir_usage();
+    return -1;
+  }
+
+  if (pin_num >= GPIO_HIGH_PIN_START_NUM) {
+    out_en_reg = AVANTA_BASE_ADDR + GPIO_HIGH_DATA_OUT_EN_REG;
+    pin_mask = 1 << (pin_num - GPIO_HIGH_PIN_START_NUM);
+  } else {
+    out_en_reg = AVANTA_BASE_ADDR + GPIO_DATA_OUT_EN_REG;
+    pin_mask = 1 << pin_num;
+  }
+  if (read_physical_addr(out_en_reg, &out_en) != 0) {
+    printf("Read address 0x%x failed\n", out_en_reg);
+    return -1;
+  }
+  if (is_output)
+    out_en &= ~pin_mask;
+  else
+    out_en |= pin_mask;
+  if (write_physical_addr(out_en_reg, out_en) != 0) {
+    printf("Write address 0x%x of value 0x%x failed\n", out_en_reg, out_en);
+    return -1;
+  }
+  printf("GPIO pin %d set as %s\n", pin_num, (is_output) ? "output" : "input");
+
+  return 0;
+}
+
+static void gpio_set_out_val_usage(void) {
+  printf("gpio_set_out_val <GPIO pin num (0 to %d)> <0 | 1>\n",
+         GPIO_MAX_PIN_NUM);
+  printf("Example:\n");
+  printf("gpio_set_out_val 21 0\n");
+  printf("set the specified GPIO pin output to 0\n");
+}
+
+int gpio_set_out_val(int argc, char *argv[]) {
+  unsigned int pin_num, pin_val, pin_mask, out, out_reg;
+
+  if (argc != 3) {
+    gpio_set_out_val_usage();
+    return -1;
+  }
+
+  pin_num = get_num(argv[1]);
+  pin_val = get_num(argv[2]);
+
+  if (pin_num > GPIO_MAX_PIN_NUM) {
+    printf("Invalid GPIO pin number %d\n", pin_num);
+    gpio_set_out_val_usage();
+    return -1;
+  }
+
+  if (pin_val != 0 && pin_val != 1) {
+    printf("Invalid GPIO pin value %d\n", pin_val);
+    gpio_set_out_val_usage();
+    return -1;
+  }
+
+  if (pin_num >= GPIO_HIGH_PIN_START_NUM) {
+    out_reg = AVANTA_BASE_ADDR + GPIO_HIGH_DATA_OUT_REG;
+    pin_mask = 1 << (pin_num - GPIO_HIGH_PIN_START_NUM);
+  } else {
+    out_reg = AVANTA_BASE_ADDR + GPIO_DATA_OUT_REG;
+    pin_mask = 1 << pin_num;
+  }
+  if (read_physical_addr(out_reg, &out) != 0) {
+    printf("Read address 0x%x failed\n", out_reg);
+    return -1;
+  }
+  if (pin_val)
+    out |= pin_mask;
+  else
+    out &= ~pin_mask;
+  if (write_physical_addr(out_reg, out) != 0) {
+    printf("Write address 0x%x of value 0x%x failed\n", out_reg, out);
+    return -1;
+  }
+  printf("GPIO pin %d output set as %d\n", pin_num, pin_val);
+
+  return 0;
+}
+
+static void gpio_mailbox_usage(void) {
+  printf("gpio_mailbox <%s | %s>\n", STOP_STR, START_STR);
+  printf("Example:\n");
+  printf("gpio_mailbox %s\n", STOP_STR);
+  printf("Stop gpio_mailbox from running\n");
+}
+
+int gpio_mailbox(int argc, char *argv[]) {
+  if (argc != 2) {
+    gpio_mailbox_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], STOP_STR) == 0) {
+    system_cmd("pkill -9 -f gpio-mailbox");
+  } else if (strcmp(argv[1], START_STR) == 0) {
+    system_cmd("gpio-mailbox 2>&1 | logos gpio-mailbox &");
+  } else {
+    gpio_mailbox_usage();
+    return -1;
+  }
+
+  return 0;
+}
+
+static void get_temp_usage(void) {
+  printf("get_temp\n");
+  printf("display CPU temperature in mili-degree C\n");
+  printf("Example\n");
+  printf("  prism-diags get_temp\n");
+}
+
+int get_temp(int argc, char *argv[]) {
+  float temp;
+  int t;
+  uint8_t value[BOARD_TEMP_LEN];
+  FILE *fp;
+
+  if (argc != 1 || argv == NULL) {
+    get_temp_usage();
+    return -1;
+  }
+  if (i2cr(BOARD_TEMP_BUS, BOARD_TEMP_ADDR, BOARD_TEMP_REG, BOARD_TEMP_ADDR_LEN,
+           BOARD_TEMP_LEN, value) != 0) {
+    printf("Temp sensor read address 0x%x failed\n", BOARD_TEMP_ADDR);
+    return -1;
+  }
+  temp = (float)(value[0]) + (((float)(value[1])) / 256.0);
+  if (value[0] & 0x80) {
+    temp -= 256.0;
+  }
+  printf("  Board Temp: %3.3f\n", temp);
+  fp = popen(GET_TEMP, "r");
+  if (fp != NULL) {
+    if (fscanf(fp, "%d", &t) <= 0) {
+      printf("Failed to read CPU temp\n");
+      pclose(fp);
+      return -1;
+    }
+    pclose(fp);
+    temp = (float)t / 1000.0;
+    printf("  CPU Temp: %3.3f\n", temp);
+  } else {
+    printf("Failed to get CPU temp\n");
+    return -1;
+  }
+
+  return 0;
+}
+
+static void set_leds_usage(void) {
+  printf("set_leds <%s | %s> <value>\n", RED_LED, BLUE_LED);
+  printf("set specified LED brightness to <valueue>\n");
+  printf("  max value is 100. set value to 0 to turn it off\n");
+  printf("Example\n");
+  printf("  prism-diags set_leds %s 10\n", RED_LED);
+}
+
+int set_leds(int argc, char *argv[]) {
+  int value;
+  char cmd[LINE_MAX];
+
+  if (argc != 3) {
+    set_leds_usage();
+    return -1;
+  }
+  value = strtol(argv[2], NULL, 10);
+
+  if (strcmp(argv[1], RED_LED) == 0) {
+    sprintf(cmd, "echo %d > %s", value, RED_LED_BRIGHTNESS);
+  } else if (strcmp(argv[1], BLUE_LED) == 0) {
+    sprintf(cmd, "echo %d > %s", value, BLUE_LED_BRIGHTNESS);
+  } else {
+    printf("Unknown LED\n");
+    set_leds_usage();
+    return -1;
+  }
+  system_cmd(cmd);
+  printf("Set %s LED brightness to %d\n", argv[1], value);
+
+  return 0;
+}
+
+static void get_leds_usage(void) {
+  printf("get_leds <%s | %s>\n", RED_LED, BLUE_LED);
+  printf("get specified LED brightness\n");
+  printf("Example\n");
+  printf("  prism-diags get_leds %s\n", RED_LED);
+}
+
+int get_leds(int argc, char *argv[]) {
+  char cmd[LINE_MAX];
+
+  if (argc != 2) {
+    get_leds_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], RED_LED) == 0) {
+    sprintf(cmd, "cat %s", RED_LED_BRIGHTNESS);
+  } else if (strcmp(argv[1], BLUE_LED) == 0) {
+    sprintf(cmd, "cat %s", BLUE_LED_BRIGHTNESS);
+  } else {
+    printf("Unknown LED\n");
+    get_leds_usage();
+    return -1;
+  }
+  printf("%s LED brightness is ", argv[1]);
+  fflush(stdout);
+  system_cmd(cmd);
+
+  return 0;
+}
diff --git a/diags/chameleon/i2c.c b/diags/chameleon/i2c.c
new file mode 100644
index 0000000..1107bfc
--- /dev/null
+++ b/diags/chameleon/i2c.c
@@ -0,0 +1,163 @@
+/*
+ * (C) Copyright 2014 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "i2c.h"
+
+extern int ioctl(int, int, void *);
+
+int i2cr(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t *buf) {
+  char filename[FILENAME_SIZE];
+  int file;
+  int i;
+  uint32_t temp;
+  unsigned char addrbuf[4]; /* up to 4 byte addressing ! */
+  int return_code = 0;
+  struct i2c_msg message[2];
+  struct i2c_rdwr_ioctl_data rdwr_arg;
+  unsigned int read_data_len;
+
+  /* open the I2C adapter */
+  snprintf(filename, sizeof(filename), I2C_DEV_FILE, controller);
+  if ((file = open(filename, O_RDWR)) < 0) {
+    printf("I2C Error open file %s: %s", filename, strerror(errno));
+    return -1;
+  }
+
+  if ((return_code = flock(file, LOCK_EX)) < 0) {
+    printf("I2C Error lock file %s: %s", filename, strerror(errno));
+    goto i2cr_cleanup;
+  }
+
+  /* if we need to send addr, use combined transaction */
+  if (addr_len > 0) {
+    /* build struct i2c_msg 0 */
+    message[0].addr = device_addr;
+    message[0].flags = 0;
+    message[0].len = addr_len;
+    temp = cell_addr;
+    /* store addr into buffer */
+    for (i = addr_len - 1; i >= 0; i--) {
+      addrbuf[i] = temp & 0xff;
+      temp >>= 8;
+    }
+    message[0].buf = addrbuf;
+
+    /* build struct i2c_msg 1 */
+    message[1].addr = device_addr;
+    message[1].flags = I2C_M_RD;
+    message[1].len = data_len;
+    message[1].buf = buf;
+
+    /* build arg */
+    rdwr_arg.msgs = message;
+    rdwr_arg.nmsgs = 2;
+
+    return_code = ioctl(file, I2C_RDWR, &rdwr_arg);
+
+    /* since we pass in rdwr_arg.nmsgs = 2, expect a return of 2 on success */
+    if (return_code == 2) {
+      return_code = 0;
+    }
+
+    goto i2cr_cleanup;
+  }
+
+  /* setup device address */
+  if ((return_code = ioctl(file, I2C_SLAVE, (void *)(uintptr_t)device_addr)) <
+      0) {
+    printf("I2C Error: Could not set device address to %x: %s", device_addr,
+           strerror(errno));
+    goto i2cr_cleanup;
+  }
+
+  /* now read data out of the device */
+  read_data_len = read(file, buf, data_len);
+  if (read_data_len == data_len) {
+    return_code = 0;
+  }
+
+i2cr_cleanup:
+  flock(file, LOCK_UN);
+  close(file);
+
+  return return_code;
+}
+
+int i2cw(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t *buf) {
+  char filename[FILENAME_SIZE];
+  int file;
+  int i;
+  uint32_t temp;
+  uint8_t tempbuf[I2C_PAGE_SIZE + 4];
+  int return_code;
+  uint8_t *writebuf = buf;
+  unsigned int write_data_len;
+
+  /* check data len */
+  if (data_len > I2C_PAGE_SIZE) {
+    return -1;
+  }
+
+  /* open the corrrsponding I2C adapter */
+  snprintf(filename, sizeof(filename), I2C_DEV_FILE, controller);
+  if ((file = open(filename, O_RDWR)) < 0) {
+    printf("I2C Error open file %s: %s", filename, strerror(errno));
+    return -1;
+  }
+
+  if ((return_code = flock(file, LOCK_EX)) < 0) {
+    printf("I2C Error lock file %s: %s", filename, strerror(errno));
+    goto i2cw_cleanup;
+  }
+
+  /* setup device address */
+  if ((return_code =
+           ioctl(file, I2C_SLAVE_FORCE, (void *)(uintptr_t)device_addr)) < 0) {
+    printf("I2C Error: Could not set device address to %x: %s", device_addr,
+           strerror(errno));
+    goto i2cw_cleanup;
+  }
+
+  /* if we need to send addr */
+  if (addr_len > 0) {
+    temp = cell_addr;
+    /* store addr into buffer */
+    for (i = (int)(addr_len - 1); i >= 0; i--) {
+      tempbuf[i] = temp & 0xff;
+      temp >>= 8;
+    }
+    /* copy data over into tempbuf, right after the cell address */
+    for (i = 0; i < (int)(data_len); i++) {
+      tempbuf[addr_len + i] = buf[i];
+    }
+    writebuf = tempbuf;
+  }
+
+  /* now write addr + data out to the device */
+  write_data_len = write(file, writebuf, addr_len + data_len);
+
+  if (write_data_len == (addr_len + data_len)) {
+    return_code = 0;
+  }
+
+i2cw_cleanup:
+  flock(file, LOCK_UN);
+  close(file);
+
+  return return_code;
+}
diff --git a/diags/chameleon/i2c.h b/diags/chameleon/i2c.h
new file mode 100644
index 0000000..0c5f028
--- /dev/null
+++ b/diags/chameleon/i2c.h
@@ -0,0 +1,44 @@
+/*
+ * (C) Copyright 2014 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#ifndef optimus_i2c_h
+#define optimus_i2c_h
+
+#include <inttypes.h>
+
+#define I2C_DEV_FILE   "/dev/i2c-%d"
+#define I2C_PAGE_SIZE  16
+#define I2C_M_RD       0x01
+#define FILENAME_SIZE  64
+
+/*
+ * I2C Message - used for pure i2c transaction, also from /dev interface
+ */
+struct i2c_msg {
+  uint16_t addr;  /* slave address */
+  uint16_t flags;
+  uint16_t len;   /* msg length */
+  uint8_t *buf;   /* pointer to msg data */
+};
+
+/* This is the structure as used in the I2C_RDWR ioctl call */
+struct i2c_rdwr_ioctl_data {
+  struct i2c_msg *msgs;  /* pointers to i2c_msgs */
+  uint32_t nmsgs;        /* number of i2c_msgs */
+};
+
+#define I2C_SLAVE        0x0703  /* Change slave address */
+#define I2C_SLAVE_FORCE  0x0706  /* Use this slave address, even if it
+                                    is already in use by a driver! */
+#define I2C_RDWR         0x0707  /* Combined R/W transfer (one stop only) */
+
+int i2cr(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t* buf);
+
+int i2cw(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t* buf);
+
+#endif  // optimus_i2c_h
diff --git a/diags/chameleon/i2c_cmd.c b/diags/chameleon/i2c_cmd.c
new file mode 100644
index 0000000..a7bfe32
--- /dev/null
+++ b/diags/chameleon/i2c_cmd.c
@@ -0,0 +1,182 @@
+/*
+ * (C) Copyright 2014 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "i2c.h"
+
+#define I2C_READ_BUF_SIZE 1024
+#define DISPLAY_WIDTH 8
+
+int i2cread(int argc, char *argv[]);
+int i2cwrite(int argc, char *argv[]);
+int i2cprobe(int argc, char *argv[]);
+
+static void i2cread_usage(void) {
+  printf(
+      "i2cread bus# dev-address register-offset"
+      " address-len num-byte-to-read\n");
+  printf("Example:\n");
+  printf("i2cread 1 0x2c 0x40 1 1\n");
+  printf(
+      "Read from bus 1  device 0x2c, register 0x40,"
+      " address length is 1, read 1 byte\n");
+}
+
+int i2cread(int argc, char *argv[]) {
+  uint8_t device_addr;
+  uint32_t cell_addr;
+  uint32_t addr_len;
+  uint32_t data_len;
+  uint8_t *buf;
+  int j, k;
+  int return_code;
+  int controller;
+
+  if (argc < 6) {
+    i2cread_usage();
+    return -1;
+  }
+
+  controller = strtoul(argv[1], NULL, 0);
+  device_addr = (uint8_t)strtoul(argv[2], NULL, 0);
+  cell_addr = strtoul(argv[3], NULL, 0);
+  addr_len = strtoul(argv[4], NULL, 0);
+  data_len = strtoul(argv[5], NULL, 0);
+
+  if (data_len >= I2C_READ_BUF_SIZE) {
+    printf("ERROR: Size %s too large\n", argv[5]);
+    return -1;
+  }
+
+  buf = (uint8_t *)malloc(I2C_READ_BUF_SIZE);
+  if (buf == NULL) {
+    printf("ERROR: malloc failed (out of memory)\n");
+    return -1;
+  }
+
+  return_code =
+      i2cr(controller, device_addr, cell_addr, addr_len, data_len, buf);
+  if (return_code != 0) {
+    printf("Read ERROR: return code = %d\n", return_code);
+    free(buf);
+    return return_code;
+  }
+
+  /* display */
+  for (j = 0; j < (int)(data_len); j += DISPLAY_WIDTH) {
+    printf("\n@0x%04X\t:", cell_addr + j);
+    for (k = j; (k < (int)(data_len)) && (k < (j + DISPLAY_WIDTH)); k++) {
+      printf("%02X", buf[k]);
+    }
+    /* fill up space if finish before display width */
+    if ((k == (int)(data_len)) && (k < (j + DISPLAY_WIDTH))) {
+      for (k = data_len; k < (j + DISPLAY_WIDTH); k++) {
+        printf("  ");
+      }
+    }
+    printf("\t");
+    for (k = j; (k < (int)(data_len)) && (k < (j + DISPLAY_WIDTH)); k++) {
+      if ((buf[k] >= 0x20) && (buf[k] < 0x7f)) {
+        printf("%c", buf[k]);
+      } else {
+        printf("%c", '.');
+      }
+    }
+    printf("\n");
+  }
+
+  printf("\n--------------------------------------------\n");
+
+  free(buf);
+  return 0;
+}
+
+static void i2cwrite_usage(void) {
+  printf(
+      "i2cwrite bus# dev-address register-offset"
+      " address-len data-len data\n");
+  printf("Example:\n");
+  printf("i2cwrite 1 0x2c 0x40 1 1 0x80\n");
+  printf(
+      "Write to bus 1  device 0x2c, register 0x40,"
+      " address length is 1, 1 byte data, data value is 0x80\n");
+}
+
+int i2cwrite(int argc, char *argv[]) {
+  uint8_t device_addr;
+  uint32_t cell_addr;
+  uint32_t addr_len;
+  uint32_t data_len;
+  uint32_t data;
+  uint8_t buf[4];
+  int return_code;
+  int controller;
+  int i;
+
+  if (argc < 6) {
+    i2cwrite_usage();
+    return -1;
+  }
+
+  controller = strtoul(argv[1], NULL, 0);
+  device_addr = (uint8_t)strtoul(argv[2], NULL, 0);
+  cell_addr = strtoul(argv[3], NULL, 0);
+  addr_len = strtoul(argv[4], NULL, 0);
+  data_len = strtoul(argv[5], NULL, 0);
+
+  if (data_len > 4) {
+    printf("ERROR: Size %s too large\n", argv[5]);
+    return -1;
+  }
+
+  data = strtoul(argv[6], NULL, 0);
+
+  /* store data into buffer */
+  for (i = data_len - 1; i >= 0; i--) {
+    buf[i] = data & 0xff;
+    data >>= 8;
+  }
+
+  return_code =
+      i2cw(controller, device_addr, cell_addr, addr_len, data_len, buf);
+  if (return_code != 0) {
+    printf("Write ERROR: return code = %d\n", return_code);
+    return return_code;
+  }
+
+  return 0;
+}
+
+static void i2cprobe_usage(void) {
+  printf("i2cprobe bus#\n");
+  printf("Example:\n");
+  printf("i2cprobe 2\n");
+}
+
+int i2cprobe(int argc, char *argv[]) {
+  uint8_t device_addr;
+  uint8_t buf[1];
+  int return_code;
+  int controller;
+
+  if (argc < 2) {
+    i2cprobe_usage();
+    return -1;
+  }
+
+  controller = strtoul(argv[1], NULL, 0);
+
+  for (device_addr = 1; device_addr < 127; device_addr++) {
+    return_code = i2cr(controller, device_addr, 0, 1, 1, buf);
+    if (return_code == 0) {
+      printf("Address 0x%02X responding\n", device_addr);
+    }
+  }
+
+  return 0;
+}
diff --git a/diags/chameleon/mdio.c b/diags/chameleon/mdio.c
new file mode 100644
index 0000000..0df12ca
--- /dev/null
+++ b/diags/chameleon/mdio.c
@@ -0,0 +1,66 @@
+#include <errno.h>
+#include <net/if.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <linux/sockios.h>
+#include <linux/types.h>
+
+static int skfd = -1;
+static struct ifreq ifr;
+
+struct mii_data {
+  __u16 phy_id;
+  __u16 reg_num;
+  __u16 val_in;
+  __u16 val_out;
+};
+
+int mdio_read(int location) {
+  struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;
+  mii->reg_num = location;
+  if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0) {
+    fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name,
+            strerror(errno));
+    return -1;
+  }
+  return mii->val_out;
+}
+
+int mdio_write(int location, int value) {
+  struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;
+  mii->reg_num = location;
+  mii->val_in = value;
+  if (ioctl(skfd, SIOCSMIIREG, &ifr) < 0) {
+    fprintf(stderr, "SIOCSMIIREG on %s failed: %s\n", ifr.ifr_name,
+            strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+void mdio_init() {
+  if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+    perror("socket init failed");
+    exit(-1);
+  }
+}
+
+int mdio_set_interface(const char *ifname) {
+  strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+  if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) {
+    if (errno != ENODEV)
+      fprintf(stderr, "SIOCGMIIPHY on '%s' failed: %s\n", ifname,
+              strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+void mdio_done() { close(skfd); }
diff --git a/diags/chameleon/mdio.h b/diags/chameleon/mdio.h
new file mode 100644
index 0000000..16159c5
--- /dev/null
+++ b/diags/chameleon/mdio.h
@@ -0,0 +1,17 @@
+#ifndef VENDOR_GOOGLE_DIAGS_SPACECAST_MDIO_H_
+#define VENDOR_GOOGLE_DIAGS_SPACECAST_MDIO_H_
+
+// In order to read/write PHY registers, it needs to do it in the
+// following sequence:
+// mdio_init
+// mdio_set_interface
+// mdio_read and/or mdio_write (as many as required)
+// mdio_done
+
+int mdio_read(int location);
+int mdio_write(int location, int value);
+void mdio_init();
+int mdio_set_interface(const char* ifname);
+void mdio_done();
+
+#endif  // VENDOR_GOOGLE_DIAGS_SPACECAST_MDIO_H_
diff --git a/diags/chameleon/sfp.c b/diags/chameleon/sfp.c
new file mode 100644
index 0000000..e17e02d
--- /dev/null
+++ b/diags/chameleon/sfp.c
@@ -0,0 +1,350 @@
+/*
+ * (C) Copyright 2015 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "../common/io.h"
+#include "../common/util.h"
+#include "i2c.h"
+
+#define SFP_BUS 0
+#define SFP_A0_ADDR 0x50
+#define SFP_A2_ADDR 0x51
+#define SFP_ADDR_LEN 1
+#define SFP_REG_LEN 1
+#define SFP_MAX_DATA_LEN 128
+#define SFP_INFO_REG_ADDR 0x60
+#define SFP_INFO_REG_LEN 16
+#define SFP_VENDOR_REG_ADDR 20
+#define SFP_VENDOR_REG_LEN 16
+#define SFP_PN_REG_ADDR 40
+#define SFP_PN_REG_LEN 16
+#define SFP_SN_REG_ADDR 68
+#define SFP_SN_REG_LEN 16
+#define SFP_DATE_REG_ADDR 84
+#define SFP_DATE_REG_LEN 8
+#define SFP_WL_REG_ADDR 144
+#define SFP_WL_REG_LEN 2
+#define SFP_PW_REG 0x7B
+#define SFP_PW_REG_LEN 4
+
+static void sfp_reg_read_usage(void) {
+  printf("sfp_reg_read <addr>\n");
+  printf("read SFP registers\n");
+  printf("Example:\n");
+  printf("sfp_reg_read 0x40\n");
+}
+
+int sfp_reg_read(int argc, char *argv[]) {
+  unsigned int reg_addr;
+  uint8_t value;
+
+  if (argc != 2) {
+    sfp_reg_read_usage();
+    return -1;
+  }
+
+  reg_addr = get_num(argv[1]);
+  if (i2cr(SFP_BUS, SFP_A0_ADDR, reg_addr, SFP_ADDR_LEN, SFP_REG_LEN, &value) !=
+      0) {
+    printf("SFP read address 0x%x failed\n", reg_addr);
+    return -1;
+  }
+  printf("SFP 0x%x = 0x%x\n", reg_addr, value);
+
+  return 0;
+}
+
+static void sfp_reg_write_usage(void) {
+  printf("sfp_reg_write <addr> <data>\n");
+  printf("write SFP registers\n");
+  printf("Example:\n");
+  printf("sfp_reg_write 0x60 0x0\n");
+}
+
+int sfp_reg_write(int argc, char *argv[]) {
+  unsigned int reg_addr;
+  uint8_t value;
+
+  if (argc != 3) {
+    sfp_reg_write_usage();
+    return -1;
+  }
+
+  reg_addr = get_num(argv[1]);
+  value = get_num(argv[2]);
+  if (i2cw(SFP_BUS, SFP_A0_ADDR, reg_addr, SFP_ADDR_LEN, SFP_REG_LEN, &value) !=
+      0) {
+    printf("SFP write address 0x%x value 0x%x failed\n", reg_addr, value);
+    return -1;
+  }
+  printf("SFP 0x%x set to 0x%x\n", reg_addr, value);
+
+  return 0;
+}
+
+static void sfp_diags_reg_read_usage(void) {
+  printf("sfp_diags_reg_read <addr>\n");
+  printf("read SFP 0xA2 registers\n");
+  printf("Example:\n");
+  printf("sfp_diags_reg_read 0x40\n");
+}
+
+int sfp_diags_reg_read(int argc, char *argv[]) {
+  unsigned int reg_addr;
+  uint8_t value;
+
+  if (argc != 2) {
+    sfp_diags_reg_read_usage();
+    return -1;
+  }
+
+  reg_addr = get_num(argv[1]);
+  if (i2cr(SFP_BUS, SFP_A2_ADDR, reg_addr, SFP_ADDR_LEN, SFP_REG_LEN, &value) !=
+      0) {
+    printf("SFP 0xA2 read address 0x%x failed\n", reg_addr);
+    return -1;
+  }
+  printf("SFP 0xA2 0x%x = 0x%x\n", reg_addr, value);
+
+  return 0;
+}
+
+static void sfp_diags_reg_write_usage(void) {
+  printf("sfp_diags_reg_write <addr> <data>\n");
+  printf("write SFP 0xA2 registers\n");
+  printf("Example:\n");
+  printf("sfp_diags_reg_write 0x60 0x0\n");
+}
+
+int sfp_diags_reg_write(int argc, char *argv[]) {
+  unsigned int reg_addr;
+  uint8_t value;
+
+  if (argc != 3) {
+    sfp_diags_reg_write_usage();
+    return -1;
+  }
+
+  reg_addr = get_num(argv[1]);
+  value = get_num(argv[2]);
+  if (i2cw(SFP_BUS, SFP_A2_ADDR, reg_addr, SFP_ADDR_LEN, SFP_REG_LEN, &value) !=
+      0) {
+    printf("SFP write 0xA2 address 0x%x value 0x%x failed\n", reg_addr, value);
+    return -1;
+  }
+  printf("SFP 0xA2 0x%x set to 0x%x\n", reg_addr, value);
+
+  return 0;
+}
+
+static void sfp_info_usage(void) {
+  printf("sfp_info\n");
+  printf("read SFP info\n");
+  printf("Example:\n");
+  printf("sfp_info\n");
+}
+
+int sfp_info(int argc, char *argv[]) {
+  uint8_t value[SFP_INFO_REG_LEN];
+  float temp;
+  float vcc, tx_bias, tx_power, rx_power, mod_curr;
+
+  if (argc != 1 || argv == NULL) {
+    sfp_info_usage();
+    return -1;
+  }
+
+  if (i2cr(SFP_BUS, SFP_A2_ADDR, SFP_INFO_REG_ADDR, SFP_ADDR_LEN,
+           SFP_INFO_REG_LEN, value) != 0) {
+    printf("SFP read address %d failed\n", SFP_INFO_REG_ADDR);
+    return -1;
+  }
+  if (value[0] & 0x80) {
+    value[0] &= 0x7F;
+    temp = -128.0 + value[0] + ((float)value[1]) / 256.0;
+  } else {
+    temp = value[0] + ((float)value[1]) / 256.0;
+  }
+  vcc = ((float)((value[2] << 8) + value[3])) / 10000.0;
+  tx_bias = ((float)((value[4] << 8) + value[5])) / 1000.0;
+  tx_power = ((float)((value[6] << 8) + value[7])) / 10000.0;
+  rx_power = ((float)((value[8] << 8) + value[9])) / 10000.0;
+  mod_curr = ((float)((value[12] << 8) + value[13])) / 1000.0;
+  printf("SFP temp: %f, Vcc: %3.3f V, TX bias %3.3f mA\n", temp, vcc, tx_bias);
+  printf("    TX power: %3.3f mW, RX power: %3.3f mW, mod curr: %3.3f mA\n",
+         tx_power, rx_power, mod_curr);
+
+  return 0;
+}
+
+static void sfp_vendor_usage(void) {
+  printf("sfp_vendor\n");
+  printf("read SFP vendor\n");
+  printf("Example:\n");
+  printf("sfp_vendor\n");
+}
+
+int sfp_vendor(int argc, char *argv[]) {
+  uint8_t value[SFP_MAX_DATA_LEN];
+
+  if (argc != 1 || argv == NULL) {
+    sfp_vendor_usage();
+    return -1;
+  }
+
+  printf("SFP vendor:\n");
+  if (i2cr(SFP_BUS, SFP_A0_ADDR, SFP_VENDOR_REG_ADDR, SFP_ADDR_LEN,
+           SFP_VENDOR_REG_LEN, value) != 0) {
+    printf("SFP read address %d failed\n", SFP_VENDOR_REG_ADDR);
+    return -1;
+  }
+  value[SFP_VENDOR_REG_LEN] = '\0';
+  printf("  Name: %s\n", value);
+  if (i2cr(SFP_BUS, SFP_A0_ADDR, SFP_PN_REG_ADDR, SFP_ADDR_LEN, SFP_PN_REG_LEN,
+           value) != 0) {
+    printf("SFP read address %d failed\n", SFP_PN_REG_ADDR);
+    return -1;
+  }
+  value[SFP_PN_REG_LEN] = '\0';
+  printf("  PN:   %s\n", value);
+  if (i2cr(SFP_BUS, SFP_A0_ADDR, SFP_SN_REG_ADDR, SFP_ADDR_LEN, SFP_SN_REG_LEN,
+           value) != 0) {
+    printf("SFP read address %d failed\n", SFP_SN_REG_ADDR);
+    return -1;
+  }
+  value[SFP_SN_REG_LEN] = '\0';
+  printf("  SN:   %s\n", value);
+  if (i2cr(SFP_BUS, SFP_A0_ADDR, SFP_DATE_REG_ADDR, SFP_ADDR_LEN,
+           SFP_DATE_REG_LEN, value) != 0) {
+    printf("SFP read address %d failed\n", SFP_DATE_REG_ADDR);
+    return -1;
+  }
+  value[SFP_DATE_REG_LEN] = '\0';
+  printf("  Date: %s\n", value);
+
+  return 0;
+}
+
+static void sfp_pn_usage(void) {
+  printf("sfp_pn\n");
+  printf("read SFP part number\n");
+  printf("Example:\n");
+  printf("sfp_pn\n");
+}
+
+int sfp_pn(int argc, char *argv[]) {
+  uint8_t value[SFP_MAX_DATA_LEN];
+
+  if (argc != 1 || argv == NULL) {
+    sfp_pn_usage();
+    return -1;
+  }
+
+  if (i2cr(SFP_BUS, SFP_A0_ADDR, SFP_PN_REG_ADDR, SFP_ADDR_LEN, SFP_PN_REG_LEN,
+           value) != 0) {
+    printf("SFP read address %d failed\n", SFP_PN_REG_ADDR);
+    return -1;
+  }
+  value[SFP_PN_REG_LEN] = '\0';
+  printf("SFP part number: %s\n", value);
+
+  return 0;
+}
+
+static void sfp_wavelength_usage(void) {
+  printf("sfp_wavelength\n");
+  printf("read SFP laser wavelength\n");
+  printf("Example:\n");
+  printf("sfp_wavelength\n");
+}
+
+int sfp_wavelength(int argc, char *argv[]) {
+  uint8_t value[SFP_MAX_DATA_LEN];
+  unsigned int data;
+
+  if (argc != 1 || argv == NULL) {
+    sfp_wavelength_usage();
+    return -1;
+  }
+
+  if (i2cr(SFP_BUS, SFP_A2_ADDR, SFP_WL_REG_ADDR, SFP_ADDR_LEN, SFP_WL_REG_LEN,
+           value) != 0) {
+    printf("SFP read address %d failed\n", SFP_WL_REG_ADDR);
+    return -1;
+  }
+  data = (value[0] << 8) + value[1];
+  printf("SFP wavelength: %d\n", data);
+
+  return 0;
+}
+
+static void sfp_set_wavelength_usage(void) {
+  printf("sfp_set_wavelength <wavelength>\n");
+  printf("set SFP laser wavelength\n");
+  printf("Example:\n");
+  printf("sfp_set_wavelength 1520\n");
+}
+
+int sfp_set_wavelength(int argc, char *argv[]) {
+  uint8_t value[SFP_MAX_DATA_LEN];
+  unsigned int data;
+
+  if (argc != 2) {
+    sfp_set_wavelength_usage();
+    return -1;
+  }
+
+  data = get_num(argv[1]);
+  value[0] = (data >> 8);
+  value[1] = data & 0xFF;
+  if (i2cw(SFP_BUS, SFP_A2_ADDR, SFP_WL_REG_ADDR, SFP_ADDR_LEN, SFP_WL_REG_LEN,
+           value) != 0) {
+    printf("SFP write address %d of %d failed\n", SFP_WL_REG_ADDR, data);
+    return -1;
+  }
+  data = (value[0] << 8) + value[1];
+  printf("SFP wavelength: %d\n", data);
+
+  return 0;
+}
+
+static void sfp_set_pw_usage(void) {
+  printf("sfp_set_pw <password>\n");
+  printf("set SFP access password\n");
+  printf("Example:\n");
+  printf("sfp_set_pw 0x80818283\n");
+}
+
+int sfp_set_pw(int argc, char *argv[]) {
+  uint8_t value[SFP_MAX_DATA_LEN];
+  unsigned int data;
+
+  if (argc != 2) {
+    sfp_set_pw_usage();
+    return -1;
+  }
+
+  data = get_num(argv[1]);
+  value[0] = (data >> 24) & 0xFF;
+  value[1] = (data >> 16) & 0xFF;
+  value[2] = (data >> 8) & 0xFF;
+  value[3] = data & 0xFF;
+  if (i2cw(SFP_BUS, SFP_A2_ADDR, SFP_PW_REG, SFP_ADDR_LEN, SFP_PW_REG_LEN,
+           value) != 0) {
+    printf("SFP write address %d of %d failed\n", SFP_PW_REG, data);
+    return -1;
+  }
+  printf("SFP password set to 0x%2x%2x%2x%2x\n", value[0], value[1], value[2],
+         value[3]);
+
+  return 0;
+}
diff --git a/diags/common/util.c b/diags/common/util.c
index 41802b1..fae99ab 100644
--- a/diags/common/util.c
+++ b/diags/common/util.c
@@ -77,3 +77,16 @@
     printf("ERROR: system command %s return %d\n", cmd, rc);
   }
 }
+
+unsigned int get_num(char *numstr) {
+  unsigned int value;
+
+  if (strlen(numstr) > 2) {
+    if (strncmp(numstr, "0x", 2) == 0) {
+      value = strtoul(numstr, NULL, 16);
+      return value;
+    }
+  }
+  value = strtoul(numstr, NULL, 10);
+  return value;
+}
diff --git a/diags/common/util.h b/diags/common/util.h
index 340b582..f6717b7 100644
--- a/diags/common/util.h
+++ b/diags/common/util.h
@@ -23,4 +23,6 @@
 
 void system_cmd(const char *cmd);
 
+unsigned int get_num(char *numstr);
+
 #endif  // VENDOR_GOOGLE_DIAGS_WINDCHARGER_UTIL_H_
diff --git a/ginstall/ginstall.py b/ginstall/ginstall.py
index 5db5493..f061157 100755
--- a/ginstall/ginstall.py
+++ b/ginstall/ginstall.py
@@ -36,16 +36,18 @@
 
 optspec = """
 ginstall -p <partition>
-ginstall -p <partition> -t <tarfile> [options...]
+ginstall [-t <tarfile>] [--drm <blob>] [options...]
 --
-t,tar=        *.gi file to install, from a local path or uri://path
+t,tar=        path to a *.gi file to install; may be - for STDIN, a file on the\
+ filesystem, or an http[s]:// URI
 skiploader    skip installing bootloader (dev-only)
 manifest=     manifest file
 drm=          drm blob filename to install
-p,partition=  partition to install to (primary, secondary, or other)
+p,partition=  partition to boot to on next boot (other, primary, or secondary)\
+ and to unpack .gi image to (if -t is given)
 q,quiet       suppress unnecessary output
 skiploadersig suppress checking the loader signature
-b,basepath=   for tests, prepend a path to all files accessed.
+b,basepath=   for tests, prepend a path to all files accessed
 """
 
 # Error codes.
@@ -625,6 +627,14 @@
                 'gftv200-39-pre1 and before.')
 
 
+def CheckMultiLoader(manifest):
+  """Check if this ginstall image supports platform-named loaders."""
+  multiloader = manifest.get('multiloader')
+  if not multiloader:
+    return False
+  return True
+
+
 class ProgressBar(object):
   """Progress bar that prints one dot per 1MB."""
 
@@ -863,6 +873,12 @@
   CheckMinimumVersion(manifest)
   CheckMisc(manifest)
 
+  loader_bin_list = ['loader.img', 'loader.bin']
+  loader_sig_list = ['loader.sig']
+  if CheckMultiLoader(manifest):
+    loader_bin_list = ['loader.%s.bin' % GetPlatform().lower()]
+    loader_sig_list = ['loader.%s.sig' % GetPlatform().lower()]
+
   uloader = loader = None
   uloadersig = FileWithSecureHash(StringIO.StringIO(''), 'badsig')
   loadersig = FileWithSecureHash(StringIO.StringIO(''), 'badsig')
@@ -878,10 +894,10 @@
     elif ti.name.startswith('rootfs.'):
       fh = FileWithSecureHash(tar.extractfile(ti), secure_hash)
       InstallRootfs(fh, partition)
-    elif ti.name in ['loader.img', 'loader.bin']:
+    elif ti.name in loader_bin_list:
       buf = StringIO.StringIO(tar.extractfile(ti).read())
       loader = FileWithSecureHash(buf, secure_hash)
-    elif ti.name == 'loader.sig':
+    elif ti.name in loader_sig_list:
       buf = StringIO.StringIO(tar.extractfile(ti).read())
       loadersig = FileWithSecureHash(buf, secure_hash)
     elif ti.name == 'uloader.img':
@@ -927,7 +943,10 @@
     pass
 
   try:
-    return open(path)
+    if path == '-':
+      return sys.stdin
+    else:
+      return open(path)
   except ValueError:
     pass
 
diff --git a/ginstall/ginstall_test.py b/ginstall/ginstall_test.py
index 8deac64..a4e3c8a 100755
--- a/ginstall/ginstall_test.py
+++ b/ginstall/ginstall_test.py
@@ -21,6 +21,7 @@
 import shutil
 import StringIO
 import struct
+import sys
 import tempfile
 import unittest
 import ginstall
@@ -365,6 +366,21 @@
     total = ginstall.GetMemTotal()
     self.assertTrue(total < 4*1e9)
 
+  def testOpenPathOrUrl(self):
+    # URL
+    two_oh_four = ginstall.OpenPathOrUrl('http://www.gstatic.com/generate_204')
+    self.assertEqual(204, two_oh_four.getcode())
+
+    # on-disk file
+    on_disk_file = tempfile.NamedTemporaryFile()
+    testdata = os.urandom(16)
+    on_disk_file.write(testdata)
+    on_disk_file.flush()
+    self.assertEqual(ginstall.OpenPathOrUrl(on_disk_file.name).read(), testdata)
+
+    # stdin (-)
+    self.assertEqual(ginstall.OpenPathOrUrl('-'), sys.stdin)
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/ginstall/install_test.sh b/ginstall/install_test.sh
index 4338bac..ec372c5 100755
--- a/ginstall/install_test.sh
+++ b/ginstall/install_test.sh
@@ -5,6 +5,7 @@
 tmpdir="$(mktemp -d)"
 export PATH="$tmpdir/bin:${PATH}"
 export GINSTALL_OUT_FILE="$tmpdir/out"
+psiz=$(stat --format=%s testdata/img/loader.gflt110.bin)
 lsiz=$(stat --format=%s testdata/img/loader.img)
 ksiz=$(stat --format=%s testdata/img/kernel.img)
 rsiz=$(stat --format=%s testdata/img/rootfs.img)
@@ -195,6 +196,45 @@
 
 
 
+# GFLT110 with gflt110.bin loader.
+echo; echo; echo GFLT110 with platform loader
+setup_fakeroot GFLT110
+expected="\
+psback
+logos ginstall
+flash_unlock ${tmpdir}/dev/mtd6
+flash_erase --quiet ${tmpdir}/dev/mtd6 0 0
+flash_unlock ${tmpdir}/dev/mtd0
+flash_erase --quiet ${tmpdir}/dev/mtd0 0 0
+hnvram -q -w ACTIVATED_KERNEL_NAME=kernel0"
+
+WVPASS ./ginstall.py --basepath="$tmpdir" --tar=./testdata/img/image_gflt110_platform_loader.gi --partition=primary --skiploadersig
+WVPASSEQ "$expected" "$(cat $GINSTALL_OUT_FILE)"
+WVPASS cmp --bytes="$psiz" "${tmpdir}/dev/mtd0" testdata/img/loader.gflt110.bin
+WVPASS cmp --bytes="$ksiz" "${tmpdir}/dev/mtd6" testdata/img/kernel.img
+
+
+
+# GFLT110 with both loaders with the MANIFEST containing "multiloader: 1"
+echo; echo; echo GFLT110 with both loaders
+setup_fakeroot GFLT110
+expected="\
+psback
+logos ginstall
+flash_unlock ${tmpdir}/dev/mtd6
+flash_erase --quiet ${tmpdir}/dev/mtd6 0 0
+flash_unlock ${tmpdir}/dev/mtd0
+flash_erase --quiet ${tmpdir}/dev/mtd0 0 0
+hnvram -q -w ACTIVATED_KERNEL_NAME=kernel0"
+
+WVPASS ./ginstall.py --basepath="$tmpdir" --tar=./testdata/img/image_gflt110_both_loaders.gi --partition=primary --skiploadersig
+WVPASSEQ "$expected" "$(cat $GINSTALL_OUT_FILE)"
+WVFAIL cmp --bytes="$lsiz" "${tmpdir}/dev/mtd0" testdata/img/loader.bin
+WVPASS cmp --bytes="$psiz" "${tmpdir}/dev/mtd0" testdata/img/loader.gflt110.bin
+WVPASS cmp --bytes="$ksiz" "${tmpdir}/dev/mtd6" testdata/img/kernel.img
+
+
+
 echo; echo; echo MANIFEST with Bad checksums
 setup_fakeroot GFHD100
 echo "This should not be touched" >"${tmpdir}/dev/mtd0"
diff --git a/ginstall/testdata/img/image_gflt110_both_loaders.gi b/ginstall/testdata/img/image_gflt110_both_loaders.gi
new file mode 100644
index 0000000..62f2a03
--- /dev/null
+++ b/ginstall/testdata/img/image_gflt110_both_loaders.gi
Binary files differ
diff --git a/ginstall/testdata/img/image_gflt110_platform_loader.gi b/ginstall/testdata/img/image_gflt110_platform_loader.gi
new file mode 100644
index 0000000..8d7ed9b
--- /dev/null
+++ b/ginstall/testdata/img/image_gflt110_platform_loader.gi
Binary files differ
diff --git a/ginstall/testdata/img/loader.gflt110.bin b/ginstall/testdata/img/loader.gflt110.bin
new file mode 100644
index 0000000..a95b6ae
--- /dev/null
+++ b/ginstall/testdata/img/loader.gflt110.bin
@@ -0,0 +1 @@
+gflt110.bin
\ No newline at end of file
diff --git a/hnvram/hnvram_main.c b/hnvram/hnvram_main.c
index 323d62e..eb0f306 100644
--- a/hnvram/hnvram_main.c
+++ b/hnvram/hnvram_main.c
@@ -1,7 +1,10 @@
 // Copyright 2011 Google Inc. All Rights Reserved.
 // Author: dgentry@google.com (Denny Gentry)
 
+#ifndef _GNU_SOURCE
 #define _GNU_SOURCE
+#endif
+
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -16,23 +19,28 @@
 // Number of bytes of GPN to be represented as hex data
 #define GPN_HEX_BYTES 4
 
+// Holds whether -w can create new variables in NVRAM. Set with -n
+int can_add_flag = 0;
+
 /* To avoid modifying the HMX code, we supply dummy versions of two
  * missing routines to satisfy the linker. These are used when writing
  * the complete NVRAM partiton, which we do not need in this utility. */
 DRV_Error DRV_NANDFLASH_GetNvramHandle(int handle) {
   return DRV_ERR;
 }
-DRV_Error DRV_FLASH_Write(int offset, char *data, int nDataSize) {
+DRV_Error DRV_FLASH_Write(int offset, char* data, int nDataSize) {
   return DRV_ERR;
 }
 
 void usage(const char* progname) {
-  printf("Usage: %s [-d | [-q|-b] -r VARNAME] [-w VARNAME=value]\n", progname);
+  printf("Usage: %s [-d | [-q|-b] [-r|-k] VARNAME] [ [-n] -w VARNAME=value]\n", progname);
   printf("\t-d : dump all NVRAM variables\n");
   printf("\t-r VARNAME : read VARNAME from NVRAM\n");
   printf("\t-q : quiet mode, suppress the variable name and equal sign\n");
   printf("\t-b : read VARNAME from NVRAM in raw binary format, e.g. dumping a binary key\n");
   printf("\t-w VARNAME=value : write value to VARNAME in NVRAM.\n");
+  printf("\t-n : toggles whether -w can create new variables. Default is off\n");
+  printf("\t-k VARNAME : delete existing key/value pair from NVRAM.\n");
 }
 
 // Format of data in the NVRAM
@@ -104,28 +112,25 @@
 // ------------------ READ NVRAM -----------------------------
 
 
-void format_string(const char* data, char* output, int outlen) {
+void format_string(const unsigned char* data, char* output, int outlen) {
   snprintf(output, outlen, "%s", data);
 }
 
-void format_mac(const char* data, char* output, int outlen) {
-  const unsigned char* mac = (const unsigned char*) data;
+void format_mac(const unsigned char* data, char* output, int outlen) {
   snprintf(output, outlen, "%02hx:%02hx:%02hx:%02hx:%02hx:%02hx",
-           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+           data[0], data[1], data[2], data[3], data[4], data[5]);
 }
 
-void format_hmxswvers(const char* data, char* output, int outlen) {
-  const unsigned char* udata = (const unsigned char*) data;
-  snprintf(output, outlen, "%hhu.%hhu", udata[1], udata[0]);
+void format_hmxswvers(const unsigned char* data, char* output, int outlen) {
+  snprintf(output, outlen, "%hhu.%hhu", data[1], data[0]);
 }
 
-void format_uint8(const char* data, char* output, int outlen) {
-  const unsigned char* d = (const unsigned char*)data;
-  snprintf(output, outlen, "%u", d[0]);
+void format_uint8(const unsigned char* data, char* output, int outlen) {
+  snprintf(output, outlen, "%u", data[0]);
 }
 
-void format_hexstring(const char* data, int datalen, char* output, int outlen) {
-  const unsigned char* d = (const unsigned char*)data;
+void format_hexstring(const unsigned char* data, int datalen, char* output,
+                      int outlen) {
   int i;
   if (outlen < (datalen * 2 + 1)) {
     fprintf(stderr, "%s buffer too small %d < %d",
@@ -133,11 +138,11 @@
     exit(1);
   }
   for (i = 0; i < datalen; ++i) {
-    snprintf(output + (i * 2), 3, "%02x", d[i]);
+    snprintf(output + (i * 2), 3, "%02x", data[i]);
   }
 }
 
-void format_gpn(const char* data, const int data_len, char* output,
+void format_gpn(const unsigned char* data, const int data_len, char* output,
                 int outlen) {
   // Format first 4 bytes as 8 digit hex.
   if (data_len == GPN_HEX_BYTES)
@@ -146,7 +151,7 @@
     format_string(data, output, outlen);
 }
 
-char* format_nvram(hnvram_format_e format, const char* data,
+char* format_nvram(hnvram_format_e format, const unsigned char* data,
                    const int data_len, char* output, int outlen) {
   output[0] = '\0';
   switch(format) {
@@ -163,7 +168,7 @@
 
 int read_raw_nvram(const char* name, char* output, int outlen) {
   const hnvram_field_t* field = get_nvram_field(name);
-  int ret;
+  unsigned int ret;
   if (field == NULL) {
     return -1;
   }
@@ -180,23 +185,33 @@
     return -1;
   }
 
-  return ret;
+  return (int)ret;
 }
 
 char* read_nvram(const char* name, char* output, int outlen, int quiet) {
   const hnvram_field_t* field = get_nvram_field(name);
-  if (field == NULL) {
-    return NULL;
-  }
+  int is_field = (field != NULL);
 
-  char data[NVRAM_MAX_DATA] = {0};
-  int data_len = 0;
-  if (HMX_NVRAM_GetField(field->nvram_type, 0, data, sizeof(data)) != DRV_OK ||
-      HMX_NVRAM_GetLength(field->nvram_type, &data_len) != DRV_OK) {
-    return NULL;
+  unsigned char data[NVRAM_MAX_DATA] = {0};
+  unsigned int data_len = 0;
+  hnvram_format_e format_type;
+  if (is_field) {
+    format_type = field->format;
+    if (HMX_NVRAM_GetField(field->nvram_type, 0, data, sizeof(data)) != DRV_OK ||
+        HMX_NVRAM_GetLength(field->nvram_type, &data_len) != DRV_OK) {
+      return NULL;
+    }
+  } else {
+    format_type = HNVRAM_STRING;
+    DRV_Error e = HMX_NVRAM_Read(HMX_NVRAM_PARTITION_RW, (unsigned char*)name,
+                                 0, data, sizeof(data), &data_len);
+    if (e != DRV_OK) {
+      return NULL;
+    }
   }
   char formatbuf[NVRAM_MAX_DATA * 2];
-  char* nv = format_nvram(field->format, data, data_len, formatbuf, sizeof(formatbuf));
+  char* nv = format_nvram(format_type, data, data_len, formatbuf,
+                          sizeof(formatbuf));
   if (quiet) {
     snprintf(output, outlen, "%s", nv);
   } else {
@@ -204,13 +219,11 @@
   }
   return output;
 }
-
-
 // ----------------- WRITE NVRAM -----------------------------
 
 
 unsigned char* parse_string(const char* input,
-                            unsigned char* output, int* outlen) {
+                            unsigned char* output, unsigned int* outlen) {
   int len = strlen(input);
   if (len > *outlen) {
     len = *outlen;
@@ -222,7 +235,7 @@
 }
 
 unsigned char* parse_mac(const char* input,
-                         unsigned char* output, int* outlen) {
+                         unsigned char* output, unsigned int* outlen) {
   if (*outlen < 6) return NULL;
 
   if (sscanf(input, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
@@ -235,7 +248,7 @@
 }
 
 unsigned char* parse_hmxswvers(const char* input,
-                               unsigned char* output, int* outlen) {
+                               unsigned char* output, unsigned int* outlen) {
   if (*outlen < 2) return NULL;
 
   if (sscanf(input, "%hhd.%hhd", &output[1], &output[0]) != 2) {
@@ -246,7 +259,7 @@
 }
 
 unsigned char* parse_uint8(const char* input,
-                           unsigned char* output, int* outlen) {
+                           unsigned char* output, unsigned int* outlen) {
   if (*outlen < 1) return NULL;
 
   output[0] = input[0] - '0';
@@ -264,8 +277,8 @@
 }
 
 unsigned char* parse_hexstring(const char* input,
-                               unsigned char* output, int* outlen) {
-  int i, len = strlen(input) / 2;
+                               unsigned char* output, unsigned int* outlen) {
+  unsigned int i, len = strlen(input) / 2;
   if (*outlen < len) {
     len = *outlen;
   }
@@ -294,7 +307,7 @@
 }
 
 unsigned char* parse_gpn(const char* input,
-                         unsigned char* output, int* outlen) {
+                         unsigned char* output, unsigned int* outlen) {
   if (*outlen < 4) return NULL;
 
   // Old GPN format: 8-digit hex string
@@ -312,7 +325,7 @@
 }
 
 unsigned char* parse_nvram(hnvram_format_e format, const char* input,
-                           unsigned char* output, int* outlen) {
+                           unsigned char* output, unsigned int* outlen) {
   output[0] = '\0';
   switch(format) {
     case HNVRAM_STRING:
@@ -337,6 +350,15 @@
   return NULL;
 }
 
+DRV_Error clear_nvram(char* optarg) {
+  DRV_Error e = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RW,
+                                 (unsigned char*)optarg);
+  if (e == DRV_ERR) {
+    // Avoid throwing error message if variable already cleared
+    return DRV_OK;
+  }
+  return e;
+}
 
 int write_nvram(char* optarg) {
   char* equal = strchr(optarg, '=');
@@ -349,25 +371,45 @@
   char* value = ++equal;
 
   const hnvram_field_t* field = get_nvram_field(name);
-  if (field == NULL) {
-    return -2;
+  int is_field = (field != NULL);
+
+  hnvram_format_e format_type;
+  if (is_field) {
+    format_type = field->format;
+  } else {
+    format_type = HNVRAM_STRING;
   }
 
   unsigned char nvram_value[NVRAM_MAX_DATA];
-  int nvram_len = sizeof(nvram_value);
-  if (parse_nvram(field->format, value, nvram_value, &nvram_len) == NULL) {
-    return -3;
+  unsigned int nvram_len = sizeof(nvram_value);
+  if (parse_nvram(format_type, value, nvram_value, &nvram_len) == NULL) {
+    return -2;
   }
 
-  if (HMX_NVRAM_SetField(field->nvram_type, 0,
-                         nvram_value, nvram_len) != DRV_OK) {
-    return -4;
+  if (!is_field) {
+    char tmp[NVRAM_MAX_DATA] = {0};
+    int key_exists = (read_nvram(name, tmp, NVRAM_MAX_DATA, 1) != NULL);
+    if (!can_add_flag && !key_exists) {
+      fprintf(stderr, "Key not found in NVRAM. Add -n to allow creation %s\n",
+              name);
+      return -3;
+    }
+    DRV_Error er = HMX_NVRAM_Write(HMX_NVRAM_PARTITION_RW, (unsigned char*)name,
+                                   0, nvram_value, nvram_len);
+    if (er != DRV_OK) {
+      return -4;
+    }
+  } else {
+    if (HMX_NVRAM_SetField(field->nvram_type, 0,
+                           nvram_value, nvram_len) != DRV_OK) {
+      return -5;
+    }
   }
 
   return 0;
 }
 
-int hnvram_main(int argc, char * const argv[]) {
+int hnvram_main(int argc, char* const argv[]) {
   DRV_Error err;
 
   libupgrade_verbose = 0;
@@ -383,7 +425,7 @@
   int b_flag = 0;  // binary: output the binary format
   char output[NVRAM_MAX_DATA];
   int c;
-  while ((c = getopt(argc, argv, "dbqrw:")) != -1) {
+  while ((c = getopt(argc, argv, "dbqrnw:k:")) != -1) {
     switch(c) {
       case 'b':
         b_flag = 1;
@@ -391,6 +433,9 @@
       case 'q':
         q_flag = 1;
         break;
+      case 'n':
+        can_add_flag = 1;
+        break;
       case 'w':
         {
           char* duparg = strdup(optarg);
@@ -402,6 +447,17 @@
           free(duparg);
         }
         break;
+      case 'k':
+        {
+          char* duparg = strdup(optarg);
+          if (clear_nvram(duparg) != DRV_OK) {
+            fprintf(stderr, "Unable to remove key %s\n", duparg);
+            free(duparg);
+            exit(1);
+          }
+          free(duparg);
+        }
+        break;
       case 'r':
       case 'd':
         if (op != c) {
@@ -459,7 +515,7 @@
 }
 
 #ifndef TEST_MAIN
-int main(int argc, char * const argv[]) {
+int main(int argc, char* const argv[]) {
   return hnvram_main(argc, argv);
 }
 #endif  // TEST_MAIN
diff --git a/hnvram/hnvram_test.cc b/hnvram/hnvram_test.cc
index d10a7a1..39f7e4a 100644
--- a/hnvram/hnvram_test.cc
+++ b/hnvram/hnvram_test.cc
@@ -8,9 +8,40 @@
 
 int libupgrade_verbose = 1;
 
+char* HMX_NVRAM_Read_Data = NULL;
+DRV_Error HMX_NVRAM_Read(HMX_NVRAM_PARTITION_E partition,
+                         unsigned char* pName, unsigned int offset,
+                         unsigned char* pValue, unsigned int ulSize,
+                         unsigned int* pLen) {
+  if (HMX_NVRAM_Read_Data == NULL) {
+    return DRV_ERR;
+  } else {
+    snprintf((char*)pValue, ulSize, "%s", HMX_NVRAM_Read_Data);
+    *pLen = strlen(HMX_NVRAM_Read_Data);
+    return DRV_OK;
+  }
+}
+
+DRV_Error HMX_NVRAM_Write(HMX_NVRAM_PARTITION_E partition,
+                         unsigned char* pName, unsigned int offset,
+                         unsigned char* pValue, unsigned int ulSize) {
+  HMX_NVRAM_Read_Data = (char*)malloc(ulSize);
+  snprintf(HMX_NVRAM_Read_Data, sizeof(pValue), "%s", (char*)pValue);
+  return DRV_OK;
+}
+
+DRV_Error HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_E partition,
+                           unsigned char* pName) {
+  if (HMX_NVRAM_Read_Data == NULL) {
+    return DRV_ERR;
+  }
+  HMX_NVRAM_Read_Data = NULL;
+  return DRV_OK;
+}
+
 const char* HMX_NVRAM_GetField_Data = NULL;
 DRV_Error HMX_NVRAM_GetField(NVRAM_FIELD_T field, unsigned int offset,
-                             void *data, int nDataSize) {
+                             void* data, int nDataSize) {
   if (HMX_NVRAM_GetField_Data == NULL) {
     return DRV_ERR;
   } else {
@@ -23,7 +54,7 @@
 int HMX_NVRAM_SetField_Len = -1;
 DRV_Error HMX_NVRAM_SetField_Return = DRV_OK;
 DRV_Error HMX_NVRAM_SetField(NVRAM_FIELD_T field, unsigned int offset,
-                             void *data, int nDataSize) {
+                             void* data, int nDataSize) {
   HMX_NVRAM_SetField_Data = (unsigned char*)malloc(nDataSize);
   memcpy(HMX_NVRAM_SetField_Data, data, nDataSize);
   HMX_NVRAM_SetField_Len = nDataSize;
@@ -38,12 +69,11 @@
   return DRV_OK;
 }
 
-DRV_Error HMX_NVRAM_GetLength(tagNVRAM_FIELD partition, int *pLen) {
+DRV_Error HMX_NVRAM_GetLength(tagNVRAM_FIELD partition, unsigned int* pLen) {
   *pLen = HMX_NVRAM_SetField_Len;
   return DRV_OK;
 }
 
-
 #define TEST_MAIN
 #include "hnvram_main.c"
 
@@ -54,6 +84,7 @@
     virtual ~HnvramTest() {}
 
     virtual void SetUp() {
+      HMX_NVRAM_Read_Data = NULL;
       HMX_NVRAM_GetField_Data = NULL;
       HMX_NVRAM_SetField_Data = NULL;
       HMX_NVRAM_SetField_Len = -1;
@@ -70,49 +101,39 @@
 
 TEST_F(HnvramTest, TestFormat) {
   char out[256];
-  EXPECT_STREQ("foo", format_nvram(HNVRAM_STRING, "foo", 3, out, sizeof(out)));
-  EXPECT_STREQ("bar", format_nvram(HNVRAM_STRING, "bar", 3, out, sizeof(out)));
+  EXPECT_STREQ("foo", format_nvram(HNVRAM_STRING, (unsigned char*)"foo", 3,
+                                   out, sizeof(out)));
+  EXPECT_STREQ("bar", format_nvram(HNVRAM_STRING, (unsigned char*)"bar", 3,
+                                   out, sizeof(out)));
 
-  char mac[6] = {0x11, 0x22, 0x03, 0x40, 0x55, 0xf6};
+  unsigned char mac[6] = {0x11, 0x22, 0x03, 0x40, 0x55, 0xf6};
   EXPECT_STREQ("11:22:03:40:55:f6",
                format_nvram(HNVRAM_MAC, mac, sizeof(mac), out, sizeof(out)));
 
-  const char in1[1] = {1};
+  const unsigned char in1[1] = {1};
   EXPECT_STREQ("1", format_nvram(HNVRAM_UINT8, in1, sizeof(in1),
                                  out, sizeof(out)));
-  const char in254[1] = {0xfe};
+  const unsigned char in254[1] = {0xfe};
   EXPECT_STREQ("254", format_nvram(HNVRAM_UINT8, in254, sizeof(in254),
                                    out, sizeof(out)));
 
-  const char vers[] = {0x02, 0x01};
+  const unsigned char vers[] = {0x02, 0x01};
   EXPECT_STREQ("1.2", format_nvram(HNVRAM_HMXSWVERS, vers, sizeof(vers),
                                    out, sizeof(out)));
 
-  const char gpn[] = {0x86, 0x0, 0x4, 0x0};
+  const unsigned char gpn[] = {0x86, 0x0, 0x4, 0x0};
   EXPECT_STREQ("86000400", format_nvram(HNVRAM_GPN, gpn, sizeof(gpn),
                                         out, sizeof(out)));
 
-  const char hex[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+  const unsigned char hex[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
   EXPECT_STREQ("0123456789abcdef", format_nvram(
       HNVRAM_HEXSTRING, hex, sizeof(hex), out, sizeof(out)));
 }
 
-TEST_F(HnvramTest, TestGetNvramField) {
-  EXPECT_EQ(NULL, get_nvram_field("nosuchfield"));
-  EXPECT_EQ(NVRAM_FIELD_SYSTEM_ID, get_nvram_field("SYSTEM_ID")->nvram_type);
-}
-
-TEST_F(HnvramTest, TestReadNvram) {
-  char output[256];
-  HMX_NVRAM_GetField_Data = "TestSystemId";
-  EXPECT_STREQ("SYSTEM_ID=TestSystemId",
-               read_nvram("SYSTEM_ID", output, sizeof(output), 0));
-}
-
 TEST_F(HnvramTest, TestParse) {
   char input[256];
   unsigned char output[256];
-  int outlen = sizeof(output);
+  unsigned int outlen = sizeof(output);
 
   snprintf(input, sizeof(input), "This is a test.");
   EXPECT_TRUE(NULL != parse_nvram(HNVRAM_STRING, input, output, &outlen));
@@ -156,16 +177,114 @@
   EXPECT_EQ(0, memcmp(hex, output, outlen));
 }
 
-TEST_F(HnvramTest, TestWriteNvram) {
+TEST_F(HnvramTest, TestGetNvramField) {
+  EXPECT_EQ(NULL, get_nvram_field("nosuchfield"));
+  EXPECT_EQ(NVRAM_FIELD_SYSTEM_ID, get_nvram_field("SYSTEM_ID")->nvram_type);
+}
+
+TEST_F(HnvramTest, TestReadFieldNvram) {
+  char output[256];
+  HMX_NVRAM_GetField_Data = "TestSystemId";
+  EXPECT_STREQ("SYSTEM_ID=TestSystemId",
+               read_nvram("SYSTEM_ID", output, sizeof(output), 0));
+  EXPECT_STREQ("TestSystemId",
+               read_nvram("SYSTEM_ID", output, sizeof(output), 1));
+  HMX_NVRAM_GetField_Data = NULL;
+  EXPECT_EQ(NULL, read_nvram("FAKE_SYSTEM_ID", output, sizeof(output), 1));
+}
+
+TEST_F(HnvramTest, TestReadVariableNvram) {
+  char output[256];
+  HMX_NVRAM_Read_Data = strdup("ABC123");
+  EXPECT_STREQ("TEST_VARIABLE=ABC123",
+               read_nvram("TEST_VARIABLE", output, sizeof(output), 0));
+  EXPECT_STREQ("ABC123",
+               read_nvram("TEST_VARIABLE", output, sizeof(output), 1));
+  HMX_NVRAM_Read_Data = NULL;
+  EXPECT_STREQ(NULL, read_nvram("TEST_VARIABLE", output, sizeof(output), 1));
+}
+
+TEST_F(HnvramTest, TestWriteFieldNvram) {
+  // Type integer
   char* testdata = strdup("ACTIVATED_KERNEL_NUM=1");
   EXPECT_EQ(DRV_OK, write_nvram(testdata));
-  unsigned char expected[] = {0x01};
-  EXPECT_EQ(0, memcmp(HMX_NVRAM_SetField_Data, expected, sizeof(expected)));
+  EXPECT_EQ(0x01, *HMX_NVRAM_SetField_Data);
   EXPECT_EQ(1, HMX_NVRAM_SetField_Len);
+
+  // Type string
+  testdata = strdup("ACTIVATED_KERNEL_NAME=kernel1");
+  EXPECT_EQ(DRV_OK, write_nvram(testdata));
+  EXPECT_STREQ("kernel1", (char*)HMX_NVRAM_SetField_Data);
+  EXPECT_EQ(7, HMX_NVRAM_SetField_Len);
+
+  // Make sure it called SetField and not HMX_NVRAM_Write
+  EXPECT_EQ (NULL, HMX_NVRAM_Read_Data);
+
+  // Should fail trying to change value of non-exsting field
+  testdata = strdup("FAKE_FIELD=abc123");
+  EXPECT_NE(0, write_nvram(testdata));
   free(testdata);
 }
 
-int main(int argc, char **argv) {
+TEST_F(HnvramTest, TestWriteVariableNvram) {
+  char* key = strdup("TEST_FIELD");
+  char* val = strdup("abc123");
+  char* keyval = strdup("TEST_FIELD=abc123");
+
+  // Fail to add new one without -n
+  EXPECT_NE(0, write_nvram(strdup(keyval)));
+
+  // Add new one successfully
+  can_add_flag = 1;
+  EXPECT_EQ(0, write_nvram(keyval));
+  EXPECT_STREQ(val,HMX_NVRAM_Read_Data);
+
+  // Should be able to read value
+  char output[256];
+  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1));
+
+  char* val2 = strdup("987def");
+  char* keyval2 = strdup("TEST_FIELD=987def");
+
+  // Should be able to change value
+  EXPECT_EQ(0, write_nvram(keyval2));
+  EXPECT_STREQ(val2,HMX_NVRAM_Read_Data);
+
+  free(key);
+  free(val);
+  free(keyval);
+  free(val2);
+  free(keyval2);
+}
+
+TEST_F(HnvramTest, TestClearNvram) {
+  char* key = strdup("TEST_FIELD2");
+  char* val = strdup("abc123");
+  char* keyval = strdup("TEST_FIELD2=abc123");
+  // No error if variable already cleared
+  EXPECT_EQ(DRV_OK, clear_nvram(key));
+
+  // Create new one
+  can_add_flag = 1;
+  EXPECT_EQ(0, write_nvram(keyval));
+  EXPECT_STREQ(val, HMX_NVRAM_Read_Data);
+
+  // Should be able to read value
+  char output[256];
+  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1));
+
+  // Should be able to kill it
+  EXPECT_EQ(DRV_OK, clear_nvram(key));
+
+  // Should fail reading value
+  EXPECT_STREQ(NULL, read_nvram(key, output, sizeof(output), 1));
+
+  free(key);
+  free(val);
+  free(keyval);
+}
+
+int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
 }
diff --git a/ledpattern/ledpatterns b/ledpattern/ledpatterns
index 2e0ab63..0b86fb7 100644
--- a/ledpattern/ledpatterns
+++ b/ledpattern/ledpatterns
@@ -1,12 +1,28 @@
-HALTED,P,R
-NO_LASER_CHANNEL,P,P
-SET_LASER_FAILED,P,R,R
-LOSLOF_ALARM,P,R,B
-OTHER_ALARM,P,R,P
-GPON_INITIAL,P,B,R
-GPON_STANDBY,P,B,P
-GPON_SERIAL,P,P,R
-GPON_RANGING,P,P,B
+SET_LASER_FAILED_0,P,R,R,R,R
+SET_LASER_FAILED_1,P,R,R,R,B
+SET_LASER_FAILED_2,P,R,R,B,R
+SET_LASER_FAILED_3,P,R,R,B,B
+SET_LASER_FAILED_4,P,R,B,R,R
+SET_LASER_FAILED_5,P,R,B,R,B
+SET_LASER_FAILED_6,P,R,B,B,R
+SET_LASER_FAILED_7,P,R,B,B,B
+SET_LASER_FAILED_8,P,B,R,R,R
+SET_LASER_FAILED_9,P,B,R,R,B
+SET_LASER_FAILED_10,P,B,R,B,R
+SET_LASER_FAILED_11,P,B,R,B,B
+SET_LASER_FAILED_12,P,B,B,R,R
+SET_LASER_FAILED_13,P,B,B,R,B
+SET_LASER_FAILED_14,P,B,B,B,R
+SET_LASER_FAILED_15,P,B,B,B,B
+GPON_INITIAL,P,P,R,R
+GPON_STANDBY,P,P,R,B
+GPON_SERIAL,P,P,B,R
+GPON_RANGING,P,P,B,B
+HALTED,P,R,R
+NO_LASER_CHANNEL,P,R,B
+LOSLOF_ALARM,P,R,P
+OTHER_ALARM,P,B,R
 WAIT_ACS,P,B,B
-ALL_OK,P,B,B,B
-UNKNOWN_ERROR,P,R,R,R
+ALL_OK,P,B
+UNKNOWN_ERROR,P,R
+
diff --git a/ledpattern/ledtapcode.sh b/ledpattern/ledtapcode.sh
index 6841f2f..7793993 100755
--- a/ledpattern/ledtapcode.sh
+++ b/ledpattern/ledtapcode.sh
@@ -44,8 +44,15 @@
 if [ -f "$LASER_STATUS_FILE" ]; then
   laser_status=$(cat "$LASER_STATUS_FILE")
   if [ "$laser_status" -ne 0 ]; then
-    echo "Playing SET_LASER_FAILED pattern"
-    PlayPatternAndExit SET_LASER_FAILED
+    # Blink out requested laser channel that we failed to tune to
+    laser_channel=$(cat "$LASER_CHANNEL_FILE")
+    if [ "$laser_channel" -eq -1 ]; then
+      echo "$LASER_STATUS_FILE indicates success but there is no requested
+        channel in $LASER_CHANNEL_FILE"
+      PlayPatternAndExit UNKNOWN_ERROR
+    fi
+    echo "Playing SET_LASER_FAILED_${laser_channel} pattern"
+    PlayPatternAndExit "SET_LASER_FAILED_${laser_channel}"
   fi
 fi
 
@@ -89,19 +96,35 @@
   PlayPatternAndExit GPON_RANGING
 fi
 
-laser_channel=$(cat "$LASER_CHANNEL_FILE")
-if [ ! -f "$ACS_FILE" ] && [ "$laser_channel" -eq "-1" ]; then
-  echo "Playing NO_LASER_CHANNEL pattern"
-  PlayPatternAndExit NO_LASER_CHANNEL
-elif [ ! -f "$ACS_FILE" ] && [ $laser_channel -ne "-1" ]; then
-  echo "Playing WAIT_ACS pattern"
-  PlayPatternAndExit WAIT_ACS
-elif [ -f "$ACS_FILE" ] && [ $laser_channel -ne "-1" ]; then
-  echo "Playing ALL_OK pattern"
-  PlayPatternAndExit ALL_OK
-else
-  # If we get all the way here and nothing triggered on the way then this really
-  # is an unknown error...
-  echo "Nothing triggered? Playing UNKNOWN_ERROR pattern..."
-  PlayPatternAndExit UNKNOWN_ERROR
+# GFLT110 does not have tuneable laser
+tuneable_laser="false"
+if startswith "$(cat /etc/platform)" "GFLT3"; then
+  tuneable_laser="true"
 fi
+
+if [ "$tuneable_laser" = false ]; then
+  if [ ! -f "$ACS_FILE" ]; then
+    echo "Playing WAIT_ACS pattern"
+    PlayPatternAndExit WAIT_ACS
+  else
+    echo "Playing ALL_OK pattern"
+    PlayPatternAndExit ALL_OK
+  fi
+else
+  laser_channel=$(cat "$LASER_CHANNEL_FILE")
+  if [ ! -f "$ACS_FILE" ] && [ "$laser_channel" -eq "-1" ]; then
+    echo "Playing NO_LASER_CHANNEL pattern"
+    PlayPatternAndExit NO_LASER_CHANNEL
+  elif [ ! -f "$ACS_FILE" ] && [ "$laser_channel" -ne "-1" ]; then
+    echo "Playing WAIT_ACS pattern"
+    PlayPatternAndExit WAIT_ACS
+  elif [ -f "$ACS_FILE" ] && [ "$laser_channel" -eq "-1" ]; then
+    echo "Has ACS but no laser channel"
+    echo "Playing NO_LASER_CHANNEL pattern"
+    PlayPatternAndExit NO_LASER_CHANNEL
+  else
+    echo "Playing ALL_OK pattern"
+    PlayPatternAndExit ALL_OK
+  fi
+fi
+
diff --git a/rcu_audio/.gitignore b/rcu_audio/.gitignore
new file mode 100644
index 0000000..e08ad34
--- /dev/null
+++ b/rcu_audio/.gitignore
@@ -0,0 +1,4 @@
+ti-rcu-audio
+gfrm100-rcu-audio
+gfrm-voice-demo
+*.o
diff --git a/rcu_audio/Makefile b/rcu_audio/Makefile
new file mode 100644
index 0000000..aa98aba
--- /dev/null
+++ b/rcu_audio/Makefile
@@ -0,0 +1,37 @@
+CC:=$(CROSS_COMPILE)gcc
+CPP:=$(CROSS_COMPILE)g++
+HOST_PROTOC ?= $(HOSTDIR)/usr/bin/protoc
+PREFIX=/usr
+BINDIR=$(DESTDIR)$(PREFIX)/bin
+CFLAGS += $(EXTRACFLAGS)
+LDFLAGS += $(EXTRALDFLAGS)
+
+BINARIES = ti-rcu-audio gfrm100-rcu-audio gfrm-voice-demo
+CHECKING = -Wall -Werror
+all: $(BINARIES)
+
+%.pb.cc: %.proto
+	echo "Building .pb.cc"
+	$(HOST_PROTOC) --cpp_out=. $<
+
+ti-rcu-audio: ti-rcu-audio.cc RAS_lib.c RAS_lib.h rcu-audio.h remote_control_audio.pb.cc rcu-utils.cc
+	$(CPP) -I. $(CHECKING) $(CFLAGS) ti-rcu-audio.cc RAS_lib.c remote_control_audio.pb.cc rcu-utils.cc -o $@ $(LDFLAGS) -lprotobuf-lite
+
+gfrm100-rcu-audio: gfrm100-rcu-audio.cc rcu-audio.h remote_control_audio.pb.cc rcu-utils.cc
+	$(CPP) -I. $(CHECKING) $(CFLAGS) gfrm100-rcu-audio.cc remote_control_audio.pb.cc rcu-utils.cc -o $@ $(LDFLAGS) -lprotobuf-lite
+
+gfrm-voice-demo: gfrm-voice-demo.cc rcu-audio.h remote_control_audio.pb.cc rcu-utils.cc
+	$(CPP) -I. $(CHECKING) $(CFLAGS) gfrm-voice-demo.cc remote_control_audio.pb.cc rcu-utils.cc -o $@ $(LDFLAGS) -lprotobuf-lite
+
+install:
+	mkdir -p $(BINDIR)
+	cp $(BINARIES) $(BINDIR)
+
+install-libs:
+	@echo "No libs to install."
+
+clean:
+	rm -f $(BINARIES) *.o
+
+test:
+	true
diff --git a/rcu_audio/RAS_lib.c b/rcu_audio/RAS_lib.c
new file mode 100755
index 0000000..cfb33df
--- /dev/null
+++ b/rcu_audio/RAS_lib.c
@@ -0,0 +1,332 @@
+/*
+* Copyright (c) [2015] Texas Instruments Incorporated
+*
+* All rights reserved not granted herein.
+* Limited License.
+*
+* Texas Instruments Incorporated grants a world-wide, royalty-free,
+* non-exclusive license under copyrights and patents it now or hereafter
+* owns or controls to make, have made, use, import, offer to sell and sell ("Utilize")
+* this software subject to the terms herein.  With respect to the foregoing patent
+*license, such license is granted  solely to the extent that any such patent is necessary
+* to Utilize the software alone.  The patent license shall not apply to any combinations which
+* include this software, other than combinations with devices manufactured by or for TI ("TI Devices").
+* No hardware patent is licensed hereunder.
+*
+* Redistributions must preserve existing copyright notices and reproduce this license (including the
+* above copyright notice and the disclaimer and (if applicable) source code license limitations below)
+* in the documentation and/or other materials provided with the distribution
+*
+* Redistribution and use in binary form, without modification, are permitted provided that the
+* following conditions are met:
+*
+*             * No reverse engineering, decompilation, or disassembly of this software is permitted
+*             	with respect to any software provided in binary form.
+*             * any redistribution and use are licensed by TI for use only with TI Devices.
+*             * Nothing shall obligate TI to provide you with source code for the software licensed
+*             	and provided to you in object code.
+*
+* If software source code is provided to you, modification and redistribution of the source code are
+* permitted provided that the following conditions are met:
+*
+*   * any redistribution and use of the source code, including any resulting derivative works, are
+*     licensed by TI for use only with TI Devices.
+*   * any redistribution and use of any object code compiled from the source code and any resulting
+*     derivative works, are licensed by TI for use only with TI Devices.
+*
+* Neither the name of Texas Instruments Incorporated nor the names of its suppliers may be used to
+* endorse or promote products derived from this software without specific prior written permission.
+*
+* DISCLAIMER.
+*
+* THIS SOFTWARE IS PROVIDED BY TI AND TI'S LICENSORS "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 TI AND TI'S LICENSORS 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "RAS_lib.h"
+
+static int16 PV_Dec;
+static int8 SI_Dec;
+
+
+#define NULL	0
+#define PRED_CYPHER_CONST 0x3292
+#define STEP_CYPHER_CONST 0x5438
+
+#define HDR_NOT_SCRAMBLED 1
+
+
+static uint8 ras_pec_mode;
+static int16 per_buff[MAX_INPUT_BUF_SIZE*4];
+
+const uint16 codec_stepsize_Lut[89] =
+{
+  7,    8,    9,   10,   11,   12,   13,   14,
+  16,   17,   19,   21,   23,   25,   28,   31,
+  34,   37,   41,   45,   50,   55,   60,   66, 73,   80,   88,   97,  107,  118,  130,  143,
+  157,  173,  190,  209,  230,  253,  279,  307, 337,  371,  408,  449,  494,  544,  598,  658,
+  724,  796,  876,  963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+  3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442,11487,12635,13899,
+  15289,16818,18500,20350,22385,24623,27086,29794, 32767
+};
+
+const int8 codec_IndexLut[16] =
+{
+  -1, -1, -1, -1, 2, 4, 6, 8,
+  -1, -1, -1, -1, 2, 4, 6, 8
+};
+
+
+/**************************************************************************************************
+*
+* @fn          codec_DecodeSingle
+*
+* @brief       This routine decode a 4bits ADPCM sample to a uin16 PCM audio sample.
+*
+* @param       uint8 a 4 bits ADPCM sample
+*
+*
+* @return      the 16 bits PCM samples.
+*/
+static int16 codec_DecodeSingle(uint8 codec_4bits)
+{
+  int16 step = codec_stepsize_Lut[SI_Dec];
+  int16 cum_diff  = step>>3;
+
+	// DBG("step %d cum_diff %d\n", step, cum_diff);
+
+  SI_Dec += codec_IndexLut[codec_4bits];
+  if(SI_Dec<0) SI_Dec = 0; else if(SI_Dec>88) SI_Dec = 88;
+
+  if(codec_4bits&4)
+     cum_diff += step;
+  if(codec_4bits&2)
+     cum_diff += step>>1;
+  if(codec_4bits&1)
+     cum_diff += step>>2;
+
+   if(codec_4bits&8)
+   {
+     if (PV_Dec < (-32767+cum_diff))
+       (PV_Dec) = -32767;
+     else
+       PV_Dec -= cum_diff;
+   }
+   else
+   {
+     if (PV_Dec > (0x7fff-cum_diff))
+       (PV_Dec) = 0x7fff;
+     else
+     PV_Dec += cum_diff;
+   }
+  return PV_Dec;
+}
+
+/**************************************************************************************************
+ *
+ * @fn          codec_DecodeBuff
+ *
+ * @brief       This routine encode a buffer with ADPCM IMA.
+ *
+ * @param       int16* dst  pointer to buffer where decoding result will be copy
+ *              uint8* src  input buffer, size must be a multiple of 4 bytes
+ *              srcSize     Number of byte that will be generated by the encoder (4* (src buffer size in byte))
+ *
+ *
+ * @return      none
+ */
+static void codec_DecodeBuff(int16* dst, uint8* src, unsigned int srcSize,  int8 *si, int16 *pv)
+{
+
+  // calculate pointers to iterate output buffer
+  int16* out = dst;
+  int16* end = out+(srcSize>>1);
+	int16 temp;
+
+  PV_Dec = *pv;
+  SI_Dec = *si;
+
+  while(out<end)
+  {
+     // get byte from src
+     uint8 codec = *src;
+	// DBG("codec %04x\n", codec);
+     // *out++ = codec_DecodeSingle((codec&0xF));  // decode value and store it
+     temp = codec_DecodeSingle((codec&0xF));  // decode value and store it
+		// DBG("from low %04x\n", temp);
+		*out++ = temp;
+     codec >>= 4;  // use high nibble of byte
+     codec &= 0xF;  // use high nibble of byte
+     // *out++ = codec_DecodeSingle(codec);  // decode value and store it
+     temp = codec_DecodeSingle((codec));  // decode value and store it
+		// DBG("from high %04x\n", temp);
+		*out++ = temp;
+     ++src;        // move on a byte for next sample
+  }
+
+  *pv = PV_Dec;
+  *si = SI_Dec;
+}
+
+
+/**************************************************************************************************
+ *
+ * @fn      RAS_Init
+ *
+ * @brief   RemoTI Audio Subsystem, initialization function
+ *
+ * input parameters
+ *
+ * @param   pec_mode:    	Packet Error concealment algorithm to apply:
+ * 							RAS_NO_PEC(0): 		None (default)
+ * 							RAS_PEC_MODE1(1): 	Replace lost packets by last valid.
+ *
+ * output parameters
+ *codec_ima_DecodeBuff
+ * None.
+ *
+ * @return      .
+ * status.	0				SUCCESS
+ * 			-1				ERROR: INVALID PARAMETER
+ */
+uint8 RAS_Init( uint8 pec_mode )
+{
+	uint16 i;
+	if (pec_mode>RAS_PEC_MODE1) return -1;
+	ras_pec_mode = pec_mode;
+
+	for (i=0; i<(MAX_INPUT_BUF_SIZE*4);i++)
+		per_buff[i]=0;
+
+	return 0;
+}
+
+/**************************************************************************************************
+ *
+ * @fn      RAS_GetVersion
+ *
+ * @brief   RemoTI Audio Subsystem, retrieve software version
+ *
+ * input parameters
+ *
+ * none
+ *
+ * output parameters
+ *
+ * None.
+ *
+ * @return      .
+ * Software Version.	MSB	 Major revision number
+ *						LSB: Minor revision number
+ */
+uint16 RAS_GetVersion( void )
+{
+	return RAS_SOFTWARE_VERSION;
+}
+/**************************************************************************************************
+ *
+ * @fn      RAS_Decode
+ *
+ * @brief   RemoTI Audio Subsystem, decoding function. decode encoded audioframe to PCM samples.
+ *
+ * input parameters
+ *
+ * @param   option:    		decoding option. can be pure decoding, or packet lot concealment algorithm:
+ * 							RAS_PACKET_LOST(0)
+ * 							RAS_DECODE(1)
+ * @param   input: 			address of the buffer to decode, this buffer must include the 3 bytes header..
+ *
+ * @param   inputLength:  	length of the buffer to decode, excluding the 3 bytes header.
+ * 							cannot be greater than 128 (MAX_INPUT_BUF_SIZE);
+ *
+ * output parameters
+ *
+ * @param   output:     	buffer where the decoded PCM will be written. This buffer must be allocated by the caller.
+ * 							it must have a length of 4 times the inputLength variable
+ *
+ * @param   outputLenght:  	length of the decoded buffer.
+ * 							max possible value 512 (4*MAX_INPUT_BUF_SIZE);
+ *
+ *
+ * @return      .
+ * status.	0				SUCCESS
+ * 			-1				ERROR: INVALID PARAMETER
+ *
+ */
+uint8 RAS_Decode( uint8 option, uint8* input, uint16 inputLength, int16* output,uint16 *outputLenght )
+{
+    int8 step_index;
+    int16 predicted_value;
+    uint16 i;
+    static uint8 *rf_DataFrame;
+    *outputLenght = 0;
+
+	// DBG("RAS_Decode option %d input %04x inputLength %d output %04x outputLength %d\n", option, input, inputLength, output, outputLenght);
+
+    if ((output == NULL) || (inputLength > MAX_INPUT_BUF_SIZE)) return -1;
+
+#ifdef HDR_NOT_SCRAMBLED
+	predicted_value = (input [0] + ((input[1])<<8));
+    step_index = input [2] & 0xFF;
+#else
+    predicted_value = (int16)(((int16)((input[0])<<8))+((int16)(input [2] ))) ^ PRED_CYPHER_CONST;
+    step_index      = (input [1] & 0xFF) ^STEP_CYPHER_CONST;
+#endif
+
+    //extract Predicted value and step index from the header.
+    inputLength-=3;  //Remove Header Size
+    // check Option
+    switch(option)
+    {
+    	case RAS_PACKET_LOST:
+    	{
+    		switch (ras_pec_mode)
+    		{
+				case RAS_PEC_MODE1:
+					for (i=0; i<(inputLength*4);i++)
+						output[i] = per_buff[i];
+					break;
+		    	default:
+		    		break;
+    		}
+
+    	}
+    	break;
+
+    	case RAS_DECODE_TI_TYPE1:
+    	    if (input == NULL) return -1;
+    	    rf_DataFrame = input+3;
+    	    codec_DecodeBuff(output, rf_DataFrame, inputLength*4,  &step_index, &predicted_value);
+
+			//Save Frame for packet error concealment
+	   		switch (ras_pec_mode)
+			{
+				case RAS_PEC_MODE1:
+					for (i=0; i<(inputLength*4);i++)
+						per_buff[i] = output[i];
+					break;
+				default:
+					break;
+			}
+
+    		break;
+
+    	default:
+    		break;
+
+
+    }
+    *outputLenght = inputLength*4;
+    return 0;
+};
+
+
diff --git a/rcu_audio/RAS_lib.h b/rcu_audio/RAS_lib.h
new file mode 100755
index 0000000..e009902
--- /dev/null
+++ b/rcu_audio/RAS_lib.h
@@ -0,0 +1,195 @@
+/*
+* Copyright (c) [2015] Texas Instruments Incorporated
+*
+* All rights reserved not granted herein.
+* Limited License.
+*
+* Texas Instruments Incorporated grants a world-wide, royalty-free,
+* non-exclusive license under copyrights and patents it now or hereafter
+* owns or controls to make, have made, use, import, offer to sell and sell ("Utilize")
+* this software subject to the terms herein.  With respect to the foregoing patent
+*license, such license is granted  solely to the extent that any such patent is necessary
+* to Utilize the software alone.  The patent license shall not apply to any combinations which
+* include this software, other than combinations with devices manufactured by or for TI ("TI Devices").
+* No hardware patent is licensed hereunder.
+*
+* Redistributions must preserve existing copyright notices and reproduce this license (including the
+* above copyright notice and the disclaimer and (if applicable) source code license limitations below)
+* in the documentation and/or other materials provided with the distribution
+*
+* Redistribution and use in binary form, without modification, are permitted provided that the
+* following conditions are met:
+*
+*             * No reverse engineering, decompilation, or disassembly of this software is permitted
+*             	with respect to any software provided in binary form.
+*             * any redistribution and use are licensed by TI for use only with TI Devices.
+*             * Nothing shall obligate TI to provide you with source code for the software licensed
+*             	and provided to you in object code.
+*
+* If software source code is provided to you, modification and redistribution of the source code are
+* permitted provided that the following conditions are met:
+*
+*   * any redistribution and use of the source code, including any resulting derivative works, are
+*     licensed by TI for use only with TI Devices.
+*   * any redistribution and use of any object code compiled from the source code and any resulting
+*     derivative works, are licensed by TI for use only with TI Devices.
+*
+* Neither the name of Texas Instruments Incorporated nor the names of its suppliers may be used to
+* endorse or promote products derived from this software without specific prior written permission.
+*
+* DISCLAIMER.
+*
+* THIS SOFTWARE IS PROVIDED BY TI AND TI'S LICENSORS "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 TI AND TI'S LICENSORS 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.
+*/
+
+
+#ifndef RSA_LIB_H
+#define RSA_LIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#if !defined PACK_1
+#define PACK_1
+#endif
+
+
+#if defined(_MSC_VER) || defined(unix) || (defined(__ICC430__) && (__ICC430__==1))
+#pragma pack(1)
+#endif
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Defines
+
+#define MAX_INPUT_BUF_SIZE 		128
+
+#define RAS_PACKET_LOST 		0
+#define RAS_DECODE_TI_TYPE1		1
+
+#define RAS_NO_PEC		   		0
+#define RAS_PEC_MODE1   		1
+
+//RAS Software Version: v1.3
+#define RAS_SOFTWARE_VERSION	0x0103
+/////////////////////////////////////////////////////////////////////////////
+// Typedefs
+#ifndef int8
+typedef signed   char   int8;
+#endif
+
+#ifndef uint8
+typedef unsigned char   uint8;
+#endif
+
+#ifndef int16
+typedef signed   short  int16;
+#endif
+
+#ifndef uint16
+typedef unsigned short  uint16;
+#endif
+
+#ifndef int32
+typedef signed   int  int32;
+#endif
+
+#ifndef uint32
+typedef unsigned int  uint32;
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// Global variable
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Function declarations
+/**************************************************************************************************
+ *
+ * @fn      RAS_GetVersion
+ *
+ * @brief   RemoTI Audio Subsystem, retrieve software version
+ *
+ * input parameters
+ *
+ * none
+ *
+ * output parameters
+ *
+ * None.
+ *
+ * @return      .
+ * Software Version.	MSB	 Major revision number
+ *						LSB: Minor revision number
+ */
+uint16 RAS_GetVersion( void );
+
+/**************************************************************************************************
+ *
+ * @fn      RAS_Init
+ *
+ * @brief   RemoTI Audio Subsystem, initialization function
+ *
+ * input parameters
+ *
+ * @param   pec_mode:    	Packet Error concealment algorithm to apply:
+ * 							RAS_NO_PEC(0): 		None (default)
+ * 							RAS_PEC_MODE1(1): 	Replace lost packets by last valid.
+ *
+ * output parameters
+ *
+ * None.
+ *
+ * @return      .
+ * status.	0				SUCCESS
+ * 			-1				ERROR: INVALID PARAMETER
+ */
+uint8 RAS_Init( uint8 pec_mode );
+
+
+/**************************************************************************************************
+ *
+ * @fn      RAS_Decode
+ *
+ * @brief   RemoTI Audio Subsystem, decoding function. decode encoded audioframe to PCM samples.
+ *
+ * input parameters
+ *
+ * @param   option:    		decoding option. can be pure decoding, or packet lot concealment algorithm:
+ * 							RAS_PACKET_LOST(0)
+ * 							RAS_DECODE(1)
+ * @param   input: 			address of the buffer to decode, this buffer must include the 3 bytes header..
+ *
+ * @param   inputLenght:  	length of the buffer to decode, excluding the 3 bytes header.
+ * 							cannot be greater than 128 (MAX_INPUT_BUF_SIZE);
+ *
+ * output parameters
+ *
+ * @param   output:     	buffer where the decoded PCM will be written. This buffer must be allocated by the caller.
+ * 							it must have a length of 4 times the inputLength variable
+ *
+ * @param   outputLenght:  	length of the decoded buffer.
+ * 							max possible value 512 (4*MAX_INPUT_BUF_SIZE);
+ *
+ *
+ * @return      .
+ * status.	0				SUCCESS
+ * 			-1				ERROR: INVALID PARAMETER
+ *
+ */
+uint8 RAS_Decode( uint8 option, uint8* input, uint16 inputLenght, int16* output,uint16 *outputLenght );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // RSA_LIB_H
diff --git a/rcu_audio/gfrm-voice-demo.cc b/rcu_audio/gfrm-voice-demo.cc
new file mode 100644
index 0000000..c097252
--- /dev/null
+++ b/rcu_audio/gfrm-voice-demo.cc
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _BSD_SOURCE
+#include <endian.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "rcu-audio.h"
+#include "remote_control_audio.pb.h"
+
+
+typedef struct WAV_hdr
+{
+  uint32_t chunk_id;
+  uint32_t chunk_size;
+  uint32_t format;
+
+  uint32_t subchunk1_id;
+  uint32_t subchunk1_size;
+  uint16_t audio_format;
+  uint16_t num_channels;
+  uint32_t sample_rate;
+  uint32_t byte_rate;
+  uint16_t block_align;
+  uint16_t bits_per_sample;
+
+  uint32_t subchunk2_id;
+  uint32_t subchunk2_size;
+} WAV_hdr_t;
+
+
+static int usage(const char *progname)
+{
+  fprintf(stderr, "usage: %s [-f outfile]\n, where:", progname);
+  fprintf(stderr, "\t-f outfile: file to write audio to in WAV format.\n");
+  exit(1);
+}
+
+
+int main(int argc, char **argv)
+{
+  mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP;
+  int fd;
+  struct sockaddr_un sun;
+  const char *outfile = "/tmp/audio.wav";
+  int outfd;
+  uint8_t buf[8192];
+  WAV_hdr_t hdr;
+  ssize_t len, totlen=0;
+  int c;
+  struct timeval tv;
+  const char *model = "UNKNOWN";
+
+  memset(buf, 0, sizeof(buf));
+  memset(&hdr, 0, sizeof(hdr));
+
+  while ((c = getopt(argc, argv, "f:")) != -1) {
+    switch (c) {
+      case 'f':
+        outfile = optarg;
+        break;
+      default:
+        usage(argv[0]);
+        break;
+    }
+  }
+
+  if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
+    perror("socket(AF_UNIX) RCU_AUDIO_PATH");
+    exit(1);
+  }
+  memset(&sun, 0, sizeof(sun));
+  sun.sun_family = AF_UNIX;
+  strncpy(&sun.sun_path[1], RCU_AUDIO_PATH, sizeof(sun.sun_path) - 2);
+  if (bind(fd, (const struct sockaddr *) &sun, sizeof(sun)) < 0) {
+    perror("bind(AF_UNIX) RCU_AUDIO_PATH");
+    exit(1);
+  }
+
+  if ((outfd = open(outfile, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0) {
+    fprintf(stderr, "Unable to open %s for writing.\n", outfile);
+    exit(1);
+  }
+
+  if ((len = write(outfd, &hdr, sizeof(WAV_hdr_t))) != sizeof(WAV_hdr_t)) {
+    fprintf(stderr, "Incorrect size for WAV header: %zd != %zd\n",
+      len, sizeof(WAV_hdr_t));
+    exit(1);
+  }
+
+  tv.tv_sec = 0x7fffffff;
+  tv.tv_usec = 0;
+
+  while (1) {
+    fd_set rfds;
+
+    FD_ZERO(&rfds);
+    FD_SET(fd, &rfds);
+    if (select(fd + 1, &rfds, NULL, NULL, &tv) <= 0) {
+      /* No more data, close the output and exit. */
+      break;
+    }
+
+    len = read(fd, buf, sizeof(buf));
+    if (len > 0) {
+      rcaudio::AudioSamples samples;
+      const char *data;
+      ssize_t data_len;
+
+      if (!samples.ParseFromArray(buf, len)) {
+        if (pacing()) {
+          printf("failed to parse rcaudio::AudioSamples.\n");
+        }
+        continue;
+      }
+
+      if (samples.audio_format() != rcaudio::AudioSamples::PCM_16BIT_16KHZ) {
+        /* if we ever build a remote with a different format, we'll need
+         * to keep track of it here and adjust the WAV header to match. */
+        if (pacing()) {
+          fprintf(stderr, "unknown audio format %d\n", samples.audio_format());
+        }
+        continue;
+      }
+
+      switch (samples.remote_type()) {
+        case rcaudio::AudioSamples::GFRM210: model = "GFRM210"; break;
+        case rcaudio::AudioSamples::GFRM100: model = "GFRM100"; break;
+
+        case rcaudio::AudioSamples::UNDEFINED_REMOTE_TYPE:
+        default:
+          model = "UNKNOWN";
+          break;
+      }
+
+      data = samples.audio_samples().c_str();
+      data_len = samples.audio_samples().size();
+      totlen += data_len;
+      if (write(outfd, data, data_len) != data_len) {
+        fprintf(stderr, "short write!\n");
+        exit(1);
+      }
+    } else if (len == 0) {
+      break;
+    } else if (len < 0) {
+      perror("read");
+      exit(1);
+    }
+    tv.tv_sec = 2;
+    tv.tv_usec = 0;
+  }
+
+  /* print the remote control type to stdout, demo script uses it. */
+  puts(model);
+
+  lseek(outfd, 0, SEEK_SET);
+
+  #define BITS_PER_SAMPLE 16
+  #define SAMPLES_PER_SECOND  16000
+  /* http://soundfile.sapp.org/doc/WaveFormat/ */
+  hdr.chunk_id = htole32(0x46464952);  // "RIFF"
+  hdr.chunk_size = htole32(36 + totlen);
+  hdr.format = htole32(0x45564157);  // "WAVE"
+
+  hdr.subchunk1_id = htole32(0x20746d66);  // "fmt "
+  hdr.subchunk1_size = htole32(16);
+  hdr.audio_format = htole16(1);
+  hdr.num_channels = htole16(1);
+  hdr.sample_rate = htole32(SAMPLES_PER_SECOND);
+  hdr.byte_rate = htole32(SAMPLES_PER_SECOND * 1 * BITS_PER_SAMPLE/8);
+  hdr.block_align = htole16(1 * BITS_PER_SAMPLE/8);
+  hdr.bits_per_sample = htole16(BITS_PER_SAMPLE);
+
+  hdr.subchunk2_id = htole32(0x61746164);  // "data"
+  hdr.subchunk2_size = htole32(totlen);
+  if ((len = write(outfd, &hdr, sizeof(WAV_hdr_t))) != sizeof(WAV_hdr_t)) {
+    fprintf(stderr, "Incorrect size for WAV header: %zd != %zd\n",
+      len, sizeof(WAV_hdr_t));
+    exit(1);
+  }
+
+  exit(0);
+}
diff --git a/rcu_audio/gfrm100-rcu-audio.cc b/rcu_audio/gfrm100-rcu-audio.cc
new file mode 100644
index 0000000..6e55e93
--- /dev/null
+++ b/rcu_audio/gfrm100-rcu-audio.cc
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Derived from hid-example.c, license:
+ *
+ * Hidraw Userspace Example
+ *
+ * Copyright (c) 2010 Alan Ott <alan@signal11.us>
+ * Copyright (c) 2010 Signal 11 Software
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using hidraw.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/hidraw.h>
+#include <linux/input.h>
+#include <linux/types.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "rcu-audio.h"
+#include "remote_control_audio.pb.h"
+
+
+int main(int argc, char **argv)
+{
+  int in = -1, out = -1, connected = 0;
+  const char *device;
+  struct sockaddr_un sun;
+  char name[16];
+  char address[64];
+
+  if (argc != 2) {
+    fprintf(stderr, "usage: %s /dev/hidraw#\n", argv[0]);
+    exit(1);
+  }
+  device = argv[1];
+
+  if ((in = open(device, O_RDWR)) < 0) {
+    perror("open /dev/hidraw");
+    exit(1);
+  }
+
+  memset(&sun, 0, sizeof(sun));
+  sun.sun_family = AF_UNIX;
+  strncpy(&sun.sun_path[1], RCU_AUDIO_PATH, sizeof(sun.sun_path) - 2);
+
+  if (ioctl(in, HIDIOCGRAWNAME(sizeof(name)), name) < 0) {
+    perror("HIDIOCGRAWNAME");
+    exit(1);
+  }
+  if (strcmp(name, "GFRM100") != 0) {
+    fprintf(stderr, "%s is not a GFRM100. Exiting.\n", device);
+    exit(0);
+  }
+
+  if (ioctl(in, HIDIOCGRAWPHYS(sizeof(address)), address) < 0) {
+    perror("HIDIOCGRAWPHYS");
+    exit(1);
+  }
+
+  /* this process will be started out of the hotplug script when a new
+   * remote appears. We either exit if not a GFRM100, or daemonize to
+   * let the hotplug script continue. */
+  if (daemon(0, 1)) {
+    perror("daemon()");
+    exit(1);
+  }
+
+  while (1) {
+    uint8_t data[2048];
+    size_t len = read(in, data, sizeof(data));
+
+    if (len < 0) {
+      fprintf(stderr, "GFRM100 has disconnected. Exiting.\n");
+      exit(0);
+    }
+
+    if (data[0] != 0xf7) {
+      /* Not an audio packet */
+      continue;
+    }
+
+    if (data[1] == 0x01 && len > 4) {
+      rcaudio::AudioSamples samples;
+      std::vector<uint8_t> pkt;
+
+      samples.set_rc_address(address);
+      samples.set_audio_format(rcaudio::AudioSamples::PCM_16BIT_16KHZ);
+      samples.set_remote_type(rcaudio::AudioSamples::GFRM100);
+
+      /*
+       * data[0] == 0xf7
+       * data[1] == 0x01
+       * data[2] and data[3] are a count of the number of samples.
+       * data[4] == first byte of audio data.
+       */
+      samples.set_audio_samples(data + 4, len - 4);
+
+      pkt.resize(samples.ByteSize());
+      samples.SerializeToArray(&pkt[0], samples.ByteSize());
+
+      if (out < 0) {
+        out = get_socket_or_die();
+      }
+
+      if (!connected) {
+        if (connect(out, (const struct sockaddr *) &sun, sizeof(sun)) == 0) {
+          connected = 1;
+        } else {
+          sleep(2);  /* rate limit how often we retry. */
+        }
+      }
+
+      if (connected) {
+        if (send(out, &pkt[0], pkt.size(), 0) != (ssize_t)pkt.size()) {
+          fprintf(stderr, "Audio send failed, will reconnect.\n");
+          close(out);
+          out = -1;
+          connected = 0;
+        }
+      }
+    }
+  }
+  return 0;
+}
diff --git a/rcu_audio/rcu-audio.h b/rcu_audio/rcu-audio.h
new file mode 100644
index 0000000..725ca10
--- /dev/null
+++ b/rcu_audio/rcu-audio.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RCU_AUDIO_H
+#define RCU_AUDIO_H
+
+#define RCU_AUDIO_PATH "rcu_audio"
+
+/* Return 1 if at least one second has passed since the
+ * last successful call to pacing(). */
+extern int pacing();
+
+/* Return an AF_UNIX socket, or die trying. */
+extern int get_socket_or_die();
+
+#endif  /* RCU_AUDIO_H */
diff --git a/rcu_audio/rcu-utils.cc b/rcu_audio/rcu-utils.cc
new file mode 100644
index 0000000..2bd1e41
--- /dev/null
+++ b/rcu_audio/rcu-utils.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+static uint64_t monotime(void) {
+  struct timespec ts;
+  if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+    perror("clock_gettime(CLOCK_MONOTONIC)");
+    exit(1);
+  } else {
+    return (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
+  }
+}
+
+
+/* Return 1 if at least one second has passed since the
+ * last successful call to pacing(). */
+int pacing() {
+  static uint64_t last = 0;
+  uint64_t now = monotime();
+  int rc = 0;
+
+  if ((now - last) > 1000000) {
+    last = now;
+    rc = 1;
+  }
+
+  return rc;
+}
+
+
+int get_socket_or_die()
+{
+  int s;
+
+  if ((s = socket(AF_UNIX, SOCK_NONBLOCK | SOCK_DGRAM, 0)) < 0) {
+    perror("socket(AF_UNIX)");
+    exit(1);
+  }
+
+  return s;
+}
diff --git a/rcu_audio/remote_control_audio.proto b/rcu_audio/remote_control_audio.proto
new file mode 100644
index 0000000..3f6b767
--- /dev/null
+++ b/rcu_audio/remote_control_audio.proto
@@ -0,0 +1,25 @@
+syntax = "proto2";
+package rcaudio;
+option optimize_for = LITE_RUNTIME;
+
+message AudioSamples {
+  // A unique identifier for the remote control.
+  // For Bluetooth this will be the BDADDR like "00:11:22:33:44:55"
+  optional string rc_address = 1;
+
+  enum AudioFormat {
+    UNDEFINED_AUDIO_FORMAT = 0;
+    PCM_16BIT_16KHZ = 1;
+  }
+  optional AudioFormat audio_format = 2;
+
+  enum RemoteType {
+    UNDEFINED_REMOTE_TYPE = 0;
+    GFRM210 = 1;
+    GFRM100 = 2;
+  }
+  optional RemoteType remote_type = 3;
+
+  optional bytes audio_samples = 4;
+};
+
diff --git a/rcu_audio/ti-rcu-audio.cc b/rcu_audio/ti-rcu-audio.cc
new file mode 100644
index 0000000..76f1126
--- /dev/null
+++ b/rcu_audio/ti-rcu-audio.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Note: though this specific file is licensed under the Apache license,
+ * it exists in order to interface with the RAS_LIB.c implementation
+ * provided by TI which is not licensed under Apache. */
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "rcu-audio.h"
+#include "remote_control_audio.pb.h"
+#include "RAS_lib.h"
+
+#define TI_AUDIO_PATH "\0rc_audio_ti"
+
+int main(int argc, char **argv)
+{
+  int is = -1, os = -1, connected = 0;
+  struct sockaddr_un sun;
+  uint8 prev = 0;
+  int msgs = 0, missed = 0, errors = 0;
+
+  is = get_socket_or_die();
+  memset(&sun, 0, sizeof(sun));
+  sun.sun_family = AF_UNIX;
+  snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", TI_AUDIO_PATH);
+  if (bind(is, (const struct sockaddr *) &sun, sizeof(sun)) < 0) {
+    perror("bind(AF_UNIX)");
+    exit(1);
+  }
+
+  memset(&sun, 0, sizeof(sun));
+  sun.sun_family = AF_UNIX;
+  strncpy(&sun.sun_path[1], RCU_AUDIO_PATH, sizeof(sun.sun_path) - 2);
+
+  while (1) {
+    uint8 ibuf[MAX_INPUT_BUF_SIZE + 6 + 1 + 4];
+    size_t ilen;
+
+    ilen = recv(is, ibuf, sizeof(ibuf), 0);
+    if (ilen < 23) {
+      /* end of an audio file, prepare for the next one */
+      printf("Finished audio stream; msgs = %d, missed = %d, errors = %d\n",
+          msgs, missed, errors);
+      msgs = missed = errors = 0;
+      RAS_Init(RAS_NO_PEC);
+    } else {
+      uint8_t remote_type = ibuf[6];
+      uint8 expected = (prev + 1) & 0x1f;
+      uint8 seqnum = (ibuf[7] >> 3) & 0x1f;
+      uint8 *data;
+      uint16 data_len;
+      int16 obuf[4 * MAX_INPUT_BUF_SIZE];
+      uint16 olen;
+
+      if (seqnum != expected) {
+        missed++;
+      }
+      prev = seqnum;
+      msgs++;
+
+      /*
+       * We skip over:
+       *   the first 6 bytes, which is the BDADDR of the remote.
+       *   the remote type byte (0 == GFRM210)
+       *   the sequence number byte
+       *
+       * The first three bytes of payload are some kind of RAS header.
+       * RAS_Decode() says the data pointer must include the three bytes
+       * of header, but the length must not include those 3 bytes.
+       * So the length is decremented by 6+1+1+3 for a total of 11.
+       */
+      data = &ibuf[6 + 1 + 1];
+      data_len = ilen - (6 + 1 + 1 + 3);
+      if (RAS_Decode(RAS_DECODE_TI_TYPE1, data, data_len, obuf, &olen) == 0) {
+        rcaudio::AudioSamples samples;
+        char bdaddr[18];
+        std::vector<uint8_t> pkt;
+
+        snprintf(bdaddr, sizeof(bdaddr),
+            "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+            ibuf[0], ibuf[1], ibuf[2], ibuf[3], ibuf[4], ibuf[5]);
+
+        samples.set_rc_address(bdaddr);
+        if (remote_type == 0) {
+          samples.set_audio_format(rcaudio::AudioSamples::PCM_16BIT_16KHZ);
+          samples.set_remote_type(rcaudio::AudioSamples::GFRM210);
+        } else {
+          samples.set_audio_format(rcaudio::AudioSamples::UNDEFINED_AUDIO_FORMAT);
+          samples.set_remote_type(rcaudio::AudioSamples::UNDEFINED_REMOTE_TYPE);
+        }
+        samples.set_audio_samples(obuf, olen);
+
+        pkt.resize(samples.ByteSize());
+        samples.SerializeToArray(&pkt[0], samples.ByteSize());
+
+        if (os < 0) {
+          os = get_socket_or_die();
+        }
+
+        if (!connected) {
+          if (connect(os, (const struct sockaddr *) &sun, sizeof(sun)) == 0) {
+            connected = 1;
+          } else {
+            sleep(2);  /* rate limit how often we try */
+          }
+        }
+
+        if (connected) {
+          if (send(os, &pkt[0], pkt.size(), 0) != (ssize_t)pkt.size()) {
+            fprintf(stderr, "Audio send failed, will reconnect.\n");
+            connected = 0;
+            close(os);
+            os = -1;
+          }
+        }
+      } else {
+        if (pacing()) {
+          printf("RAS_Decode(RAS_DECODE_TI_TYPE1) failed\n");
+        }
+        errors++;
+      }
+    }
+  }
+}
diff --git a/signing/S99readallfiles b/signing/S99readallfiles
index 87b97e0..00838fa 100755
--- a/signing/S99readallfiles
+++ b/signing/S99readallfiles
@@ -27,8 +27,11 @@
 case "$1" in
   start)
     (
-      nice -n 19 readallfiles -q / &&
-      clear_failure_count
+      if is-fiberjack; then
+        nice -n 19 readallfiles -q /config && clear_failure_count
+      else
+        nice -n 19 readallfiles -q / && clear_failure_count
+      fi
     ) 2>&1 | logos readall &
     ;;
   stop)
diff --git a/taxonomy/pcaptest.py b/taxonomy/pcaptest.py
index 2b5d90f..02c2039 100644
--- a/taxonomy/pcaptest.py
+++ b/taxonomy/pcaptest.py
@@ -71,8 +71,10 @@
   ('iPhone 6/6+', './testdata/pcaps/iPhone 6+ 5GHz.pcap'),
   ('iPhone 6s/6s+', './testdata/pcaps/iPhone 6s 2.4GHz.pcap'),
   ('iPhone 6s/6s+', './testdata/pcaps/iPhone 6s+ 2.4GHz.pcap'),
+  ('iPhone 6s/6s+', './testdata/pcaps/iPhone 6s+ 2.4GHz RRM.pcap'),
   ('iPhone 6s/6s+', './testdata/pcaps/iPhone 6s 5GHz.pcap'),
   ('iPhone 6s/6s+', './testdata/pcaps/iPhone 6s+ 5GHz.pcap'),
+  ('iPhone 6s/6s+', './testdata/pcaps/iPhone 6s+ 5GHz RRM.pcap'),
   ('iPod Touch 1st or 2nd gen', './testdata/pcaps/iPod Touch 1st gen 2.4GHz.pcap'),
   ('Moto G or Moto X', './testdata/pcaps/Moto X 2.4GHz Specific.pcap'),
   ('Moto G or Moto X', './testdata/pcaps/Moto X 2.4GHz.pcap'),
diff --git a/taxonomy/testdata/dhcp.leases b/taxonomy/testdata/dhcp.leases
index ab83a43..f3eae5e 100644
--- a/taxonomy/testdata/dhcp.leases
+++ b/taxonomy/testdata/dhcp.leases
@@ -69,3 +69,4 @@
 1432237016 a4:d1:d2:00:00:00 192.168.42.58 iPaadOldiOS
 1432237016 70:48:0f:00:00:00 192.168.42.59 iPadPro12_9
 1432237016 6c:c2:17:00:00:00 192.168.42.60 HPPrinter
+1432237016 dc:2b:2a:95:bc:77 192.168.42.61 iPhoone 6s+
diff --git a/taxonomy/testdata/dhcp.signatures b/taxonomy/testdata/dhcp.signatures
index 1fcfa61..0c79a18 100644
--- a/taxonomy/testdata/dhcp.signatures
+++ b/taxonomy/testdata/dhcp.signatures
@@ -61,3 +61,4 @@
 a4:d1:d2:00:00:00 1,3,6,15,119,252
 70:48:0f:00:00:00 1,3,6,15,119,252
 6c:c2:17:00:00:00 6,3,1,15,66,67,13,44,12,81,252
+dc:2b:2a:95:bc:77 1,3,6,15,119,252
diff --git a/taxonomy/testdata/pcaps/iPhone 6s+ 2.4GHz RRM.pcap b/taxonomy/testdata/pcaps/iPhone 6s+ 2.4GHz RRM.pcap
new file mode 100644
index 0000000..28cbed8
--- /dev/null
+++ b/taxonomy/testdata/pcaps/iPhone 6s+ 2.4GHz RRM.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/iPhone 6s+ 5GHz RRM.pcap b/taxonomy/testdata/pcaps/iPhone 6s+ 5GHz RRM.pcap
new file mode 100644
index 0000000..1ba43ad
--- /dev/null
+++ b/taxonomy/testdata/pcaps/iPhone 6s+ 5GHz RRM.pcap
Binary files differ
diff --git a/wifi/configs.py b/wifi/configs.py
index 743fe10..97a27ce 100644
--- a/wifi/configs.py
+++ b/wifi/configs.py
@@ -375,7 +375,7 @@
                                utils.validate_and_sanitize_bssid(opt.bssid))
   network_block = make_network_block(network_block_lines)
 
-  freq_list = ','.join(autochannel.get_all_frequencies(opt.band))
+  freq_list = ' '.join(autochannel.get_all_frequencies(opt.band))
 
   lines = [
       'ctrl_interface=/var/run/wpa_supplicant',
diff --git a/wifi/configs_test.py b/wifi/configs_test.py
index 64e05c6..016fc27 100755
--- a/wifi/configs_test.py
+++ b/wifi/configs_test.py
@@ -11,9 +11,9 @@
 
 
 _FREQ_LIST = {
-    '2.4': '2412,2417,2422,2427,2432,2437,2442,2447,2452,2457,2462',
-    '5': ('5180,5200,5220,5240,5745,5765,5785,5805,5825,5260,5280,5300,5320,'
-          '5500,5520,5540,5560,5580,5660,5680,5700'),
+    '2.4': '2412 2417 2422 2427 2432 2437 2442 2447 2452 2457 2462',
+    '5': ('5180 5200 5220 5240 5745 5765 5785 5805 5825 5260 5280 5300 5320 '
+          '5500 5520 5540 5560 5580 5660 5680 5700'),
 }
 
 
diff --git a/wifi/quantenna.py b/wifi/quantenna.py
index 1408574..7aad0d0 100755
--- a/wifi/quantenna.py
+++ b/wifi/quantenna.py
@@ -34,20 +34,27 @@
   raise utils.BinWifiException('no VLAN ID for interface %s' % hif)
 
 
-def _get_interface(mode, suffix):
+def _get_interfaces(mode, suffix):
   # Each host interface (hif) maps to exactly one LHOST interface (lif) based on
   # the VLAN ID as follows: the lif is wifiX where X is the VLAN ID - 2 (VLAN
   # IDs start at 2). The client interface must map to wifi0, so it must have
   # VLAN ID 2.
   prefix = 'wlan' if mode == 'ap' else 'wcli'
-  suffix = '_' + suffix if suffix else ''
+  suffix = r'.*' if suffix == 'ALL' else suffix
   for hif in _get_quantenna_interfaces():
-    if re.match(prefix + r'\d*' + suffix, hif):
+    if re.match(r'^' + prefix + r'\d*' + suffix + r'$', hif):
       vlan = _get_vlan(hif)
       lif = 'wifi%d' % (vlan - 2)
       mac = _get_external_mac(hif)
-      return hif, lif, mac, vlan
-  return None, None, None, None
+      yield hif, lif, mac, vlan
+
+
+def _get_interface(mode, suffix):
+  return next(_get_interfaces(mode, suffix), (None, None, None, None))
+
+
+def _set_link_state(hif, state):
+  subprocess.check_output(['ip', 'link', 'set', 'dev', hif, state])
 
 
 def _ifplugd_action(hif, state):
@@ -145,6 +152,7 @@
     _qcsapi('vlan_config', 'pcie0', 'trunk', vlan)
 
     _qcsapi('block_bss', lif, 0)
+    _set_link_state(hif, 'up')
     _ifplugd_action(hif, 'up')
   except:
     stop_ap_wifi(opt)
@@ -188,6 +196,7 @@
     _qcsapi('vlan_config', 'pcie0', 'enable')
     _qcsapi('vlan_config', 'pcie0', 'trunk', vlan)
 
+    _set_link_state(hif, 'up')
     _ifplugd_action(hif, 'up')
   except:
     stop_client_wifi(opt)
@@ -198,34 +207,32 @@
 
 def stop_ap_wifi(opt):
   """Disable AP."""
-  hif, lif, _, _ = _get_interface('ap', opt.interface_suffix)
-  if not hif:
-    return False
+  hif = None
+  for hif, lif, _, _ in _get_interfaces('ap', opt.interface_suffix):
+    try:
+      _qcsapi('wifi_remove_bss', lif)
+    except subprocess.CalledProcessError:
+      pass
 
-  try:
-    _qcsapi('wifi_remove_bss', lif)
-  except subprocess.CalledProcessError:
-    pass
+    _set_link_state(hif, 'down')
+    _ifplugd_action(hif, 'down')
 
-  _ifplugd_action(hif, 'down')
-
-  return True
+  return hif is not None
 
 
 def stop_client_wifi(opt):
   """Disable client."""
-  hif, lif, _, _ = _get_interface('sta', opt.interface_suffix)
-  if not hif:
-    return False
+  hif = None
+  for hif, lif, _, _ in _get_interfaces('sta', opt.interface_suffix):
+    try:
+      _qcsapi('remove_ssid', lif, _qcsapi('get_ssid_list', lif, 1))
+    except subprocess.CalledProcessError:
+      pass
 
-  try:
-    _qcsapi('remove_ssid', lif, _qcsapi('get_ssid_list', lif, 1))
-  except subprocess.CalledProcessError:
-    pass
+    _set_link_state(hif, 'down')
+    _ifplugd_action(hif, 'down')
 
-  _ifplugd_action(hif, 'down')
-
-  return True
+  return hif is not None
 
 
 def scan_wifi(_):
diff --git a/wifi/quantenna_test.py b/wifi/quantenna_test.py
index 177f557..b0fb485 100755
--- a/wifi/quantenna_test.py
+++ b/wifi/quantenna_test.py
@@ -35,7 +35,7 @@
   quantenna._get_vlan = lambda _: 3
   wvtest.WVPASSEQ(quantenna._get_interface('ap', ''),
                   ('wlan0', 'wifi1', '00:00:00:00:00:00', 3))
-  wvtest.WVPASSEQ(quantenna._get_interface('ap', 'portal'),
+  wvtest.WVPASSEQ(quantenna._get_interface('ap', '_portal'),
                   ('wlan0_portal', 'wifi1', '00:00:00:00:00:00', 3))
   wvtest.WVPASSEQ(quantenna._get_interface('sta', ''),
                   (None, None, None, None))