Merge "Strip hyphens from hostname when generating /tmp/serial."
diff --git a/Makefile b/Makefile
index 1852f66..82a7d8b 100644
--- a/Makefile
+++ b/Makefile
@@ -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_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
@@ -122,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 6c19ab8..b058618 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -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/conman/connection_manager.py b/conman/connection_manager.py
index 374d495..8307efd 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)
@@ -640,7 +671,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."""
@@ -674,7 +705,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)
@@ -688,6 +719,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)
@@ -721,7 +756,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
@@ -776,20 +811,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
@@ -797,7 +831,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
@@ -814,6 +849,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)
@@ -827,7 +867,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)
@@ -887,6 +927,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/diags/Makefile b/diags/Makefile
index b0fcdb9..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 chameleon/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/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