nl80211: Add frequency configuration (including HT40)

This patch adds new NL80211_CMD_SET_WIPHY attributes
NL80211_ATTR_WIPHY_FREQ and NL80211_ATTR_WIPHY_SEC_CHAN_OFFSET to allow
userspace to set the operating channel (e.g., hostapd for AP mode).

Signed-off-by: Jouni Malinen <jouni.malinen@atheros.com>
Acked-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index c9141e3..9caee60 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -59,6 +59,8 @@
 	[NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING,
 				      .len = BUS_ID_SIZE-1 },
 	[NL80211_ATTR_WIPHY_TXQ_PARAMS] = { .type = NLA_NESTED },
+	[NL80211_ATTR_WIPHY_FREQ] = { .type = NLA_U32 },
+	[NL80211_ATTR_WIPHY_SEC_CHAN_OFFSET] = { .type = NLA_U32 },
 
 	[NL80211_ATTR_IFTYPE] = { .type = NLA_U32 },
 	[NL80211_ATTR_IFINDEX] = { .type = NLA_U32 },
@@ -359,6 +361,61 @@
 		}
 	}
 
+	if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
+		enum nl80211_sec_chan_offset sec_chan_offset =
+			NL80211_SEC_CHAN_NO_HT;
+		struct ieee80211_channel *chan;
+		u32 freq, sec_freq;
+
+		if (!rdev->ops->set_channel) {
+			result = -EOPNOTSUPP;
+			goto bad_res;
+		}
+
+		if (info->attrs[NL80211_ATTR_WIPHY_SEC_CHAN_OFFSET]) {
+			sec_chan_offset = nla_get_u32(
+				info->attrs[
+					NL80211_ATTR_WIPHY_SEC_CHAN_OFFSET]);
+			if (sec_chan_offset != NL80211_SEC_CHAN_NO_HT &&
+			    sec_chan_offset != NL80211_SEC_CHAN_DISABLED &&
+			    sec_chan_offset != NL80211_SEC_CHAN_BELOW &&
+			    sec_chan_offset != NL80211_SEC_CHAN_ABOVE) {
+				result = -EINVAL;
+				goto bad_res;
+			}
+		}
+
+		freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
+		chan = ieee80211_get_channel(&rdev->wiphy, freq);
+		if (!chan || chan->flags & IEEE80211_CHAN_DISABLED) {
+			/* Primary channel not allowed */
+			result = -EINVAL;
+			goto bad_res;
+		}
+		if (sec_chan_offset == NL80211_SEC_CHAN_BELOW)
+			sec_freq = freq - 20;
+		else if (sec_chan_offset == NL80211_SEC_CHAN_ABOVE)
+			sec_freq = freq + 20;
+		else
+			sec_freq = 0;
+
+		if (sec_freq) {
+			struct ieee80211_channel *schan;
+			schan = ieee80211_get_channel(&rdev->wiphy, sec_freq);
+			if (!schan || schan->flags & IEEE80211_CHAN_DISABLED) {
+				/* Secondary channel not allowed */
+				result = -EINVAL;
+				goto bad_res;
+			}
+		}
+
+		result = rdev->ops->set_channel(&rdev->wiphy, chan,
+						sec_chan_offset);
+		if (result)
+			goto bad_res;
+	}
+
+
 bad_res:
 	cfg80211_put_dev(rdev);
 	return result;