Merge "/bin/wifi:  Make --band work for 'wifi setclient'."
diff --git a/cmds/Makefile b/cmds/Makefile
index 14daeff..3d53395 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -118,6 +118,7 @@
 	for n in $(SCRIPT_TARGETS); do \
 		test ! -f $$n.$(BR2_TARGET_GENERIC_PLATFORM_NAME) || \
 			cp -f $$n.$(BR2_TARGET_GENERIC_PLATFORM_NAME) $(BINDIR)/$$n; \
+		test ! -f $$n || cp -f $$n $(BINDIR)/$$n; \
 	done
 
 install-libs:
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index 6ccdaa2..a312b2c 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -19,11 +19,15 @@
 import pyinotify
 
 import cycler
+import experiment
 import interface
 import iw
 import status
 
 
+experiment.register('WifiNo2GClient')
+
+
 class FileChangeHandler(pyinotify.ProcessEvent):
   """Connects pyinotify events to ConnectionManager."""
 
@@ -95,7 +99,9 @@
   def client_up(self):
     wpa_status = self.wifi.wpa_status()
     return (wpa_status.get('wpa_state') == 'COMPLETED'
-            and wpa_status.get('ssid') == self.ssid)
+            # NONE indicates we're on a provisioning network; anything else
+            # suggests we're already on the WLAN.
+            and wpa_status.get('key_mgmt') != 'NONE')
 
   def start_access_point(self):
     """Start an access point."""
@@ -133,11 +139,24 @@
 
   def start_client(self):
     """Join the WLAN as a client."""
+    if experiment.enabled('WifiNo2GClient') and self.band == '2.4':
+      logging.debug('WifiNo2GClient enabled; not starting 2.4 GHz client.')
+      return
+
     up = self.client_up
     if up:
       logging.debug('Wifi client already started on %s GHz', self.band)
       return
 
+    if self._actually_start_client():
+      self._post_start_client()
+
+  def _actually_start_client(self):
+    """Actually run wifi setclient.
+
+    Returns:
+      Whether the command succeeded.
+    """
     command = self.WIFI_SETCLIENT + ['--ssid', self.ssid, '--band', self.band]
     env = dict(os.environ)
     if self.passphrase:
@@ -147,8 +166,11 @@
       subprocess.check_output(command, stderr=subprocess.STDOUT, env=env)
     except subprocess.CalledProcessError as e:
       logging.error('Failed to start wifi client: %s', e.output)
-      return
+      return False
 
+    return True
+
+  def _post_start_client(self):
     self._status.connected_to_wlan = True
     logging.info('Started wifi client on %s GHz', self.band)
     self.wifi.attach_wpa_control(self._wpa_control_interface)
@@ -192,6 +214,7 @@
   IP_LINK = ['ip', 'link']
   IFPLUGD_ACTION = ['/etc/ifplugd/ifplugd.action']
   BINWIFI = ['wifi']
+  UPLOAD_LOGS_AND_WAIT = ['timeout', '60', 'upload-logs-and-wait']
 
   def __init__(self,
                bridge_interface='br0',
@@ -216,6 +239,7 @@
     self._acs_update_wait_s = acs_update_wait_s
     self._bssid_cycle_length_s = bssid_cycle_length_s
     self._wlan_configuration = {}
+    self._try_to_upload_logs = False
 
     # Make sure all necessary directories exist.
     for directory in (self._tmp_dir, self._config_dir, self._moca_tmp_dir,
@@ -465,6 +489,10 @@
         self._status.connected_to_wlan = False
         if self.acs():
           logging.debug('Connected to ACS on %s', wifi.name)
+          if self._try_to_upload_logs:
+            self._try_upload_logs()
+            self._try_to_upload_logs = False
+
           wifi.last_successful_bss_info = getattr(wifi,
                                                   'last_attempted_bss_info',
                                                   None)
@@ -693,6 +721,7 @@
         wifi.waiting_for_acs_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
@@ -788,6 +817,11 @@
     subprocess.check_output(self.BINWIFI + list(command),
                             stderr=subprocess.STDOUT)
 
+  def _try_upload_logs(self):
+    logging.debug('Attempting to upload logs')
+    if subprocess.call(self.UPLOAD_LOGS_AND_WAIT) != 0:
+      logging.error('Failed to upload logs')
+
 
 def _wifi_show():
   try:
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 954e2e3..db8855d 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -9,6 +9,7 @@
 import time
 
 import connection_manager
+import experiment_testutils
 import interface_test
 import iw
 import status
@@ -180,22 +181,25 @@
   WIFI_SETCLIENT = ['echo', 'setclient']
   WIFI_STOPCLIENT = ['echo', 'stopclient']
 
-  def start_client(self):
-    client_was_up = self.client_up
-    was_attached = self.wifi.attached()
+  def _actually_start_client(self):
+    self.client_was_up = self.client_up
+    self.was_attached = self.wifi.attached()
+    self.wifi._secure_testonly = True
     # Do this before calling the super method so that the attach call at the end
     # succeeds.
-    if not client_was_up and not was_attached:
+    if not self.client_was_up and not self.was_attached:
       self.wifi._initial_ssid_testonly = self.ssid
       self.wifi.start_wpa_supplicant_testonly(self._wpa_control_interface)
 
-    super(WLANConfiguration, self).start_client()
+    return True
 
-    if not client_was_up:
+  def _post_start_client(self):
+    if not self.client_was_up:
       self.wifi.set_connection_check_result('succeed')
 
-      if was_attached:
+      if self.was_attached:
         self.wifi._wpa_control.ssid_testonly = self.ssid
+        self.wifi._wpa_control.secure_testonly = True
         self.wifi.add_connected_event()
 
       # Normally, wpa_supplicant would bring up the client interface, which
@@ -263,6 +267,7 @@
   IFUP = ['echo', 'ifup']
   IFPLUGD_ACTION = ['echo', 'ifplugd.action']
   BINWIFI = ['echo', 'wifi']
+  UPLOAD_LOGS_AND_WAIT = ['echo', 'upload-logs-and-wait']
 
   def __init__(self, *args, **kwargs):
     self._binwifi_commands = []
@@ -295,12 +300,14 @@
     self.can_connect_to_s3 = True
     # Will s2 fail rather than providing ACS access?
     self.s2_fail = False
+    self.log_upload_count = 0
 
   def create_wifi_interfaces(self):
     super(ConnectionManager, self).create_wifi_interfaces()
     for wifi in self.wifi_interfaces_already_up:
       # pylint: disable=protected-access
       self.interface_by_name(wifi)._initial_ssid_testonly = 'my ssid'
+      self.interface_by_name(wifi)._secure_testonly = True
 
   @property
   def IP_LINK(self):
@@ -323,10 +330,12 @@
     def connect(connection_check_result):
       # pylint: disable=protected-access
       if wifi.attached():
-        wifi._wpa_control._ssid_testonly = bss_info.ssid
+        wifi._wpa_control.ssid_testonly = bss_info.ssid
+        wifi._wpa_control.secure_testonly = False
         wifi.add_connected_event()
       else:
         wifi._initial_ssid_testonly = bss_info.ssid
+        wifi._secure_testonly = False
         wifi.start_wpa_supplicant_testonly(self._wpa_control_interface)
       wifi.set_connection_check_result(connection_check_result)
       self.ifplugd_action(wifi.name, True)
@@ -401,6 +410,10 @@
 
     return self._wlan_configuration[band].client_up
 
+  def _try_upload_logs(self):
+    self.log_upload_count += 1
+    return super(ConnectionManager, self)._try_upload_logs()
+
   # Test methods
 
   def delete_wlan_config(self, band):
@@ -653,6 +666,7 @@
   c.interface_with_scan_results = c.wifi_for_band(band).name
   # Wait for a scan, plus 3 cycles, so that s2 will have been tried.
   c.run_until_scan(band)
+  wvtest.WVPASSEQ(c.log_upload_count, 0)
   for _ in range(3):
     c.run_once()
     wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_OPEN]))
@@ -667,6 +681,7 @@
   wvtest.WVPASS(c.internet())
   wvtest.WVFAIL(c.client_up(band))
   wvtest.WVPASS(c.wifi_for_band(band).current_route())
+  wvtest.WVPASSEQ(c.log_upload_count, 1)
   # Disable scan results again.
   c.interface_with_scan_results = None
 
@@ -706,6 +721,9 @@
   wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_OPEN]))
   wvtest.WVPASSEQ(c.last_provisioning_attempt.ssid, 's3')
   wvtest.WVPASSEQ(c.last_provisioning_attempt.bssid, 'ff:ee:dd:cc:bb:aa')
+  # The log upload happens on the next main loop after joining s3.
+  c.run_once()
+  wvtest.WVPASSEQ(c.log_upload_count, 2)
 
   # Now, recreate the same WLAN configuration, which should be connected to.
   # Also, test that atomic writes/renames work.
@@ -773,6 +791,8 @@
   wvtest.WVPASS(c.acs())
   # Make sure we didn't scan on `band`.
   wvtest.WVPASSEQ(scan_count_for_band, c.wifi_for_band(band).wifi_scan_counter)
+  c.run_once()
+  wvtest.WVPASSEQ(c.log_upload_count, 3)
 
   # Now re-create the WLAN config, connect to the WLAN, and make sure that s3 is
   # unset as last_successful_bss_info, since it is no longer available.
@@ -815,6 +835,8 @@
     c.run_once()
   s2_bss = iw.BssInfo('01:23:45:67:89:ab', 's2')
   wvtest.WVPASSEQ(c.wifi_for_band(band).last_successful_bss_info, s2_bss)
+  c.run_once()
+  wvtest.WVPASSEQ(c.log_upload_count, 4)
 
   c.s2_fail = True
   c.write_wlan_config(band, ssid, psk)
@@ -967,6 +989,8 @@
   wvtest.WVFAIL(c.bridge.current_route())
   wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
   wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  c.run_once()
+  wvtest.WVPASSEQ(c.log_upload_count, 1)
 
 
 @wvtest.wvtest
@@ -1059,6 +1083,8 @@
   wvtest.WVFAIL(c.bridge.current_route())
   wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
   wvtest.WVPASS(c.wifi_for_band('5').current_route())
+  c.run_once()
+  wvtest.WVPASSEQ(c.log_upload_count, 1)
 
 
 @wvtest.wvtest
@@ -1144,5 +1170,29 @@
                 in c._binwifi_commands)
 
 
+@wvtest.wvtest
+@connection_manager_test(WIFI_SHOW_OUTPUT_MARVELL8897)
+def connection_manager_conman_no_2g_wlan(c):
+  unused_raii = experiment_testutils.MakeExperimentDirs()
+
+  # First, establish that we connect on 2.4 without the experiment, to make sure
+  # this test doesn't spuriously pass.
+  c.write_wlan_config('2.4', 'my ssid', 'my psk')
+  c.run_once()
+  wvtest.WVPASS(c.client_up('2.4'))
+
+  # Now, force a disconnect by deleting the config.
+  c.delete_wlan_config('2.4')
+  c.run_once()
+  wvtest.WVFAIL(c.client_up('2.4'))
+
+  # Now enable the experiment, recreate the config, and make sure we don't
+  # connect.
+  experiment_testutils.enable('WifiNo2GClient')
+  c.write_wlan_config('2.4', 'my ssid', 'my psk')
+  c.run_once()
+  wvtest.WVFAIL(c.client_up('2.4'))
+
+
 if __name__ == '__main__':
   wvtest.wvtest_main()
diff --git a/conman/interface.py b/conman/interface.py
index 7e37306..1b77b18 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -474,6 +474,7 @@
     self._client_mode = False
     self._ssid = None
     self._status = None
+    self._security = None
 
     self._events = []
 
@@ -500,6 +501,7 @@
       client_mode = self._qcsapi('get_mode', 'wifi0') == 'Station'
       ssid = self._qcsapi('get_ssid', 'wifi0')
       status = self._qcsapi('get_status', 'wifi0')
+      security = self._qcsapi('ssid_get_authentication_mode', 'wifi0', ssid)
     except subprocess.CalledProcessError:
       # If QCSAPI failed, skip update.
       return
@@ -529,6 +531,7 @@
     self._client_mode = client_mode
     self._ssid = ssid
     self._status = status
+    self._security = security
 
   def recv(self):
     return self._events.pop(0)
@@ -544,7 +547,8 @@
     if not self._client_mode or not self._ssid:
       return ''
 
-    return 'wpa_state=COMPLETED\nssid=%s' % self._ssid
+    return ('wpa_state=COMPLETED\nssid=%s\nkey_mgmt=%s' %
+            (self._ssid, self._security or 'NONE'))
 
 
 class FrenzyWifi(Wifi):
diff --git a/conman/interface_test.py b/conman/interface_test.py
index 4c7d52b..5b9d431 100755
--- a/conman/interface_test.py
+++ b/conman/interface_test.py
@@ -78,6 +78,7 @@
     self.attached = False
     self.connected = False
     self.ssid_testonly = None
+    self.secure_testonly = False
     self.request_status_fails = False
 
   def pending(self):
@@ -96,6 +97,7 @@
   def detach(self):
     self.attached = False
     self.ssid_testonly = None
+    self.secure_testonly = False
     self.connected = False
     self.check_socket_exists('wpactrl_detach failed')
 
@@ -103,8 +105,12 @@
     if request_type == 'STATUS':
       if self.request_status_fails:
         raise wpactrl.error('test error')
-      return ('foo\nwpa_state=COMPLETED\nssid=%s\nbar' % self.ssid_testonly
-              if self.connected else 'foo')
+      if self.connected:
+        return ('foo\nwpa_state=COMPLETED\nssid=%s\nkey_mgmt=%s\nbar' %
+                (self.ssid_testonly,
+                 'WPA2-PSK' if self.secure_testonly else 'NONE'))
+      else:
+        return 'wpa_state=SCANNING\naddress=12:34:56:78:90:ab'
     else:
       raise ValueError('Invalid request_type %s' % request_type)
 
@@ -142,6 +148,7 @@
   def __init__(self, *args, **kwargs):
     super(Wifi, self).__init__(*args, **kwargs)
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
 
   def attach_wpa_control(self, path):
     if self._initial_ssid_testonly and self._wpa_control:
@@ -153,6 +160,7 @@
     if self._initial_ssid_testonly:
       result.connected = True
       result.ssid_testonly = self._initial_ssid_testonly
+      result.secure_testonly = self._secure_testonly
     return result
 
   def add_connected_event(self):
@@ -161,16 +169,19 @@
 
   def add_disconnected_event(self):
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
     if self.attached():
       self._wpa_control.add_disconnected_event()
 
   def add_terminating_event(self):
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
     if self.attached():
       self._wpa_control.add_terminating_event()
 
   def detach_wpa_control(self):
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
     super(Wifi, self).detach_wpa_control()
 
   def start_wpa_supplicant_testonly(self, path):
@@ -191,6 +202,7 @@
   def __init__(self, *args, **kwargs):
     super(FrenzyWPACtrl, self).__init__(*args, **kwargs)
     self.ssid_testonly = None
+    self.secure_testonly = False
     self.request_status_fails = False
 
   def _qcsapi(self, *command):
@@ -199,15 +211,21 @@
   def add_connected_event(self):
     self.fake_qcsapi['get_mode'] = 'Station'
     self.fake_qcsapi['get_ssid'] = self.ssid_testonly
+    security = 'PSKAuthentication' if self.secure_testonly else 'NONE'
+    self.fake_qcsapi['ssid_get_authentication_mode'] = security
 
   def add_disconnected_event(self):
     self.ssid_testonly = None
+    self.secure_testonly = False
     self.fake_qcsapi['get_ssid'] = None
+    self.fake_qcsapi['ssid_get_authentication_mode'] = 'NONE'
 
   def add_terminating_event(self):
     self.ssid_testonly = None
+    self.secure_testonly = False
     self.fake_qcsapi['get_ssid'] = None
     self.fake_qcsapi['get_mode'] = 'AP'
+    self.fake_qcsapi['ssid_get_authentication_mode'] = 'NONE'
 
   def detach(self):
     self.add_terminating_event()
@@ -226,12 +244,14 @@
   def __init__(self, *args, **kwargs):
     super(FrenzyWifi, self).__init__(*args, **kwargs)
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
     self.fake_qcsapi = {}
 
   def attach_wpa_control(self, *args, **kwargs):
     super(FrenzyWifi, self).attach_wpa_control(*args, **kwargs)
     if self._wpa_control:
       self._wpa_control.ssid_testonly = self._initial_ssid_testonly
+      self._wpa_control.secure_testonly = self._secure_testonly
       if self._initial_ssid_testonly:
         self._wpa_control.add_connected_event()
 
@@ -241,6 +261,7 @@
     if self._initial_ssid_testonly:
       result.fake_qcsapi['get_mode'] = 'Station'
       result.ssid_testonly = self._initial_ssid_testonly
+      result.secure_testonly = self._secure_testonly
       result.add_connected_event()
     return result
 
@@ -250,16 +271,19 @@
 
   def add_disconnected_event(self):
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
     if self.attached():
       self._wpa_control.add_disconnected_event()
 
   def add_terminating_event(self):
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
     if self.attached():
       self._wpa_control.add_terminating_event()
 
   def detach_wpa_control(self):
     self._initial_ssid_testonly = None
+    self._secure_testonly = False
     super(FrenzyWifi, self).detach_wpa_control()
 
   def start_wpa_supplicant_testonly(self, unused_path):
diff --git a/gpio-mailbox/Makefile b/gpio-mailbox/Makefile
index 1dc26a4..eaaee20 100644
--- a/gpio-mailbox/Makefile
+++ b/gpio-mailbox/Makefile
@@ -29,8 +29,6 @@
   CFLAGS += -DGFIBER_LT
 else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gflt200)
   CFLAGS += -DGFIBER_LT
-else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gflt300)
-  CFLAGS += -DGFIBER_LT
 else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfmn100)
   CFLAGS += -DWINDCHARGER
 else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfch100)
diff --git a/gpio-mailbox/TEST.gpio-mailbox b/gpio-mailbox/TEST.gpio-mailbox
index 3cd5dee..edc8f60 100644
--- a/gpio-mailbox/TEST.gpio-mailbox
+++ b/gpio-mailbox/TEST.gpio-mailbox
@@ -1,4 +1,4 @@
-rm -rf /tmp/gpio /tmp/led
+rm -rf /tmp/gpio /tmp/leds
 
 mkdir -p /tmp/gpio
 echo x5 0 1 0 2 0 0x0f > /tmp/gpio/leds
diff --git a/gpio-mailbox/broadcom.c b/gpio-mailbox/broadcom.c
index 575f197..a53e51e 100644
--- a/gpio-mailbox/broadcom.c
+++ b/gpio-mailbox/broadcom.c
@@ -614,6 +614,15 @@
   }
 }
 
+static void *mmap_(void* addr, size_t size, int prot, int flags, int fd,
+                   off_t offset) {
+#ifdef __ANDROID__
+  return mmap64(addr, size, prot, flags, fd, (off64_t)(uint64_t)(uint32_t)offset);
+#else
+  return mmap(addr, size, prot, flags, fd, offset);
+#endif
+}
+
 static int platform_init(struct platform_info* p) {
   platform_cleanup();
 
@@ -623,8 +632,8 @@
     return -1;
   }
   mmap_size = p->mmap_size;
-  mmap_addr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
-                   mmap_fd, p->mmap_base);
+  mmap_addr = mmap_(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
+                    mmap_fd, p->mmap_base);
   if (mmap_addr == MAP_FAILED) {
     perror("mmap");
     platform_cleanup();
diff --git a/gpio-mailbox/gfch100.c b/gpio-mailbox/gfch100.c
index 73d97bd..2cf1f23 100644
--- a/gpio-mailbox/gfch100.c
+++ b/gpio-mailbox/gfch100.c
@@ -18,15 +18,13 @@
 #define GPIO_OUT                "out"
 
 /* GPIO_ACTIVITY LED is blue on Chimera. */
-#define GPIO_ACTIVITY           "30"
-#define GPIO_RED                "31"
+#define GPIO_ACTIVITY		"/led_activity"
+#define GPIO_RED		"/led_red"
 
-#define GPIO_BASE_DIR           "/sys/class/gpio"
-#define GPIO_EXPORT             GPIO_BASE_DIR "/export"
+#define GPIO_BASE_DIR		"/dev/gpio"
 
-#define GPIO_DIR(n)             GPIO_BASE_DIR "/gpio" n
+#define GPIO_DIR(n)             GPIO_BASE_DIR n
 
-#define GPIO_DIRECTION(dir)     dir "/direction"
 #define GPIO_VALUE(dir)         dir "/value"
 
 struct PinHandle_s {
@@ -38,9 +36,7 @@
 };
 
 struct sysgpio {
-  const char* export_value;
   const char* value_path;
-  const char* direction_path;
 };
 
 struct platform_info {
@@ -57,13 +53,9 @@
       .value_path = "/sys/class/hwmon/hwmon0/temp1_input",
     },
     .led_red = {
-      .export_value = GPIO_RED,
-      .direction_path = GPIO_DIRECTION(GPIO_DIR(GPIO_RED)),
       .value_path = GPIO_VALUE(GPIO_DIR(GPIO_RED)),
     },
     .led_activity = {
-      .export_value = GPIO_ACTIVITY,
-      .direction_path = GPIO_DIRECTION(GPIO_DIR(GPIO_ACTIVITY)),
       .value_path = GPIO_VALUE(GPIO_DIR(GPIO_ACTIVITY)),
     },
   }
@@ -89,16 +81,6 @@
     perror("calloc(PinHandle)");
     return NULL;
   }
-
-  // initialize leds to match boot values
-  write_file_string(GPIO_EXPORT, GPIO_RED);
-  write_file_string(platform->led_red.direction_path, GPIO_OUT);
-  write_file_string(platform->led_red.value_path, GPIO_OFF);
-
-  write_file_string(GPIO_EXPORT, GPIO_ACTIVITY);
-  write_file_string(platform->led_activity.direction_path, GPIO_OUT);
-  write_file_string(platform->led_activity.value_path, GPIO_ON);
-
   return handle;
 }
 
diff --git a/wifi/wifi.py b/wifi/wifi.py
index b0ef7f9..8797633 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -542,9 +542,20 @@
       ('hostapd_cli', '-i', interface, 'status'), no_stdout=True) == 0
 
 
-def _is_wpa_supplicant_running(interface):
+def _wpa_cli(program, interface, command):
   return utils.subprocess_quiet(
-      ('wpa_cli', '-i', interface, 'status'), no_stdout=True) == 0
+      (program, '-i', interface, command), no_stdout=True) == 0
+
+
+def _is_wpa_supplicant_running(interface):
+  return _wpa_cli('wpa_cli', interface, 'status')
+
+
+def _reconfigure_wpa_supplicant(interface):
+  if not _wpa_cli('wpa_cli', interface, 'reconfigure'):
+    return False
+
+  return _wait_for_wpa_supplicant_to_associate(interface)
 
 
 def _hostapd_debug_options():
@@ -653,6 +664,38 @@
         return None
 
 
+def _wait_for_wpa_supplicant_to_associate(interface):
+  """Wait for wpa_supplicant to associate.
+
+  If it does not associate within a certain period of time, terminate it.
+
+  Args:
+    interface: The interface on which wpa_supplicant is running.
+
+  Raises:
+    BinWifiException: if wpa_supplicant fails to associate and
+    also cannot be stopped to cleanup after the failure.
+
+  Returns:
+    Whether wpa_supplicant associated within the timeout.
+  """
+  utils.log('Waiting for wpa_supplicant to connect')
+  for _ in xrange(100):
+    if _get_wpa_state(interface) == 'COMPLETED':
+      utils.log('ok')
+      return True
+    sys.stderr.write('.')
+    time.sleep(0.1)
+
+  utils.log('wpa_supplicant did not connect.')
+  if not _stop_wpa_supplicant(interface):
+    raise utils.BinWifiException(
+        "Couldn't stop wpa_supplicant after it failed to connect.  "
+        "Consider killing it manually.")
+
+  return False
+
+
 def _start_wpa_supplicant(interface, config_filename):
   """Starts a babysat wpa_supplicant.
 
@@ -704,21 +747,7 @@
   else:
     return False
 
-  utils.log('Waiting for wpa_supplicant to connect')
-  for _ in xrange(100):
-    if _get_wpa_state(interface) == 'COMPLETED':
-      utils.log('ok')
-      return True
-    sys.stderr.write('.')
-    time.sleep(0.1)
-
-  utils.log('wpa_supplicant did not connect.')
-  if not _stop_wpa_supplicant(interface):
-    raise utils.BinWifiException(
-        "Couldn't stop wpa_supplicant after it failed to connect.  "
-        "Consider killing it manually.")
-
-  return False
+  return _wait_for_wpa_supplicant_to_associate(interface)
 
 
 def _maybe_restart_hostapd(interface, config, opt):
@@ -777,8 +806,7 @@
 def _restart_hostapd(band):
   """Restart hostapd from previous options.
 
-  Only used by _maybe_restart_wpa_supplicant, to restart hostapd after stopping
-  it.
+  Only used by _set_wpa_supplicant_config, to restart hostapd after stopping it.
 
   Args:
     band: The band on which to restart hostapd.
@@ -797,7 +825,7 @@
   _run(argv)
 
 
-def _maybe_restart_wpa_supplicant(interface, config, opt):
+def _set_wpa_supplicant_config(interface, config, opt):
   """Starts or restarts wpa_supplicant unless doing so would be a no-op.
 
   The no-op case (i.e. wpa_supplicant is already running with an equivalent
@@ -826,11 +854,12 @@
   except IOError:
     pass
 
-  if not _is_wpa_supplicant_running(interface):
+  already_running = _is_wpa_supplicant_running(interface)
+  if not already_running:
     utils.log('wpa_supplicant not running yet, starting.')
   elif current_config != config:
     # TODO(rofrankel): Consider using wpa_cli reconfigure here.
-    utils.log('wpa_supplicant config changed, restarting.')
+    utils.log('wpa_supplicant config changed, reconfiguring.')
   elif opt.force_restart:
     utils.log('Forced restart requested.')
     forced = True
@@ -838,12 +867,12 @@
     utils.log('wpa_supplicant-%s already configured and running', interface)
     return True
 
-  if not _stop_wpa_supplicant(interface):
-    raise utils.BinWifiException("Couldn't stop wpa_supplicant")
-
   if not forced:
     utils.atomic_write(tmp_config_filename, config)
 
+  # TODO(rofrankel): Consider removing all the restart hostapd stuff when
+  # b/30140131 is resolved.  hostapd seems to keep working without being
+  # restarted, at least on Camaro.
   restart_hostapd = False
   ap_interface = iw.find_interface_from_band(band, iw.INTERFACE_TYPE.ap,
                                              opt.interface_suffix)
@@ -852,13 +881,15 @@
     opt_without_persist = options.OptDict({})
     opt_without_persist.persist = False
     opt_without_persist.band = opt.band
-    # Code review: Will AP and client always have the same suffix?
     opt_without_persist.interface_suffix = opt.interface_suffix
     if not stop_ap_wifi(opt_without_persist):
       raise utils.BinWifiException(
           "Couldn't stop hostapd to start wpa_supplicant.")
 
-  if not _start_wpa_supplicant(interface, tmp_config_filename):
+  if already_running:
+    if not _reconfigure_wpa_supplicant(interface):
+      raise utils.BinWifiException('Failed to reconfigure wpa_supplicant.')
+  elif not _start_wpa_supplicant(interface, tmp_config_filename):
     raise utils.BinWifiException(
         'wpa_supplicant failed to start.  Look at wpa_supplicant logs for '
         'details.')
@@ -934,7 +965,7 @@
           ('ip', 'link', 'set', interface, 'address', mac_address))
 
   wpa_config = configs.generate_wpa_supplicant_config(opt.ssid, psk, opt)
-  if not _maybe_restart_wpa_supplicant(interface, wpa_config, opt):
+  if not _set_wpa_supplicant_config(interface, wpa_config, opt):
     return False
 
   return True