Patchwork [PATCHv2,0/3] hostap: Add AP DFS support

login
register
mail settings
Submitter Jouni Malinen
Date May 9, 2013, 5:34 p.m.
Message ID <20130509173431.GA25904@w1.fi>
Download mbox | patch
Permalink /patch/242801/
State Superseded
Headers show

Comments

Jouni Malinen - May 9, 2013, 5:34 p.m.
On Mon, Mar 25, 2013 at 06:29:37PM +0100, Simon Wunderlich wrote:
> Seems patch no 2 did not make it on the mailing list because it was
> too big (got "Message body is too big: 32009 bytes with a limit of 25 KB"
> as a reply).

That is usually a good sign of the patch benefiting from being split
into smaller parts.. I tried to review this and picked the parts I can
easily agree with and added those in smaller commits. The remaining
changes (with some cleanup made during the review) are below. I'm a bit
suspicious of this type of eloop use, so need to think about that more
before applying this.


 From a93bb0e8d67e727c9ae800931bb41c5382855dd4 Mon Sep 17 00:00:00 2001
 From: Simon Wunderlich <siwu@hrz.tu-chemnitz.de>
 Date: Thu, 9 May 2013 20:16:06 +0300
 Subject: [PATCH 1/2] hostapd: Add AP DFS feature

This patch adds support for AP DFS feature, which is now included in the
Linux wireless driver. A config option ieee80211h must be enabled to use
this feature. Events are added and handled. When a radar was detected
during Channel Availability Check (CAC) or during operation, a new
channel is selected. If the radar is detected during operation, the AP
is stopped and restarted on a new channel.

This patch is based on the original work by Boris Presman and
Victor Goldenshtein. Channel Switch Announcement support has been
removed and event handling as well as channel set handling was
changed, among various other changes.

Cc: Boris Presman <boris.presman@ti.com>
Cc: Victor Goldenshtein <victorg@ti.com>
Signed-hostap: Simon Wunderlich <siwu@hrz.tu-chemnitz.de>
---
 src/ap/ap_drv_ops.c    |   27 +++++++
 src/ap/ap_drv_ops.h    |    1 +
 src/ap/drv_callbacks.c |   77 +++++++++++++++++++
 src/ap/hostapd.c       |  193 +++++++++++++++++++++++++++++++++++++++++++++---
 src/ap/hostapd.h       |   16 ++++
 src/ap/hw_features.c   |   78 ++++++++++++++++---
 src/ap/hw_features.h   |    5 ++
 src/ap/ieee802_11.c    |   89 ++++++++++++++++++++++
 src/ap/ieee802_11.h    |    3 +
 9 files changed, 467 insertions(+), 22 deletions(-)
Simon Wunderlich - May 31, 2013, 12:31 p.m.
Hey Jouni,

On Thu, May 09, 2013 at 08:34:31PM +0300, Jouni Malinen wrote:
> On Mon, Mar 25, 2013 at 06:29:37PM +0100, Simon Wunderlich wrote:
> > Seems patch no 2 did not make it on the mailing list because it was
> > too big (got "Message body is too big: 32009 bytes with a limit of 25 KB"
> > as a reply).
> 
> That is usually a good sign of the patch benefiting from being split
> into smaller parts.. I tried to review this and picked the parts I can
> easily agree with and added those in smaller commits. The remaining
> changes (with some cleanup made during the review) are below. I'm a bit
> suspicious of this type of eloop use, so need to think about that more
> before applying this.

Thanks for reviewing and applying these smaller commits. Could you get
the chance to review the rest? As far as I could see, quite a few important
parts like the events and their handling are still missing ...

Thanks,
	Simon
Simon Wunderlich - July 4, 2013, 2:23 p.m.
Hey Jouni,

I'd like to bump here again, after roughly one more month of silence - are
you going to merge or reject the rest of the patchset? Leaving the DFS support
in this half-integrated state is probably not good.

Thanks,
	Simon


On Fri, May 31, 2013 at 02:31:05PM +0200, Simon Wunderlich wrote:
> Hey Jouni,
> 
> On Thu, May 09, 2013 at 08:34:31PM +0300, Jouni Malinen wrote:
> > On Mon, Mar 25, 2013 at 06:29:37PM +0100, Simon Wunderlich wrote:
> > > Seems patch no 2 did not make it on the mailing list because it was
> > > too big (got "Message body is too big: 32009 bytes with a limit of 25 KB"
> > > as a reply).
> > 
> > That is usually a good sign of the patch benefiting from being split
> > into smaller parts.. I tried to review this and picked the parts I can
> > easily agree with and added those in smaller commits. The remaining
> > changes (with some cleanup made during the review) are below. I'm a bit
> > suspicious of this type of eloop use, so need to think about that more
> > before applying this.
> 
> Thanks for reviewing and applying these smaller commits. Could you get
> the chance to review the rest? As far as I could see, quite a few important
> parts like the events and their handling are still missing ...
> 
> Thanks,
> 	Simon



> _______________________________________________
> HostAP mailing list
> HostAP@lists.shmoo.com
> http://lists.shmoo.com/mailman/listinfo/hostap
Simon Wunderlich - Aug. 5, 2013, 2:55 p.m.
Hey Jouni,

I'd like to bump after another month of silence - how can we proceed with this
patchset?

Thanks,
	Simon

On Thu, Jul 04, 2013 at 04:23:51PM +0200, Simon Wunderlich wrote:
> Hey Jouni,
> 
> I'd like to bump here again, after roughly one more month of silence - are
> you going to merge or reject the rest of the patchset? Leaving the DFS support
> in this half-integrated state is probably not good.
> 
> Thanks,
> 	Simon
> 
> 
> On Fri, May 31, 2013 at 02:31:05PM +0200, Simon Wunderlich wrote:
> > Hey Jouni,
> > 
> > On Thu, May 09, 2013 at 08:34:31PM +0300, Jouni Malinen wrote:
> > > On Mon, Mar 25, 2013 at 06:29:37PM +0100, Simon Wunderlich wrote:
> > > > Seems patch no 2 did not make it on the mailing list because it was
> > > > too big (got "Message body is too big: 32009 bytes with a limit of 25 KB"
> > > > as a reply).
> > > 
> > > That is usually a good sign of the patch benefiting from being split
> > > into smaller parts.. I tried to review this and picked the parts I can
> > > easily agree with and added those in smaller commits. The remaining
> > > changes (with some cleanup made during the review) are below. I'm a bit
> > > suspicious of this type of eloop use, so need to think about that more
> > > before applying this.
> > 
> > Thanks for reviewing and applying these smaller commits. Could you get
> > the chance to review the rest? As far as I could see, quite a few important
> > parts like the events and their handling are still missing ...
> > 
> > Thanks,
> > 	Simon
> 
> 
> 
> > _______________________________________________
> > HostAP mailing list
> > HostAP@lists.shmoo.com
> > http://lists.shmoo.com/mailman/listinfo/hostap
>
Jouni Malinen - Aug. 7, 2013, 9:13 a.m.
On Mon, Aug 05, 2013 at 04:55:43PM +0200, Simon Wunderlich wrote:
> I'd like to bump after another month of silence - how can we proceed with this
> patchset?

Sorry about the delay. This has been one of the victims of travel,
vacation, and too good weather.. I think the only real blocker here is
the unconventional use of eloop. I was trying to come up with a proposal
on direction on how to make that more acceptable, but have not found
time to do that, so let me such provide some points about the issues for
now.

There needs to be possibility of running CAC while the main event loop
has already started and is running. There can be multiple interfaces
being controlled through the same eloop and one of the interfaces doing
long operations cannot block other interfaces from operating or even the
same interface from being able to reply to control interface commands.
As such, I would like to get rid of all the concurrent eloop instances;
there should be no eloop_run() or eloop_terminate() call added in the
patch.

The part that I did not find time for yet was to go through each case
here and propose a better approach. In general, I'd expect these
functions to register a timeout and return without initializing the
interface. Actual interface start would happen from a callback once the
DFS related operations have been completed.

I have the pending changes in a branch that is based on top of the
current master branch and the current snapshot is here:
http://w1.fi/p/dfs/. Like I mentioned previously, 2/2 is fine, so it is
just the eloop design in 1/2 that needs to be address for these to be
accepted.

Patch

diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index 8205d13..942324a 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -699,3 +699,30 @@  int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
 					 hapd->own_addr, hapd->own_addr, data,
 					 len, 0);
 }
+
+
+int hostapd_start_dfs_cac(struct hostapd_data *hapd, int freq, int flags)
+{
+	if (!hapd->driver || !hapd->driver->start_dfs_cac)
+		return 0;
+
+	if (!(flags & HOSTAPD_CHAN_RADAR)) {
+		wpa_printf(MSG_ERROR, "Can't start DFS_CAC, the channel %u is "
+			   "not DFS channel", hapd->iconf->channel);
+		return -1;
+	}
+
+	if (!(hapd->iface->dfs_state & DFS_ENABLED)) {
+		wpa_printf(MSG_ERROR, "Can't start DFS CAC, DFS functionality "
+			   "is not enabled");
+		return -1;
+	}
+
+	if (hapd->iface->conf->secondary_channel) {
+		wpa_printf(MSG_ERROR, "Can't start DFS CAC, DFS functionality "
+			   "on HT40 is not supported");
+		return -1;
+	}
+
+	return hapd->driver->start_dfs_cac(hapd->drv_priv, freq);
+}
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index ceb7e68..ff4011e 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -101,6 +101,7 @@  int hostapd_sta_assoc(struct hostapd_data *hapd, const u8 *addr,
 		      int reassoc, u16 status, const u8 *ie, size_t len);
 int hostapd_add_tspec(struct hostapd_data *hapd, const u8 *addr,
 		      u8 *tspec_ie, size_t tspec_ielen);
+int hostapd_start_dfs_cac(struct hostapd_data *hapd, int freq, int flags);
 
 
 #include "drivers/driver.h"
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 07fd11d..2b8d435 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -714,6 +714,56 @@  static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
 }
 
 
+static void hostapd_event_dfs_radar_detected(struct hostapd_data *hapd,
+					     struct dfs_event *radar)
+{
+	wpa_printf(MSG_DEBUG, "DFS radar detected on %d MHz", radar->freq);
+
+	if (!hapd->iconf->ieee80211h)
+		return;
+
+	/* mark radar frequency as invalid */
+	ieee802_11_set_dfs_state(hapd, radar->freq,
+				 HOSTAPD_CHAN_DFS_UNAVAILABLE);
+
+	/* other frequency, just mark it and return. */
+	if (hapd->iface->freq != radar->freq)
+		return;
+
+	/* CAC handling function will care about this, an abort event
+	 * will come soon. */
+	if (hapd->iface->dfs_state & DFS_CAC_ACTIVE)
+		return;
+
+	/* radar detected while operating, switch the channel. */
+	ieee802_11_start_channel_switch(hapd);
+}
+
+
+static void hostapd_event_dfs_cac_finished(struct hostapd_data *hapd,
+					   struct dfs_event *radar)
+{
+	wpa_printf(MSG_DEBUG, "DFS CAC finished on %d MHz", radar->freq);
+	ieee802_11_complete_cac(hapd, 1, radar->freq);
+}
+
+
+static void hostapd_event_dfs_cac_aborted(struct hostapd_data *hapd,
+					  struct dfs_event *radar)
+{
+	wpa_printf(MSG_DEBUG, "DFS CAC aborted on %d MHz", radar->freq);
+	ieee802_11_complete_cac(hapd, 0, radar->freq);
+}
+
+
+static void hostapd_event_dfs_nop_finished(struct hostapd_data *hapd,
+					   struct dfs_event *radar)
+{
+	wpa_printf(MSG_DEBUG, "DFS NOP finished on %d MHz", radar->freq);
+	ieee802_11_set_dfs_state(hapd, radar->freq, HOSTAPD_CHAN_DFS_USABLE);
+}
+
+
 void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 			  union wpa_event_data *data)
 {
@@ -855,6 +905,33 @@  void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 			hapd, data->connect_failed_reason.addr,
 			data->connect_failed_reason.code);
 		break;
+	case EVENT_DFS_RADAR_DETECTED:
+		if (!data)
+			break;
+		hostapd_event_dfs_radar_detected(hapd, &data->dfs_event);
+		break;
+	case EVENT_DFS_CAC_FINISHED:
+		if (!data)
+			break;
+		hostapd_event_dfs_cac_finished(hapd, &data->dfs_event);
+		break;
+	case EVENT_DFS_CAC_ABORTED:
+		if (!data)
+			break;
+		hostapd_event_dfs_cac_aborted(hapd, &data->dfs_event);
+		break;
+	case EVENT_DFS_NOP_FINISHED:
+		if (!data)
+			break;
+		hostapd_event_dfs_nop_finished(hapd, &data->dfs_event);
+		break;
+	case EVENT_CHANNEL_LIST_CHANGED:
+		/* channel list changed (regulatory?), update channel list */
+		/* TODO: check this. hostapd_get_hw_features() initializes
+		 * too much stuff. */
+		/* hostapd_get_hw_features(hapd->iface); */
+		break;
+
 	default:
 		wpa_printf(MSG_DEBUG, "Unknown event %d", event);
 		break;
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index a0ac38c..021fbc4 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -882,15 +882,28 @@  static int setup_interface(struct hostapd_iface *iface)
 				   "be completed in a callback");
 			return 0;
 		}
+
+		if (iface->conf->ieee80211h) {
+			wpa_printf(MSG_DEBUG, "DFS support is enabled");
+			iface->dfs_state |= DFS_ENABLED;
+		}
 	}
 	return hostapd_setup_interface_complete(iface, 0);
 }
 
 
+void hostapd_dfs_check_events_complete(void *eloop_ctx, void *sock_ctx)
+{
+	/* this essentialy does nothing but to terminate the event loop. */
+	eloop_terminate();
+}
+
+
 int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err)
 {
 	struct hostapd_data *hapd = iface->bss[0];
 	size_t j;
+	int channel_set = 0;
 	u8 *prev_addr;
 
 	if (err) {
@@ -907,18 +920,39 @@  int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err)
 			   hostapd_hw_mode_txt(hapd->iconf->hw_mode),
 			   hapd->iconf->channel, iface->freq);
 
-		if (hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq,
-				     hapd->iconf->channel,
-				     hapd->iconf->ieee80211n,
-				     hapd->iconf->ieee80211ac,
-				     hapd->iconf->secondary_channel,
-				     hapd->iconf->vht_oper_chwidth,
-				     hapd->iconf->vht_oper_centr_freq_seg0_idx,
-				     hapd->iconf->vht_oper_centr_freq_seg1_idx)) {
-			wpa_printf(MSG_ERROR, "Could not set channel for "
-				   "kernel driver");
-			return -1;
-		}
+		/* first try the initial frequency ... */
+		if (hostapd_check_set_freq(hapd) == 0)
+			channel_set = 1;
+	}
+
+	if (!channel_set && hapd->iconf->ieee80211h) {
+		/* if this fails, try any other frequency */
+		struct hostapd_channel_data *channel;
+
+		do {
+			/* wait for a moment and check events (e.g., radar
+			 * events). */
+			eloop_register_timeout(
+				0, 10000, hostapd_dfs_check_events_complete,
+				NULL, NULL);
+			eloop_run();
+
+			channel = hostapd_dfs_get_valid_channel(hapd);
+			if (!channel) {
+				wpa_printf(MSG_ERROR, "No valid channel "
+					   "available");
+				/* TODO: we could wait for the next valid
+				 * channel to become available. */
+				return -1;
+			}
+
+			hapd->iconf->channel = channel->chan;
+			iface->freq = channel->freq;
+			wpa_printf(MSG_DEBUG, "Auto-selected channel: %s  "
+				   "Channel: %d  Frequency: %d MHz",
+				   hostapd_hw_mode_txt(hapd->iconf->hw_mode),
+				   hapd->iconf->channel, iface->freq);
+		} while (hostapd_check_set_freq(hapd));
 	}
 
 	if (iface->current_mode) {
@@ -1421,3 +1455,138 @@  void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
 	eloop_register_timeout(hapd->conf->ap_max_inactivity, 0,
 			       ap_handle_timer, hapd, sta);
 }
+
+
+/*
+ * if ret_chan == NULL: returns the number of channels with this dfs state
+ * if ret_chan != NULL: return channel at the supplied position.
+ */
+static int hostapd_dfs_find_channel(struct hostapd_data *hapd, u32 state,
+				    struct hostapd_channel_data **ret_chan,
+				    int idx)
+{
+	struct hostapd_hw_modes *mode;
+	struct hostapd_channel_data *chan;
+	int i, channel_idx = 0;
+
+	mode = hapd->iface->current_mode;
+
+	for (i = 0; i < mode->num_channels; i++) {
+		chan = &mode->channels[i];
+
+		if (chan->flag & HOSTAPD_CHAN_DISABLED)
+			continue;
+
+		if ((chan->flag & HOSTAPD_CHAN_RADAR) &&
+		    ((chan->flag & HOSTAPD_CHAN_DFS_MASK) != state))
+			continue;
+
+		if (ret_chan && idx == channel_idx) {
+			wpa_printf(MSG_DEBUG, "Selected ch. #%d", chan->chan);
+			*ret_chan = chan;
+			return idx;
+		}
+		channel_idx++;
+	}
+	return channel_idx;
+}
+
+
+struct hostapd_channel_data * hostapd_dfs_get_valid_channel(
+	struct hostapd_data *hapd)
+{
+	struct hostapd_hw_modes *mode;
+	struct hostapd_channel_data *chan = NULL;
+	int channel_idx, new_channel_idx;
+	u32 _rand;
+
+	wpa_printf(MSG_DEBUG, "DFS: Selecting channel");
+
+	if (hapd->iface->current_mode == NULL)
+		return NULL;
+
+	mode = hapd->iface->current_mode;
+	if (mode->mode != HOSTAPD_MODE_IEEE80211A)
+		return NULL;
+
+	os_get_random((u8 *) &_rand, sizeof(_rand));
+
+	/* first, try to find an available channel */
+	channel_idx = hostapd_dfs_find_channel(hapd, HOSTAPD_CHAN_DFS_AVAILABLE,
+					       NULL, 0);
+	if (channel_idx > 0) {
+		new_channel_idx = _rand % channel_idx;
+		hostapd_dfs_find_channel(hapd, HOSTAPD_CHAN_DFS_AVAILABLE,
+					 &chan, new_channel_idx);
+		if (chan)
+			return chan;
+	}
+
+	/* ... then, a usable channel */
+	channel_idx = hostapd_dfs_find_channel(hapd, HOSTAPD_CHAN_DFS_USABLE,
+					       NULL, 0);
+	if (channel_idx > 0) {
+		new_channel_idx = _rand % channel_idx;
+		hostapd_dfs_find_channel(hapd, HOSTAPD_CHAN_DFS_USABLE, &chan,
+					 new_channel_idx);
+	}
+	return chan;
+}
+
+
+int hostapd_check_set_freq(struct hostapd_data *hapd)
+{
+	int flag;
+
+	wpa_printf(MSG_DEBUG, "Check and set freq %d, channel %d",
+		   hapd->iface->freq, hapd->iconf->channel);
+
+	flag = hostapd_hw_get_channel_flag(hapd, hapd->iconf->channel);
+
+	if ((flag & HOSTAPD_CHAN_RADAR) &&
+	    !(hapd->iface->dfs_state & DFS_ENABLED)) {
+		wpa_printf(MSG_ERROR, "Can't set DFS frequency, "
+			   "DFS functionality is not enabled/supported");
+		return -1;
+	}
+
+	if (flag & HOSTAPD_CHAN_RADAR &&
+	    ((flag & HOSTAPD_CHAN_DFS_MASK) == HOSTAPD_CHAN_DFS_USABLE)) {
+		if (hostapd_start_dfs_cac(hapd, hapd->iface->freq, flag)) {
+			wpa_printf(MSG_ERROR, "Could not start "
+				   "radar detection for kernel driver");
+			return -1;
+		}
+
+		hapd->iface->dfs_state |= DFS_CAC_ACTIVE;
+		hapd->iface->dfs_state &= ~DFS_CAC_SUCCESS;
+
+		/*
+		 * Listen for radar interference for CAC period, eloop_run()
+		 * will be terminated after CAC period.
+		 */
+		eloop_run();
+	}
+	flag = hostapd_hw_get_channel_flag(hapd, hapd->iconf->channel);
+
+	if (flag & HOSTAPD_CHAN_RADAR &&
+	    ((flag & HOSTAPD_CHAN_DFS_MASK) != HOSTAPD_CHAN_DFS_AVAILABLE)) {
+		wpa_printf(MSG_ERROR, "Channel is not available, can't use it");
+		return -1;
+	}
+
+	if (hostapd_set_freq(hapd, hapd->iconf->hw_mode, hapd->iface->freq,
+			     hapd->iconf->channel,
+			     hapd->iconf->ieee80211n,
+			     hapd->iconf->ieee80211ac,
+			     hapd->iconf->secondary_channel,
+			     hapd->iconf->vht_oper_chwidth,
+			     hapd->iconf->vht_oper_centr_freq_seg0_idx,
+			     hapd->iconf->vht_oper_centr_freq_seg1_idx)) {
+		wpa_printf(MSG_ERROR, "Could not set channel for "
+			   "kernel driver");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 9a3bb68..213d1ee 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -270,6 +270,18 @@  struct hostapd_iface {
 	int olbc_ht;
 
 	u16 ht_op_mode;
+
+	/* DFS functionality is enabled. */
+#define DFS_ENABLED BIT(0)
+
+	/* DFS channel availability check during initialization. */
+#define DFS_CAC_ACTIVE BIT(1)
+
+	/* DFS channel availability check completed successfully. */
+#define DFS_CAC_SUCCESS BIT(2)
+
+	u8 dfs_state; /* DFS_* bitfield */
+
 	void (*scan_cb)(struct hostapd_iface *iface);
 };
 
@@ -294,6 +306,10 @@  int hostapd_reload_iface(struct hostapd_iface *hapd_iface);
 int hostapd_disable_iface(struct hostapd_iface *hapd_iface);
 int hostapd_add_iface(struct hapd_interfaces *ifaces, char *buf);
 int hostapd_remove_iface(struct hapd_interfaces *ifaces, char *buf);
+struct hostapd_channel_data * hostapd_dfs_get_valid_channel(
+	struct hostapd_data *hapd);
+void hostapd_dfs_check_events_complete(void *eloop_ctx, void *sock_ctx);
+int hostapd_check_set_freq(struct hostapd_data *hapd);
 
 /* utils.c */
 int hostapd_register_probereq_cb(struct hostapd_data *hapd,
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 37112bd..22b3ee8 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -44,6 +44,36 @@  void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
 }
 
 
+#ifndef CONFIG_NO_STDOUT_DEBUG
+static char * dfs_info(struct hostapd_channel_data *chan)
+{
+	static char info[256], *state;
+
+	switch (chan->flag & HOSTAPD_CHAN_DFS_MASK) {
+	case HOSTAPD_CHAN_DFS_UNKNOWN:
+		state = "unknown";
+		break;
+	case HOSTAPD_CHAN_DFS_USABLE:
+		state = "usable";
+		break;
+	case HOSTAPD_CHAN_DFS_UNAVAILABLE:
+		state = "unavailable";
+		break;
+	case HOSTAPD_CHAN_DFS_AVAILABLE:
+		state = "available";
+		break;
+	default:
+		return "";
+	}
+	os_snprintf(info, sizeof(info), " (DFS state = %s)",
+		    state);
+	info[sizeof(info) - 1] = '\0';
+
+	return info;
+}
+#endif /* CONFIG_NO_STDOUT_DEBUG */
+
+
 int hostapd_get_hw_features(struct hostapd_iface *iface)
 {
 	struct hostapd_data *hapd = iface->bss[0];
@@ -70,30 +100,40 @@  int hostapd_get_hw_features(struct hostapd_iface *iface)
 
 	for (i = 0; i < num_modes; i++) {
 		struct hostapd_hw_modes *feature = &modes[i];
+		int dfs_enabled = hapd->iconf->ieee80211h &&
+			(iface->drv_flags & WPA_DRIVER_FLAGS_RADAR);
+
 		/* set flag for channels we can use in current regulatory
 		 * domain */
 		for (j = 0; j < feature->num_channels; j++) {
+			int dfs = 0;
+
 			/*
 			 * Disable all channels that are marked not to allow
-			 * IBSS operation or active scanning. In addition,
-			 * disable all channels that require radar detection,
-			 * since that (in addition to full DFS) is not yet
-			 * supported.
+			 * IBSS operation or active scanning.
+			 * Use radar channels only if the driver supports DFS.
 			 */
-			if (feature->channels[j].flag &
-			    (HOSTAPD_CHAN_NO_IBSS |
-			     HOSTAPD_CHAN_PASSIVE_SCAN |
-			     HOSTAPD_CHAN_RADAR))
+			if ((feature->channels[j].flag &
+			     HOSTAPD_CHAN_RADAR) && dfs_enabled) {
+				dfs = 1;
+			} else if (feature->channels[j].flag &
+				   (HOSTAPD_CHAN_NO_IBSS |
+				   HOSTAPD_CHAN_PASSIVE_SCAN |
+				   HOSTAPD_CHAN_RADAR)) {
 				feature->channels[j].flag |=
 					HOSTAPD_CHAN_DISABLED;
+			}
+
 			if (feature->channels[j].flag & HOSTAPD_CHAN_DISABLED)
 				continue;
+
 			wpa_printf(MSG_MSGDUMP, "Allowed channel: mode=%d "
-				   "chan=%d freq=%d MHz max_tx_power=%d dBm",
+				   "chan=%d freq=%d MHz max_tx_power=%d dBm%s",
 				   feature->mode,
 				   feature->channels[j].chan,
 				   feature->channels[j].freq,
-				   feature->channels[j].max_tx_power);
+				   feature->channels[j].max_tx_power,
+				   dfs ? dfs_info(&feature->channels[j]) : "");
 		}
 	}
 
@@ -799,3 +839,21 @@  int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq)
 
 	return 0;
 }
+
+
+int hostapd_hw_get_channel_flag(struct hostapd_data *hapd, int chan)
+{
+	int i;
+
+	if (!hapd->iface->current_mode)
+		return 0;
+
+	for (i = 0; i < hapd->iface->current_mode->num_channels; i++) {
+		struct hostapd_channel_data *ch =
+			&hapd->iface->current_mode->channels[i];
+		if (ch->chan == chan)
+			return ch->flag;
+	}
+
+	return 0;
+}
diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h
index abadcd1..fe18d7c 100644
--- a/src/ap/hw_features.h
+++ b/src/ap/hw_features.h
@@ -28,6 +28,7 @@  int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq);
 int hostapd_check_ht_capab(struct hostapd_iface *iface);
 int hostapd_prepare_rates(struct hostapd_iface *iface,
 			  struct hostapd_hw_modes *mode);
+int hostapd_hw_get_channel_flag(struct hostapd_data *hapd, int chan);
 #else /* NEED_AP_MLME */
 static inline void
 hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
@@ -66,6 +67,10 @@  static inline int hostapd_prepare_rates(struct hostapd_iface *iface,
 	return 0;
 }
 
+static inline int hostapd_get_channel_flag(struct hostapd_data *hapd, int chan)
+{
+	return 0;
+}
 #endif /* NEED_AP_MLME */
 
 #endif /* HW_FEATURES_H */
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 2e570c0..b946e6e 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -2198,4 +2198,93 @@  void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src,
 }
 
 
+int ieee802_11_set_dfs_state(struct hostapd_data *hapd, int freq, u32 state)
+{
+	struct hostapd_hw_modes *mode;
+	struct hostapd_channel_data *chan = NULL;
+	int i;
+
+	mode = hapd->iface->current_mode;
+	if (mode == NULL)
+		return 0;
+
+	if (mode->mode != HOSTAPD_MODE_IEEE80211A) {
+		wpa_printf(MSG_WARNING, "current_mode != IEEE80211A");
+		return 0;
+	}
+
+	for (i = 0; i < hapd->iface->current_mode->num_channels; i++) {
+		chan = &hapd->iface->current_mode->channels[i];
+		if (chan->freq == freq) {
+			if (chan->flag & HOSTAPD_CHAN_RADAR) {
+				chan->flag &= ~HOSTAPD_CHAN_DFS_MASK;
+				chan->flag |= state;
+				return 1; /* Channel found */
+			}
+		}
+	}
+	wpa_printf(MSG_WARNING, "Can't set DFS state for freq %d MHz", freq);
+	return 0;
+}
+
+
+int ieee802_11_complete_cac(struct hostapd_data *hapd, int success, int freq)
+{
+	hapd->iface->dfs_state &= ~DFS_CAC_ACTIVE;
+
+	if (success) {
+		hapd->iface->dfs_state |= DFS_CAC_SUCCESS;
+		ieee802_11_set_dfs_state(hapd, freq,
+					 HOSTAPD_CHAN_DFS_AVAILABLE);
+	} else {
+		hapd->iface->dfs_state &= ~DFS_CAC_SUCCESS;
+	}
+	eloop_terminate();
+
+	return 0;
+}
+
+
+static int ieee802_11_try_channel_switch(
+	struct hostapd_data *hapd, struct hostapd_channel_data *next_channel)
+{
+	hapd->iconf->channel = (u8) next_channel->chan;
+	hapd->iface->freq = next_channel->freq;
+
+	hapd->driver->stop_ap(hapd->drv_priv);
+
+	if (hostapd_check_set_freq(hapd))
+		return -1;
+
+	ieee802_11_set_beacon(hapd);
+
+	return 0;
+}
+
+
+int ieee802_11_start_channel_switch(struct hostapd_data *hapd)
+{
+	struct hostapd_channel_data *next_channel;
+
+	do {
+		/* wait for a moment and check events (e.g. radar events). */
+		eloop_register_timeout(0, 10000,
+				       hostapd_dfs_check_events_complete,
+				       NULL, NULL);
+		eloop_run();
+
+		next_channel = hostapd_dfs_get_valid_channel(hapd);
+		if (!next_channel) {
+			/* TODO: we could wait for the next valid channel to
+			 * become available. */
+			wpa_printf(MSG_ERROR, "Can't switch channel, no "
+				   "channel available. PANIC.");
+			eloop_terminate();
+			return -1;
+		}
+	} while (ieee802_11_try_channel_switch(hapd, next_channel));
+
+	return 0;
+}
+
 #endif /* CONFIG_NATIVE_WINDOWS */
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 2aab56d..45d213d 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -80,5 +80,8 @@  u8 * hostapd_eid_time_zone(struct hostapd_data *hapd, u8 *eid);
 int hostapd_update_time_adv(struct hostapd_data *hapd);
 void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr);
 u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid);
+int ieee802_11_set_dfs_state(struct hostapd_data *hapd, int freq, u32 state);
+int ieee802_11_start_channel_switch(struct hostapd_data *hapd);
+int ieee802_11_complete_cac(struct hostapd_data *hapd, int success, int freq);
 
 #endif /* IEEE802_11_H */