Merge "conman:  Minor cleanup."
diff --git a/Makefile b/Makefile
index b0d99f0..82a7d8b 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
@@ -15,11 +15,12 @@
 BUILD_CRYPTDEV?=    # default off: needs libdevmapper
 BUILD_SIGNING?=     # default off: needs libgtest
 BUILD_JSONPOLL?=n
+BUILD_BOUNCER?=     # default off: costly
 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
+	BUILD_PRESTERASTATS BUILD_CACHE_WARMING BUILD_BOUNCER
 
 # note: libgpio is not built here.  It's conditionally built
 # via buildroot/packages/google/google_platform/google_platform.mk
@@ -73,6 +74,10 @@
 DIRS+=craftui
 endif
 
+ifeq ($(BUILD_BOUNCER),y)
+DIRS+=bouncer
+endif
+
 ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfsc100)
 DIRS+=diags
 endif
@@ -85,6 +90,10 @@
 DIRS+=diags
 endif
 
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfrg240)
+DIRS+=diags
+endif
+
 ifeq ($(BUILD_CONMAN),y)
 DIRS+=conman
 endif
@@ -97,6 +106,10 @@
 DIRS+=ledpattern
 endif
 
+ifeq ($(BUILD_BLUETOOTH),y)
+DIRS+=rcu_audio
+endif
+
 PREFIX=/usr
 BINDIR=$(DESTDIR)$(PREFIX)/bin
 LIBDIR=$(DESTDIR)$(PREFIX)/lib
@@ -118,7 +131,7 @@
 	$(MAKE) install-commonpy
 	mkdir -p $(BINDIR)
 	rm -fv $(BINDIR)/hnvram
-ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME), gfmn110)
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME), gfmn100)
 	ln -s /usr/bin/hnvram_wrapper $(BINDIR)/hnvram
 else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME), gflt110)
 	ln -s /usr/bin/hnvram_wrapper $(BINDIR)/hnvram
diff --git a/bouncer/.gitignore b/bouncer/.gitignore
new file mode 100644
index 0000000..2ad94f4
--- /dev/null
+++ b/bouncer/.gitignore
@@ -0,0 +1,6 @@
+authorizer
+host-authorizer
+hash_mac_addr
+host-hash_mac_addr
+http_bouncer
+host-http_bouncer
diff --git a/bouncer/Makefile b/bouncer/Makefile
new file mode 100644
index 0000000..db66253
--- /dev/null
+++ b/bouncer/Makefile
@@ -0,0 +1,46 @@
+default: all
+
+INSTALL?=install
+BINDIR=$(DESTDIR)/bin
+LIBDIR=$(DESTDIR)/usr/bouncer
+GPYLINT=$(shell \
+    if which gpylint >/dev/null; then \
+      echo gpylint; \
+    else \
+      echo 'echo "(gpylint-missing)" >&2'; \
+    fi \
+)
+NOINSTALL=options.py
+
+all:
+
+install:
+	mkdir -p $(LIBDIR) $(BINDIR)
+	$(INSTALL) -m 0644 $(filter-out $(NOINSTALL) $(TARGETS), $(wildcard *.py)) $(LIBDIR)/
+	for t in authorizer hash_mac_addr http_bouncer; do \
+		$(INSTALL) -m 0755 $$t.py $(LIBDIR)/; \
+		ln -fs /usr/bouncer/$$t.py $(BINDIR)/$$t; \
+	done
+
+install-libs:
+	@echo "No libs to install."
+
+TESTS = $(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py)
+runtests: all $(TESTS)
+	set -e; \
+	for d in $(TESTS); do \
+		echo Running $$d; \
+		./$$d; \
+	done
+
+lint: $(filter-out options.py,$(wildcard *.py))
+	$(GPYLINT) $^
+
+test: all $(TESTS)
+	./wvtest/wvtestrun $(MAKE) runtests
+
+clean:
+	rm -f *.o $(TARGETS) \
+		$(HOST_TARGETS) \
+		*~ .*~ */*.pyc test_file *.pb.* *.tmp.*
+	rm -rf test_dir
diff --git a/bouncer/authorizer.py b/bouncer/authorizer.py
new file mode 100755
index 0000000..0c5e0b1
--- /dev/null
+++ b/bouncer/authorizer.py
@@ -0,0 +1,193 @@
+#!/usr/bin/python
+# 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.
+#
+"""authorizer: processes Terms of Service acceptance for users."""
+
+import logging
+import subprocess
+import sys
+import time
+
+import hash_mac_addr
+import options
+
+import tornado.escape
+import tornado.httpclient
+import tornado.ioloop
+import tornado.netutil
+
+
+optspec = """
+authorizer [options...]
+--
+c,filter-chain= iptables chain to operate on [captive-portal-guests]
+C,ca-certs=     path to CA certificates [/etc/ssl/certs/ca-certificates.crt]
+d,dry-run       don't modify iptables
+m,max-age=      oldest acceptance to consider as valid, in days [60]
+n,nat-chain=    iptables NAT chain to operate on [captive-portal-guests-nat]
+U,unix-path=    Unix socket to listen on [/tmp/authorizer.sock]
+u,url=          URL to query for authentication [https://fiber-managed-wifi-tos.appspot.com/tos-accepted?id=%(mac)s]
+"""
+
+MAX_TRIES = 300
+
+in_progress_users = {}
+known_users = {}
+
+
+def ip46tables(*args):
+  if opt.dry_run:
+    return 0
+
+  x = subprocess.call(['iptables'] + list(args))
+  y = subprocess.call(['ip6tables'] + list(args))
+  return x | y
+
+
+def is_valid_acceptance(response_obj):
+  accepted_time = response_obj.get('accepted')
+  return accepted_time + (opt.max_age * 86400) > time.time()
+
+
+def allow_mac_rule(mac_addr):
+  # iptables, unlike other Linux utilities, capitalizes MAC addresses
+  return ('-m', 'mac', '--mac-source', mac_addr.upper(), '-j', 'ACCEPT')
+
+
+class Checker(object):
+  """Manage checking and polling for Terms of Service acceptance."""
+
+  def __init__(self, mac_addr, url):
+    self.mac_addr = mac_addr
+    self.url = url % {'mac': hash_mac_addr.hash_mac_addr(self.mac_addr)}
+    self.tries = 0
+    self.callback = None
+
+  def check(self):
+    """Check if a remote service knows about a device with a supplied MAC."""
+    logging.info('Checking TOS for %s', self.mac_addr)
+    http_client = tornado.httpclient.HTTPClient()
+    self.tries += 1
+
+    try:
+      response = http_client.fetch(self.url, ca_certs=opt.ca_certs)
+      response_obj = tornado.escape.json_decode(response.body)
+      valid = is_valid_acceptance(response_obj)
+    except tornado.httpclient.HTTPError as e:
+      logging.warning('Error checking authorization: %r', e)
+      valid = False
+
+    if valid:
+      logging.info('TOS accepted for %s', self.mac_addr)
+
+      known_users[self.mac_addr] = response_obj
+      result = ip46tables('-A', opt.filter_chain,
+                          *allow_mac_rule(self.mac_addr))
+      result |= ip46tables('-t', 'nat', '-A', opt.nat_chain,
+                           *allow_mac_rule(self.mac_addr))
+      if result:
+        logging.error('Could not update firewall for device %s',
+                      self.mac_addr)
+
+    if valid or self.tries > MAX_TRIES:
+      if self.callback:
+        self.callback.stop()
+      if self.mac_addr in in_progress_users:
+        del in_progress_users[self.mac_addr]
+    else:
+      in_progress_users[self.mac_addr] = self
+      self.poll()
+
+    return response
+
+  def poll(self):
+    if not self.callback:
+      self.callback = tornado.ioloop.PeriodicCallback(self.check, 1000)
+      self.callback.start()
+
+
+def accept(connection, unused_address):
+  """Accept a MAC address and find out if it's authorized."""
+  cf = connection.makefile()
+
+  maybe_mac_addr = cf.readline().strip()
+  try:
+    mac_addr = hash_mac_addr.normalize_mac_addr(maybe_mac_addr)
+  except ValueError:
+    logging.warning('can only check authorization for a MAC address.')
+    cf.write('{}')
+    return
+
+  if mac_addr in known_users:
+    cached_response = known_users[mac_addr]
+    if is_valid_acceptance(cached_response):
+      logging.info('TOS accepted (cached) for %s', mac_addr)
+      cached_response['cached'] = True
+      cf.write(tornado.escape.json_encode(cached_response))
+      return
+
+  if mac_addr in in_progress_users:
+    checker = in_progress_users[mac_addr]
+  else:
+    checker = Checker(mac_addr, opt.url)
+
+  response = checker.check()
+  cf.write(response.body)
+
+
+def expire_cache():
+  """Remove users whose authorization has expired from the cache."""
+  expired_users = set(mac_addr for mac_addr, cached_response
+                      in known_users.items()
+                      if not is_valid_acceptance(cached_response))
+
+  for mac_addr in expired_users:
+    logging.info('Removing expired user %s', mac_addr)
+    del known_users[mac_addr]
+
+    result = ip46tables('-D', opt.filter_chain, *allow_mac_rule(mac_addr))
+    result |= ip46tables('-t', 'nat', '-D', opt.nat_chain,
+                         *allow_mac_rule(mac_addr))
+    if result:
+      logging.warning('Error removing expired user %s !', mac_addr)
+
+
+if __name__ == '__main__':
+  o = options.Options(optspec)
+  opt, flags, extra = o.parse(sys.argv[1:])
+
+  if not opt.unix_path:
+    o.fatal('unix-path is required\n')
+
+  if not (opt.filter_chain and opt.nat_chain) and not opt.dry_run:
+    o.fatal('(filter-chain and nat-chain) or dry-run is required\n')
+
+  # work whether or not Tornado has configured the root logger already
+  logging.basicConfig(level=logging.INFO)
+  logging.getLogger().setLevel(logging.INFO)
+
+  ip46tables('-F', opt.filter_chain)
+  ip46tables('-t', 'nat', '-F', opt.nat_chain)
+
+  sock = tornado.netutil.bind_unix_socket(opt.unix_path)
+  ioloop = tornado.ioloop.IOLoop.instance()
+  tornado.netutil.add_accept_handler(sock, accept, ioloop)
+
+  logging.info('Started authorizer.')
+  ioloop.start()
+
+  expirer = tornado.ioloop.PeriodicCallback(expire_cache, 60 * 60 * 1000)
+  expirer.start()
+
diff --git a/bouncer/hash_mac_addr.py b/bouncer/hash_mac_addr.py
new file mode 100755
index 0000000..23b45d1
--- /dev/null
+++ b/bouncer/hash_mac_addr.py
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+
+"""hash_mac_addr: hash MAC addresses for privacy."""
+
+import hashlib
+import re
+import sys
+
+import options
+
+optspec = """
+hash_mac_addr -a ##:##:##:##:##:##
+--
+a,addr= MAC address to hash
+"""
+
+
+def normalize_mac_addr(maybe_mac_addr):
+  if re.match('([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$', maybe_mac_addr):
+    return maybe_mac_addr.lower()
+  else:
+    raise ValueError('%r not a MAC address' % maybe_mac_addr)
+
+
+def hash_mac_addr(maybe_mac_addr):
+  mac_addr = normalize_mac_addr(maybe_mac_addr)
+  return hashlib.sha1(mac_addr).hexdigest()
+
+
+if __name__ == '__main__':
+  o = options.Options(optspec)
+  opt, unused_flags, unused_extra = o.parse(sys.argv[1:])
+
+  if not opt.addr:
+    o.usage()
+
+  try:
+    hashed_mac_addr = hash_mac_addr(str(opt.addr))
+    print hashed_mac_addr
+  except ValueError as e:
+    print >>sys.stderr, 'error:', e.message
+    sys.exit(1)
diff --git a/bouncer/http_bouncer.py b/bouncer/http_bouncer.py
new file mode 100755
index 0000000..5fe0a53
--- /dev/null
+++ b/bouncer/http_bouncer.py
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+# 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.
+#
+"""Redirects all HTTP requests to the specified URL."""
+
+import logging
+import socket
+import subprocess
+import sys
+import urllib2
+
+import hash_mac_addr
+import options
+
+import tornado.httpclient
+import tornado.ioloop
+import tornado.web
+
+
+optspec = """
+http_bouncer [options...]
+--
+p,port=      TCP port to listen on [8888]
+u,url=       URL to redirect ("bounce") users to. Include the format specifier %(mac)s to write the users' MAC address into the URL when bouncing. []
+U,unix-path= Unix socket to use for authorization checking [/tmp/authorizer.sock]
+"""
+
+PKI_HOSTS = set(['pki.google.com', 'clients1.google.com'])
+
+
+def mac_for_ip(remote_ip):
+  arp_response = subprocess.check_output(['arp', remote_ip])
+  return arp_response.split()[3]
+
+
+class Redirector(tornado.web.RequestHandler):
+  """Redirect users' HTTP connections to a captive portal landing page."""
+
+  def initialize(self, substitute_mac):
+    self.substitute_mac = substitute_mac
+    self._http_client = tornado.httpclient.HTTPClient()
+
+  def get(self):
+    if self._is_crl_request():
+      # proxy CRL/OCSP requests. Workaround for b/19825798.
+      url = '%s://%s%s' % (self.request.protocol, self.request.host,
+                           self.request.uri)
+      logging.info('Forwarding request to %s', url)
+      response = self._http_client.fetch(url)
+      for (name, value) in response.headers.get_all():
+        self.set_header(name, value)
+
+      if response.body:
+        self.set_header('Content-Length', len(response.body))
+        self.write(response.body)
+    else:
+      if self.substitute_mac:
+        mac = mac_for_ip(self.request.remote_ip)
+        self.redirect(opt.url % {'mac': hash_mac_addr.hash_mac_addr(mac)})
+
+        if opt.unix_path:
+          try:
+            s = socket.socket(socket.AF_UNIX)
+            s.connect(opt.unix_path)
+            s.sendall('%s\n' % mac)
+            s.close()
+          except socket.error:
+            logging.warning('Could not contact authorizer.')
+      else:
+        self.redirect(opt.url)
+
+  def _is_crl_request(self):
+    uri = self.request.uri
+    return self.request.host in PKI_HOSTS and (uri.startswith('/ocsp/')
+                                               or uri.endswith('.crl'))
+
+if __name__ == '__main__':
+  o = options.Options(optspec)
+  opt, flags, extra = o.parse(sys.argv[1:])
+
+  if not opt.port or not opt.url:
+    o.fatal('port and url are required\n')
+
+  # work whether or not Tornado has configured the root logger already
+  logging.basicConfig(level=logging.INFO)
+  logging.getLogger().setLevel(logging.INFO)
+
+  try:
+    formatted_url = opt.url % {'mac': '00:00:00:00:00:00'}
+    urllib2.urlopen(formatted_url).getcode()
+  except (TypeError, ValueError, urllib2.URLError):
+    o.fatal('url must be a URL.')
+
+  url_needs_mac = formatted_url != opt.url
+  if url_needs_mac and not opt.unix_path:
+    o.fatal('unix-path missing but URL requested MAC-based authorization')
+
+  application = tornado.web.Application([
+      (r'.*', Redirector, dict(substitute_mac=url_needs_mac)),
+  ])
+
+  try:
+    application.listen(opt.port)
+  except socket.gaierror:
+    o.fatal('port must be a TCP port, and we must be able to bind it.')
+
+  logging.info('Starting http_bouncer.')
+  tornado.ioloop.IOLoop.instance().start()
diff --git a/bouncer/options.py b/bouncer/options.py
new file mode 120000
index 0000000..3508154
--- /dev/null
+++ b/bouncer/options.py
@@ -0,0 +1 @@
+../options.py
\ No newline at end of file
diff --git a/bouncer/test-hash_mac_addr.sh b/bouncer/test-hash_mac_addr.sh
new file mode 100755
index 0000000..db6f10b
--- /dev/null
+++ b/bouncer/test-hash_mac_addr.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+. ./wvtest/wvtest.sh
+
+WVSTART "hash_mac_addr test"
+
+HASH_MAC_ADDR=./hash_mac_addr.py
+
+WVFAIL $HASH_MAC_ADDR
+WVFAIL $HASH_MAC_ADDR -a nonsense
+
+WVPASSEQ "$($HASH_MAC_ADDR -a 00:00:00:00:00:00)" \
+  85cce83032eb6bd39ddea68e0be917e4665b5d26
+
+WVPASSEQ "$($HASH_MAC_ADDR -a aa:bb:cc:dd:ee:ff)" \
+  "$($HASH_MAC_ADDR -a AA:BB:CC:DD:EE:FF)"
diff --git a/bouncer/test-http_bouncer.sh b/bouncer/test-http_bouncer.sh
new file mode 100755
index 0000000..b5bd72a
--- /dev/null
+++ b/bouncer/test-http_bouncer.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+#
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+
+. ./wvtest/wvtest.sh
+
+HTTP_BOUNCER=./http_bouncer.py
+PORT="1337"
+URL="http://example.com"
+
+# command substition strips off trailing newlines, so we add a one-character
+# sentinel to the command's output
+SENTINEL="X"
+
+function run_http_bouncer() {
+  $HTTP_BOUNCER -u $URL -p $PORT &
+  pid=$!
+  trap 'kill $pid' EXIT
+}
+
+function wait_for_socket() {
+  i=0
+  retries=100
+  while ! nc -z localhost $PORT && [ $i -lt $retries ] ; do sleep 0.1; i=$(expr $i + 1); done
+}
+
+WVSTART "http_bouncer test"
+
+# fail with no arguments
+WVFAIL $HTTP_BOUNCER
+# fail with extra arguments
+WVFAIL $HTTP_BOUNCER -u $URL -p $PORT --EXTRA_ARGUMENT
+# fail with invalid port
+WVFAIL $HTTP_BOUNCER -p $URL -u $PORT
+
+run_http_bouncer
+wait_for_socket
+
+redirect0=$(printf "< HTTP/1.0 302 Found\r\n< Location: $URL\r")
+redirect1=$(printf "< HTTP/1.1 302 Found\r\n< Location: $URL\r")
+
+WVPASSEQ "$(curl -0vH 'Host: google.com' "localhost:$PORT" 2>&1 |\
+  egrep '< HTTP|< Location')" "$redirect0"
+
+WVPASSEQ "$(curl -vH 'Host: google.com' "localhost:$PORT/path?arg" 2>&1 |\
+  egrep '< HTTP|< Location')" "$redirect1"
+
+WVPASSEQ "$(curl -0vH '' "localhost:$PORT" 2>&1 |\
+  egrep '< HTTP|< Location')" "$redirect0"
+
+# Make sure we can download a CRL even through the bouncer.
+# Some Internet Explorer versions will refuse to connect if we can't.
+WVPASS curl -H 'Host: pki.google.com' 'http://localhost:1337/GIAG2.crl' |\
+  openssl crl -inform DER
diff --git a/bouncer/wvtest b/bouncer/wvtest
new file mode 120000
index 0000000..75927a5
--- /dev/null
+++ b/bouncer/wvtest
@@ -0,0 +1 @@
+../cmds/wvtest
\ No newline at end of file
diff --git a/cmds/Makefile b/cmds/Makefile
index 24bc8a1..b058618 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
 
@@ -97,6 +97,10 @@
 	-g -O -std=c99 -D_GNU_SOURCE $(EXTRACFLAGS)
 CXXFLAGS += -Wall -Wextra -Wswitch-enum -Werror -Wno-unused-parameter \
 	-g -O -std=gnu++0x -D_GNU_SOURCE $(EXTRACXXFLAGS)
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfrg240)
+CFLAGS += -Wno-error=format
+CXXFLAGS += -Wno-error=format
+endif
 LDFLAGS += $(EXTRALDFLAGS)
 HOST_INCS=-I$(HOSTDIR)/usr/include
 HOST_LIBS=-L$(HOSTDIR)/usr/lib -Wl,-rpath=$(HOSTDIR)/usr/lib
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 fd2bcc8..c27ef8d 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -23,11 +23,13 @@
 import experiment
 import interface
 import iw
+import ratchet
 import status
 
 
 HOSTNAME = socket.gethostname()
 TMP_HOSTS = '/tmp/hosts'
+CWMP_PATH = '/tmp/cwmp'
 
 experiment.register('WifiNo2GClient')
 
@@ -63,7 +65,7 @@
   WIFI_SETCLIENT = ['wifi', 'setclient', '--persist']
   WIFI_STOPCLIENT = ['wifi', 'stopclient', '--persist']
 
-  def __init__(self, band, wifi, command_lines, _status, wpa_control_interface):
+  def __init__(self, band, wifi, command_lines, wpa_control_interface):
     self.band = band
     self.wifi = wifi
     self.command = command_lines.splitlines()
@@ -72,7 +74,6 @@
     self.passphrase = None
     self.interface_suffix = None
     self.access_point = None
-    self._status = _status
     self._wpa_control_interface = wpa_control_interface
 
     binwifi_option_attrs = {
@@ -161,22 +162,24 @@
     Returns:
       Whether the command succeeded.
     """
+    self.wifi.set_gateway_ip(None)
+    self.wifi.set_subnet(None)
     command = self.WIFI_SETCLIENT + ['--ssid', self.ssid, '--band', self.band]
     env = dict(os.environ)
     if self.passphrase:
       env['WIFI_CLIENT_PSK'] = self.passphrase
     try:
-      self._status.trying_wlan = True
+      self.wifi.status.trying_wlan = True
       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
+      self.wifi.status.wlan_failed = True
       return False
 
     return True
 
   def _post_start_client(self):
-    self._status.connected_to_wlan = True
+    self.wifi.status.connected_to_wlan = True
     logging.info('Started wifi client on %s GHz', self.band)
     self.wifi.attach_wpa_control(self._wpa_control_interface)
 
@@ -191,7 +194,7 @@
       subprocess.check_output(self.WIFI_STOPCLIENT + ['-b', self.band],
                               stderr=subprocess.STDOUT)
       # TODO(rofrankel): Make this work for dual-radio devices.
-      self._status.connected_to_wlan = False
+      self.wifi.status.connected_to_wlan = False
       logging.info('Stopped wifi client on %s GHz', self.band)
     except subprocess.CalledProcessError as e:
       logging.error('Failed to stop wifi client: %s', e.output)
@@ -221,6 +224,7 @@
   IFPLUGD_ACTION = ['/etc/ifplugd/ifplugd.action']
   BINWIFI = ['wifi']
   UPLOAD_LOGS_AND_WAIT = ['timeout', '60', 'upload-logs-and-wait']
+  CWMP_WAKEUP = ['cwmp', 'wakeup']
 
   def __init__(self,
                bridge_interface='br0',
@@ -229,8 +233,9 @@
                moca_tmp_dir='/tmp/cwmp/monitoring/moca2',
                wpa_control_interface='/var/run/wpa_supplicant',
                run_duration_s=1, interface_update_period=5,
-               wifi_scan_period_s=120, wlan_retry_s=15, acs_update_wait_s=10,
-               dhcp_wait_s=10, bssid_cycle_length_s=30):
+               wifi_scan_period_s=120, wlan_retry_s=15, associate_wait_s=15,
+               dhcp_wait_s=10, acs_start_wait_s=20, acs_finish_wait_s=120,
+               bssid_cycle_length_s=30):
 
     self._tmp_dir = tmp_dir
     self._config_dir = config_dir
@@ -242,8 +247,10 @@
     self._interface_update_period = interface_update_period
     self._wifi_scan_period_s = wifi_scan_period_s
     self._wlan_retry_s = wlan_retry_s
-    self._acs_update_wait_s = acs_update_wait_s
+    self._associate_wait_s = associate_wait_s
     self._dhcp_wait_s = dhcp_wait_s
+    self._acs_start_wait_s = acs_start_wait_s
+    self._acs_finish_wait_s = acs_finish_wait_s
     self._bssid_cycle_length_s = bssid_cycle_length_s
     self._wlan_configuration = {}
     self._try_to_upload_logs = False
@@ -263,7 +270,13 @@
 
     self.create_wifi_interfaces()
 
-    self._status = status.Status(self._status_dir)
+    for ifc in self.interfaces():
+      status_dir = os.path.join(self._status_dir, ifc.name)
+      if not os.path.exists(status_dir):
+        os.makedirs(status_dir)
+      ifc.status = status.Status(status_dir)
+    self._status = status.CompositeStatus(self._status_dir,
+                                          [i.status for i in self.interfaces()])
 
     wm = pyinotify.WatchManager()
     wm.add_watch(self._config_dir,
@@ -293,7 +306,7 @@
         wifi_up = self.is_interface_up(wifi.name)
         self.ifplugd_action(wifi.name, wifi_up)
         if wifi_up:
-          self._status.attached_to_wpa_supplicant = wifi.attach_wpa_control(
+          wifi.status.attached_to_wpa_supplicant = wifi.attach_wpa_control(
               self._wpa_control_interface)
 
     for path, prefix in ((self._tmp_dir, self.GATEWAY_FILE_PREFIX),
@@ -324,13 +337,32 @@
 
     # Now that we've read any existing state, it's okay to let interfaces touch
     # the routing table.
-    for ifc in [self.bridge] + self.wifi:
+    for ifc in self.interfaces():
       ifc.initialize()
       logging.info('%s initialized', ifc.name)
 
     self._interface_update_counter = 0
     self._try_wlan_after = {'5': 0, '2.4': 0}
 
+    for wifi in self.wifi:
+      ratchet_name = '%s provisioning' % wifi.name
+      wifi.provisioning_ratchet = ratchet.Ratchet(ratchet_name, [
+          ratchet.Condition('trying_open', wifi.connected_to_open,
+                            self._associate_wait_s,
+                            callback=wifi.expire_connection_status_cache),
+          ratchet.Condition('waiting_for_dhcp', wifi.gateway, self._dhcp_wait_s,
+                            callback=self.cwmp_wakeup),
+          ratchet.FileTouchedCondition('waiting_for_cwmp_wakeup',
+                                       os.path.join(CWMP_PATH, 'acscontact'),
+                                       self._acs_start_wait_s),
+          ratchet.FileTouchedCondition('waiting_for_acs_session',
+                                       os.path.join(CWMP_PATH, 'acsconnected'),
+                                       self._acs_finish_wait_s),
+      ], wifi.status)
+
+  def interfaces(self):
+    return [self.bridge] + self.wifi
+
   def create_wifi_interfaces(self):
     """Create Wifi interfaces."""
 
@@ -444,7 +476,7 @@
       if not wifi.attached():
         logging.debug('Attempting to attach to wpa control interface for %s',
                       wifi.name)
-        self._status.attached_to_wpa_supplicant = wifi.attach_wpa_control(
+        wifi.status.attached_to_wpa_supplicant = wifi.attach_wpa_control(
             self._wpa_control_interface)
       wifi.handle_wpa_events()
 
@@ -455,13 +487,13 @@
       # If this interface is connected to the user's WLAN, there is nothing else
       # to do.
       if self._connected_to_wlan(wifi):
-        self._status.connected_to_wlan = True
+        wifi.status.connected_to_wlan = True
         logging.debug('Connected to WLAN on %s, nothing else to do.', wifi.name)
         break
 
       # This interface is not connected to the WLAN, so scan for potential
       # routes to the ACS for provisioning.
-      if (not self.acs() and
+      if ((not self.acs() or self.provisioning_failed(wifi)) and
           not getattr(wifi, 'last_successful_bss_info', None) and
           time.time() > wifi.last_wifi_scan_time + self._wifi_scan_period_s):
         logging.debug('Performing scan on %s.', wifi.name)
@@ -479,12 +511,12 @@
           wlan_configuration.start_client()
           if self._connected_to_wlan(wifi):
             logging.info('Joined WLAN on %s.', wifi.name)
-            self._status.connected_to_wlan = True
+            wifi.status.connected_to_wlan = True
             self._try_wlan_after[band] = 0
             break
           else:
             logging.error('Failed to connect to WLAN on %s.', wifi.name)
-            self._status.connected_to_wlan = False
+            wifi.status.connected_to_wlan = False
             self._try_wlan_after[band] = time.time() + self._wlan_retry_s
       else:
         # If we are aren't on the WLAN, can ping the ACS, and haven't gotten a
@@ -494,7 +526,8 @@
         # 2) cwmpd isn't writing a configuration, possibly because the device
         #    isn't registered to any accounts.
         logging.debug('Unable to join WLAN on %s', wifi.name)
-        self._status.connected_to_wlan = False
+        wifi.status.connected_to_wlan = False
+        provisioning_failed = self.provisioning_failed(wifi)
         if self.acs():
           logging.debug('Connected to ACS')
           if self._try_to_upload_logs:
@@ -505,54 +538,52 @@
             wifi.last_successful_bss_info = getattr(wifi,
                                                     'last_attempted_bss_info',
                                                     None)
+            if provisioning_failed:
+              wifi.last_successful_bss_info = None
+
           now = time.time()
-          if (self._wlan_configuration and
-              hasattr(wifi, 'waiting_for_acs_since')):
-            if now - wifi.waiting_for_acs_since > self._acs_update_wait_s:
-              logging.info('ACS has not updated WLAN configuration; will retry '
-                           ' with old config.')
-              for w in self.wifi:
-                for b in w.bands:
-                  self._try_wlan_after[b] = now
-              continue
+          if self._wlan_configuration:
+            logging.info('ACS has not updated WLAN configuration; will retry '
+                         ' with old config.')
+            for w in self.wifi:
+              for b in w.bands:
+                self._try_wlan_after[b] = now
+            continue
           # We don't want to want to log this spammily, so do exponential
           # backoff.
           elif (hasattr(wifi, 'complain_about_acs_at')
                 and now >= wifi.complain_about_acs_at):
-            wait = wifi.complain_about_acs_at - wifi.waiting_for_acs_since
+            wait = wifi.complain_about_acs_at - self.provisioning_since(wifi)
             logging.info('Can ping ACS, but no WLAN configuration for %ds.',
                          wait)
             wifi.complain_about_acs_at += wait
-        # If we didn't manage to join the WLAN and we don't have an ACS
-        # connection, we should try to establish one.
-        else:
-          # If we are associated but waiting for a DHCP lease, try again later.
+        # If we didn't manage to join the WLAN, and we don't have an ACS
+        # connection or the ACS session failed, we should try another open AP.
+        if not self.acs() or provisioning_failed:
           now = time.time()
-          connected_to_open = (
-              wifi.wpa_status().get('wpa_state', None) == 'COMPLETED' and
-              wifi.wpa_status().get('key_mgmt', None) == 'NONE')
-          wait_for_dhcp = (
-              not wifi.gateway() and
-              hasattr(wifi, 'waiting_for_dhcp_since') and
-              now - wifi.waiting_for_dhcp_since < self._dhcp_wait_s)
-          if connected_to_open and wait_for_dhcp:
-            logging.debug('Waiting for DHCP lease after %ds.',
-                          now - wifi.waiting_for_acs_since)
+          if self._connected_to_open(wifi) and not provisioning_failed:
+            logging.debug('Waiting for provisioning for %ds.',
+                          now - self.provisioning_since(wifi))
           else:
-            logging.debug('Not connected to ACS')
+            logging.debug('Not connected to ACS or provisioning failed')
             self._try_next_bssid(wifi)
 
     time.sleep(max(0, self._run_duration_s - (time.time() - start_time)))
 
   def acs(self):
-    result = self.bridge.acs() or any(wifi.acs() for wifi in self.wifi)
-    self._status.can_reach_acs = result
+    result = False
+    for ifc in self.interfaces():
+      acs = ifc.acs()
+      ifc.status.can_reach_acs = acs
+      result |= acs
     return result
 
   def internet(self):
-    result = self.bridge.internet() or any(wifi.internet()
-                                           for wifi in self.wifi)
-    self._status.can_reach_internet = result
+    result = False
+    for ifc in self.interfaces():
+      internet = ifc.internet()
+      ifc.status.can_reach_internet = internet
+      result |= internet
     return result
 
   def _update_interfaces_and_routes(self):
@@ -585,7 +616,7 @@
   def _update_tmp_hosts(self):
     """Update the contents of /tmp/hosts."""
     lowest_metric_interface = None
-    for ifc in [self.bridge] + self.wifi:
+    for ifc in self.interfaces():
       route = ifc.current_routes().get('default', None)
       if route:
         metric = route.get('metric', 0)
@@ -598,6 +629,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
 
@@ -635,7 +668,7 @@
     config.stop_access_point()
     del self._wlan_configuration[band]
     if not self._wlan_configuration:
-      self._status.have_config = False
+      self.wifi_for_band(band).status.have_config = False
 
   def _process_file(self, path, filename):
     """Process or ignore an updated file in a watched directory."""
@@ -669,7 +702,7 @@
           wifi = self.wifi_for_band(band)
           if wifi:
             self._update_wlan_configuration(
-                self.WLANConfiguration(band, wifi, contents, self._status,
+                self.WLANConfiguration(band, wifi, contents,
                                        self._wpa_control_interface))
       elif filename.startswith(self.ACCESS_POINT_FILE_PREFIX):
         match = re.match(self.ACCESS_POINT_FILE_REGEXP, filename)
@@ -683,6 +716,10 @@
     elif path == self._tmp_dir:
       if filename.startswith(self.GATEWAY_FILE_PREFIX):
         interface_name = filename.split(self.GATEWAY_FILE_PREFIX)[-1]
+        # If we get a new gateway file, we probably have a new subnet file, and
+        # we need the subnet in order to add the gateway route.  So try to
+        # process the subnet file before doing anything further with this file.
+        self._process_file(path, self.SUBNET_FILE_PREFIX + interface_name)
         ifc = self.interface_by_name(interface_name)
         if ifc:
           ifc.set_gateway_ip(contents)
@@ -716,7 +753,7 @@
           self.ifplugd_action('moca0', has_moca)
 
   def interface_by_name(self, interface_name):
-    for ifc in [self.bridge] + self.wifi:
+    for ifc in self.interfaces():
       if ifc.name == interface_name:
         return ifc
 
@@ -771,20 +808,19 @@
     if bss_info is not None:
       logging.info('Attempting to connect to SSID %s (%s) for provisioning',
                    bss_info.ssid, bss_info.bssid)
-      self._status.trying_open = True
-      wifi.set_gateway_ip(None)
+      self.start_provisioning(wifi)
       connected = self._try_bssid(wifi, bss_info)
       if connected:
-        self._status.connected_to_open = True
+        wifi.status.connected_to_open = True
         now = time.time()
-        wifi.waiting_for_acs_since = now
-        wifi.waiting_for_dhcp_since = now
         wifi.complain_about_acs_at = now + 5
         logging.info('Attempting to provision via SSID %s', bss_info.ssid)
         self._try_to_upload_logs = True
       # If we can no longer connect to this, it's no longer successful.
-      elif bss_info == last_successful_bss_info:
-        wifi.last_successful_bss_info = None
+      else:
+        wifi.status.connected_to_open = False
+        if bss_info == last_successful_bss_info:
+          wifi.last_successful_bss_info = None
       return connected
     else:
       # TODO(rofrankel):  There are probably more cases in which this should be
@@ -792,7 +828,8 @@
       # Relatedly, once we find ACS access on an open network we may want to
       # save that SSID/BSSID and that first in future.  If we do that then we
       # can declare that provisioning has failed much more aggressively.
-      self._status.provisioning_failed = True
+      logging.info('Ran out of BSSIDs to try on %s', wifi.name)
+      wifi.status.provisioning_failed = True
 
     return False
 
@@ -809,6 +846,11 @@
                 in self._wlan_configuration.iteritems()
                 if band in wifi.bands))
 
+  def _connected_to_open(self, wifi):
+    result = wifi.connected_to_open()
+    wifi.status.connected_to_open = result
+    return result
+
   def _update_wlan_configuration(self, wlan_configuration):
     band = wlan_configuration.band
     current = self._wlan_configuration.get(band, None)
@@ -822,7 +864,7 @@
              if wlan_configuration.interface_suffix else '') + band)
         wlan_configuration.access_point = os.path.exists(ap_file)
       self._wlan_configuration[band] = wlan_configuration
-      self._status.have_config = True
+      self.wifi_for_band(band).status.have_config = True
       logging.info('Updated WLAN configuration for %s GHz', band)
       self._update_access_point(wlan_configuration)
 
@@ -882,6 +924,31 @@
     if subprocess.call(self.UPLOAD_LOGS_AND_WAIT) != 0:
       logging.error('Failed to upload logs')
 
+  def cwmp_wakeup(self):
+    if subprocess.call(self.CWMP_WAKEUP) != 0:
+      logging.error('cwmp wakeup failed')
+
+  def start_provisioning(self, wifi):
+    wifi.set_gateway_ip(None)
+    wifi.set_subnet(None)
+    wifi.provisioning_ratchet.reset()
+
+  def provisioning_failed(self, wifi):
+    try:
+      wifi.provisioning_ratchet.check()
+      if wifi.provisioning_ratchet.done_after:
+        wifi.status.provisioning_completed = True
+        logging.info('%s successfully provisioned', wifi.name)
+      return False
+    except ratchet.TimeoutException:
+      wifi.status.provisioning_failed = True
+      logging.info('%s failed to provision: %s', wifi.name,
+                   wifi.provisioning_ratchet.current_step().name)
+      return True
+
+  def provisioning_since(self, wifi):
+    return wifi.provisioning_ratchet.t0
+
 
 def _wifi_show():
   try:
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 7cf1785..a64ab79 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -255,8 +255,7 @@
       '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)
+  config = WLANConfiguration('5', interface_test.Wifi('wcli0', 20), cmd, None)
 
   wvtest.WVPASSEQ('my ssid=1', config.ssid)
   wvtest.WVPASSEQ('abcdWIFI_PSK=qwer', config.passphrase)
@@ -291,6 +290,7 @@
   IFPLUGD_ACTION = ['echo', 'ifplugd.action']
   BINWIFI = ['echo', 'wifi']
   UPLOAD_LOGS_AND_WAIT = ['echo', 'upload-logs-and-wait']
+  CWMP_WAKEUP = ['echo', 'cwmp', 'wakeup']
 
   def __init__(self, *args, **kwargs):
     self._binwifi_commands = []
@@ -326,6 +326,7 @@
     # Will s3 fail to acquire a DHCP lease?
     self.dhcp_failure_on_s3 = False
     self.log_upload_count = 0
+    self.acs_session_fails = False
 
   def create_wifi_interfaces(self):
     super(ConnectionManager, self).create_wifi_interfaces()
@@ -461,6 +462,7 @@
     gateway_file = os.path.join(self._tmp_dir,
                                 self.GATEWAY_FILE_PREFIX + interface_name)
     with open(gateway_file, 'w') as f:
+      logging.debug('Writing gateway file %s', gateway_file)
       # This value doesn't matter to conman, so it's fine to hard code it here.
       f.write('192.168.1.1')
 
@@ -468,6 +470,7 @@
     subnet_file = os.path.join(self._tmp_dir,
                                self.SUBNET_FILE_PREFIX + interface_name)
     with open(subnet_file, 'w') as f:
+      logging.debug('Writing subnet file %s', subnet_file)
       # This value doesn't matter to conman, so it's fine to hard code it here.
       f.write('192.168.1.0/24')
 
@@ -509,6 +512,19 @@
   def has_status_files(self, files):
     return not set(files) - set(os.listdir(self._status_dir))
 
+  def cwmp_wakeup(self):
+    super(ConnectionManager, self).cwmp_wakeup()
+    self.write_acscontact()
+    if self.acs():
+      self.write_acsconnected()
+
+  def write_acscontact(self):
+    open(os.path.join(connection_manager.CWMP_PATH, 'acscontact'), 'w')
+
+  def write_acsconnected(self):
+    if not self.acs_session_fails:
+      open(os.path.join(connection_manager.CWMP_PATH, 'acsconnected'), 'w')
+
 
 def wlan_config_filename(path, band):
   return os.path.join(path, 'command.%s' % band)
@@ -560,7 +576,10 @@
       interface_update_period = 5
       wifi_scan_period = 15
       wifi_scan_period_s = run_duration_s * wifi_scan_period
+      associate_wait_s = 0
       dhcp_wait_s = .5
+      acs_start_wait_s = 0
+      acs_finish_wait_s = 0
 
       # pylint: disable=protected-access
       old_wifi_show = connection_manager._wifi_show
@@ -579,6 +598,7 @@
         moca_tmp_dir = tempfile.mkdtemp()
         wpa_control_interface = tempfile.mkdtemp()
         FrenzyWifi.WPACtrl.WIFIINFO_PATH = tempfile.mkdtemp()
+        connection_manager.CWMP_PATH = tempfile.mkdtemp()
 
         for band, access_point in wlan_configs.iteritems():
           write_wlan_config(config_dir, band, 'initial ssid', 'initial psk')
@@ -595,14 +615,13 @@
                               run_duration_s=run_duration_s,
                               interface_update_period=interface_update_period,
                               wifi_scan_period_s=wifi_scan_period_s,
+                              associate_wait_s=associate_wait_s,
                               dhcp_wait_s=dhcp_wait_s,
+                              acs_start_wait_s=acs_start_wait_s,
+                              acs_finish_wait_s=acs_finish_wait_s,
                               bssid_cycle_length_s=1,
                               **cm_kwargs)
 
-        c.test_interface_update_period = interface_update_period
-        c.test_wifi_scan_period = wifi_scan_period
-        c.test_dhcp_wait_s = dhcp_wait_s
-
         f(c)
       finally:
         if os.path.exists(connection_manager.TMP_HOSTS):
@@ -612,6 +631,7 @@
         shutil.rmtree(moca_tmp_dir)
         shutil.rmtree(wpa_control_interface)
         shutil.rmtree(FrenzyWifi.WPACtrl.WIFIINFO_PATH)
+        shutil.rmtree(connection_manager.CWMP_PATH)
         # pylint: disable=protected-access
         connection_manager._wifi_show = old_wifi_show
         connection_manager._get_quantenna_interfaces = old_gqi
@@ -889,7 +909,7 @@
   c.can_connect_to_s2 = True
   # Give it time to try all BSSIDs.  This means sleeping long enough that
   # everything in the cycler is active, then doing n+1 loops (the n+1st loop is
-  # when we decided that the SSID in the nth loop was successful).
+  # when we decide that the SSID in the nth loop was successful).
   time.sleep(c._bssid_cycle_length_s)
   for _ in range(len(c.wifi_for_band(band).cycler) + 1):
     c.run_once()
@@ -931,13 +951,68 @@
   last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
   wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
   wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+  wvtest.WVPASS(c.has_status_files([status.P.WAITING_FOR_DHCP,
+                                    status.P.WAITING_FOR_PROVISIONING]))
   # Third iteration: sleep for dhcp_wait_s and check that we try another AP.
-  time.sleep(c.test_dhcp_wait_s)
+  time.sleep(c._dhcp_wait_s)
   c.run_once()
   last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
   wvtest.WVPASSNE(last_bss_info.ssid, 's3')
   wvtest.WVPASSNE(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
 
+  # Now repeat the above, but for an ACS session that takes a while.  We don't
+  # necessarily want to leave if it fails (so we don't want the third check),
+  # but we do want to make sure we don't leave while we're still waiting for it.
+  #
+  # Unlike DHCP, which we can always simulate working immediately above, it is
+  # wrong to simulate ACS sessions working for connections without ACS access.
+  # This means we can either always wait for the ACS session timeout in every
+  # test above, making the tests much slower, or we can set that timeout to 0
+  # and then be a little gross here and change it.  The latter is unfortunately
+  # the lesser evil, because slow tests are bad.
+  del c.wifi_for_band(band).cycler
+  c.dhcp_failure_on_s3 = False
+  c.acs_session_fails = True
+  # First iteration: check that we try s3.
+  c.run_until_scan(band)
+  last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+  wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
+  wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+
+  # This is the gross part.
+  # pylint: disable=protected-access
+  c.wifi_for_band(band).provisioning_ratchet.steps[3].timeout = 0.5
+
+  # Second iteration: check that we don't leave while waiting.
+  c.run_once()
+  last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+  wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
+  wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+  wvtest.WVPASS(c.has_status_files([status.P.WAITING_FOR_ACS_SESSION,
+                                    status.P.WAITING_FOR_PROVISIONING]))
+  time.sleep(0.5)
+  c.run_once()
+  wvtest.WVPASS(c.has_status_files([status.P.PROVISIONING_FAILED]))
+  c.wifi_for_band(band).provisioning_ratchet.steps[3].timeout = 0
+
+  # Finally, test successful provisioning.
+  del c.wifi_for_band(band).cycler
+  c.dhcp_failure_on_s3 = False
+  c.acs_session_fails = False
+  # First iteration: check that we try s3.
+  c.run_until_scan(band)
+  last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+  wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
+  wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+
+  c.run_once()
+  last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+  wvtest.WVPASSEQ(last_bss_info.ssid, 's3')
+  wvtest.WVPASSEQ(last_bss_info.bssid, 'ff:ee:dd:cc:bb:aa')
+  wvtest.WVFAIL(c.has_status_files([status.P.WAITING_FOR_ACS_SESSION,
+                                    status.P.WAITING_FOR_PROVISIONING]))
+  wvtest.WVPASS(c.has_status_files([status.P.PROVISIONING_COMPLETED]))
+
 
 @wvtest.wvtest
 @connection_manager_test(WIFI_SHOW_OUTPUT_MARVELL8897)
diff --git a/conman/interface.py b/conman/interface.py
index 2d0fbbe..cdeaf67 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -83,6 +83,8 @@
       return False
 
     self.add_routes()
+    if 'default' not in self.current_routes():
+      return False
 
     cmd = [self.CONNECTION_CHECK, '-I', self.name]
     if check_acs:
@@ -547,6 +549,10 @@
     self.initial_ssid = None
     super(Wifi, self).initialize()
 
+  def connected_to_open(self):
+    return (self.wpa_status().get('wpa_state', None) == 'COMPLETED' and
+            self.wpa_status().get('key_mgmt', None) == 'NONE')
+
 
 class FrenzyWPACtrl(object):
   """A WPACtrl for Frenzy devices.
diff --git a/conman/interface_test.py b/conman/interface_test.py
index 79c58ae..dd5e037 100755
--- a/conman/interface_test.py
+++ b/conman/interface_test.py
@@ -248,8 +248,10 @@
     super(Wifi, self).detach_wpa_control()
 
   def start_wpa_supplicant_testonly(self, path):
-    logging.debug('Starting fake wpa_supplicant for %s', self.name)
-    open(os.path.join(path, self.name), 'w')
+    wpa_socket = os.path.join(path, self.name)
+    logging.debug('Starting fake wpa_supplicant for %s: %s',
+                  self.name, wpa_socket)
+    open(wpa_socket, 'w')
 
   def kill_wpa_supplicant_testonly(self, path):
     logging.debug('Killing fake wpa_supplicant for %s', self.name)
diff --git a/conman/ratchet.py b/conman/ratchet.py
new file mode 100644
index 0000000..7873c39
--- /dev/null
+++ b/conman/ratchet.py
@@ -0,0 +1,181 @@
+#!/usr/bin/python -S
+
+"""Utility for ensuring a series of events occur or time out."""
+
+import logging
+import os
+import time
+
+# 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)
+
+
+class TimeoutException(Exception):
+  pass
+
+
+class Condition(object):
+  """Wrapper for a function that may time out."""
+
+  def __init__(self, name, evaluate, timeout, logger=None, callback=None):
+    self.name = name
+    if evaluate:
+      self.evaluate = evaluate
+    self.timeout = timeout
+    self.logger = logger or logging
+    self.callback = callback
+    self.reset()
+
+  def reset(self, t0=None, start_at=None):
+    """Reset the Condition to an initial state.
+
+    Takes two different timestamp values to account for uncertainty in when a
+    previous condition may have been met.
+
+    Args:
+      t0:  The timestamp after which to evaluate the condition.
+      start_at:  The timestamp from which to compute the timeout.
+    """
+    self.t0 = t0 or time.time()
+    self.start_at = start_at or time.time()
+    self.done_after = None
+    self.done_by = None
+    self.timed_out = False
+    self.not_done_before = self.t0
+
+  def check(self):
+    """Check whether the condition has completed or timed out."""
+    if self.timed_out:
+      raise TimeoutException()
+
+    if self.done_after:
+      return True
+
+    if self.evaluate():
+      self.mark_done()
+      return True
+
+    now = time.time()
+    if now > self.start_at + self.timeout:
+      self.timed_out = True
+      self.logger.info('%s timed out after %.2f seconds',
+                       self.name, now - self.t0)
+      raise TimeoutException()
+
+    self.not_done_before = time.time()
+    return False
+
+  def mark_done(self):
+    # In general, we don't know when a condition finished, but we know it was
+    # *after* whenever it was most recently not done.
+    self.done_after = self.not_done_before
+    self.done_by = time.time()
+    self.logger.info('%s completed after %.2f seconds',
+                     self.name, self.done_by - self.t0)
+
+    if self.callback:
+      self.callback()
+
+
+class FileExistsCondition(Condition):
+  """A condition that checks for the existence of a file."""
+
+  def __init__(self, name, filename, timeout):
+    self._filename = filename
+    super(FileExistsCondition, self).__init__(name, None, timeout)
+
+  def evaluate(self):
+    return os.path.exists(self._filename)
+
+  def mtime(self):
+    if os.path.exists(self._filename):
+      return os.stat(self._filename).st_mtime
+
+    return None
+
+  def mark_done(self):
+    super(FileExistsCondition, self).mark_done()
+    # We have to check this because the file could have been deleted while this
+    # was being called.  But this condition should almost always be true.
+    mtime = self.mtime()
+    if mtime:
+      self.done_after = self.done_by = mtime
+
+
+class FileTouchedCondition(FileExistsCondition):
+  """A condition that checks that a file was touched after a certain time."""
+
+  def reset(self, t0=None, start_at=None):
+    mtime = self.mtime
+    if t0 and mtime and mtime < t0:
+      self.initial_mtime = self.mtime()
+    else:
+      self.initial_mtime = None
+    super(FileTouchedCondition, self).reset(t0, start_at)
+
+  def evaluate(self):
+    if not super(FileTouchedCondition, self).evaluate():
+      return False
+
+    if self.initial_mtime:
+      return self.mtime() > self.initial_mtime
+
+    return self.mtime() >= self.t0
+
+
+class Ratchet(Condition):
+  """A condition that comprises a series of subconditions."""
+
+  def __init__(self, name, steps, status):
+    self.name = name
+    self.steps = steps
+    for step in self.steps:
+      step.logger = logging.getLogger(self.name)
+    self._status = status
+    super(Ratchet, self).__init__(name, None, 0)
+
+  def reset(self):
+    self._current_step = 0
+    for step in self.steps:
+      step.reset()
+      self._set_step_status(step, False)
+    self._set_current_step_status(True)
+    super(Ratchet, self).reset()
+
+  # Override check rather than evaluate because we don't want the Ratchet to
+  # time out unless one of its steps does.
+  def check(self):
+    if not self.done_after:
+      while self.current_step().check():
+        if not self.advance():
+          self.mark_done()
+          break
+
+    return self.done_after
+
+  def current_step(self):
+    return self.steps[self._current_step]
+
+  def on_final_step(self):
+    return self._current_step == len(self.steps) - 1
+
+  def advance(self):
+    if self.on_final_step():
+      return False
+    else:
+      prev_step = self.current_step()
+      self._current_step += 1
+      self.current_step().start_at = prev_step.done_by
+      self._set_current_step_status(True)
+      return True
+
+  def mark_done(self):
+    super(Ratchet, self).mark_done()
+    self.done_after = self.steps[-1].done_after
+
+  def _set_step_status(self, step, value):
+    setattr(self._status, step.name, value)
+
+  def _set_current_step_status(self, value):
+    self._set_step_status(self.current_step(), value)
diff --git a/conman/ratchet_test.py b/conman/ratchet_test.py
new file mode 100755
index 0000000..10c10ab
--- /dev/null
+++ b/conman/ratchet_test.py
@@ -0,0 +1,128 @@
+#!/usr/bin/python
+
+"""Tests for ratchet.py."""
+
+import os
+import shutil
+import tempfile
+import time
+
+import ratchet
+import status
+from wvtest import wvtest
+
+
+@wvtest.wvtest
+def condition_test():
+  """Test basic Condition functionality."""
+  x = y = 0
+  callback_sink = []
+  cx = ratchet.Condition('cx', lambda: x != 0, 0)
+  cy = ratchet.Condition('cx', lambda: y != 0, 0.1,
+                         callback=lambda: callback_sink.append([0]))
+  wvtest.WVEXCEPT(ratchet.TimeoutException, cx.check)
+  wvtest.WVFAIL(cy.check())
+  time.sleep(0.1)
+  wvtest.WVEXCEPT(ratchet.TimeoutException, cy.check)
+
+  x = 1
+  wvtest.WVEXCEPT(ratchet.TimeoutException, cx.check)
+  cx.reset()
+  wvtest.WVPASS(cx.check())
+
+  y = 1
+  cy.reset()
+  wvtest.WVPASS(cy.check())
+  wvtest.WVPASSEQ(len(callback_sink), 1)
+  # Callback shouldn't fire again.
+  wvtest.WVPASS(cy.check())
+  wvtest.WVPASSEQ(len(callback_sink), 1)
+  cy.reset()
+  wvtest.WVPASS(cy.check())
+  wvtest.WVPASSEQ(len(callback_sink), 2)
+
+
+@wvtest.wvtest
+def file_condition_test():
+  """Test File*Condition functionality."""
+  try:
+    _, filename = tempfile.mkstemp()
+    c_exists = ratchet.FileExistsCondition('c exists', filename, 0.1)
+    c_mtime = ratchet.FileTouchedCondition('c mtime', filename, 0.1)
+    wvtest.WVPASS(c_exists.check())
+    wvtest.WVFAIL(c_mtime.check())
+    # mtime precision is too low to notice that we're touching the file *after*
+    # capturing its initial mtime rather than at the same time, so take a short
+    # nap before touching it.
+    time.sleep(0.01)
+    open(filename, 'w')
+    wvtest.WVPASS(c_mtime.check())
+
+    # Test that old mtimes don't count.
+    time.sleep(0.01)
+    c_mtime.reset()
+    wvtest.WVFAIL(c_mtime.check())
+    time.sleep(0.1)
+    wvtest.WVEXCEPT(ratchet.TimeoutException, c_mtime.check)
+
+    # Test t0 and start_at.
+    os.unlink(filename)
+    now = time.time()
+    c_mtime.reset(t0=now, start_at=now + 0.2)
+    wvtest.WVFAIL(c_mtime.check())
+    time.sleep(0.15)
+    wvtest.WVFAIL(c_mtime.check())
+    open(filename, 'w')
+    wvtest.WVPASS(c_mtime.check())
+
+  finally:
+    os.unlink(filename)
+
+
+@wvtest.wvtest
+def ratchet_test():
+  """Test Ratchet functionality."""
+
+  class P(object):
+    X = 'X'
+    Y = 'Y'
+    Z = 'Z'
+  status.P = P
+  status.IMPLICATIONS = {}
+
+  status_export_path = tempfile.mkdtemp()
+  try:
+    x = y = z = 0
+    r = ratchet.Ratchet('test ratchet', [
+        ratchet.Condition('x', lambda: x, 0.1),
+        ratchet.Condition('y', lambda: y, 0.1),
+        ratchet.Condition('z', lambda: z, 0.1),
+    ], status.Status(status_export_path))
+    x = y = 1
+
+    # Test that timeouts are not just summed, but start whenever the previous
+    # step completed.
+    wvtest.WVPASSEQ(r._current_step, 0)  # pylint: disable=protected-access
+    wvtest.WVPASS(os.path.isfile(os.path.join(status_export_path, 'X')))
+    wvtest.WVFAIL(os.path.isfile(os.path.join(status_export_path, 'Y')))
+    wvtest.WVFAIL(os.path.isfile(os.path.join(status_export_path, 'Z')))
+    time.sleep(0.05)
+    wvtest.WVFAIL(r.check())
+    wvtest.WVPASSEQ(r._current_step, 2)  # pylint: disable=protected-access
+    wvtest.WVPASS(os.path.isfile(os.path.join(status_export_path, 'X')))
+    wvtest.WVPASS(os.path.isfile(os.path.join(status_export_path, 'Y')))
+    wvtest.WVPASS(os.path.isfile(os.path.join(status_export_path, 'Z')))
+    wvtest.WVFAIL(r.check())
+    wvtest.WVPASSEQ(r._current_step, 2)  # pylint: disable=protected-access
+    time.sleep(0.1)
+    wvtest.WVEXCEPT(ratchet.TimeoutException, r.check)
+
+    x = y = z = 1
+    r.reset()
+    wvtest.WVPASS(r.check())
+  finally:
+    shutil.rmtree(status_export_path)
+
+
+if __name__ == '__main__':
+  wvtest.wvtest_main()
diff --git a/conman/status.py b/conman/status.py
index 7f75682..8f8d3a1 100644
--- a/conman/status.py
+++ b/conman/status.py
@@ -32,17 +32,25 @@
   PROVISIONING_FAILED = 'PROVISIONING_FAILED'
   ATTACHED_TO_WPA_SUPPLICANT = 'ATTACHED_TO_WPA_SUPPLICANT'
 
+  WAITING_FOR_PROVISIONING = 'WAITING_FOR_PROVISIONING'
+  WAITING_FOR_DHCP = 'WAITING_FOR_DHCP'
+  WAITING_FOR_CWMP_WAKEUP = 'WAITING_FOR_CWMP_WAKEUP'
+  WAITING_FOR_ACS_SESSION = 'WAITING_FOR_ACS_SESSION'
+  PROVISIONING_COMPLETED = 'PROVISIONING_COMPLETED'
+
 
 # Format:  { proposition: (implications, counter-implications), ... }
 # If you want to add a new proposition to the Status class, just edit this dict.
 IMPLICATIONS = {
     P.TRYING_OPEN: (
         (),
-        (P.CONNECTED_TO_OPEN, P.TRYING_WLAN, P.CONNECTED_TO_WLAN)
+        (P.CONNECTED_TO_OPEN, P.TRYING_WLAN, P.CONNECTED_TO_WLAN,
+         P.WAITING_FOR_PROVISIONING)
     ),
     P.TRYING_WLAN: (
         (),
-        (P.TRYING_OPEN, P.CONNECTED_TO_OPEN, P.CONNECTED_TO_WLAN)
+        (P.TRYING_OPEN, P.CONNECTED_TO_OPEN, P.CONNECTED_TO_WLAN,
+         P.WAITING_FOR_PROVISIONING)
     ),
     P.WLAN_FAILED: (
         (),
@@ -54,7 +62,8 @@
     ),
     P.CONNECTED_TO_WLAN: (
         (P.HAVE_WORKING_CONFIG,),
-        (P.CONNECTED_TO_OPEN, P.TRYING_OPEN, P.TRYING_WLAN)
+        (P.CONNECTED_TO_OPEN, P.TRYING_OPEN, P.TRYING_WLAN,
+         P.WAITING_FOR_PROVISIONING)
     ),
     P.CAN_REACH_ACS: (
         (P.COULD_REACH_ACS,),
@@ -62,11 +71,11 @@
     ),
     P.COULD_REACH_ACS: (
         (),
-        (P.PROVISIONING_FAILED,),
+        (),
     ),
     P.PROVISIONING_FAILED: (
         (),
-        (P.COULD_REACH_ACS,),
+        (P.WAITING_FOR_PROVISIONING, P.PROVISIONING_COMPLETED,),
     ),
     P.HAVE_WORKING_CONFIG: (
         (P.HAVE_CONFIG,),
@@ -75,24 +84,60 @@
     P.ATTACHED_TO_WPA_SUPPLICANT: (
         (),
         (),
-    )
+    ),
+    P.WAITING_FOR_PROVISIONING: (
+        (P.CONNECTED_TO_OPEN,),
+        (),
+    ),
+    P.WAITING_FOR_DHCP: (
+        (P.WAITING_FOR_PROVISIONING,),
+        (P.WAITING_FOR_CWMP_WAKEUP, P.WAITING_FOR_ACS_SESSION),
+    ),
+    P.WAITING_FOR_CWMP_WAKEUP: (
+        (P.WAITING_FOR_PROVISIONING,),
+        (P.WAITING_FOR_DHCP, P.WAITING_FOR_ACS_SESSION),
+    ),
+    P.WAITING_FOR_ACS_SESSION: (
+        (P.WAITING_FOR_PROVISIONING,),
+        (P.WAITING_FOR_DHCP, P.WAITING_FOR_CWMP_WAKEUP),
+    ),
+    P.PROVISIONING_COMPLETED: (
+        (),
+        (P.WAITING_FOR_PROVISIONING, P.PROVISIONING_FAILED,),
+    ),
 }
 
 
-class Proposition(object):
+class ExportedValue(object):
+
+  def __init__(self, name, export_path):
+    self._name = name
+    self._export_path = export_path
+
+  def export(self):
+    filepath = os.path.join(self._export_path, self._name)
+    if self.value():
+      if not os.path.exists(filepath):
+        open(filepath, 'w')
+    else:
+      if os.path.exists(filepath):
+        os.unlink(filepath)
+
+
+class Proposition(ExportedValue):
   """Represents a proposition.
 
   May imply truth or falsity of other propositions.
   """
 
-  def __init__(self, name, export_path):
-    self._name = name
-    self._export_path = export_path
+  def __init__(self, *args, **kwargs):
+    super(Proposition, self).__init__(*args, **kwargs)
     self._value = None
     self._implications = set()
     self._counter_implications = set()
     self._impliers = set()
     self._counter_impliers = set()
+    self.parents = set()
 
   def implies(self, implication):
     self._counter_implications.discard(implication)
@@ -115,11 +160,20 @@
     self._counter_impliers.add(counter_implier)
 
   def set(self, value):
+    """Set this Proposition's value.
+
+    If the value changed, update any dependent values/files.
+
+    Args:
+      value:  The new value.
+    """
     if value == self._value:
       return
 
     self._value = value
     self.export()
+    for parent in self.parents:
+      parent.export()
     logging.debug('%s is now %s', self._name, self._value)
 
     if value:
@@ -135,14 +189,20 @@
       for implier in self._impliers:
         implier.set(False)
 
-  def export(self):
-    filepath = os.path.join(self._export_path, self._name)
-    if self._value:
-      if not os.path.exists(filepath):
-        open(filepath, 'w')
-    else:
-      if os.path.exists(filepath):
-        os.unlink(filepath)
+  def value(self):
+    return self._value
+
+
+class Disjunction(ExportedValue):
+
+  def __init__(self, name, export_path, disjuncts):
+    super(Disjunction, self).__init__(name, export_path)
+    self.disjuncts = disjuncts
+    for disjunct in self.disjuncts:
+      disjunct.parents.add(self)
+
+  def value(self):
+    return any(d.value() for d in self.disjuncts)
 
 
 class Status(object):
@@ -154,6 +214,9 @@
 
     self._export_path = export_path
 
+    self._set_up_propositions()
+
+  def _set_up_propositions(self):
     self._propositions = {
         p: Proposition(p, self._export_path)
         for p in dict(inspect.getmembers(P)) if not p.startswith('_')
@@ -166,7 +229,7 @@
         self._propositions[p].implies_not(
             self._propositions[counter_implication])
 
-  def _proposition(self, p):
+  def proposition(self, p):
     return self._propositions[p]
 
   def __setattr__(self, attr, value):
@@ -187,3 +250,17 @@
           return
 
     super(Status, self).__setattr__(attr, value)
+
+
+class CompositeStatus(Status):
+
+  def __init__(self, export_path, children):
+    self._children = children
+    super(CompositeStatus, self).__init__(export_path)
+
+  def _set_up_propositions(self):
+    self._propositions = {
+        p: Disjunction(p, self._export_path,
+                       [c.proposition(p) for c in self._children])
+        for p in dict(inspect.getmembers(P)) if not p.startswith('_')
+    }
diff --git a/conman/status_test.py b/conman/status_test.py
index 03223f8..befebbf 100755
--- a/conman/status_test.py
+++ b/conman/status_test.py
@@ -17,6 +17,10 @@
   return os.path.exists(os.path.join(path, filename))
 
 
+def has_file(s, filename):
+  return file_in(s._export_path, filename)
+
+
 @wvtest.wvtest
 def test_proposition():
   export_path = tempfile.mkdtemp()
@@ -71,51 +75,85 @@
 
 @wvtest.wvtest
 def test_status():
-  export_path = tempfile.mkdtemp()
+  export_path_s = tempfile.mkdtemp()
+  export_path_t = tempfile.mkdtemp()
+  export_path_st = tempfile.mkdtemp()
 
   try:
-    s = status.Status(export_path)
+    s = status.Status(export_path_s)
+    t = status.Status(export_path_t)
+    st = status.CompositeStatus(export_path_st, [s, t])
 
     # Sanity check that there are no contradictions.
     for p, (want_true, want_false) in status.IMPLICATIONS.iteritems():
       setattr(s, p.lower(), True)
-      wvtest.WVPASS(file_in(export_path, p))
+      wvtest.WVPASS(has_file(s, p))
       for wt in want_true:
-        wvtest.WVPASS(file_in(export_path, wt))
+        wvtest.WVPASS(has_file(s, wt))
       for wf in want_false:
-        wvtest.WVFAIL(file_in(export_path, wf))
+        wvtest.WVFAIL(has_file(s, wf))
+
+    def check_exported(check_s, check_t, filename):
+      wvtest.WVPASSEQ(check_s, has_file(s, filename))
+      wvtest.WVPASSEQ(check_t, has_file(t, filename))
+      wvtest.WVPASSEQ(check_s or check_t, has_file(st, filename))
 
     s.trying_wlan = True
-    wvtest.WVPASS(file_in(export_path, status.P.TRYING_WLAN))
-    wvtest.WVFAIL(file_in(export_path, status.P.CONNECTED_TO_WLAN))
+    t.trying_wlan = False
+    check_exported(True, False, status.P.TRYING_WLAN)
+    check_exported(False, False, status.P.CONNECTED_TO_WLAN)
 
     s.connected_to_open = True
-    wvtest.WVPASS(file_in(export_path, status.P.CONNECTED_TO_OPEN))
-    wvtest.WVFAIL(file_in(export_path, status.P.CONNECTED_TO_WLAN))
+    check_exported(True, False, status.P.CONNECTED_TO_OPEN)
+    check_exported(False, False, status.P.CONNECTED_TO_WLAN)
 
     s.connected_to_wlan = True
-    wvtest.WVPASS(file_in(export_path, status.P.CONNECTED_TO_WLAN))
-    wvtest.WVPASS(file_in(export_path, status.P.HAVE_WORKING_CONFIG))
-    wvtest.WVFAIL(file_in(export_path, status.P.CONNECTED_TO_OPEN))
-    wvtest.WVFAIL(file_in(export_path, status.P.TRYING_WLAN))
-    wvtest.WVFAIL(file_in(export_path, status.P.TRYING_OPEN))
+    t.trying_wlan = True
+    check_exported(True, False, status.P.CONNECTED_TO_WLAN)
+    check_exported(True, False, status.P.HAVE_WORKING_CONFIG)
+    check_exported(False, False, status.P.CONNECTED_TO_OPEN)
+    check_exported(False, True, status.P.TRYING_WLAN)
+    check_exported(False, False, status.P.TRYING_OPEN)
 
     s.can_reach_acs = True
     s.can_reach_internet = True
-    wvtest.WVPASS(file_in(export_path, status.P.CAN_REACH_ACS))
-    wvtest.WVPASS(file_in(export_path, status.P.COULD_REACH_ACS))
-    wvtest.WVPASS(file_in(export_path, status.P.CAN_REACH_INTERNET))
-    wvtest.WVFAIL(file_in(export_path, status.P.PROVISIONING_FAILED))
+    check_exported(True, False, status.P.CAN_REACH_ACS)
+    check_exported(True, False, status.P.COULD_REACH_ACS)
+    check_exported(True, False, status.P.CAN_REACH_INTERNET)
+    check_exported(False, False, status.P.PROVISIONING_FAILED)
 
     # These should not have changed
-    wvtest.WVPASS(file_in(export_path, status.P.CONNECTED_TO_WLAN))
-    wvtest.WVPASS(file_in(export_path, status.P.HAVE_WORKING_CONFIG))
-    wvtest.WVFAIL(file_in(export_path, status.P.CONNECTED_TO_OPEN))
-    wvtest.WVFAIL(file_in(export_path, status.P.TRYING_WLAN))
-    wvtest.WVFAIL(file_in(export_path, status.P.TRYING_OPEN))
+    check_exported(True, False, status.P.CONNECTED_TO_WLAN)
+    check_exported(True, False, status.P.HAVE_WORKING_CONFIG)
+    check_exported(False, False, status.P.CONNECTED_TO_OPEN)
+    check_exported(False, True, status.P.TRYING_WLAN)
+    check_exported(False, False, status.P.TRYING_OPEN)
+
+    # Test provisioning statuses.
+    s.waiting_for_dhcp = True
+    check_exported(False, True, status.P.TRYING_WLAN)
+    check_exported(False, False, status.P.TRYING_OPEN)
+    check_exported(False, False, status.P.CONNECTED_TO_WLAN)
+    check_exported(True, False, status.P.CONNECTED_TO_OPEN)
+    check_exported(True, False, status.P.WAITING_FOR_PROVISIONING)
+    check_exported(True, False, status.P.WAITING_FOR_DHCP)
+    s.waiting_for_cwmp_wakeup = True
+    check_exported(False, False, status.P.WAITING_FOR_DHCP)
+    check_exported(True, False, status.P.WAITING_FOR_CWMP_WAKEUP)
+    s.waiting_for_acs_session = True
+    check_exported(False, False, status.P.WAITING_FOR_DHCP)
+    check_exported(False, False, status.P.WAITING_FOR_CWMP_WAKEUP)
+    check_exported(True, False, status.P.WAITING_FOR_ACS_SESSION)
+    s.provisioning_completed = True
+    check_exported(False, False, status.P.WAITING_FOR_PROVISIONING)
+    check_exported(False, False, status.P.WAITING_FOR_DHCP)
+    check_exported(False, False, status.P.WAITING_FOR_CWMP_WAKEUP)
+    check_exported(False, False, status.P.WAITING_FOR_CWMP_WAKEUP)
 
   finally:
-    shutil.rmtree(export_path)
+    shutil.rmtree(export_path_s)
+    shutil.rmtree(export_path_t)
+    shutil.rmtree(export_path_st)
 
 
 if __name__ == '__main__':
diff --git a/devcert/gfiber-device-cert b/devcert/gfiber-device-cert
index 10e259a..bc9ca07 100755
--- a/devcert/gfiber-device-cert
+++ b/devcert/gfiber-device-cert
@@ -13,7 +13,8 @@
 host=$(
   hostname -f |
     sed -e 's/\([^.]*\.[^.]*\).*/\1/' \
-        -e 's/\./_/g'
+        -e 's/\./_/g' \
+        -e 's/-//g'
 )
 full_cn="f88fca-Linux-$host"
 
diff --git a/diags/Makefile b/diags/Makefile
index 399c44a..1a7386b 100644
--- a/diags/Makefile
+++ b/diags/Makefile
@@ -10,6 +10,9 @@
 ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gflt110)
  DIRS += chameleon
 endif
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfrg240)
+ DIRS += prowl
+endif
 
 PYTHON?=python
 
@@ -24,7 +27,7 @@
 clean:   $(addsuffix /clean,$(DIRS))
 install-libs: $(addsuffix /install-libs,$(DIRS))
 
-windcharger/all spacecast/all:	common/all
+windcharger/all spacecast/all chameleon/all prowl/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/prowl/Makefile b/diags/prowl/Makefile
new file mode 100644
index 0000000..fee512d
--- /dev/null
+++ b/diags/prowl/Makefile
@@ -0,0 +1,65 @@
+default:	all
+
+BINARY = diags
+
+TARGETS=$(BINARY)
+INSTALL=install
+PREFIX=$(DESTDIR)/usr
+BINDIR=$(PREFIX)/bin
+LIBDIR=$(PREFIX)/lib
+INCLUDEDIR=$(PREFIX)/include
+
+CC=$(CROSS_COMPILE)gcc
+CXX=$(CROSS_COMPILE)g++
+RM=rm -f
+CFLAGS = -Wall -Wimplicit -Wno-unknown-pragmas -W -std=c99 -D_GNU_SOURCE
+
+CFLAGS += $(EXTRA_CFLAGS)
+LDFLAGS += $(EXTRA_LDFLAGS)
+
+# enable the platform we're supporting
+ifeq ($(BR2_PACKAGE_BCM_NEXUS),y)
+  CFLAGS += -DBROADCOM
+  NOSTUB=1
+endif
+ifeq ($(BR2_PACKAGE_MINDSPEED_DRIVERS),y)
+  CFLAGS += -DMINDSPEED
+  NOSTUB=1
+endif
+
+ifeq ($(BR2_TARGET_GOOGLE_PLATFORM),gfiberlt)
+  CFLAGS += -DGFIBER_LT
+  NOSTUB=1
+endif
+
+ifndef NOSTUB
+  CFLAGS += -DSTUB
+  LDFLAGS += -lm
+endif
+
+CFLAGS += -g
+
+IFLAGS += $(patsubst %,-I%,$(INC_DIRS))
+
+CFILES = $(wildcard ../common/*.c *.c)
+OFILES = $(patsubst %.c,%.o,$(CFILES))
+
+all:	$(TARGETS)
+
+install:
+	$(INSTALL) -m 0755 diags $(BINDIR)/
+
+install-libs:
+	@:
+
+test:
+	@:
+
+$(BINARY):	$(OFILES)
+	$(CC) $^ $(LDFLAGS) -o $@
+
+%.o:	%.c
+	$(CC) $(CFLAGS) $(IFLAGS) -c $^ -c
+
+clean:
+	$(RM) $(OFILES) $(BINARY)
diff --git a/diags/prowl/common.h b/diags/prowl/common.h
new file mode 100644
index 0000000..d932ed1
--- /dev/null
+++ b/diags/prowl/common.h
@@ -0,0 +1,14 @@
+/*
+ * (C) Copyright 2015 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#ifndef VENDOR_GOOGLE_DIAGS_SPACECAST_COMMON_H_
+#define VENDOR_GOOGLE_DIAGS_SPACECAST_COMMON_H_
+
+#define MAX_PKT_SIZE 4096
+#define FAIL_TEXT "FAILED:"
+#define PASS_TEXT "PASSED:"
+
+#endif  // VENDOR_GOOGLE_DIAGS_SPACECAST_COMMON_H_
diff --git a/diags/prowl/diagutil.c b/diags/prowl/diagutil.c
new file mode 100644
index 0000000..76410d1
--- /dev/null
+++ b/diags/prowl/diagutil.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015 Google Inc.
+ * All rights reserved.
+ * diagutil -- Linux-based Hardware Diagnostic Utilities
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define DIAGS_VERSION "1.2"
+
+/* External Functions */
+int ioread(int argc, char **argv);
+int iowrite(int argc, char **argv);
+int iowrite_only(int argc, char *argv[]);
+int i2cprobe(int argc, char **argv);
+int i2cread(int argc, char **argv);
+int i2cwrite(int argc, char **argv);
+int board_temp(int argc, char *argv[]);
+int led_set(int argc, char *argv[]);
+int led_set_pwm(int argc, char *argv[]);
+int switch_state(int argc, char *argv[]);
+int poe_disable(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[]);
+
+/* 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},
+    {"board_temp", board_temp},
+    {"led_set", led_set},
+    {"led_set_pwm", led_set_pwm},
+    {"", NULL},
+    {"switch_state", switch_state},
+    {"poe_disable", poe_disable},
+    {"", NULL},
+    {"phy_read", phy_read},
+    {"phy_write", phy_write},
+    {"loopback_test", loopback_test},
+    {"", 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/prowl/eth_test.c b/diags/prowl/eth_test.c
new file mode 100644
index 0000000..f59fab5
--- /dev/null
+++ b/diags/prowl/eth_test.c
@@ -0,0 +1,519 @@
+/*
+ * (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 <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <unistd.h>
+#include "../common/util.h"
+#include "../common/io.h"
+#include "common.h"
+
+#define WAN_PORT_NAME "eth1_1"
+#define ETH_TEST_LINE_MAX 4096
+#define TX_BYTES_PATH "cat /sys/class/net/" WAN_PORT_NAME "/statistics/tx_bytes"
+#define RX_BYTES_APTH "cat /sys/class/net/" WAN_PORT_NAME "/statistics/rx_bytes"
+#define ETH_TRAFFIC_TEST_PERIOD_SYMBOL "-p"
+#define ETH_TRAFFIC_MAX_REPORT_PERIOD 50
+#define ETH_TRAFFIC_MAX_GE_REPORT_PERIOD 15
+#define ETH_TRAFFIC_REPORT_PERIOD 50
+#define ETH_PKTS_LEN_DEFAULT 32
+#define ETH_STAT_WAIT_PERIOD 1  // sec
+#define BUF_SIZ 1536
+#define ETH_PKTS_SENT_BEFORE_WAIT 0xFF
+#define SCAN_CMD_FORMAT "%256s"
+// 1G
+#define ETH_TRAFFIC_PER_PERIOD_MAX \
+  (((unsigned int)ETH_TRAFFIC_MAX_REPORT_PERIOD) * ((unsigned int)131072000))
+#define ONE_MEG (1024 * 1024)
+#define ETH_STAT_PERCENT_MARGIN 95
+
+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] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+  /* 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);
+  // printf("TX MAC %02x:%02x:%02x:%02x:%02x:%02x RX MAC %02x:%02x:%02x:%02x:%02x:%02x\n", eh->ether_shost[0],eh->ether_shost[1],eh->ether_shost[2],eh->ether_shost[3],eh->ether_shost[4],eh->ether_shost[5],eh->ether_dhost[0],eh->ether_dhost[1],eh->ether_dhost[2],eh->ether_dhost[3],eh->ether_dhost[4],eh->ether_dhost[5]);
+
+  /* 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);
+}
+
+static void phy_read_usage(void) {
+  printf("phy_read <ifname> <reg>\n");
+  printf("Example:\n");
+  printf("phy_read lan0 2\n");
+}
+
+int phy_read(int argc, char *argv[]) {
+  int reg, val;
+
+  if (argc != 3) {
+    phy_read_usage();
+    return -1;
+  }
+
+  reg = strtol(argv[2], NULL, 0);
+  mdio_init();
+  mdio_set_interface(argv[1]);
+  val = mdio_read(reg);
+  mdio_done();
+
+  if (val < 0) {
+    printf("Read PHY %s reg %d failed\n", argv[1], reg);
+    return -1;
+  }
+  printf("PHY %s Reg %d = 0x%x\n", argv[1], reg, val);
+  return 0;
+}
+
+static void phy_write_usage(void) {
+  printf("phy_write <ifname> <reg> <val>\n");
+  printf("Example:\n");
+  printf("phy_write lan0 22 0x6\n");
+}
+
+int phy_write(int argc, char *argv[]) {
+  int reg, val, rc;
+
+  if (argc != 4) {
+    phy_write_usage();
+    return -1;
+  }
+
+  reg = strtol(argv[2], NULL, 0);
+  val = strtol(argv[3], NULL, 16);
+  mdio_init();
+  mdio_set_interface(argv[1]);
+  rc = mdio_write(reg, val);
+  mdio_done();
+
+  if (rc < 0) {
+    printf("Write PHY %s reg %d val 0x%x failed\n", argv[1], reg, val);
+    return -1;
+  }
+  printf("PHY %s Reg %d = 0x%x\n", argv[1], reg, val);
+  return 0;
+}
+
+/* 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) {
+  static unsigned int tx_stat = 0;
+  static unsigned int rx_stat = 0;
+  char rsp[ETH_TEST_LINE_MAX];
+  unsigned int tmp;
+
+  for (tmp = 0; tmp < 2; ++tmp) {
+    // system_cmd(RX_BYTES_APTH);
+    // system_cmd(TX_BYTES_PATH);
+    scan_command(RX_BYTES_APTH, rsp, NULL);
+    scan_command(TX_BYTES_PATH, rsp, NULL);
+    sleep(1);
+  }
+  if (scan_command(RX_BYTES_APTH, rsp, NULL) == 0) *rx_bytes = strtoul(rsp, NULL, 10);
+  if (scan_command(TX_BYTES_PATH, rsp, NULL) == 0) *tx_bytes = strtoul(rsp, NULL, 10);
+  // printf("STAT: TX %d RX %d\n", *tx_bytes, *rx_bytes);
+
+  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[ETH_TEST_LINE_MAX], rsp[ETH_TEST_LINE_MAX];
+
+  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_LINE_MAX], rsp[ETH_TEST_LINE_MAX];
+  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 loopback_test_usage(void) {
+  printf(
+      "loopback_test <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};
+  bool gig_traffic = false;
+
+  if ((argc < 2) || (argc > 5)) {
+    printf("Invalid number of parameters: %d\n", argc);
+    loopback_test_usage();
+    return -1;
+  }
+
+  duration = strtol(argv[1], NULL, 0);
+  if ((duration < -1) || (duration == 0)) {
+    printf("Invalid duration %d:%s\n", duration, argv[1]);
+    loopback_test_usage();
+    return -1;
+  }
+
+  if (argc == 3) {
+    if (strcmp(argv[2], "-g") != 0) {
+      printf("Invalid option %s\n", argv[4]);
+      loopback_test_usage();
+      return -1;
+    } else {
+      gig_traffic = true;
+    }
+  }
+
+  if (argc >= 4) {
+    if (strcmp(argv[2], ETH_TRAFFIC_TEST_PERIOD_SYMBOL) != 0) {
+      printf("Invalid option %s\n", argv[2]);
+      loopback_test_usage();
+      return -1;
+    }
+
+    print_period = strtoul(argv[3], NULL, 0);
+    if (((print_period == 0) && (duration < 0)) || (print_period < 0) ||
+        (print_period > ETH_TRAFFIC_MAX_REPORT_PERIOD)) {
+      printf("Invalid print period: %d:%s\n", print_period, argv[3]);
+      loopback_test_usage();
+      return -1;
+    }
+    if (print_period == 0) {
+      print_every_period = false;
+      print_period = ETH_TRAFFIC_REPORT_PERIOD;
+    }
+  }
+
+  if (argc == 5) {
+    if (strcmp(argv[4], "-g") != 0) {
+      printf("Invalid option %s\n", argv[4]);
+      loopback_test_usage();
+      return -1;
+    } else {
+      gig_traffic = true;
+    }
+  }
+
+  net_stat(&rx_bytes, &tx_bytes);
+
+  if (gig_traffic) {
+    /*
+    system_cmd("ethtool -s eth1_0 autoneg off");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg on");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg off");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg on");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg off");
+    sleep(1);
+    */
+    if (print_period > ETH_TRAFFIC_MAX_GE_REPORT_PERIOD)
+      print_period = ETH_TRAFFIC_MAX_GE_REPORT_PERIOD;
+    system_cmd("ethtool -s eth1_1 autoneg off duplex full speed 1000");
+    // printf("1G loopback\n");
+    // Need to set crossover
+  } else {
+    system_cmd("ethtool -s eth1_1 autoneg off duplex full speed 10");
+    // printf("10M loopback\n");
+  }
+  // system_cmd("brctl delif br0 eth1_0");
+  system_cmd("brctl delif br0 eth1_1");
+  // system_cmd("brctl delif br0 wifi0");
+  // system_cmd("ethtool -s " WAN_PORT_NAME " autoneg off duplex full speed 10");
+  // printf("ethtool -s eth1_0 autoneg off\n");
+  sleep(9);
+
+  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(WAN_PORT_NAME, NULL, pkt_len, 1000, num, dst_mac);
+    // send_mac_pkt(WAN_PORT_NAME, "eth1_0", 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, WAN_PORT_NAME);
+        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, WAN_PORT_NAME);
+      }
+    } else {
+      problem = !sleep_and_check_carrier(print_period, WAN_PORT_NAME);
+    }
+
+    if (duration > 0) kill(pid1, SIGSTOP);
+    sleep(ETH_STAT_WAIT_PERIOD);
+    net_stat(&rx_bytes, &tx_bytes);
+    printf("carrier %d: TX %d RX %d\n", !(problem), tx_bytes, rx_bytes);
+    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,
+             WAN_PORT_NAME, throughput, tx_bytes, rx_bytes);
+    }
+    problem = false;
+  }
+
+  if (gig_traffic) {
+    // Need to enable crossover
+  }
+
+  average_throughput /= ((float)collected_count);
+  printf("%s overall %s: %3.3f Mb/s\n",
+         (traffic_problem) ? FAIL_TEXT : PASS_TEXT, argv[1],
+         average_throughput);
+
+  system_cmd("ethtool -s " WAN_PORT_NAME " autoneg on");
+  // system_cmd("brctl addif br0 " WAN_PORT_NAME);
+  // printf("ethtool -s eth1_0 autoneg on\n");
+  return 0;
+}
diff --git a/diags/prowl/gpio.c b/diags/prowl/gpio.c
new file mode 100644
index 0000000..5602fed
--- /dev/null
+++ b/diags/prowl/gpio.c
@@ -0,0 +1,81 @@
+/*
+ * (C) Copyright 2016 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include "../common/util.h"
+#include "i2c.h"
+
+static void switch_state_usage(void) {
+  printf("switch_state\n");
+  printf("Example:\n");
+  printf(" switch_state\n");
+}
+
+int  switch_state(int argc, char *argv[]) {
+  if (argc != 1 || argv == NULL) {
+    switch_state_usage();
+    return -1;
+  }
+
+  system_cmd("echo 5 > /sys/class/gpio/export");
+  system_cmd("cat /sys/class/gpio/gpio5/value");
+
+  return 0;
+}
+
+static void poe_disable_usage(void) {
+  printf("poe_disable [<0 | 1>]\n");
+  printf("Example:\n");
+  printf(" poe_disable 1\n");
+}
+
+int poe_disable(int argc, char *argv[]) {
+  FILE *fp;
+  char rsp[1024];
+
+  if (argc > 2) {
+    poe_disable_usage();
+    return -1;
+  }
+
+  system_cmd("echo 4 > /sys/class/gpio/export");
+  fp = popen("cat /sys/class/gpio/gpio4/direction", "r");
+  if (fp == NULL) {
+    printf("Failed to open GPIO4\n");
+    return -1;
+  }
+  fscanf(fp, "%s", rsp);
+  pclose(fp);
+  if (strcmp(rsp, "out") != 0) {
+    system_cmd("echo \"out\" > /sys/class/gpio/gpio4/direction");
+  }
+  if ( argc == 1) {
+    printf("PoE Disable: ");
+    fflush(stdout);
+    system_cmd("cat /sys/class/gpio/gpio4/value");
+  } else {
+    if (strcmp(argv[1], "0") == 0) {
+      system_cmd("echo 0 > /sys/class/gpio/gpio4/value");
+      printf("PoE Disable set to ");
+      fflush(stdout);
+      system_cmd("cat /sys/class/gpio/gpio4/value");
+    } else if (strcmp(argv[1], "1") == 0) {
+      system_cmd("echo 1 > /sys/class/gpio/gpio4/value");
+      printf("PoE Disable set to ");
+      fflush(stdout);
+      system_cmd("cat /sys/class/gpio/gpio4/value");
+    } else {
+      poe_disable_usage();
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
diff --git a/diags/prowl/i2c.c b/diags/prowl/i2c.c
new file mode 100644
index 0000000..1107bfc
--- /dev/null
+++ b/diags/prowl/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/prowl/i2c.h b/diags/prowl/i2c.h
new file mode 100644
index 0000000..0c5f028
--- /dev/null
+++ b/diags/prowl/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/prowl/i2c_cmd.c b/diags/prowl/i2c_cmd.c
new file mode 100644
index 0000000..d85f7e0
--- /dev/null
+++ b/diags/prowl/i2c_cmd.c
@@ -0,0 +1,328 @@
+/*
+ * (C) Copyright 2014 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include "../common/util.h"
+#include "i2c.h"
+
+#define I2C_READ_BUF_SIZE 1024
+#define DISPLAY_WIDTH 8
+#define LED_BUS 0
+#define LED_ADDR 0x62
+#define LED_SELECT_REG 0x5
+
+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++) {
+    /* Avoid probing these devices */
+    if ((device_addr == 0x69) || (device_addr == 0x0C)) {
+      continue;
+    }
+    return_code = i2cr(controller, device_addr, 0, 1, 1, buf);
+    /*
+    if (return_code != 0) {
+      return_code = i2cr(controller, device_addr, 0, 0, 1, buf);
+    }
+    */
+    if (return_code == 0) {
+      printf("Address 0x%02X responding\n", device_addr);
+    }
+  }
+
+  return 0;
+}
+
+static void board_temp_usage(void) {
+  printf("board_temp\n");
+  printf("Example:\n");
+  printf("board_temp\n");
+}
+
+int board_temp(int argc, char *argv[]) {
+  if (argc != 1 || argv == NULL) {
+    board_temp_usage();
+    return -1;
+  }
+
+  system_cmd("cat /sys/bus/i2c/drivers/ds1775/0-0048/temp_val");
+
+  return 0;
+}
+
+static void led_set_usage(void) {
+  printf("led_set <red | blue> <on | off>\n");
+  printf("Example:\n");
+  printf("led_set blue on\n");
+}
+
+int led_set(int argc, char *argv[]) {
+  int led = 0; // 0 for blue and 1 for read
+  bool is_off = true;
+  static const int kLedMask[2] = {0x3, 0xc}, kLedOffMask[2] = {0x1, 0x4};
+  uint8_t setting;
+
+  if (argc != 3) {
+    led_set_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], "blue") == 0) {
+    led = 0;
+  } else if (strcmp(argv[1], "red") == 0) {
+    led = 1;
+  } else {
+    printf("Unknown LED %s\n", argv[1]);
+    led_set_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[2], "on") == 0) {
+    is_off = false;
+  } else if (strcmp(argv[2], "off") == 0) {
+    is_off = true;
+  } else {
+    printf("Unknown LED setting %s\n", argv[2]);
+    led_set_usage();
+    return -1;
+  }
+
+  if (i2cr(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to read LED selector register.\n");
+    return -1;
+  }
+
+  setting &= ~kLedMask[led];
+  if (is_off) setting |= kLedOffMask[led];
+
+  if (i2cw(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to write LED selector register of 0x%x.\n", setting);
+    return -1;
+  }
+
+  printf("LED %s is set to %s\n", (led == 0) ? "blue" : "red", (is_off) ? "off" : "on");
+
+  return 0;
+}
+
+static void led_set_pwm_usage(void) {
+  printf("led_set_pwm <red | blue> <0-255>\n");
+  printf("Example:\n");
+  printf("led_set_pwm blue 10\n");
+}
+
+int led_set_pwm(int argc, char *argv[]) {
+  int led = 0; // 0 for blue and 1 for read
+  bool is_off = true;
+  static const int kLedPwmMask[2] = {0x3, 0xc}, kLedPwmVal[2] = {0x2, 0xc};
+  static const int kLedPwmReg[2] = {2, 4};
+  uint8_t setting, pwm;
+  unsigned int tmp;
+
+  if (argc != 3) {
+    led_set_pwm_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], "blue") == 0) {
+    led = 0;
+  } else if (strcmp(argv[1], "red") == 0) {
+    led = 1;
+  } else {
+    printf("Unknown LED %s\n", argv[1]);
+    led_set_pwm_usage();
+    return -1;
+  }
+
+  tmp = get_num(argv[2]);
+  if (tmp > 255) {
+    printf("Invalid pwm value: %d\n", tmp);
+    led_set_pwm_usage();
+    return -1;
+  }
+  pwm = tmp;
+
+  if (i2cr(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to read LED selector register.\n");
+    return -1;
+  }
+
+  setting &= ~kLedPwmMask[led];
+  if (is_off) setting |= kLedPwmVal[led];
+
+  if (i2cw(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to write LED selector register of 0x%x.\n", setting);
+    return -1;
+  }
+
+  if (i2cw(LED_BUS, LED_ADDR, kLedPwmReg[led], 1, 1, &pwm) < 0) {
+    printf("Failed to write LED PWM register %d of 0x%x.\n", kLedPwmReg[led], pwm);
+    return -1;
+  }
+
+  printf("LED %s PWM is set to %d\n", (led == 0) ? "blue" : "red", pwm);
+
+  return 0;
+}
diff --git a/diags/prowl/mdio.c b/diags/prowl/mdio.c
new file mode 100644
index 0000000..357f949
--- /dev/null
+++ b/diags/prowl/mdio.c
@@ -0,0 +1,66 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.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/prowl/mdio.h b/diags/prowl/mdio.h
new file mode 100644
index 0000000..16159c5
--- /dev/null
+++ b/diags/prowl/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/gpio-mailbox/Makefile b/gpio-mailbox/Makefile
index eaaee20..b73d99f 100644
--- a/gpio-mailbox/Makefile
+++ b/gpio-mailbox/Makefile
@@ -33,6 +33,10 @@
   CFLAGS += -DWINDCHARGER
 else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfch100)
   CFLAGS += -DGFCH100
+else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfrg240)
+  # stub out gpio mailbox on gfrg240
+  CFLAGS += -DSTUB
+  LDFLAGS += -lm
 else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),kvm)
   CFLAGS += -DSTUB
   LDFLAGS += -lm
diff --git a/gpio-mailbox/brcm-direct.c b/gpio-mailbox/brcm-direct.c
new file mode 100644
index 0000000..8abacbb
--- /dev/null
+++ b/gpio-mailbox/brcm-direct.c
@@ -0,0 +1,653 @@
+#ifdef BROADCOM
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "brcm-platform.h"
+
+#define UNUSED        __attribute__((unused))
+#define DEVMEM       "/dev/mem"
+
+static volatile void* mmap_addr = MAP_FAILED;
+static size_t mmap_size = 0;
+static int mmap_fd = -1;
+
+static void init_gfhd200(UNUSED struct platform_info* p);
+static void init_gfhd254(UNUSED struct platform_info* p);
+static double get_avs_voltage_7252(struct Voltage* v);
+static double get_avs_voltage_74xx(struct Voltage* v);
+static double get_avs_temperature_7252(struct Temp* t);
+static double get_avs_temperature_74xx(struct Temp* t);
+
+struct platform_info platforms[] = {
+  {
+    .name = "GFHD100",
+    .mmap_base = 0x10400000,            // base of many brcm registers
+    .mmap_size = 0x40000,
+    .leds = {
+      .led_red = {
+        .is_present = 1,                  // GPIO 17
+        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
+        .mask = 0x00020000,               // 1<<17
+        .shift = 17,
+        .off_value = 0,
+        .on_value = 1,
+        .direction_value = 0,
+        .old_val = -1,
+      },
+      .led_blue = {
+        .is_present = 1,                  // GPIO 12
+        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
+        .mask = 0x00001000,               // 1<<12
+        .shift = 12,
+        .off_value = 0,
+        .on_value = 1,
+        .direction_value = 0,
+        .old_val = -1,
+      },
+      .led_activity = {
+        .is_present = 1,                  // GPIO 13
+        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
+        .mask = 0x00002000,               // 1<<13
+        .shift = 13,
+        .off_value = 0,
+        .on_value = 1,
+        .direction_value = 0,
+        .old_val = -1,
+      },
+      .led_standby = {
+        .is_present = 1,                  // GPIO 10
+        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
+        .mask = 0x00000400,               // 1<<10
+        .shift = 10,
+        .off_value = 0,
+        .on_value = 1,
+        .direction_value = 0,
+        .old_val = -1,
+      },
+    },
+    .reset_button = {
+      .is_present = 1,                  // GPIO 4
+      .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+      .offset_data = 0x94c4,            // GIO_AON_DATA_LO
+      .mask = 0x00000010,               // 1<<4
+      .shift = 4,
+      .off_value = 0,
+      .on_value = 1,
+      .direction_value = 1,
+      .old_val = -1,
+    },
+    .fan_tick = {
+      .is_present = 1,                  // GPIO 98
+      .offset_direction = 0x6768,       // GIO_IODIR_EXT_HI
+      .offset_data = 0x6764,            // GIO_DATA_EXT_HI
+      .mask = 0x00000100,               // 1<<8
+      .shift = 8,
+      .off_value = 0,
+      .on_value = 1,
+      .direction_value = 1,
+      .old_val = -1,
+    },
+    .fan_control = {
+      .is_present = 1,                  // PWM 1
+      .offset_data = 0x6580,            // PWM_CTRL ...
+      .channel = 0,
+      .open_drain = 1,
+      .period = 0x63,
+    },
+    .temp_monitor = {
+      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
+      .offset_data = 0x32b00,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
+      .get_temp = get_avs_temperature_74xx,
+    },
+    .voltage_monitor = {
+      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
+      .offset_data = 0x32b0c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1P10V_0_MNTR_STATUS
+      .get_voltage = get_avs_voltage_74xx,
+    },
+  },
+  {
+    .name = "GFMS100",
+    .mmap_base = 0x10400000,            // base of many brcm registers
+    .mmap_size = 0x40000,
+    .leds = {
+      .led_red = {
+        .is_present = 1,                  // GPIO 17
+        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+        .offset_data = 0x94c4,            // GIO_AON_DATA_LO 0..17
+        .mask = 0x00020000,               // 1<<17
+        .shift = 17,
+        .off_value = 0,
+        .on_value = 1,
+        .direction_value = 0,
+        .old_val = -1,
+      },
+      .led_blue = {
+        .is_present = 0,
+      },
+      .led_activity = {
+        .is_present = 1,                  // GPIO 13
+        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
+        .mask = 0x00002000,               // 1<<13
+        .shift = 13,
+        .off_value = 0,
+        .on_value = 1,
+        .direction_value = 0,
+        .old_val = -1,
+      },
+      .led_standby = {
+        .is_present = 0,
+      },
+    },
+    .reset_button = {
+      .is_present = 1,                  // GPIO 4
+      .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
+      .offset_data = 0x94c4,            // GIO_AON_DATA_LO
+      .mask = 0x00000010,               // 1<<4
+      .shift = 4,
+      .off_value = 0,
+      .on_value = 1,
+      .direction_value = 1,
+      .old_val = -1,
+    },
+    .fan_tick = {
+      .is_present = 1,                  // GPIO 98
+      .offset_direction = 0x6768,       // GIO_IODIR_EXT_HI
+      .offset_data = 0x6764,            // GIO_DATA_EXT_HI
+      .mask = 0x00000100,               // 1<<8
+      .shift = 8,
+      .off_value = 0,
+      .on_value = 1,
+      .direction_value = 1,
+      .old_val = -1,
+    },
+    .fan_control = {
+      .is_present = 1,                  // PWM 1
+      .offset_data = 0x6580,            // PWM_CTRL ...
+      .channel = 0,
+      .open_drain = 1,
+      .period = 0x63,
+    },
+    .temp_monitor = {
+      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
+      .offset_data = 0x32b00,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
+      .get_temp = get_avs_temperature_74xx,
+    },
+    .voltage_monitor = {
+      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
+      .offset_data = 0x32b0c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1P10V_0_MNTR_STATUS
+      .get_voltage = get_avs_voltage_74xx,
+    },
+  },
+  {
+    .name = "GFHD200",
+    .init = init_gfhd200,
+    .mmap_base = 0x10400000,            // AON_PIN_CTRL ...
+    .mmap_size = 0x30000,
+    .leds = {
+      .led_red = {
+        .is_present = 1,                  // GPIO 5
+        .pinmux_offset = 0x8500,          // PIN_MUX_CTRL_0
+        .pinmux_mask = 0xf0000000,
+        .pinmux_value = 0x10000000,       // LED_LD1 (segment 1 on led digit1)
+        .offset_data = 0x9018,            // GIO_AON_DATA_LO
+        .mask = 0x00000002,               // 1<<1
+        .shift = 1,
+        .off_value =1,
+        .on_value = 0,
+        .old_val = -1,
+      },
+      .led_blue = {
+        .is_present = 0,
+      },
+      .led_activity = {
+        .is_present = 1,                  // GPIO 4
+        .pinmux_offset = 0x8500,          // PIN_MUX_CTRL_0
+        .pinmux_mask = 0x0f000000,
+        .pinmux_value = 0x01000000,       // LED_LD0 (segment 0 on led digit1)
+        .offset_data = 0x9018,            // GIO_AON_DATA_LO
+        .mask = 0x00000001,               // 1<<0
+        .shift = 0,
+        .off_value = 1,
+        .on_value = 0,
+        .old_val = -1,
+      },
+      .led_standby = {
+        .is_present = 0,
+      },
+    },
+    .reset_button = {
+      .is_present = 1,                  // GPIO 3
+      .offset_direction = 0x9808,       // GIO_AON_IODIR_LO
+      .offset_data = 0x9804,            // GIO_AON_DATA_LO
+      .mask = 0x00000008,               // 1<<3
+      .shift = 3,
+      .off_value = 0,
+      .on_value = 1,
+      .direction_value = 1,
+      .old_val = -1,
+    },
+    .fan_control = {
+      .is_present = 0,
+    },
+    .temp_monitor = {
+      .is_present = 1,                  // 7429 AVS_RO_REGISTERS_0
+      .offset_data = 0x23300,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
+      .get_temp = get_avs_temperature_74xx,
+    },
+    .voltage_monitor = {
+      .is_present = 1,                  // 7429 AVS_RO_REGISTERS_0
+      .offset_data = 0x2330c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1P10V_0_MNTR_STATUS
+      .get_voltage = get_avs_voltage_74xx,
+    },
+  },
+  {
+    .name = "GFHD254",
+    .init = init_gfhd254,
+    .mmap_base = 0xf0400000,            // AON_PIN_CTRL ...
+    .mmap_size =    0xe0000,
+    .leds = {
+      .led_red = {
+        .is_present = 1,                  // AON_GPIO_05
+        .pinmux_offset = 0x10700,         // PIN_MUX_CTRL_0
+        .pinmux_mask =  0x00f00000,
+        .pinmux_value = 0x00200000,       // LED_LD_13
+        .offset_data = 0x1701c,           // LDK_DIGIT1
+        .mask = 1<<13,                    // 1<<13
+        .shift = 13,
+        .off_value =1,
+        .on_value = 0,
+        .old_val = -1,
+      },
+      .led_blue = {
+        .is_present = 0,
+      },
+      .led_activity = {
+        .is_present = 1,                  // AON_GPIO_04
+        .pinmux_offset = 0x10700,         // PIN_MUX_CTRL_0
+        .pinmux_mask = 0x000f0000,
+        .pinmux_value = 0x00020000,       // LED_LD_12
+        .offset_data = 0x1701c,           // LDK_DIGIT1
+        .mask = 1<<12,                    // 1<<12
+        .shift = 12,
+        .off_value = 1,
+        .on_value = 0,
+        .old_val = -1,
+      },
+      .led_standby = {
+        .is_present = 0,
+      },
+      .led_brightness = {
+        .is_present = 1,                // GPIO_098
+        .open_drain = 0,
+        .offset_data = 0x9000,          // PWM_2
+        .channel = 0,
+        .old_percent = -1,
+        .period = 0x63,
+      },
+    },
+    .reset_button = {
+      .is_present = 1,                  // GPIO_009
+      .pinmux_offset = 0x4120,          // SUN_TOP_CTRL_PIN_MUX_CTRL_8
+      .pinmux_mask = 0xf0000000,
+      .pinmux_value = 0x00000000,       // GPIO_009
+      .offset_direction = 0xa608,       // GIO_IODIR_LO
+      .offset_data = 0xa604,            // GIO_DATA_LO
+      .mask = 0x00000200,               // 1<<9
+      .shift = 9,
+      .off_value = 0,
+      .on_value = 1,
+      .direction_value = 1,
+      .old_val = -1,
+    },
+    .fan_tick = {
+      .is_present = 1,                  // GPIO 78
+      .offset_direction = 0xa648,       // GIO_IODIR_EXT_HI
+      .offset_data = 0xa644,            // GIO_DATA_EXT_HI
+      .mask = 1<<14,
+      .shift = 14,
+      .off_value = 0,
+      .on_value = 1,
+      .direction_value = 1,
+      .old_val = -1,
+    },
+    .fan_control = {
+      .is_present = 1,                  // PWM 3
+      .offset_data = 0x9000,            // PWM_CTRL ...
+      .channel = 1,
+      .open_drain = 0,
+      .period = 0x91,
+    },
+    .temp_monitor = {
+      .is_present = 1,                  // 7252 AVS_RO_REGISTERS_0
+      .offset_data = 0xd2200,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
+      .get_temp = get_avs_temperature_7252,
+    },
+    .voltage_monitor = {
+      .is_present = 1,                  // 7252 AVS_RO_REGISTERS_0
+      .offset_data = 0xd220c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1V_0_MNTR_STATUS
+      .get_voltage = get_avs_voltage_7252,
+    },
+  }
+};
+
+
+struct platform_info *get_platform_info(const char *platform_name) {
+  int lim = sizeof(platforms) / sizeof(platforms[0]);
+  for (int i = 0; i < lim; ++i) {
+    struct platform_info *p = &platforms[i];
+    if (0 == strncmp(platform_name, p->name, strlen(p->name))) {
+      return p;
+    }
+  }
+  fprintf(stderr, "No support for platform %s", platform_name);
+  return NULL;
+}
+
+/* set LED/Keypad timings to control LED brightness */
+static void init_gfhd200(UNUSED struct platform_info* p) {
+  volatile uint32_t* reg;
+
+  reg = mmap_addr + 0x9034;     // LDK_CONTROL
+  *reg = 0x01;                  // reset
+  *reg = 0x18;                  // ver=1 inv_led=1
+
+  reg = mmap_addr + 0x9008;     // LDK_PRESCHI, LO (clock divisor)
+  reg[0] = 0x00;
+  reg[1] = 0x10;                // tick = clock / 0x0010, not sure what clock is
+
+  reg = mmap_addr + 0x9010;     // LDK_DUTYOFF, ON
+  reg[0] = 0x40;
+  reg[1] = 0xc0;                // 0x40 off ticks then 0xc0 on ticks == 75% brightness
+}
+
+/* set LED/Keypad timings to control LED brightness */
+static void init_gfhd254(struct platform_info* p) {
+  volatile uint32_t* reg;
+
+  // The following comment explains how the LED controller works on <= EVT3.
+  //  For EVT4+, the LED controller was changed to control via PWM. We currently
+  //  configure both. The EVT3 specific code can be removed at a later date.
+  //
+  // The led display controller works like this:
+  //  - there are 16 gpios (we connect our leds to 2 of these)
+  //  - the controller steps through digit1-4 and then status
+  //  - bit0 in a register maps to a particular gpio
+  //     when digit1 is being displayed the controller uses digit1_bit[15:0] to
+  //     drive the gpios.  When digit 2 is displayed digit2[15:0] and so forth.
+  //  - duty_on controls how many clocks a digit is displayed
+  //  - duty_off controls number of clocks of all off time when switching
+  //    between digits
+  //
+  //  To get 100% brightness you set all of digit1-4 and status to 1 for the led
+  //  you are drivng, and set duty_off to 0.
+  //
+  //  Here we also invert the values, so a 1 means off, and 0 means on, this is
+  //  done because for unknown reasons the time between status and digit1 is on,
+  //  so we can't get the brightness to 0 unless we invert.
+  //
+  //  For simplicity we enable only one of the digits because the leds are
+  //  already insanely bright, and then to disable an led we simply toggle the
+  //  bit in that one digit register.
+  //
+  //  The red led is attached to bit 13 and blue led is attached to bit 12.
+  reg = mmap_addr + 0x17034;     // LDK_CONTROL
+  *reg = 0x01;                   // reset
+  *reg = 0x18;                   // ver=1
+
+  reg = mmap_addr + 0x17018;
+  reg[0] = 0xffff;               // LDK_DIGIT2
+  reg[1] = 0xcfff;               // LDK_DIGIT1
+  reg[2] = 0xffff;               // LDK_DIGIT4
+  reg[3] = 0xffff;               // LDK_DIGIT3
+  reg[5] = 0xffff;               // LDK_STATUS
+
+  reg = mmap_addr + 0x17008;     // LDK_PRESCHI, LO (clock divisor)
+  reg[0] = 0x00;
+  reg[1] = 0x10;                 // tick = clock / 0x0010, not sure what clock is
+
+  reg = mmap_addr + 0x17010;     // LDK_DUTYOFF, ON
+  reg[0] = 0x40;
+  reg[1] = 0xc0;                 // 0x40 off ticks then 0xc0 on ticks to dim a bit more.
+
+  // The fan is connected to PWM3, the register PWM3_CWORD_LSB is set to 1,
+  // this is the frequency of the PWM, the other pwm register control
+  // the duty cycle.
+  reg = mmap_addr + 0x9010;       // PWM3_CWORD_MSB
+  reg[0] = 0x20;
+  reg[1] = 0x0;                   // PWM3_CWORD_LSB
+
+  // LEDs are connected to PWM2. Setting CWORD_LSB to 0xf to control
+  // the output freq of the var rate clock.
+  reg = mmap_addr + 0x900c;
+  reg[0] = 0xf;
+
+  // Default the LED brightness to 50.
+  set_pwm(&p->leds.led_brightness, 50);
+}
+
+static double get_avs_voltage_7252(struct Voltage* v) {
+  volatile uint32_t* reg;
+  uint32_t value, valid, raw_data;
+
+  reg = mmap_addr + v->offset_data;
+  value = *reg;
+  valid = (value & 0x00000400) >> 10;
+  raw_data = value & 0x000003ff;
+  if (!valid) return -1.0;
+  return ((880.0/1024.0)/(0.7)*raw_data) / 1000;
+}
+
+static double get_avs_voltage_74xx(struct Voltage* v) {
+  volatile uint32_t* reg;
+  uint32_t value, valid, raw_data;
+
+  reg = mmap_addr + v->offset_data;
+  value = *reg;
+  // see 7425-PR500-RDS.pdf
+  valid = (value & 0x00000400) >> 10;
+  raw_data = value & 0x000003ff;
+  if (!valid) return -1.0;
+  return ((990 * raw_data * 8) / (7*1024)) / 1000.0;
+}
+
+static double get_avs_temperature_74xx(struct Temp* t) {
+  volatile uint32_t* reg;
+  uint32_t value, valid, raw_data;
+
+  reg = mmap_addr + t->offset_data;
+  value = *reg;
+  // see 7425-PR500-RDS.pdf
+  valid = (value & 0x00000400) >> 10;
+  raw_data = value & 0x000003ff;
+  if (!valid) return -1.0;
+  return (418000 - (556 * raw_data)) / 1000.0;
+}
+
+static double get_avs_temperature_7252(struct Temp* t) {
+  volatile uint32_t* reg;
+  uint32_t value, valid, raw_data;
+
+  reg = mmap_addr + t->offset_data;
+  value = *reg;
+  valid = (value & 0x00000400) >> 10;
+  raw_data = value & 0x000003ff;
+  if (!valid) return -1.0;
+  return 410.04 - (0.48705 * raw_data);
+}
+
+/*
+   platform_init has three steps:
+   1) Generic platform init. This sets up the mmap.
+   2) Platform-specific init. Calls the gf* function
+   corresponding to the passed in platform.
+   3) GPIO init. Sets up the gpios properly.
+*/
+int platform_init(struct platform_info* p) {
+  platform_cleanup();
+
+  mmap_fd = open(DEVMEM, O_RDWR);
+  if (mmap_fd < 0) {
+    perror(DEVMEM);
+    return -1;
+  }
+  mmap_size = p->mmap_size;
+  mmap_addr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, mmap_fd, p->mmap_base);
+  if (mmap_addr == MAP_FAILED) {
+    perror("mmap");
+    platform_cleanup();
+    return -1;
+  }
+
+  if (p->init) {
+    (*p->init)(p);
+  }
+
+  set_pinmux(&p->leds.led_red);
+  set_pinmux(&p->leds.led_blue);
+  set_pinmux(&p->leds.led_activity);
+  set_pinmux(&p->leds.led_standby);
+
+  set_direction(&p->leds.led_red);
+  set_direction(&p->leds.led_blue);
+  set_direction(&p->leds.led_activity);
+  set_direction(&p->leds.led_standby);
+  set_direction(&p->reset_button);
+  set_direction(&p->fan_tick);
+
+  return 0;
+}
+
+void platform_cleanup() {
+  if (mmap_addr != MAP_FAILED) {
+    if (munmap((void*) mmap_addr, mmap_size) < 0) {
+      perror("munmap");
+    }
+    mmap_addr = MAP_FAILED;
+    mmap_size = 0;
+  }
+  if (mmap_fd >= 0) {
+    close(mmap_fd);
+    mmap_fd = -1;
+  }
+
+}
+
+void set_gpio(struct Gpio *g, int level) {
+  volatile uint32_t* reg;
+  uint32_t value;
+
+  if (g->old_val == level) {
+    // If this is the same value as last time, don't do anything, for two
+    // reasons:
+    //   1) If you set the gpio too often, it seems to stay low (the led
+    //      stays off).
+    //   2) If some process other than us is twiddling a led, this way we
+    //      won't interfere with it.
+    return;
+  }
+  g->old_val = level;
+
+  reg = mmap_addr + g->offset_data;
+  value = *reg;
+  value &= ~g->mask;
+  value |= (level ? g->on_value : g->off_value) << g->shift;
+  *reg = value;
+}
+
+int get_gpio(struct Gpio *g) {
+  volatile uint32_t* reg;
+  uint32_t value;
+
+  reg = mmap_addr + g->offset_data;
+  value = (*reg & g->mask) >> g->shift;
+  return (value == g->on_value);
+}
+
+/*
+  Set the PWM duty duty cycle. Percent bounded [0, 100].
+
+  The output period of the constant-freq PWM is calculated
+  by (period_programmed + 1) / Fv, where Fv is the output
+  of the variable-frequency PWM (in mhz).
+
+  Fv is calculated by the following formula:
+
+    Fv = (cword) * 2^-16 * 27MHz
+
+  cword is the programmed frequency control word.
+
+  The fan on lockdown must stay at a constant 23KHz
+*/
+void set_pwm(struct PwmControl *f, int percent) {
+  volatile uint32_t* reg;
+  uint32_t mask0, val0, mask1, val1, on;
+
+  if (percent < 0) percent = 0;
+  if (percent > 100) percent = 100;
+  if (f->old_percent == percent) return;
+  f->old_percent = percent;
+
+  reg = mmap_addr + f->offset_data;
+  if (f->channel == 0) {
+    mask0 = 0xf0;       // preserve other channel
+    val0 = 0x01;        // open-drain|start
+    if (f->open_drain)
+      val0 |= 0x08;
+    mask1 = 0x10;       // preserve
+    val1 = 0x01;        // constant-freq
+    on = 6;
+  } else {
+    mask0 = 0x0f;       // see above
+    val0 = 0x10;
+    if (f->open_drain)
+      val0 |= 0x80;
+    mask1 = 0x01;
+    val1 = 0x10;
+    on = 8;
+  }
+  reg[0] = (reg[0] & mask0) | val0;
+  reg[1] = (reg[1] & mask1) | val1;
+  reg[on] = (f->period * percent)/100;
+  reg[on+1] = f->period;
+}
+
+void set_direction(struct Gpio *g) {
+  volatile uint32_t* reg;
+  uint32_t value;
+
+  if (!g->is_present || g->offset_direction == 0)
+    return;
+
+  reg = mmap_addr + g->offset_direction;
+  value = *reg;
+  value &= ~g->mask;
+  value |= g->direction_value << g->shift;
+  *reg = value;
+}
+
+void set_pinmux(struct Gpio *g) {
+  volatile uint32_t* reg;
+  uint32_t value;
+
+  if (!g->is_present || g->pinmux_offset == 0)
+    return;
+
+  reg = mmap_addr + g->pinmux_offset;
+  value = *reg;
+  value &= ~g->pinmux_mask;
+  value |= g->pinmux_value;
+  *reg = value;
+}
+
+#endif // BROADCOM
diff --git a/gpio-mailbox/brcm-nexus.c b/gpio-mailbox/brcm-nexus.c
new file mode 100644
index 0000000..f559c1b
--- /dev/null
+++ b/gpio-mailbox/brcm-nexus.c
@@ -0,0 +1,329 @@
+#ifdef ANDROID    /* Should work fine on non Android platforms, but is only necessary on Android */
+#ifdef BROADCOM
+
+/* Broadcom platform implementation using the NEXUS API.
+   Only the GFHD254 is supported via this API as of now. */
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "brcm-platform.h"
+
+#include "nexus_avs.h"
+#include "nexus_pwm.h"
+#include "nexus_gpio.h"
+#include "nxclient.h"
+
+#define UNUSED        __attribute__((unused))
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+
+static void init_gfhd254(struct platform_info* p);
+static double get_avs_voltage_7252(struct Voltage* v);
+static double get_avs_temperature_7252(struct Temp* t);
+
+/* This is an array. Of structs! It contains structs of
+   the type platform_info. The platform_info struct provides
+   much useful information for use in all sorts of fun
+   operations. Structs are considered "aggregate types",
+   which you can of course read all about by pointing your
+   browser at Google and searching for your favorite version
+   of our beloved C standard, such as ISO/IEC 9899:TC3! */
+struct platform_info platforms[] = {
+  {
+    .name = "GFHD254",
+    .init = init_gfhd254,
+    .leds = {
+      .led_red = {
+        .is_present = 1,                  // AON_GPIO_05
+        .type = AON,
+        .pin = 5,
+        .old_val = -1,
+      },
+      .led_blue = {
+        .is_present = 0,
+      },
+      .led_activity = {
+        .is_present = 1,                  // AON_GPIO_04
+        .pin = 4,
+        .type = AON,
+        .old_val = -1,
+      },
+      .led_standby = {
+        .is_present = 0,
+      },
+      .led_brightness = {
+        .is_present = 1,                // GPIO_098
+        .open_drain = 0,
+        .pwm_index = 2,
+        .old_percent = -1,
+      },
+    },
+    .reset_button = {
+      .is_present = 1,                  // GPIO_009
+      .pin = 9,
+      .type = STANDARD,
+      .old_val = -1,
+    },
+    .fan_tick = {
+      .is_present = 1,                  // GPIO 78
+      .pin = 78,
+      .type = STANDARD,
+      .old_val = -1,
+    },
+    .fan_control = {
+      .is_present = 1,                // GPIO_098
+      .open_drain = 0,
+      .pwm_index = 3,
+      .old_percent = -1,
+    },
+    .temp_monitor = {
+      .is_present = 1,                  // 7252 AVS_RO_REGISTERS_0
+      .get_temp = get_avs_temperature_7252,
+    },
+    .voltage_monitor = {
+      .is_present = 1,
+      .get_voltage = get_avs_voltage_7252,
+    },
+  }
+};
+
+struct platform_info *get_platform_info(const char *platform_name) {
+  for (unsigned int i = 0; i < ARRAYSIZE(platforms); ++i) {
+    struct platform_info *p = &platforms[i];
+    if (0 == strncmp(platform_name, p->name, strlen(p->name))) {
+      return p;
+    }
+  }
+  fprintf(stderr, "No support for platform %s", platform_name);
+  return NULL;
+}
+
+static NEXUS_GpioType get_nexus_type(enum GpioType type) {
+  switch (type) {
+    case STANDARD:
+      return NEXUS_GpioType_eStandard;
+    case AON:
+      return NEXUS_GpioType_eAonStandard;
+  }
+
+  /* If we added a GpioType and are using it and never updated this, we
+     are going to have a bad time somewhere. Return an invalid val and let
+     nxclient handle it, while logging the problem. */
+  fprintf(stderr, "No matching NEXUS type for GPIO: %d\n", type);
+  return -1;
+}
+
+// Don't need to set direction for GFHD254
+static void initialize_gpio(struct Gpio* gpio) {
+  if (!gpio || !gpio->is_present)
+    return;
+
+  /* TODO(doughorn): Cannot set pinmux from NEXUS? Can retrieve
+     pinmux information but can't set it because it is 'dangerous'.
+     So we can use the raw Read/WriteRegister functions. These, however,
+     are also dangerous and their "indiscriminate use will result in
+     system failure." Seems to work properly without us setting... */
+  NEXUS_GpioSettings gpioSettings;
+  NEXUS_GpioHandle handle;
+  NEXUS_GpioType type = get_nexus_type(gpio->type);
+
+  NEXUS_Gpio_GetDefaultSettings(type, &gpioSettings);
+  gpioSettings.mode = NEXUS_GpioMode_eOutputPushPull;
+  gpioSettings.interruptMode = NEXUS_GpioInterrupt_eDisabled;
+
+  handle = NEXUS_Gpio_Open(type, gpio->pin, &gpioSettings);
+  if (!handle) {
+    fprintf(stderr, "Failed opening GPIO pin %d. gpio-mailbox cannot continue.\n", gpio->pin);
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  NEXUS_Gpio_Close(handle);
+}
+
+/* TODO(doughorn): We can probably avoid calling GetAvsStatus twice for
+   the voltage and temp individually, but the poll rate is low enough
+   that it most likely doesn't matter...*/
+static double get_avs_voltage_7252(UNUSED struct Voltage* v) {
+  NEXUS_AvsStatus status;
+  if (NEXUS_GetAvsStatus(&status)) {
+    fprintf(stderr, "Could not get AVS status. Aborting...\n");
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  /* NEXUS_AvsStatus.voltage measured in millivolts */
+  return (double)status.voltage / 1000;
+}
+
+static double get_avs_temperature_7252(UNUSED struct Temp* t) {
+  NEXUS_AvsStatus status;
+
+  if (NEXUS_GetAvsStatus(&status)) {
+    fprintf(stderr, "Could not get AVS status. Aborting...\n");
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  /* Temp is in thousands of a degree. */
+  return (double)status.temperature / 1000;
+}
+
+static void init_gfhd254(struct platform_info* p) {
+  NEXUS_PwmChannelSettings pwmSettings;
+  NEXUS_PwmChannelHandle pwm;
+  NEXUS_PwmFreqModeType frequencyMode;
+
+  /* Set the control word for the led brightness PWM to 0xf.
+   This is used to control the output frequency from the
+   variable rate clock. */
+  NEXUS_Pwm_GetDefaultChannelSettings(&pwmSettings);
+  pwm = NEXUS_Pwm_OpenChannel(2, &pwmSettings);
+  if (NEXUS_Pwm_SetControlWord(pwm, 0xf)) {
+    fprintf(stderr, "Failed setting control word for PWM.\n");
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  NEXUS_Pwm_CloseChannel(pwm);
+
+  /* Set the control word for the fan to 0x2000. */
+  NEXUS_Pwm_GetDefaultChannelSettings(&pwmSettings);
+  pwm = NEXUS_Pwm_OpenChannel(3, &pwmSettings);
+  if (NEXUS_Pwm_SetControlWord(pwm, 0x2000)) {
+    fprintf(stderr, "Failed setting control word for PWM.\n");
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  NEXUS_Pwm_CloseChannel(pwm);
+
+  // Default the LED brightness to 50.
+  set_pwm(&p->leds.led_brightness, 50);
+}
+
+void platform_cleanup() {
+  NxClient_Uninit();
+}
+
+int platform_init(struct platform_info* p) {
+  if (NxClient_Join(NULL)) {
+    fprintf(stderr, "gpio-mailbox failed to connect to nxserver. Aborting...\n");
+    return -1;
+  }
+
+  if (p->init) {
+    (*p->init)(p);
+  }
+
+  initialize_gpio(&p->leds.led_red);
+  initialize_gpio(&p->leds.led_blue);
+  initialize_gpio(&p->leds.led_activity);
+  initialize_gpio(&p->leds.led_standby);
+  return 0;
+}
+
+void set_gpio(struct Gpio *gpio, int level) {
+  if (!gpio || !gpio->is_present || gpio->old_val == level) {
+    // If this is the same value as last time, don't do anything, for two
+    // reasons:
+    //   1) If you set the gpio too often, it seems to stay low (the led
+    //      stays off).
+    //   2) If some process other than us is twiddling a led, this way we
+    //      won't interfere with it.
+    return;
+  }
+
+  gpio->old_val = level;
+
+  NEXUS_GpioSettings gpioSettings;
+  NEXUS_GpioHandle handle;
+  uint32_t error;
+  NEXUS_GpioType type = get_nexus_type(gpio->type);
+
+  handle = NEXUS_Gpio_Open(type, gpio->pin, NULL);
+  if (!handle) {
+    fprintf(stderr, "Failed opening GPIO pin %d. Cannot continue.\n", gpio->pin);
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  NEXUS_Gpio_GetSettings(handle, &gpioSettings);
+  gpioSettings.value = level ? NEXUS_GpioValue_eHigh : NEXUS_GpioValue_eLow;
+  if ((error = NEXUS_Gpio_SetSettings(handle, &gpioSettings))) {
+    fprintf(stderr, "Failed setting GPIO pin %d. Cannot continue.\n", gpio->pin);
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  NEXUS_Gpio_Close(handle);
+}
+
+int get_gpio(struct Gpio *gpio) {
+  if (!gpio || !gpio->is_present)
+    return 0;
+
+  NEXUS_GpioStatus status;
+  NEXUS_GpioHandle handle;
+  uint32_t error;
+  NEXUS_GpioType type = get_nexus_type(gpio->type);
+
+  handle = NEXUS_Gpio_Open(type, gpio->pin, NULL);
+
+  if (!handle) {
+    fprintf(stderr, "Failed opening GPIO pin %d. Cannot continue.\n", gpio->pin);
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  if ((error = NEXUS_Gpio_GetStatus(handle, &status))) {
+    fprintf(stderr, "Failed getting status of GPIO pin %d. Cannot continue.\n", gpio->pin);
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  NEXUS_Gpio_Close(handle);
+
+  return status.value != NEXUS_GpioValue_eLow;
+}
+
+/*
+  Set the pwm. See set_pwm in brcm-direct.c
+  for details.
+*/
+void set_pwm(struct PwmControl *f, int percent) {
+  if (percent < 0) percent = 0;
+  if (percent > 100) percent = 100;
+  if (percent == f->old_percent) return;
+  f->old_percent = percent;
+  uint32_t period = f->pwm_index % 2 ? 0x91 : 0x63;
+
+  NEXUS_PwmChannelSettings pwmSettings;
+  NEXUS_PwmChannelHandle pwm;
+  uint16_t onInterval = (period * percent)/100;
+
+  NEXUS_Pwm_GetDefaultChannelSettings(&pwmSettings);
+  pwmSettings.openDrain = f->open_drain;
+  pwmSettings.eFreqMode = NEXUS_PwmFreqModeType_eConstant;
+  pwm = NEXUS_Pwm_OpenChannel(f->pwm_index, &pwmSettings);
+
+  if (NEXUS_Pwm_SetOnAndPeriodInterval(pwm, onInterval, period)) {
+    fprintf(stderr, "Could not set ON and PERIOD for PWM. Aborting...\n");
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  if (NEXUS_Pwm_Start(pwm)) {
+    fprintf(stderr, "Could not start PWM %d!\n", f->pwm_index);
+    platform_cleanup();
+    exit(EXIT_FAILURE);
+  }
+
+  NEXUS_Pwm_CloseChannel(pwm);
+}
+
+#endif // BROADCOM
+#endif // ANDROID
diff --git a/gpio-mailbox/brcm-platform.h b/gpio-mailbox/brcm-platform.h
new file mode 100644
index 0000000..4b53be9
--- /dev/null
+++ b/gpio-mailbox/brcm-platform.h
@@ -0,0 +1,111 @@
+/*
+ * Hardware abstraction layer for GPIO's and PWMs.
+ */
+
+#ifndef BRCM_PLATFORM_
+#define BRCM_PLATFORM_
+
+/*
+ * Defines the category that a given GPIO falls under.
+ */
+enum GpioType {
+  STANDARD,
+  AON,
+};
+
+struct Gpio {
+  int is_present;
+
+  unsigned int pinmux_offset;
+  unsigned int pinmux_mask;
+  unsigned int pinmux_value;
+
+  unsigned int offset_direction;
+  unsigned int offset_data;
+
+  /* for offset_direction and offset_data */
+  unsigned int mask;                    // eg, (*reg & mask) >> shift == on_value
+  unsigned int shift;
+  unsigned int off_value;
+  unsigned int on_value;
+  unsigned int direction_value;         // 0 is output
+  unsigned int pin;                     // gpio #
+  enum GpioType type;                   // 'type' of gpio (aon/standard)
+  int old_val;
+};
+
+struct PwmControl {
+  int is_present;
+  int open_drain;
+  unsigned int offset_data;
+  unsigned int pwm_index;               // index of this pwm.
+  unsigned int channel;
+  int old_percent;
+  int period;
+};
+
+struct Temp {
+  int is_present;
+  unsigned int offset_data;
+  double (*get_temp)(struct Temp* t);
+};
+
+struct Voltage {
+  int is_present;
+  unsigned int offset_data;
+  double (*get_voltage)(struct Voltage* v);
+};
+
+struct Leds {
+  struct Gpio led_red;
+  struct Gpio led_blue;
+  struct Gpio led_activity;
+  struct Gpio led_standby;
+  struct PwmControl led_brightness;
+};
+
+struct platform_info {
+  const char *name;
+  off_t mmap_base;
+  size_t mmap_size;
+  void (*init)(struct platform_info* p);
+  struct Leds leds;
+  struct Gpio reset_button;
+  struct Gpio fan_tick;
+  struct PwmControl fan_control;
+  struct Temp temp_monitor;
+  struct Voltage voltage_monitor;
+};
+
+/* This value, from old code, controls the pwm period. The duty cycle
+  is defined as on/(period + 1) and on is defined as (on/Fv). Fv is
+  the frequency of the variable rate PWM.*/
+extern const int PWM_CYCLE_PERIOD;
+
+/* Return the master platform_info struct for the provided platforn_name.
+   If no platform matches, returns NULL */
+extern struct platform_info *get_platform_info(const char *);
+
+/* Initialize the platform! */
+extern int platform_init(struct platform_info *);
+
+/* Cleanup the platform! */
+extern void platform_cleanup();
+
+/* Set the gpio represented by to the provided level.
+   Level is restricted to [0, 1] */
+extern void set_gpio(struct Gpio *, int);
+
+/* Get the value of the gpio provided. */
+extern int get_gpio(struct Gpio *);
+
+/* Set the provided PWM to the given duty cycle percent */
+extern void set_pwm(struct PwmControl *, int);
+
+/* Init GPIO to input or output. */
+extern void set_direction(struct Gpio *);
+
+/* Set the pinmux (init pin to LED, GPIO, etc) */
+extern void set_pinmux(struct Gpio *);
+
+#endif
diff --git a/gpio-mailbox/broadcom.c b/gpio-mailbox/broadcom.c
index a53e51e..7540571 100644
--- a/gpio-mailbox/broadcom.c
+++ b/gpio-mailbox/broadcom.c
@@ -2,35 +2,19 @@
 
 #define _POSIX_C_SOURCE 199309L /* for clock_gettime */
 #define _BSD_SOURCE             /* for usleep */
-#include <features.h>
 
-#include <assert.h>
 #include <fcntl.h>
-#include <signal.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <sys/select.h>
-#include <sys/time.h>
-#include <sys/uio.h>
-#include <sys/wait.h>
-#include <sys/mman.h>
+#include <stacktrace.h>
+#include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
-#include <stacktrace.h>
 
+#include "brcm-platform.h"
 #include "pin.h"
 
-#define UNUSED        __attribute__((unused))
-
-#define DEVMEM          "/dev/mem"
-
-/* This value, from old code, controls the pwm period. The duty cycle
-  is defined as on/(period + 1) and on is defined as (on/Fv). Fv is
-  the frequency of the variable rate PWM.*/
-static const int PWM_CYCLE_PERIOD = 0x63;
-
 struct PinHandle_s {
   int   unused;
 };
@@ -43,556 +27,8 @@
     } \
   } while (0)
 
-static volatile void* mmap_addr = MAP_FAILED;
-static size_t mmap_size = 0;
-static int mmap_fd = -1;
-
-struct Gpio {
-  int is_present;
-
-  unsigned int pinmux_offset;
-  unsigned int pinmux_mask;
-  unsigned int pinmux_value;
-
-  unsigned int offset_direction;
-  unsigned int offset_data;
-
-  /* for offset_direction and offset_data */
-  unsigned int mask;                    // eg, (*reg & mask) >> shift == on_value
-  unsigned int shift;
-  unsigned int off_value;
-  unsigned int on_value;
-  unsigned int direction_value;         // 0 is output
-  int old_val;
-};
-
-struct PwmControl {
-  int is_present;
-  int open_drain;
-  unsigned int offset_data;
-  unsigned int channel;
-  int old_percent;
-};
-
-struct Temp {
-  int is_present;
-  unsigned int offset_data;
-  double (*get_temp)(struct Temp* t);
-};
-
-struct Voltage {
-  int is_present;
-  unsigned int offset_data;
-  double (*get_voltage)(struct Voltage* v);
-};
-
-struct Leds {
-  struct Gpio led_red;
-  struct Gpio led_blue;
-  struct Gpio led_activity;
-  struct Gpio led_standby;
-  struct PwmControl led_brightness;
-};
-
-struct platform_info {
-  const char *name;
-  off_t mmap_base;
-  size_t mmap_size;
-  void (*init)(struct platform_info* p);
-  struct Leds leds;
-  struct Gpio reset_button;
-  struct Gpio fan_tick;
-  struct PwmControl fan_control;
-  struct Temp temp_monitor;
-  struct Voltage voltage_monitor;
-};
-
-static void init_gfhd200(struct platform_info* p);
-static void init_gfhd254(struct platform_info* p);
-static double get_avs_temperature_74xx(struct Temp* t);
-static double get_avs_temperature_7252(struct Temp* t);
-static double get_avs_voltage_74xx(struct Voltage* v);
-static double get_avs_voltage_7252(struct Voltage* v);
-
-struct platform_info platforms[] = {
-  {
-    .name = "GFHD100",
-    .mmap_base = 0x10400000,            // base of many brcm registers
-    .mmap_size = 0x40000,
-    .leds = {
-      .led_red = {
-        .is_present = 1,                  // GPIO 17
-        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
-        .mask = 0x00020000,               // 1<<17
-        .shift = 17,
-        .off_value = 0,
-        .on_value = 1,
-        .direction_value = 0,
-        .old_val = -1,
-      },
-      .led_blue = {
-        .is_present = 1,                  // GPIO 12
-        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
-        .mask = 0x00001000,               // 1<<12
-        .shift = 12,
-        .off_value = 0,
-        .on_value = 1,
-        .direction_value = 0,
-        .old_val = -1,
-      },
-      .led_activity = {
-        .is_present = 1,                  // GPIO 13
-        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
-        .mask = 0x00002000,               // 1<<13
-        .shift = 13,
-        .off_value = 0,
-        .on_value = 1,
-        .direction_value = 0,
-        .old_val = -1,
-      },
-      .led_standby = {
-        .is_present = 1,                  // GPIO 10
-        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
-        .mask = 0x00000400,               // 1<<10
-        .shift = 10,
-        .off_value = 0,
-        .on_value = 1,
-        .direction_value = 0,
-        .old_val = -1,
-      },
-    },
-    .reset_button = {
-      .is_present = 1,                  // GPIO 4
-      .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-      .offset_data = 0x94c4,            // GIO_AON_DATA_LO
-      .mask = 0x00000010,               // 1<<4
-      .shift = 4,
-      .off_value = 0,
-      .on_value = 1,
-      .direction_value = 1,
-      .old_val = -1,
-    },
-    .fan_tick = {
-      .is_present = 1,                  // GPIO 98
-      .offset_direction = 0x6768,       // GIO_IODIR_EXT_HI
-      .offset_data = 0x6764,            // GIO_DATA_EXT_HI
-      .mask = 0x00000100,               // 1<<8
-      .shift = 8,
-      .off_value = 0,
-      .on_value = 1,
-      .direction_value = 1,
-      .old_val = -1,
-    },
-    .fan_control = {
-      .is_present = 1,                  // PWM 1
-      .offset_data = 0x6580,            // PWM_CTRL ...
-      .channel = 0,
-      .open_drain = 1,
-    },
-    .temp_monitor = {
-      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
-      .offset_data = 0x32b00,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
-      .get_temp = get_avs_temperature_74xx,
-    },
-    .voltage_monitor = {
-      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
-      .offset_data = 0x32b0c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1P10V_0_MNTR_STATUS
-      .get_voltage = get_avs_voltage_74xx,
-    },
-  },
-  {
-    .name = "GFMS100",
-    .mmap_base = 0x10400000,            // base of many brcm registers
-    .mmap_size = 0x40000,
-    .leds = {
-      .led_red = {
-        .is_present = 1,                  // GPIO 17
-        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-        .offset_data = 0x94c4,            // GIO_AON_DATA_LO 0..17
-        .mask = 0x00020000,               // 1<<17
-        .shift = 17,
-        .off_value = 0,
-        .on_value = 1,
-        .direction_value = 0,
-        .old_val = -1,
-      },
-      .led_blue = {
-        .is_present = 0,
-      },
-      .led_activity = {
-        .is_present = 1,                  // GPIO 13
-        .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-        .offset_data = 0x94c4,            // GIO_AON_DATA_LO
-        .mask = 0x00002000,               // 1<<13
-        .shift = 13,
-        .off_value = 0,
-        .on_value = 1,
-        .direction_value = 0,
-        .old_val = -1,
-      },
-      .led_standby = {
-        .is_present = 0,
-      },
-    },
-    .reset_button = {
-      .is_present = 1,                  // GPIO 4
-      .offset_direction = 0x94c8,       // GIO_AON_IODIR_LO
-      .offset_data = 0x94c4,            // GIO_AON_DATA_LO
-      .mask = 0x00000010,               // 1<<4
-      .shift = 4,
-      .off_value = 0,
-      .on_value = 1,
-      .direction_value = 1,
-      .old_val = -1,
-    },
-    .fan_tick = {
-      .is_present = 1,                  // GPIO 98
-      .offset_direction = 0x6768,       // GIO_IODIR_EXT_HI
-      .offset_data = 0x6764,            // GIO_DATA_EXT_HI
-      .mask = 0x00000100,               // 1<<8
-      .shift = 8,
-      .off_value = 0,
-      .on_value = 1,
-      .direction_value = 1,
-      .old_val = -1,
-    },
-    .fan_control = {
-      .is_present = 1,                  // PWM 1
-      .offset_data = 0x6580,            // PWM_CTRL ...
-      .channel = 0,
-      .open_drain = 1,
-    },
-    .temp_monitor = {
-      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
-      .offset_data = 0x32b00,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
-      .get_temp = get_avs_temperature_74xx,
-    },
-    .voltage_monitor = {
-      .is_present = 1,                  // 7425 AVS_RO_REGISTERS_0
-      .offset_data = 0x32b0c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1P10V_0_MNTR_STATUS
-      .get_voltage = get_avs_voltage_74xx,
-    },
-  },
-  {
-    .name = "GFHD200",
-    .init = init_gfhd200,
-    .mmap_base = 0x10400000,            // AON_PIN_CTRL ...
-    .mmap_size = 0x30000,
-    .leds = {
-      .led_red = {
-        .is_present = 1,                  // GPIO 5
-        .pinmux_offset = 0x8500,          // PIN_MUX_CTRL_0
-        .pinmux_mask = 0xf0000000,
-        .pinmux_value = 0x10000000,       // LED_LD1 (segment 1 on led digit1)
-        .offset_data = 0x9018,            // GIO_AON_DATA_LO
-        .mask = 0x00000002,               // 1<<1
-        .shift = 1,
-        .off_value =1,
-        .on_value = 0,
-        .old_val = -1,
-      },
-      .led_blue = {
-        .is_present = 0,
-      },
-      .led_activity = {
-        .is_present = 1,                  // GPIO 4
-        .pinmux_offset = 0x8500,          // PIN_MUX_CTRL_0
-        .pinmux_mask = 0x0f000000,
-        .pinmux_value = 0x01000000,       // LED_LD0 (segment 0 on led digit1)
-        .offset_data = 0x9018,            // GIO_AON_DATA_LO
-        .mask = 0x00000001,               // 1<<0
-        .shift = 0,
-        .off_value = 1,
-        .on_value = 0,
-        .old_val = -1,
-      },
-      .led_standby = {
-        .is_present = 0,
-      },
-    },
-    .reset_button = {
-      .is_present = 1,                  // GPIO 3
-      .offset_direction = 0x9808,       // GIO_AON_IODIR_LO
-      .offset_data = 0x9804,            // GIO_AON_DATA_LO
-      .mask = 0x00000008,               // 1<<3
-      .shift = 3,
-      .off_value = 0,
-      .on_value = 1,
-      .direction_value = 1,
-      .old_val = -1,
-    },
-    .fan_control = {
-      .is_present = 0,
-    },
-    .temp_monitor = {
-      .is_present = 1,                  // 7429 AVS_RO_REGISTERS_0
-      .offset_data = 0x23300,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
-      .get_temp = get_avs_temperature_74xx,
-    },
-    .voltage_monitor = {
-      .is_present = 1,                  // 7429 AVS_RO_REGISTERS_0
-      .offset_data = 0x2330c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1P10V_0_MNTR_STATUS
-      .get_voltage = get_avs_voltage_74xx,
-    },
-  },
-  {
-    .name = "GFHD254",
-    .init = init_gfhd254,
-    .mmap_base = 0xf0400000,            // AON_PIN_CTRL ...
-    .mmap_size =    0xe0000,
-    .leds = {
-      .led_red = {
-        .is_present = 1,                  // AON_GPIO_05
-        .pinmux_offset = 0x10700,         // PIN_MUX_CTRL_0
-        .pinmux_mask =  0x00f00000,
-        .pinmux_value = 0x00200000,       // LED_LD_13
-        .offset_data = 0x1701c,           // LDK_DIGIT1
-        .mask = 1<<13,                    // 1<<13
-        .shift = 13,
-        .off_value =1,
-        .on_value = 0,
-        .old_val = -1,
-      },
-      .led_blue = {
-        .is_present = 0,
-      },
-      .led_activity = {
-        .is_present = 1,                  // AON_GPIO_04
-        .pinmux_offset = 0x10700,         // PIN_MUX_CTRL_0
-        .pinmux_mask = 0x000f0000,
-        .pinmux_value = 0x00020000,       // LED_LD_12
-        .offset_data = 0x1701c,           // LDK_DIGIT1
-        .mask = 1<<12,                    // 1<<12
-        .shift = 12,
-        .off_value = 1,
-        .on_value = 0,
-        .old_val = -1,
-      },
-      .led_standby = {
-        .is_present = 0,
-      },
-      .led_brightness = {
-        .is_present = 1,                // GPIO_098
-        .open_drain = 0,
-        .offset_data = 0x9000,          // PWM_2
-        .channel = 0,
-        .old_percent = -1,
-      },
-    },
-    .reset_button = {
-      .is_present = 1,                  // GPIO_009
-      .pinmux_offset = 0x4120,          // SUN_TOP_CTRL_PIN_MUX_CTRL_8
-      .pinmux_mask = 0xf0000000,
-      .pinmux_value = 0x00000000,       // GPIO_009
-      .offset_direction = 0xa608,       // GIO_IODIR_LO
-      .offset_data = 0xa604,            // GIO_DATA_LO
-      .mask = 0x00000200,               // 1<<9
-      .shift = 9,
-      .off_value = 0,
-      .on_value = 1,
-      .direction_value = 1,
-      .old_val = -1,
-    },
-    .fan_tick = {
-      .is_present = 1,                  // GPIO 78
-      .offset_direction = 0xa648,       // GIO_IODIR_EXT_HI
-      .offset_data = 0xa644,            // GIO_DATA_EXT_HI
-      .mask = 1<<14,
-      .shift = 14,
-      .off_value = 0,
-      .on_value = 1,
-      .direction_value = 1,
-      .old_val = -1,
-    },
-    .fan_control = {
-      .is_present = 1,                  // PWM 3
-      .offset_data = 0x9000,            // PWM_CTRL ...
-      .channel = 1,
-      .open_drain = 0,
-    },
-    .temp_monitor = {
-      .is_present = 1,                  // 7252 AVS_RO_REGISTERS_0
-      .offset_data = 0xd2200,           // BCHP_AVS_RO_REGISTERS_0_PVT_TEMPERATURE_MNTR_STATUS
-      .get_temp = get_avs_temperature_7252,
-    },
-    .voltage_monitor = {
-      .is_present = 1,                  // 7252 AVS_RO_REGISTERS_0
-      .offset_data = 0xd220c,           // BCHP_AVS_RO_REGISTERS_0_PVT_1V_0_MNTR_STATUS
-      .get_voltage = get_avs_voltage_7252,
-    },
-  }
-};
-
 struct platform_info *platform = NULL;
 
-/* PWM operates on either channel 0 or 1. We want to get the duty cycle value
-   by calculating it from the "ON" register, located offset 6 for channel 0
-   and 8 for channel 1.
-
-   Duty cycle is calculated by ON / Period.
-*/
-static UNUSED int get_pwm(struct PwmControl *f) {
-  volatile uint32_t* reg = mmap_addr + f->offset_data;
-  uint8_t offset = f->channel ? 8 : 6;
-  uint32_t val = reg[offset];
-  return ((uint64_t)val * 100) / PWM_CYCLE_PERIOD;
-}
-
-// Set the given PWM (pulse width modulator) to the given percent duty cycle.
-static void set_pwm(struct PwmControl *f, int percent) {
-  volatile uint32_t* reg;
-  uint32_t mask0, val0, mask1, val1, on;
-
-  if (percent < 0) percent = 0;
-  if (percent > 100) percent = 100;
-  if (f->old_percent == percent) return;
-  f->old_percent = percent;
-
-  reg = mmap_addr + f->offset_data;
-  if (f->channel == 0) {
-    mask0 = 0xf0;       // preserve other channel
-    val0 = 0x01;        // open-drain|start
-    if (f->open_drain)
-      val0 |= 0x08;
-    mask1 = 0x10;       // preserve
-    val1 = 0x01;        // constant-freq
-    on = 6;
-  } else {
-    mask0 = 0x0f;       // see above
-    val0 = 0x10;
-    if (f->open_drain)
-      val0 |= 0x80;
-    mask1 = 0x01;
-    val1 = 0x10;
-    on = 8;
-  }
-  reg[0] = (reg[0] & mask0) | val0;
-  reg[1] = (reg[1] & mask1) | val1;
-  reg[on] = (PWM_CYCLE_PERIOD * percent)/100;         // 0x63 is what old code used
-  reg[on+1] = PWM_CYCLE_PERIOD;
-}
-
-// Get the CPU temperature.  I think it's in Celsius.
-static double get_avs_temperature_74xx(struct Temp* t) {
-  volatile uint32_t* reg;
-  uint32_t value, valid, raw_data;
-
-  reg = mmap_addr + t->offset_data;
-  value = *reg;
-  // see 7425-PR500-RDS.pdf
-  valid = (value & 0x00000400) >> 10;
-  raw_data = value & 0x000003ff;
-  if (!valid) return -1.0;
-  return (418000 - (556 * raw_data)) / 1000.0;
-}
-
-static double get_avs_temperature_7252(struct Temp* t) {
-  volatile uint32_t* reg;
-  uint32_t value, valid, raw_data;
-
-  reg = mmap_addr + t->offset_data;
-  value = *reg;
-  valid = (value & 0x00000400) >> 10;
-  raw_data = value & 0x000003ff;
-  if (!valid) return -1.0;
-  return 410.04 - (0.48705 * raw_data);
-}
-
-static double get_avs_voltage_74xx(struct Voltage* v) {
-  volatile uint32_t* reg;
-  uint32_t value, valid, raw_data;
-
-  reg = mmap_addr + v->offset_data;
-  value = *reg;
-  // see 7425-PR500-RDS.pdf
-  valid = (value & 0x00000400) >> 10;
-  raw_data = value & 0x000003ff;
-  if (!valid) return -1.0;
-  return ((990 * raw_data * 8) / (7*1024)) / 1000.0;
-}
-
-static double get_avs_voltage_7252(struct Voltage* v) {
-  volatile uint32_t* reg;
-  uint32_t value, valid, raw_data;
-
-  reg = mmap_addr + v->offset_data;
-  value = *reg;
-  valid = (value & 0x00000400) >> 10;
-  raw_data = value & 0x000003ff;
-  if (!valid) return -1.0;
-  return ((880.0/1024.0)/(0.7)*raw_data) / 1000;
-}
-
-// Write the given GPIO pin.
-static void set_gpio(struct Gpio *g, int level) {
-  volatile uint32_t* reg;
-  uint32_t value;
-
-  if (g->old_val == level) {
-    // If this is the same value as last time, don't do anything, for two
-    // reasons:
-    //   1) If you set the gpio too often, it seems to stay low (the led
-    //      stays off).
-    //   2) If some process other than us is twiddling a led, this way we
-    //      won't interfere with it.
-    return;
-  }
-  g->old_val = level;
-
-  reg = mmap_addr + g->offset_data;
-  value = *reg;
-  value &= ~g->mask;
-  value |= (level ? g->on_value : g->off_value) << g->shift;
-  *reg = value;
-}
-
-
-// Read the given GPIO pin
-static int get_gpio(struct Gpio *g) {
-  volatile uint32_t* reg;
-  uint32_t value;
-
-  reg = mmap_addr + g->offset_data;
-  value = (*reg & g->mask) >> g->shift;
-  return (value == g->on_value);
-}
-
-
-// initialize GPIO to input or output
-static void set_direction(struct Gpio *g)
-{
-  volatile uint32_t* reg;
-  uint32_t value;
-
-  if (!g->is_present || g->offset_direction == 0)
-    return;
-
-  reg = mmap_addr + g->offset_direction;
-  value = *reg;
-  value &= ~g->mask;
-  value |= g->direction_value << g->shift;
-  *reg = value;
-}
-
-// initialize pin to LED or GPIO etc
-static void set_pinmux(struct Gpio *g) {
-  volatile uint32_t* reg;
-  uint32_t value;
-
-  if (!g->is_present || g->pinmux_offset == 0)
-    return;
-
-  reg = mmap_addr + g->pinmux_offset;
-  value = *reg;
-  value &= ~g->pinmux_mask;
-  value |= g->pinmux_value;
-  *reg = value;
-}
-
 // Same as time(), but in monotonic clock milliseconds instead.
 static long long msec_now(void) {
   struct timespec ts;
@@ -600,61 +36,6 @@
   return ((long long)ts.tv_sec) * 1000 + (ts.tv_nsec / 1000000);
 }
 
-static void platform_cleanup(void) {
-  if (mmap_addr != MAP_FAILED) {
-    if (munmap((void*) mmap_addr, mmap_size) < 0) {
-      perror("munmap");
-    }
-    mmap_addr = MAP_FAILED;
-    mmap_size = 0;
-  }
-  if (mmap_fd >= 0) {
-    close(mmap_fd);
-    mmap_fd = -1;
-  }
-}
-
-static void *mmap_(void* addr, size_t size, int prot, int flags, int fd,
-                   off_t offset) {
-#ifdef __ANDROID__
-  return mmap64(addr, size, prot, flags, fd, (off64_t)(uint64_t)(uint32_t)offset);
-#else
-  return mmap(addr, size, prot, flags, fd, offset);
-#endif
-}
-
-static int platform_init(struct platform_info* p) {
-  platform_cleanup();
-
-  mmap_fd = open(DEVMEM, O_RDWR);
-  if (mmap_fd < 0) {
-    perror(DEVMEM);
-    return -1;
-  }
-  mmap_size = p->mmap_size;
-  mmap_addr = mmap_(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
-                    mmap_fd, p->mmap_base);
-  if (mmap_addr == MAP_FAILED) {
-    perror("mmap");
-    platform_cleanup();
-    return -1;
-  }
-  return 0;
-}
-
-static struct platform_info *get_platform_info(const char *platform_name) {
-  int lim = sizeof(platforms) / sizeof(platforms[0]);
-  for (int i = 0; i < lim; ++i) {
-    struct platform_info *p = &platforms[i];
-    if (0 == strncmp(platform_name, p->name, strlen(p->name))) {
-      return p;
-    }
-  }
-  fprintf(stderr, "No support for platform %s", platform_name);
-  exit(1);
-}
-
-
 // read a file containing a single short string.
 // Returns a static buffer.  Be careful!
 static char *read_file(const char *filename) {
@@ -677,7 +58,6 @@
   return (platform->fan_control.is_present);
 }
 
-
 /*
  * We're polling at a very high frequency, which is a pain.  This would be
  * slightly less gross inside the kernel (for less context switching and
@@ -796,109 +176,6 @@
   set_pwm(&platform->leds.led_brightness, level);
 }
 
-/* set LED/Keypad timings to control LED brightness */
-static void init_gfhd200(UNUSED struct platform_info* p) {
-  volatile uint32_t* reg;
-
-  reg = mmap_addr + 0x9034;     // LDK_CONTROL
-  *reg = 0x01;                  // reset
-  *reg = 0x18;                  // ver=1 inv_led=1
-
-  reg = mmap_addr + 0x9008;     // LDK_PRESCHI, LO (clock divisor)
-  reg[0] = 0x00;
-  reg[1] = 0x10;                // tick = clock / 0x0010, not sure what clock is
-
-  reg = mmap_addr + 0x9010;     // LDK_DUTYOFF, ON
-  reg[0] = 0x40;
-  reg[1] = 0xc0;                // 0x40 off ticks then 0xc0 on ticks == 75% brightness
-}
-
-/* set LED/Keypad timings to control LED brightness */
-static void init_gfhd254(UNUSED struct platform_info* p) {
-  volatile uint32_t* reg;
-
-  // The following comment explains how the LED controller works on <= EVT3.
-  //  For EVT4+, the LED controller was changed to control via PWM. We currently
-  //  configure both. The EVT3 specific code can be removed at a later date.
-  //
-  // The led display controller works like this:
-  //  - there are 16 gpios (we connect our leds to 2 of these)
-  //  - the controller steps through digit1-4 and then status
-  //  - bit0 in a register maps to a particular gpio
-  //     when digit1 is being displayed the controller uses digit1_bit[15:0] to
-  //     drive the gpios.  When digit 2 is displayed digit2[15:0] and so forth.
-  //  - duty_on controls how many clocks a digit is displayed
-  //  - duty_off controls number of clocks of all off time when switching
-  //    between digits
-  //
-  //  To get 100% brightness you set all of digit1-4 and status to 1 for the led
-  //  you are drivng, and set duty_off to 0.
-  //
-  //  Here we also invert the values, so a 1 means off, and 0 means on, this is
-  //  done because for unknown reasons the time between status and digit1 is on,
-  //  so we can't get the brightness to 0 unless we invert.
-  //
-  //  For simplicity we enable only one of the digits because the leds are
-  //  already insanely bright, and then to disable an led we simply toggle the
-  //  bit in that one digit register.
-  //
-  //  The red led is attached to bit 13 and blue led is attached to bit 12.
-  reg = mmap_addr + 0x17034;     // LDK_CONTROL
-  *reg = 0x01;                   // reset
-  *reg = 0x18;                   // ver=1
-
-  reg = mmap_addr + 0x17018;
-  reg[0] = 0xffff;               // LDK_DIGIT2
-  reg[1] = 0xcfff;               // LDK_DIGIT1
-  reg[2] = 0xffff;               // LDK_DIGIT4
-  reg[3] = 0xffff;               // LDK_DIGIT3
-  reg[5] = 0xffff;               // LDK_STATUS
-
-  reg = mmap_addr + 0x17008;     // LDK_PRESCHI, LO (clock divisor)
-  reg[0] = 0x00;
-  reg[1] = 0x10;                 // tick = clock / 0x0010, not sure what clock is
-
-  reg = mmap_addr + 0x17010;     // LDK_DUTYOFF, ON
-  reg[0] = 0x40;
-  reg[1] = 0xc0;                 // 0x40 off ticks then 0xc0 on ticks to dim a bit more.
-
-  // The fan is connected to PWM3, the register PWM3_CWORD_LSB is set to 1,
-  // this is the frequency of the PWM, the other pwm register control
-  // the duty cycle.
-  reg = mmap_addr + 0x9014;       // PWM3_CWORD_LSB
-  reg[0] = 1;
-
-  // LEDs are connected to PWM2. Setting CWORD_LSB to 0xf to control
-  // the output freq of the var rate clock.
-  reg = mmap_addr + 0x900c;
-  reg[0] = 0xf;
-
-  // Default the LED brightness to 50.
-  set_led_brightness(50);
-}
-
-static void init_platform(struct platform_info* p) {
-  if (p->init) {
-    (*p->init)(p);
-  }
-}
-
-static void initialize_gpios(void) {
-  init_platform(platform);
-
-  set_pinmux(&platform->leds.led_red);
-  set_pinmux(&platform->leds.led_blue);
-  set_pinmux(&platform->leds.led_activity);
-  set_pinmux(&platform->leds.led_standby);
-
-  set_direction(&platform->leds.led_red);
-  set_direction(&platform->leds.led_blue);
-  set_direction(&platform->leds.led_activity);
-  set_direction(&platform->leds.led_standby);
-  set_direction(&platform->reset_button);
-  set_direction(&platform->fan_tick);
-}
-
 /* standard API follows */
 
 PinHandle PinCreate(void) {
@@ -907,13 +184,14 @@
     perror("calloc(PinHandle)");
     return NULL;
   }
+
   platform = get_platform_info(read_file("/etc/platform"));
   if (platform_init(platform) < 0) {
     fprintf(stderr, "platform_init failed\n");
     PinDestroy(handle);
     return NULL;
   }
-  initialize_gpios();
+
   return handle;
 }
 
diff --git a/logupload/client/debian/init b/logupload/client/debian/init
index b349b5e..bbf7cad 100755
--- a/logupload/client/debian/init
+++ b/logupload/client/debian/init
@@ -36,10 +36,11 @@
     # Our hostnames are something like hostname.cluster.whatever.com.
     # We want the hostname.cluster part to be part of the certname, but
     # sadly the name field in our certs isn't super happy about that, so
-    # let's use _ instead of dot, where relevant.
+    # let's use _ instead of dot and strip hyphens, where relevant.
     hostname -f |
       sed -e 's/\([^.]*\.[^.]*\).*/\1/' \
-          -e 's/\./_/g' |
+          -e 's/\./_/g' \
+          -e 's/-//g' |
       atomic_stdin /tmp/serial
     cd /
     upload-logs-loop </dev/null &
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/spectralanalyzer/Makefile b/spectralanalyzer/Makefile
index 047dcb3..21531d6 100644
--- a/spectralanalyzer/Makefile
+++ b/spectralanalyzer/Makefile
@@ -7,6 +7,9 @@
 BINDIR=$(DESTDIR)/bin
 
 CFLAGS += -Wall -Werror $(EXTRACFLAGS)
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfrg240)
+CFLAGS += -Wno-error=format
+endif
 LDFLAGS += -lm -lrt $(EXTRALDFLAGS)
 
 all: spectral