From 9f5e8f6efc7c2601136b27d9c7325c245f8fd19a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Thu, 22 Nov 2012 16:59:45 +0100 Subject: cfg80211: rework chandef checking and export it Some of the chandef checking that we do in cfg80211 to check if a channel is supported or not is also needed in mac80211, so rework that a bit and export the functions that are needed. Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index e78db2cf3d1b..a238f41e55c2 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -58,6 +58,8 @@ * structures here describe these capabilities in detail. */ +struct wiphy; + /* * wireless hardware capability structures */ @@ -387,6 +389,22 @@ const struct cfg80211_chan_def * cfg80211_chandef_compatible(const struct cfg80211_chan_def *chandef1, const struct cfg80211_chan_def *chandef2); +/** + * cfg80211_chandef_valid - check if a channel definition is valid + * @chandef: the channel definition to check + */ +bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef); + +/** + * cfg80211_chandef_usable - check if secondary channels can be used + * @wiphy: the wiphy to validate against + * @chandef: the channel definition to check + * @prohibited_flags: the regulatory chanenl flags that must not be set + */ +bool cfg80211_chandef_usable(struct wiphy *wiphy, + const struct cfg80211_chan_def *chandef, + u32 prohibited_flags); + /** * enum survey_info_flags - survey information flags * @@ -1045,9 +1063,6 @@ struct ieee80211_txq_params { u8 aifs; }; -/* from net/wireless.h */ -struct wiphy; - /** * DOC: Scanning and BSS list handling * -- cgit v1.2.3-59-g8ed1b From f2d9d270c15ae0139b54a7e7466d738327e97e03 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Thu, 22 Nov 2012 14:11:39 +0100 Subject: mac80211: support VHT association Determine the VHT channel from the AP's VHT operation IE (if present) and configure the hardware to that channel if it is supported. If channel contexts cause a channel to not be usable, try a smaller bandwidth. Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 15 ++ net/mac80211/ieee80211_i.h | 2 + net/mac80211/mlme.c | 368 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 313 insertions(+), 72 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index f9c5a787d350..8f690e53dd89 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -1212,6 +1212,21 @@ struct ieee80211_vht_cap { struct ieee80211_vht_mcs_info supp_mcs; } __packed; +/** + * enum ieee80211_vht_chanwidth - VHT channel width + * @IEEE80211_VHT_CHANWIDTH_USE_HT: use the HT operation IE to + * determine the channel width (20 or 40 MHz) + * @IEEE80211_VHT_CHANWIDTH_80MHZ: 80 MHz bandwidth + * @IEEE80211_VHT_CHANWIDTH_160MHZ: 160 MHz bandwidth + * @IEEE80211_VHT_CHANWIDTH_80P80MHZ: 80+80 MHz bandwidth + */ +enum ieee80211_vht_chanwidth { + IEEE80211_VHT_CHANWIDTH_USE_HT = 0, + IEEE80211_VHT_CHANWIDTH_80MHZ = 1, + IEEE80211_VHT_CHANWIDTH_160MHZ = 2, + IEEE80211_VHT_CHANWIDTH_80P80MHZ = 3, +}; + /** * struct ieee80211_vht_operation - VHT operation IE * diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 0a8f83d142f9..8d8bf7136386 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -371,6 +371,8 @@ enum ieee80211_sta_flags { IEEE80211_STA_RESET_SIGNAL_AVE = BIT(9), IEEE80211_STA_DISABLE_40MHZ = BIT(10), IEEE80211_STA_DISABLE_VHT = BIT(11), + IEEE80211_STA_DISABLE_80P80MHZ = BIT(12), + IEEE80211_STA_DISABLE_160MHZ = BIT(13), }; struct ieee80211_mgd_auth_data { diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index d2a4f78b4b0f..35d8cffc973a 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -354,6 +354,16 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata, /* determine capability flags */ cap = vht_cap.cap; + if (sdata->u.mgd.flags & IEEE80211_STA_DISABLE_80P80MHZ) { + cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; + cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + } + + if (sdata->u.mgd.flags & IEEE80211_STA_DISABLE_160MHZ) { + cap &= ~IEEE80211_VHT_CAP_SHORT_GI_160; + cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + } + /* reserve and fill IE */ pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2); ieee80211_ie_build_vht_cap(pos, &vht_cap, cap); @@ -543,6 +553,10 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata) offset = noffset; } + if (WARN_ON_ONCE((ifmgd->flags & IEEE80211_STA_DISABLE_HT) && + !(ifmgd->flags & IEEE80211_STA_DISABLE_VHT))) + ifmgd->flags |= IEEE80211_STA_DISABLE_VHT; + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT)) ieee80211_add_ht_ie(sdata, skb, assoc_data->ap_ht_param, sband, chan, sdata->smps_mode); @@ -3183,23 +3197,270 @@ int ieee80211_max_network_latency(struct notifier_block *nb, return 0; } +static u32 chandef_downgrade(struct cfg80211_chan_def *c) +{ + u32 ret; + int tmp; + + switch (c->width) { + case NL80211_CHAN_WIDTH_20: + c->width = NL80211_CHAN_WIDTH_20_NOHT; + ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; + break; + case NL80211_CHAN_WIDTH_40: + c->width = NL80211_CHAN_WIDTH_20; + c->center_freq1 = c->chan->center_freq; + ret = IEEE80211_STA_DISABLE_40MHZ | + IEEE80211_STA_DISABLE_VHT; + break; + case NL80211_CHAN_WIDTH_80: + tmp = (30 + c->chan->center_freq - c->center_freq1)/20; + /* n_P40 */ + tmp /= 2; + /* freq_P40 */ + c->center_freq1 = c->center_freq1 - 20 + 40 * tmp; + c->width = NL80211_CHAN_WIDTH_40; + ret = IEEE80211_STA_DISABLE_VHT; + break; + case NL80211_CHAN_WIDTH_80P80: + c->center_freq2 = 0; + c->width = NL80211_CHAN_WIDTH_80; + ret = IEEE80211_STA_DISABLE_80P80MHZ | + IEEE80211_STA_DISABLE_160MHZ; + break; + case NL80211_CHAN_WIDTH_160: + /* n_P20 */ + tmp = (70 + c->chan->center_freq - c->center_freq1)/20; + /* n_P80 */ + tmp /= 4; + c->center_freq1 = c->center_freq1 - 40 + 80 * tmp; + c->width = NL80211_CHAN_WIDTH_80; + ret = IEEE80211_STA_DISABLE_80P80MHZ | + IEEE80211_STA_DISABLE_160MHZ; + break; + default: + case NL80211_CHAN_WIDTH_20_NOHT: + WARN_ON_ONCE(1); + c->width = NL80211_CHAN_WIDTH_20_NOHT; + ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; + break; + } + + WARN_ON_ONCE(!cfg80211_chandef_valid(c)); + + return ret; +} + +static u32 +ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata, + struct ieee80211_supported_band *sband, + struct ieee80211_channel *channel, + const struct ieee80211_ht_operation *ht_oper, + const struct ieee80211_vht_operation *vht_oper, + struct cfg80211_chan_def *chandef) +{ + struct cfg80211_chan_def vht_chandef; + u32 ht_cfreq, ret; + + chandef->chan = channel; + chandef->width = NL80211_CHAN_WIDTH_20_NOHT; + chandef->center_freq1 = channel->center_freq; + chandef->center_freq2 = 0; + + if (!ht_oper || !sband->ht_cap.ht_supported) { + ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; + goto out; + } + + chandef->width = NL80211_CHAN_WIDTH_20; + + ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan, + channel->band); + /* check that channel matches the right operating channel */ + if (channel->center_freq != ht_cfreq) { + /* + * It's possible that some APs are confused here; + * Netgear WNDR3700 sometimes reports 4 higher than + * the actual channel in association responses, but + * since we look at probe response/beacon data here + * it should be OK. + */ + sdata_info(sdata, + "Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n", + channel->center_freq, ht_cfreq, + ht_oper->primary_chan, channel->band); + ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT; + goto out; + } + + /* check 40 MHz support, if we have it */ + if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) { + switch (ht_oper->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + chandef->width = NL80211_CHAN_WIDTH_40; + chandef->center_freq1 += 10; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + chandef->width = NL80211_CHAN_WIDTH_40; + chandef->center_freq1 -= 10; + break; + } + } else { + /* 40 MHz (and 80 MHz) must be supported for VHT */ + ret = IEEE80211_STA_DISABLE_VHT; + goto out; + } + + if (!vht_oper || !sband->vht_cap.vht_supported) { + ret = IEEE80211_STA_DISABLE_VHT; + goto out; + } + + vht_chandef.chan = channel; + vht_chandef.center_freq1 = + ieee80211_channel_to_frequency(vht_oper->center_freq_seg1_idx, + channel->band); + vht_chandef.center_freq2 = 0; + + if (vht_oper->center_freq_seg2_idx) + vht_chandef.center_freq2 = + ieee80211_channel_to_frequency( + vht_oper->center_freq_seg2_idx, + channel->band); + + switch (vht_oper->chan_width) { + case IEEE80211_VHT_CHANWIDTH_USE_HT: + vht_chandef.width = chandef->width; + break; + case IEEE80211_VHT_CHANWIDTH_80MHZ: + vht_chandef.width = NL80211_CHAN_WIDTH_80; + break; + case IEEE80211_VHT_CHANWIDTH_160MHZ: + vht_chandef.width = NL80211_CHAN_WIDTH_160; + break; + case IEEE80211_VHT_CHANWIDTH_80P80MHZ: + vht_chandef.width = NL80211_CHAN_WIDTH_80P80; + break; + default: + sdata_info(sdata, + "AP VHT operation IE has invalid channel width (%d), disable VHT\n", + vht_oper->chan_width); + ret = IEEE80211_STA_DISABLE_VHT; + goto out; + } + + if (!cfg80211_chandef_valid(&vht_chandef)) { + sdata_info(sdata, + "AP VHT information is invalid, disable VHT\n"); + ret = IEEE80211_STA_DISABLE_VHT; + goto out; + } + + if (cfg80211_chandef_identical(chandef, &vht_chandef)) { + ret = 0; + goto out; + } + + if (!cfg80211_chandef_compatible(chandef, &vht_chandef)) { + sdata_info(sdata, + "AP VHT information doesn't match HT, disable VHT\n"); + ret = IEEE80211_STA_DISABLE_VHT; + goto out; + } + + *chandef = vht_chandef; + + ret = 0; + + while (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef, + IEEE80211_CHAN_DISABLED)) { + if (WARN_ON(chandef->width == NL80211_CHAN_WIDTH_20_NOHT)) { + ret = IEEE80211_STA_DISABLE_HT | + IEEE80211_STA_DISABLE_VHT; + goto out; + } + + ret = chandef_downgrade(chandef); + } + + if (chandef->width != vht_chandef.width) + sdata_info(sdata, + "local regulatory prevented using AP HT/VHT configuration, downgraded\n"); + +out: + WARN_ON_ONCE(!cfg80211_chandef_valid(chandef)); + return ret; +} + +static u8 ieee80211_ht_vht_rx_chains(struct ieee80211_sub_if_data *sdata, + struct cfg80211_bss *cbss) +{ + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + const u8 *ht_cap_ie, *vht_cap_ie; + const struct ieee80211_ht_cap *ht_cap; + const struct ieee80211_vht_cap *vht_cap; + u8 chains = 1; + + if (ifmgd->flags & IEEE80211_STA_DISABLE_HT) + return chains; + + ht_cap_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, + cbss->information_elements, + cbss->len_information_elements); + if (ht_cap_ie && ht_cap_ie[1] >= sizeof(*ht_cap)) { + ht_cap = (void *)(ht_cap_ie + 2); + chains = ieee80211_mcs_to_chains(&ht_cap->mcs); + /* + * TODO: use "Tx Maximum Number Spatial Streams Supported" and + * "Tx Unequal Modulation Supported" fields. + */ + } + + if (ifmgd->flags & IEEE80211_STA_DISABLE_VHT) + return chains; + + vht_cap_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, + cbss->information_elements, + cbss->len_information_elements); + if (vht_cap_ie && vht_cap_ie[1] >= sizeof(*vht_cap)) { + u8 nss; + u16 tx_mcs_map; + + vht_cap = (void *)(vht_cap_ie + 2); + tx_mcs_map = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map); + for (nss = 8; nss > 0; nss--) { + if (((tx_mcs_map >> (2 * (nss - 1))) & 3) != + IEEE80211_VHT_MCS_NOT_SUPPORTED) + break; + } + /* TODO: use "Tx Highest Supported Long GI Data Rate" field? */ + chains = max(chains, nss); + } + + return chains; +} + static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, struct cfg80211_bss *cbss) { struct ieee80211_local *local = sdata->local; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - int ht_cfreq; - enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT; - const u8 *ht_oper_ie; const struct ieee80211_ht_operation *ht_oper = NULL; + const struct ieee80211_vht_operation *vht_oper = NULL; struct ieee80211_supported_band *sband; struct cfg80211_chan_def chandef; + int ret; sband = local->hw.wiphy->bands[cbss->channel->band]; - ifmgd->flags &= ~IEEE80211_STA_DISABLE_40MHZ; + ifmgd->flags &= ~(IEEE80211_STA_DISABLE_40MHZ | + IEEE80211_STA_DISABLE_80P80MHZ | + IEEE80211_STA_DISABLE_160MHZ); + + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT) && + sband->ht_cap.ht_supported) { + const u8 *ht_oper_ie; - if (sband->ht_cap.ht_supported) { ht_oper_ie = cfg80211_find_ie(WLAN_EID_HT_OPERATION, cbss->information_elements, cbss->len_information_elements); @@ -3207,82 +3468,45 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, ht_oper = (void *)(ht_oper_ie + 2); } - if (ht_oper) { - ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan, - cbss->channel->band); - /* check that channel matches the right operating channel */ - if (cbss->channel->center_freq != ht_cfreq) { - /* - * It's possible that some APs are confused here; - * Netgear WNDR3700 sometimes reports 4 higher than - * the actual channel in association responses, but - * since we look at probe response/beacon data here - * it should be OK. - */ + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT) && + sband->vht_cap.vht_supported) { + const u8 *vht_oper_ie; + + vht_oper_ie = cfg80211_find_ie(WLAN_EID_VHT_OPERATION, + cbss->information_elements, + cbss->len_information_elements); + if (vht_oper_ie && vht_oper_ie[1] >= sizeof(*vht_oper)) + vht_oper = (void *)(vht_oper_ie + 2); + if (vht_oper && !ht_oper) { + vht_oper = NULL; sdata_info(sdata, - "Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n", - cbss->channel->center_freq, - ht_cfreq, ht_oper->primary_chan, - cbss->channel->band); - ht_oper = NULL; + "AP advertised VHT without HT, disabling both\n"); + sdata->flags |= IEEE80211_STA_DISABLE_HT; + sdata->flags |= IEEE80211_STA_DISABLE_VHT; } } - if (ht_oper) { - /* - * cfg80211 already verified that the channel itself can - * be used, but it didn't check that we can do the right - * HT type, so do that here as well. If HT40 isn't allowed - * on this channel, disable 40 MHz operation. - */ - const u8 *ht_cap_ie; - const struct ieee80211_ht_cap *ht_cap; - u8 chains = 1; - - channel_type = NL80211_CHAN_HT20; - - if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) { - switch (ht_oper->ht_param & - IEEE80211_HT_PARAM_CHA_SEC_OFFSET) { - case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: - if (cbss->channel->flags & - IEEE80211_CHAN_NO_HT40PLUS) - ifmgd->flags |= - IEEE80211_STA_DISABLE_40MHZ; - else - channel_type = NL80211_CHAN_HT40PLUS; - break; - case IEEE80211_HT_PARAM_CHA_SEC_BELOW: - if (cbss->channel->flags & - IEEE80211_CHAN_NO_HT40MINUS) - ifmgd->flags |= - IEEE80211_STA_DISABLE_40MHZ; - else - channel_type = NL80211_CHAN_HT40MINUS; - break; - } - } + ifmgd->flags |= ieee80211_determine_chantype(sdata, sband, + cbss->channel, + ht_oper, vht_oper, + &chandef); - ht_cap_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, - cbss->information_elements, - cbss->len_information_elements); - if (ht_cap_ie && ht_cap_ie[1] >= sizeof(*ht_cap)) { - ht_cap = (void *)(ht_cap_ie + 2); - chains = ieee80211_mcs_to_chains(&ht_cap->mcs); - } - sdata->needed_rx_chains = min(chains, local->rx_chains); - } else { - sdata->needed_rx_chains = 1; - sdata->u.mgd.flags |= IEEE80211_STA_DISABLE_HT; - } + sdata->needed_rx_chains = min(ieee80211_ht_vht_rx_chains(sdata, cbss), + local->rx_chains); /* will change later if needed */ sdata->smps_mode = IEEE80211_SMPS_OFF; - ieee80211_vif_release_channel(sdata); - cfg80211_chandef_create(&chandef, cbss->channel, channel_type); - return ieee80211_vif_use_channel(sdata, &chandef, - IEEE80211_CHANCTX_SHARED); + /* + * If this fails (possibly due to channel context sharing + * on incompatible channels, e.g. 80+80 and 160 sharing the + * same control channel) try to use a smaller bandwidth. + */ + ret = ieee80211_vif_use_channel(sdata, &chandef, + IEEE80211_CHANCTX_SHARED); + while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) + ifmgd->flags |= chandef_downgrade(&chandef); + return ret; } static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, -- cgit v1.2.3-59-g8ed1b From 5164892184d1b9ce19e45e97e9ca405ea8b9ceb2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Thu, 22 Nov 2012 23:00:18 +0100 Subject: mac80211: support (partial) VHT radiotap information Add some information that we have about VHT to radiotap. This at least lets one see the MCS and NSS information. Signed-off-by: Johannes Berg --- include/net/ieee80211_radiotap.h | 24 ++++++++++++++++++++++++ include/net/mac80211.h | 5 +++++ net/mac80211/main.c | 2 ++ net/mac80211/rx.c | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) (limited to 'include') diff --git a/include/net/ieee80211_radiotap.h b/include/net/ieee80211_radiotap.h index 7f0df133d119..c3999632e616 100644 --- a/include/net/ieee80211_radiotap.h +++ b/include/net/ieee80211_radiotap.h @@ -186,6 +186,10 @@ struct ieee80211_radiotap_header { * IEEE80211_RADIOTAP_AMPDU_STATUS u32, u16, u8, u8 unitless * * Contains the AMPDU information for the subframe. + * + * IEEE80211_RADIOTAP_VHT u16, u8, u8, u8[4], u8, u8, u16 + * + * Contains VHT information about this frame. */ enum ieee80211_radiotap_type { IEEE80211_RADIOTAP_TSFT = 0, @@ -209,6 +213,7 @@ enum ieee80211_radiotap_type { IEEE80211_RADIOTAP_MCS = 19, IEEE80211_RADIOTAP_AMPDU_STATUS = 20, + IEEE80211_RADIOTAP_VHT = 21, /* valid in every it_present bitmap, even vendor namespaces */ IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE = 29, @@ -282,6 +287,25 @@ enum ieee80211_radiotap_type { #define IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_ERR 0x0010 #define IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_KNOWN 0x0020 +/* For IEEE80211_RADIOTAP_VHT */ +#define IEEE80211_RADIOTAP_VHT_KNOWN_STBC 0x0001 +#define IEEE80211_RADIOTAP_VHT_KNOWN_TXOP_PS_NA 0x0002 +#define IEEE80211_RADIOTAP_VHT_KNOWN_GI 0x0004 +#define IEEE80211_RADIOTAP_VHT_KNOWN_SGI_NSYM_DIS 0x0008 +#define IEEE80211_RADIOTAP_VHT_KNOWN_LDPC_EXTRA_OFDM_SYM 0x0010 +#define IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED 0x0020 +#define IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH 0x0040 +#define IEEE80211_RADIOTAP_VHT_KNOWN_GROUP_ID 0x0080 +#define IEEE80211_RADIOTAP_VHT_KNOWN_PARTIAL_AID 0x0100 + +#define IEEE80211_RADIOTAP_VHT_FLAG_STBC 0x01 +#define IEEE80211_RADIOTAP_VHT_FLAG_TXOP_PS_NA 0x02 +#define IEEE80211_RADIOTAP_VHT_FLAG_SGI 0x04 +#define IEEE80211_RADIOTAP_VHT_FLAG_SGI_NSYM_M10_9 0x08 +#define IEEE80211_RADIOTAP_VHT_FLAG_LDPC_EXTRA_OFDM_SYM 0x10 +#define IEEE80211_RADIOTAP_VHT_FLAG_BEAMFORMED 0x20 + + /* helpers */ static inline int ieee80211_get_radiotap_len(unsigned char *data) { diff --git a/include/net/mac80211.h b/include/net/mac80211.h index db7680acd0da..e806f1d81f66 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1473,6 +1473,10 @@ enum ieee80211_hw_flags { * include _FMT. Use %IEEE80211_RADIOTAP_MCS_HAVE_* values, only * adding _BW is supported today. * + * @radiotap_vht_details: lists which VHT MCS information the HW reports, + * the default is _GI | _BANDWIDTH. + * Use the %IEEE80211_RADIOTAP_VHT_KNOWN_* values. + * * @netdev_features: netdev features to be set in each netdev created * from this HW. Note only HW checksum features are currently * compatible with mac80211. Other feature bits will be rejected. @@ -1499,6 +1503,7 @@ struct ieee80211_hw { u8 max_tx_aggregation_subframes; u8 offchannel_tx_hw_queue; u8 radiotap_mcs_details; + u16 radiotap_vht_details; netdev_features_t netdev_features; }; diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 6e933409979a..c4ed83b74e52 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -638,6 +638,8 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, local->hw.radiotap_mcs_details = IEEE80211_RADIOTAP_MCS_HAVE_MCS | IEEE80211_RADIOTAP_MCS_HAVE_GI | IEEE80211_RADIOTAP_MCS_HAVE_BW; + local->hw.radiotap_vht_details = IEEE80211_RADIOTAP_VHT_KNOWN_GI | + IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH; local->user_power_level = IEEE80211_UNSET_POWER_LEVEL; wiphy->ht_capa_mod_mask = &mac80211_ht_capa_mod_mask; diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 825f33cf7bbc..9b13b8b24245 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -111,6 +111,11 @@ ieee80211_rx_radiotap_space(struct ieee80211_local *local, len += 8; } + if (status->flag & RX_FLAG_VHT) { + len = ALIGN(len, 2); + len += 12; + } + if (status->vendor_radiotap_len) { if (WARN_ON_ONCE(status->vendor_radiotap_align == 0)) status->vendor_radiotap_align = 1; @@ -297,6 +302,41 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, *pos++ = 0; } + if (status->flag & RX_FLAG_VHT) { + u16 known = local->hw.radiotap_vht_details; + + rthdr->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_VHT); + /* known field - how to handle 80+80? */ + if (status->flag & RX_FLAG_80P80MHZ) + known &= ~IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH; + put_unaligned_le16(known, pos); + pos += 2; + /* flags */ + if (status->flag & RX_FLAG_SHORT_GI) + *pos |= IEEE80211_RADIOTAP_VHT_FLAG_SGI; + pos++; + /* bandwidth */ + if (status->flag & RX_FLAG_80MHZ) + *pos++ = 4; + else if (status->flag & RX_FLAG_80P80MHZ) + *pos++ = 0; /* marked not known above */ + else if (status->flag & RX_FLAG_160MHZ) + *pos++ = 11; + else if (status->flag & RX_FLAG_40MHZ) + *pos++ = 1; + else /* 20 MHz */ + *pos++ = 0; + /* MCS/NSS */ + *pos = (status->rate_idx << 4) | status->vht_nss; + pos += 4; + /* coding field */ + pos++; + /* group ID */ + pos++; + /* partial_aid */ + pos += 2; + } + if (status->vendor_radiotap_len) { /* ensure 2 byte alignment for the vendor field as required */ if ((pos - (u8 *)rthdr) & 1) -- cgit v1.2.3-59-g8ed1b From 53cabad70ecf0c245b41285de64a74a6c3ee9933 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 14 Nov 2012 15:17:28 +0100 Subject: nl80211: support P2P GO powersave configuration If a driver supports P2P GO powersave, allow it to set the new feature flags for it and allow userspace to configure the parameters for it. This can be done at GO startup and later changed with SET_BSS. Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 7 ++++++ include/uapi/linux/nl80211.h | 16 +++++++++++++ net/wireless/nl80211.c | 56 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index a238f41e55c2..731b48fa238b 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -538,6 +538,8 @@ struct cfg80211_beacon_data { * @privacy: the BSS uses privacy * @auth_type: Authentication type (algorithm) * @inactivity_timeout: time in seconds to determine station's inactivity. + * @p2p_ctwindow: P2P CT Window + * @p2p_opp_ps: P2P opportunistic PS */ struct cfg80211_ap_settings { struct cfg80211_chan_def chandef; @@ -552,6 +554,8 @@ struct cfg80211_ap_settings { bool privacy; enum nl80211_auth_type auth_type; int inactivity_timeout; + u8 p2p_ctwindow; + bool p2p_opp_ps; }; /** @@ -913,6 +917,8 @@ struct mpath_info { * @ap_isolate: do not forward packets between connected stations * @ht_opmode: HT Operation mode * (u16 = opmode, -1 = do not change) + * @p2p_ctwindow: P2P CT Window (-1 = no change) + * @p2p_opp_ps: P2P opportunistic PS (-1 = no change) */ struct bss_parameters { int use_cts_prot; @@ -922,6 +928,7 @@ struct bss_parameters { u8 basic_rates_len; int ap_isolate; int ht_opmode; + s8 p2p_ctwindow, p2p_opp_ps; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 33a417481ad8..e3e19f8b16f2 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1303,6 +1303,13 @@ enum nl80211_commands { * * @NL80211_ATTR_SCAN_FLAGS: scan request control flags (u32) * + * @NL80211_ATTR_P2P_CTWINDOW: P2P GO Client Traffic Window (u8), used with + * the START_AP and SET_BSS commands + * @NL80211_ATTR_P2P_OPPPS: P2P GO opportunistic PS (u8), used with the + * START_AP and SET_BSS commands. This can have the values 0 or 1; + * if not given in START_AP 0 is assumed, if not given in SET_BSS + * no change is made. + * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -1570,6 +1577,9 @@ enum nl80211_attrs { NL80211_ATTR_CENTER_FREQ1, NL80211_ATTR_CENTER_FREQ2, + NL80211_ATTR_P2P_CTWINDOW, + NL80211_ATTR_P2P_OPPPS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -3126,6 +3136,10 @@ enum nl80211_ap_sme_features { * @NL80211_FEATURE_NEED_OBSS_SCAN: The driver expects userspace to perform * OBSS scans and generate 20/40 BSS coex reports. This flag is used only * for drivers implementing the CONNECT API, for AUTH/ASSOC it is implied. + * @NL80211_FEATURE_P2P_GO_CTWIN: P2P GO implementation supports CT Window + * setting + * @NL80211_FEATURE_P2P_GO_OPPPS: P2P GO implementation supports opportunistic + * powersave */ enum nl80211_feature_flags { NL80211_FEATURE_SK_TX_STATUS = 1 << 0, @@ -3139,6 +3153,8 @@ enum nl80211_feature_flags { NL80211_FEATURE_AP_SCAN = 1 << 8, NL80211_FEATURE_VIF_TXPOWER = 1 << 9, NL80211_FEATURE_NEED_OBSS_SCAN = 1 << 10, + NL80211_FEATURE_P2P_GO_CTWIN = 1 << 11, + NL80211_FEATURE_P2P_GO_OPPPS = 1 << 12, }; /** diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index eb0aa71a02b3..7f53aafd47f7 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -363,6 +363,8 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = { [NL80211_ATTR_SAE_DATA] = { .type = NLA_BINARY, }, [NL80211_ATTR_VHT_CAPABILITY] = { .len = NL80211_VHT_CAPABILITY_LEN }, [NL80211_ATTR_SCAN_FLAGS] = { .type = NLA_U32 }, + [NL80211_ATTR_P2P_CTWINDOW] = { .type = NLA_U8 }, + [NL80211_ATTR_P2P_OPPPS] = { .type = NLA_U8 }, }; /* policy for the key attributes */ @@ -2702,6 +2704,32 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info) info->attrs[NL80211_ATTR_INACTIVITY_TIMEOUT]); } + if (info->attrs[NL80211_ATTR_P2P_CTWINDOW]) { + if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) + return -EINVAL; + params.p2p_ctwindow = + nla_get_u8(info->attrs[NL80211_ATTR_P2P_CTWINDOW]); + if (params.p2p_ctwindow > 127) + return -EINVAL; + if (params.p2p_ctwindow != 0 && + !(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_CTWIN)) + return -EINVAL; + } + + if (info->attrs[NL80211_ATTR_P2P_OPPPS]) { + u8 tmp; + + if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) + return -EINVAL; + tmp = nla_get_u8(info->attrs[NL80211_ATTR_P2P_OPPPS]); + if (tmp > 1) + return -EINVAL; + params.p2p_opp_ps = tmp; + if (params.p2p_opp_ps != 0 && + !(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_OPPPS)) + return -EINVAL; + } + if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) { err = nl80211_parse_chandef(rdev, info, ¶ms.chandef); if (err) @@ -3668,6 +3696,8 @@ static int nl80211_set_bss(struct sk_buff *skb, struct genl_info *info) params.use_short_slot_time = -1; params.ap_isolate = -1; params.ht_opmode = -1; + params.p2p_ctwindow = -1; + params.p2p_opp_ps = -1; if (info->attrs[NL80211_ATTR_BSS_CTS_PROT]) params.use_cts_prot = @@ -3690,6 +3720,32 @@ static int nl80211_set_bss(struct sk_buff *skb, struct genl_info *info) params.ht_opmode = nla_get_u16(info->attrs[NL80211_ATTR_BSS_HT_OPMODE]); + if (info->attrs[NL80211_ATTR_P2P_CTWINDOW]) { + if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) + return -EINVAL; + params.p2p_ctwindow = + nla_get_s8(info->attrs[NL80211_ATTR_P2P_CTWINDOW]); + if (params.p2p_ctwindow < 0) + return -EINVAL; + if (params.p2p_ctwindow != 0 && + !(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_CTWIN)) + return -EINVAL; + } + + if (info->attrs[NL80211_ATTR_P2P_OPPPS]) { + u8 tmp; + + if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) + return -EINVAL; + tmp = nla_get_u8(info->attrs[NL80211_ATTR_P2P_OPPPS]); + if (tmp > 1) + return -EINVAL; + params.p2p_opp_ps = tmp; + if (params.p2p_opp_ps && + !(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_OPPPS)) + return -EINVAL; + } + if (!rdev->ops->change_bss) return -EOPNOTSUPP; -- cgit v1.2.3-59-g8ed1b From b9a9ada14aab17f08c1d9735601f1097cdcfc6de Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Thu, 29 Nov 2012 13:00:10 +0100 Subject: mac80211: remove probe response temporary buffer allocation Instead of allocating a temporary buffer to build IEs build them right into the SKB. Signed-off-by: Johannes Berg --- drivers/net/wireless/mac80211_hwsim.c | 7 ++++++- drivers/net/wireless/ti/wl1251/main.c | 4 +++- drivers/net/wireless/ti/wlcore/cmd.c | 4 +++- include/net/mac80211.h | 5 ++--- net/mac80211/tx.c | 9 ++------- net/mac80211/util.c | 26 +++++++++----------------- 6 files changed, 25 insertions(+), 30 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c index 2aa8a1aa1184..8a61dbd320e6 100644 --- a/drivers/net/wireless/mac80211_hwsim.c +++ b/drivers/net/wireless/mac80211_hwsim.c @@ -1347,9 +1347,14 @@ static void hw_scan_work(struct work_struct *work) hwsim->hw_scan_vif, req->ssids[i].ssid, req->ssids[i].ssid_len, - req->ie, req->ie_len); + req->ie_len); if (!probe) continue; + + if (req->ie_len) + memcpy(skb_put(probe, req->ie_len), req->ie, + req->ie_len); + local_bh_disable(); mac80211_hwsim_tx_frame(hwsim->hw, probe, hwsim->tmp_chan); diff --git a/drivers/net/wireless/ti/wl1251/main.c b/drivers/net/wireless/ti/wl1251/main.c index 441cbccbd381..f47e8b0482ad 100644 --- a/drivers/net/wireless/ti/wl1251/main.c +++ b/drivers/net/wireless/ti/wl1251/main.c @@ -896,11 +896,13 @@ static int wl1251_op_hw_scan(struct ieee80211_hw *hw, goto out; skb = ieee80211_probereq_get(wl->hw, wl->vif, ssid, ssid_len, - req->ie, req->ie_len); + req->ie_len); if (!skb) { ret = -ENOMEM; goto out; } + if (req->ie_len) + memcpy(skb_put(skb, req->ie_len), req->ie, req->ie_len); ret = wl1251_cmd_template_set(wl, CMD_PROBE_REQ, skb->data, skb->len); diff --git a/drivers/net/wireless/ti/wlcore/cmd.c b/drivers/net/wireless/ti/wlcore/cmd.c index eaef3f41b252..27f83f72a93b 100644 --- a/drivers/net/wireless/ti/wlcore/cmd.c +++ b/drivers/net/wireless/ti/wlcore/cmd.c @@ -1038,11 +1038,13 @@ int wl12xx_cmd_build_probe_req(struct wl1271 *wl, struct wl12xx_vif *wlvif, u16 template_id_5 = CMD_TEMPL_CFG_PROBE_REQ_5; skb = ieee80211_probereq_get(wl->hw, vif, ssid, ssid_len, - ie, ie_len); + ie_len); if (!skb) { ret = -ENOMEM; goto out; } + if (ie_len) + memcpy(skb_put(skb, ie_len), ie, ie_len); wl1271_dump(DEBUG_SCAN, "PROBE REQ: ", skb->data, skb->len); diff --git a/include/net/mac80211.h b/include/net/mac80211.h index e806f1d81f66..0472d806fb41 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -3144,8 +3144,7 @@ struct sk_buff *ieee80211_nullfunc_get(struct ieee80211_hw *hw, * @vif: &struct ieee80211_vif pointer from the add_interface callback. * @ssid: SSID buffer * @ssid_len: length of SSID - * @ie: buffer containing all IEs except SSID for the template - * @ie_len: length of the IE buffer + * @tailroom: tailroom to reserve at end of SKB for IEs * * Creates a Probe Request template which can, for example, be uploaded to * hardware. @@ -3153,7 +3152,7 @@ struct sk_buff *ieee80211_nullfunc_get(struct ieee80211_hw *hw, struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *ssid, size_t ssid_len, - const u8 *ie, size_t ie_len); + size_t tailroom); /** * ieee80211_rts_get - RTS frame generation function diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index d8ef3417bf2b..ba1ac9d62b08 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2620,7 +2620,7 @@ EXPORT_SYMBOL(ieee80211_nullfunc_get); struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *ssid, size_t ssid_len, - const u8 *ie, size_t ie_len) + size_t tailroom) { struct ieee80211_sub_if_data *sdata; struct ieee80211_local *local; @@ -2634,7 +2634,7 @@ struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw, ie_ssid_len = 2 + ssid_len; skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*hdr) + - ie_ssid_len + ie_len); + ie_ssid_len + tailroom); if (!skb) return NULL; @@ -2655,11 +2655,6 @@ struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw, memcpy(pos, ssid, ssid_len); pos += ssid_len; - if (ie) { - pos = skb_put(skb, ie_len); - memcpy(pos, ie, ie_len); - } - return skb; } EXPORT_SYMBOL(ieee80211_probereq_get); diff --git a/net/mac80211/util.c b/net/mac80211/util.c index f119b1b6c1a7..41c9841614b8 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -1239,14 +1239,8 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, struct ieee80211_local *local = sdata->local; struct sk_buff *skb; struct ieee80211_mgmt *mgmt; - size_t buf_len; - u8 *buf; u8 chan_no; - - /* FIXME: come up with a proper value */ - buf = kmalloc(200 + ie_len, GFP_KERNEL); - if (!buf) - return NULL; + int ies_len; /* * Do not send DS Channel parameter for directed probe requests @@ -1258,15 +1252,16 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, else chan_no = ieee80211_frequency_to_channel(chan->center_freq); - buf_len = ieee80211_build_preq_ies(local, buf, 200 + ie_len, - ie, ie_len, chan->band, - ratemask, chan_no); - skb = ieee80211_probereq_get(&local->hw, &sdata->vif, - ssid, ssid_len, - buf, buf_len); + ssid, ssid_len, 100 + ie_len); if (!skb) - goto out; + return NULL; + + ies_len = ieee80211_build_preq_ies(local, skb_tail_pointer(skb), + skb_tailroom(skb), + ie, ie_len, chan->band, + ratemask, chan_no); + skb_put(skb, ies_len); if (dst) { mgmt = (struct ieee80211_mgmt *) skb->data; @@ -1276,9 +1271,6 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT; - out: - kfree(buf); - return skb; } -- cgit v1.2.3-59-g8ed1b From 9caf03640279e64d0ba36539b42daa1b43a49486 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Thu, 29 Nov 2012 01:25:20 +0100 Subject: cfg80211: fix BSS struct IE access races When a BSS struct is updated, the IEs are currently overwritten or freed. This can lead to races if some other CPU is accessing the BSS struct and using the IEs concurrently. Fix this by always allocating the IEs in a new struct that holds the data and length and protecting access to this new struct with RCU. Signed-off-by: Johannes Berg --- drivers/net/wireless/libertas/cfg.c | 9 +- drivers/net/wireless/mwifiex/sta_ioctl.c | 35 ++- include/net/cfg80211.h | 41 ++-- net/mac80211/mlme.c | 80 +++--- net/wireless/core.h | 2 - net/wireless/nl80211.c | 27 +- net/wireless/reg.c | 2 +- net/wireless/reg.h | 2 +- net/wireless/scan.c | 409 +++++++++++++++---------------- net/wireless/sme.c | 13 +- net/wireless/util.c | 9 +- net/wireless/wext-sme.c | 8 +- 12 files changed, 353 insertions(+), 284 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/libertas/cfg.c b/drivers/net/wireless/libertas/cfg.c index ec36868f6fc5..ec6d5d6b452e 100644 --- a/drivers/net/wireless/libertas/cfg.c +++ b/drivers/net/wireless/libertas/cfg.c @@ -298,6 +298,7 @@ static int lbs_add_common_rates_tlv(u8 *tlv, struct cfg80211_bss *bss) const u8 *rates_eid, *ext_rates_eid; int n = 0; + rcu_read_lock(); rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES); ext_rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_EXT_SUPP_RATES); @@ -325,6 +326,7 @@ static int lbs_add_common_rates_tlv(u8 *tlv, struct cfg80211_bss *bss) *tlv++ = 0x96; n = 4; } + rcu_read_unlock(); rate_tlv->header.len = cpu_to_le16(n); return sizeof(rate_tlv->header) + n; @@ -1140,11 +1142,13 @@ static int lbs_associate(struct lbs_private *priv, cmd->capability = cpu_to_le16(bss->capability); /* add SSID TLV */ + rcu_read_lock(); ssid_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SSID); if (ssid_eid) pos += lbs_add_ssid_tlv(pos, ssid_eid + 2, ssid_eid[1]); else lbs_deb_assoc("no SSID\n"); + rcu_read_unlock(); /* add DS param TLV */ if (bss->channel) @@ -1782,7 +1786,7 @@ static int lbs_ibss_join_existing(struct lbs_private *priv, struct cfg80211_ibss_params *params, struct cfg80211_bss *bss) { - const u8 *rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES); + const u8 *rates_eid; struct cmd_ds_802_11_ad_hoc_join cmd; u8 preamble = RADIO_PREAMBLE_SHORT; int ret = 0; @@ -1841,6 +1845,8 @@ static int lbs_ibss_join_existing(struct lbs_private *priv, /* set rates to the intersection of our rates and the rates in the bss */ + rcu_read_lock(); + rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES); if (!rates_eid) { lbs_add_rates(cmd.bss.rates); } else { @@ -1860,6 +1866,7 @@ static int lbs_ibss_join_existing(struct lbs_private *priv, } } } + rcu_read_unlock(); /* Only v8 and below support setting this */ if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8) { diff --git a/drivers/net/wireless/mwifiex/sta_ioctl.c b/drivers/net/wireless/mwifiex/sta_ioctl.c index 24af6ba7d8a1..5d7b83e2dc4d 100644 --- a/drivers/net/wireless/mwifiex/sta_ioctl.c +++ b/drivers/net/wireless/mwifiex/sta_ioctl.c @@ -158,12 +158,22 @@ int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv, struct cfg80211_bss *bss, struct mwifiex_bssdescriptor *bss_desc) { - int ret; + int ret, beacon_ie_len; u8 *beacon_ie; struct mwifiex_bss_priv *bss_priv = (void *)bss->priv; + const struct cfg80211_bss_ies *ies; + + rcu_read_lock(); + ies = rcu_dereference(bss->ies); + if (WARN_ON(!ies)) { + /* should never happen */ + rcu_read_unlock(); + return -EINVAL; + } + beacon_ie = kmemdup(ies->data, ies->len, GFP_ATOMIC); + beacon_ie_len = ies->len; + rcu_read_unlock(); - beacon_ie = kmemdup(bss->information_elements, bss->len_beacon_ies, - GFP_KERNEL); if (!beacon_ie) { dev_err(priv->adapter->dev, " failed to alloc beacon_ie\n"); return -ENOMEM; @@ -172,7 +182,7 @@ int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv, memcpy(bss_desc->mac_address, bss->bssid, ETH_ALEN); bss_desc->rssi = bss->signal; bss_desc->beacon_buf = beacon_ie; - bss_desc->beacon_buf_size = bss->len_beacon_ies; + bss_desc->beacon_buf_size = beacon_ie_len; bss_desc->beacon_period = bss->beacon_interval; bss_desc->cap_info_bitmap = bss->capability; bss_desc->bss_band = bss_priv->band; @@ -198,18 +208,23 @@ int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv, static int mwifiex_process_country_ie(struct mwifiex_private *priv, struct cfg80211_bss *bss) { - u8 *country_ie, country_ie_len; + const u8 *country_ie; + u8 country_ie_len; struct mwifiex_802_11d_domain_reg *domain_info = &priv->adapter->domain_reg; - country_ie = (u8 *)ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); - - if (!country_ie) + rcu_read_lock(); + country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); + if (!country_ie) { + rcu_read_unlock(); return 0; + } country_ie_len = country_ie[1]; - if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) + if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) { + rcu_read_unlock(); return 0; + } domain_info->country_code[0] = country_ie[2]; domain_info->country_code[1] = country_ie[3]; @@ -223,6 +238,8 @@ static int mwifiex_process_country_ie(struct mwifiex_private *priv, memcpy((u8 *)domain_info->triplet, &country_ie[2] + IEEE80211_COUNTRY_STRING_LEN, country_ie_len); + rcu_read_unlock(); + if (mwifiex_send_cmd_async(priv, HostCmd_CMD_802_11D_DOMAIN_INFO, HostCmd_ACT_GEN_SET, 0, NULL)) { wiphy_err(priv->adapter->wiphy, diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 731b48fa238b..8e6a6b73b9c9 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1205,6 +1205,18 @@ enum cfg80211_signal_type { CFG80211_SIGNAL_TYPE_UNSPEC, }; +/** + * struct cfg80211_bss_ie_data - BSS entry IE data + * @rcu_head: internal use, for freeing + * @len: length of the IEs + * @data: IE data + */ +struct cfg80211_bss_ies { + struct rcu_head rcu_head; + int len; + u8 data[]; +}; + /** * struct cfg80211_bss - BSS description * @@ -1216,36 +1228,34 @@ enum cfg80211_signal_type { * @tsf: timestamp of last received update * @beacon_interval: the beacon interval as from the frame * @capability: the capability field in host byte order - * @information_elements: the information elements (Note that there + * @ies: the information elements (Note that there * is no guarantee that these are well-formed!); this is a pointer to * either the beacon_ies or proberesp_ies depending on whether Probe * Response frame has been received - * @len_information_elements: total length of the information elements * @beacon_ies: the information elements from the last Beacon frame - * @len_beacon_ies: total length of the beacon_ies * @proberesp_ies: the information elements from the last Probe Response frame - * @len_proberesp_ies: total length of the proberesp_ies * @signal: signal strength value (type depends on the wiphy's signal_type) * @free_priv: function pointer to free private data * @priv: private area for driver use, has at least wiphy->bss_priv_size bytes */ struct cfg80211_bss { + u64 tsf; + struct ieee80211_channel *channel; - u8 bssid[ETH_ALEN]; - u64 tsf; + const struct cfg80211_bss_ies __rcu *ies; + const struct cfg80211_bss_ies __rcu *beacon_ies; + const struct cfg80211_bss_ies __rcu *proberesp_ies; + + void (*free_priv)(struct cfg80211_bss *bss); + + s32 signal; + u16 beacon_interval; u16 capability; - u8 *information_elements; - size_t len_information_elements; - u8 *beacon_ies; - size_t len_beacon_ies; - u8 *proberesp_ies; - size_t len_proberesp_ies; - s32 signal; + u8 bssid[ETH_ALEN]; - void (*free_priv)(struct cfg80211_bss *bss); u8 priv[0] __attribute__((__aligned__(sizeof(void *)))); }; @@ -1253,6 +1263,9 @@ struct cfg80211_bss { * ieee80211_bss_get_ie - find IE with given ID * @bss: the bss to search * @ie: the IE ID + * + * Note that the return value is an RCU-protected pointer, so + * rcu_read_lock() must be held when calling this function. * Returns %NULL if not found. */ const u8 *ieee80211_bss_get_ie(struct cfg80211_bss *bss, u8 ie); diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 35d8cffc973a..481d5035b397 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -1382,19 +1382,26 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata, sdata->u.mgd.flags |= IEEE80211_STA_RESET_SIGNAL_AVE; if (sdata->vif.p2p) { - u8 noa[2]; - int ret; + const struct cfg80211_bss_ies *ies; - ret = cfg80211_get_p2p_attr(cbss->information_elements, - cbss->len_information_elements, - IEEE80211_P2P_ATTR_ABSENCE_NOTICE, - noa, sizeof(noa)); - if (ret >= 2) { - bss_conf->p2p_oppps = noa[1] & 0x80; - bss_conf->p2p_ctwindow = noa[1] & 0x7f; - bss_info_changed |= BSS_CHANGED_P2P_PS; - sdata->u.mgd.p2p_noa_index = noa[0]; + rcu_read_lock(); + ies = rcu_dereference(cbss->ies); + if (ies) { + u8 noa[2]; + int ret; + + ret = cfg80211_get_p2p_attr( + ies->data, ies->len, + IEEE80211_P2P_ATTR_ABSENCE_NOTICE, + noa, sizeof(noa)); + if (ret >= 2) { + bss_conf->p2p_oppps = noa[1] & 0x80; + bss_conf->p2p_ctwindow = noa[1] & 0x7f; + bss_info_changed |= BSS_CHANGED_P2P_PS; + sdata->u.mgd.p2p_noa_index = noa[0]; + } } + rcu_read_unlock(); } /* just to be sure */ @@ -1659,6 +1666,7 @@ static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata) } else { int ssid_len; + rcu_read_lock(); ssid = ieee80211_bss_get_ie(ifmgd->associated, WLAN_EID_SSID); if (WARN_ON_ONCE(ssid == NULL)) ssid_len = 0; @@ -1668,6 +1676,7 @@ static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata) ieee80211_send_probe_req(sdata, dst, ssid + 2, ssid_len, NULL, 0, (u32) -1, true, false, ifmgd->associated->channel, false); + rcu_read_unlock(); } ifmgd->probe_timeout = jiffies + msecs_to_jiffies(probe_wait_ms); @@ -1763,6 +1772,7 @@ struct sk_buff *ieee80211_ap_probereq_get(struct ieee80211_hw *hw, else return NULL; + rcu_read_lock(); ssid = ieee80211_bss_get_ie(cbss, WLAN_EID_SSID); if (WARN_ON_ONCE(ssid == NULL)) ssid_len = 0; @@ -1773,6 +1783,7 @@ struct sk_buff *ieee80211_ap_probereq_get(struct ieee80211_hw *hw, (u32) -1, cbss->channel, ssid + 2, ssid_len, NULL, 0, true); + rcu_read_unlock(); return skb; } @@ -2858,9 +2869,12 @@ static int ieee80211_probe_auth(struct ieee80211_sub_if_data *sdata) auth_data->bss->bssid, auth_data->tries, IEEE80211_AUTH_MAX_TRIES); + rcu_read_lock(); ssidie = ieee80211_bss_get_ie(auth_data->bss, WLAN_EID_SSID); - if (!ssidie) + if (!ssidie) { + rcu_read_unlock(); return -EINVAL; + } /* * Direct probe is sent to broadcast address as some APs * will not answer to direct packet in unassociated state. @@ -2868,6 +2882,7 @@ static int ieee80211_probe_auth(struct ieee80211_sub_if_data *sdata) ieee80211_send_probe_req(sdata, NULL, ssidie + 2, ssidie[1], NULL, 0, (u32) -1, true, false, auth_data->bss->channel, false); + rcu_read_unlock(); } auth_data->timeout = jiffies + IEEE80211_AUTH_TIMEOUT; @@ -3404,9 +3419,7 @@ static u8 ieee80211_ht_vht_rx_chains(struct ieee80211_sub_if_data *sdata, if (ifmgd->flags & IEEE80211_STA_DISABLE_HT) return chains; - ht_cap_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, - cbss->information_elements, - cbss->len_information_elements); + ht_cap_ie = ieee80211_bss_get_ie(cbss, WLAN_EID_HT_CAPABILITY); if (ht_cap_ie && ht_cap_ie[1] >= sizeof(*ht_cap)) { ht_cap = (void *)(ht_cap_ie + 2); chains = ieee80211_mcs_to_chains(&ht_cap->mcs); @@ -3419,9 +3432,7 @@ static u8 ieee80211_ht_vht_rx_chains(struct ieee80211_sub_if_data *sdata, if (ifmgd->flags & IEEE80211_STA_DISABLE_VHT) return chains; - vht_cap_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, - cbss->information_elements, - cbss->len_information_elements); + vht_cap_ie = ieee80211_bss_get_ie(cbss, WLAN_EID_VHT_CAPABILITY); if (vht_cap_ie && vht_cap_ie[1] >= sizeof(*vht_cap)) { u8 nss; u16 tx_mcs_map; @@ -3457,13 +3468,13 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, IEEE80211_STA_DISABLE_80P80MHZ | IEEE80211_STA_DISABLE_160MHZ); + rcu_read_lock(); + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT) && sband->ht_cap.ht_supported) { const u8 *ht_oper_ie; - ht_oper_ie = cfg80211_find_ie(WLAN_EID_HT_OPERATION, - cbss->information_elements, - cbss->len_information_elements); + ht_oper_ie = ieee80211_bss_get_ie(cbss, WLAN_EID_HT_OPERATION); if (ht_oper_ie && ht_oper_ie[1] >= sizeof(*ht_oper)) ht_oper = (void *)(ht_oper_ie + 2); } @@ -3472,9 +3483,8 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, sband->vht_cap.vht_supported) { const u8 *vht_oper_ie; - vht_oper_ie = cfg80211_find_ie(WLAN_EID_VHT_OPERATION, - cbss->information_elements, - cbss->len_information_elements); + vht_oper_ie = ieee80211_bss_get_ie(cbss, + WLAN_EID_VHT_OPERATION); if (vht_oper_ie && vht_oper_ie[1] >= sizeof(*vht_oper)) vht_oper = (void *)(vht_oper_ie + 2); if (vht_oper && !ht_oper) { @@ -3494,6 +3504,8 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, sdata->needed_rx_chains = min(ieee80211_ht_vht_rx_chains(sdata, cbss), local->rx_chains); + rcu_read_unlock(); + /* will change later if needed */ sdata->smps_mode = IEEE80211_SMPS_OFF; @@ -3734,14 +3746,21 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, const u8 *ssidie, *ht_ie; int i, err; - ssidie = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID); - if (!ssidie) - return -EINVAL; - assoc_data = kzalloc(sizeof(*assoc_data) + req->ie_len, GFP_KERNEL); if (!assoc_data) return -ENOMEM; + rcu_read_lock(); + ssidie = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID); + if (!ssidie) { + rcu_read_unlock(); + kfree(assoc_data); + return -EINVAL; + } + memcpy(assoc_data->ssid, ssidie + 2, ssidie[1]); + assoc_data->ssid_len = ssidie[1]; + rcu_read_unlock(); + mutex_lock(&ifmgd->mtx); if (ifmgd->associated) @@ -3836,12 +3855,14 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, assoc_data->supp_rates = bss->supp_rates; assoc_data->supp_rates_len = bss->supp_rates_len; + rcu_read_lock(); ht_ie = ieee80211_bss_get_ie(req->bss, WLAN_EID_HT_OPERATION); if (ht_ie && ht_ie[1] >= sizeof(struct ieee80211_ht_operation)) assoc_data->ap_ht_param = ((struct ieee80211_ht_operation *)(ht_ie + 2))->ht_param; else ifmgd->flags |= IEEE80211_STA_DISABLE_HT; + rcu_read_unlock(); if (bss->wmm_used && bss->uapsd_supported && (sdata->local->hw.flags & IEEE80211_HW_SUPPORTS_UAPSD)) { @@ -3852,9 +3873,6 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, ifmgd->flags &= ~IEEE80211_STA_UAPSD_ENABLED; } - memcpy(assoc_data->ssid, ssidie + 2, ssidie[1]); - assoc_data->ssid_len = ssidie[1]; - if (req->prev_bssid) memcpy(assoc_data->prev_bssid, req->prev_bssid, ETH_ALEN); diff --git a/net/wireless/core.h b/net/wireless/core.h index 6183a0d25b8b..3563097169cb 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -138,8 +138,6 @@ struct cfg80211_internal_bss { unsigned long ts; struct kref ref; atomic_t hold; - bool beacon_ies_allocated; - bool proberesp_ies_allocated; /* must be last because of priv member */ struct cfg80211_bss pub; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 4a719770eaaf..f45706adaf34 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -4808,6 +4808,7 @@ static int nl80211_send_bss(struct sk_buff *msg, struct netlink_callback *cb, struct cfg80211_internal_bss *intbss) { struct cfg80211_bss *res = &intbss->pub; + const struct cfg80211_bss_ies *ies; void *hdr; struct nlattr *bss; @@ -4828,16 +4829,24 @@ static int nl80211_send_bss(struct sk_buff *msg, struct netlink_callback *cb, if (!bss) goto nla_put_failure; if ((!is_zero_ether_addr(res->bssid) && - nla_put(msg, NL80211_BSS_BSSID, ETH_ALEN, res->bssid)) || - (res->information_elements && res->len_information_elements && - nla_put(msg, NL80211_BSS_INFORMATION_ELEMENTS, - res->len_information_elements, - res->information_elements)) || - (res->beacon_ies && res->len_beacon_ies && - res->beacon_ies != res->information_elements && - nla_put(msg, NL80211_BSS_BEACON_IES, - res->len_beacon_ies, res->beacon_ies))) + nla_put(msg, NL80211_BSS_BSSID, ETH_ALEN, res->bssid))) goto nla_put_failure; + + rcu_read_lock(); + ies = rcu_dereference(res->ies); + if (ies && ies->len && nla_put(msg, NL80211_BSS_INFORMATION_ELEMENTS, + ies->len, ies->data)) { + rcu_read_unlock(); + goto nla_put_failure; + } + ies = rcu_dereference(res->beacon_ies); + if (ies && ies->len && nla_put(msg, NL80211_BSS_BEACON_IES, + ies->len, ies->data)) { + rcu_read_unlock(); + goto nla_put_failure; + } + rcu_read_unlock(); + if (res->tsf && nla_put_u64(msg, NL80211_BSS_TSF, res->tsf)) goto nla_put_failure; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index bcc7d7ee5a51..b6c7ea6d98f8 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -1797,7 +1797,7 @@ EXPORT_SYMBOL(regulatory_hint); */ void regulatory_hint_11d(struct wiphy *wiphy, enum ieee80211_band band, - u8 *country_ie, + const u8 *country_ie, u8 country_ie_len) { char alpha2[2]; diff --git a/net/wireless/reg.h b/net/wireless/reg.h index f023c8a31c60..4c0a32ffd530 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -81,7 +81,7 @@ int regulatory_hint_found_beacon(struct wiphy *wiphy, */ void regulatory_hint_11d(struct wiphy *wiphy, enum ieee80211_band band, - u8 *country_ie, + const u8 *country_ie, u8 country_ie_len); /** diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 834e0d153fbe..01592d7d4789 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -23,6 +23,7 @@ static void bss_release(struct kref *ref) { + struct cfg80211_bss_ies *ies; struct cfg80211_internal_bss *bss; bss = container_of(ref, struct cfg80211_internal_bss, ref); @@ -33,10 +34,12 @@ static void bss_release(struct kref *ref) if (bss->pub.free_priv) bss->pub.free_priv(&bss->pub); - if (bss->beacon_ies_allocated) - kfree(bss->pub.beacon_ies); - if (bss->proberesp_ies_allocated) - kfree(bss->pub.proberesp_ies); + ies = (void *)rcu_access_pointer(bss->pub.beacon_ies); + if (ies) + kfree_rcu(ies, rcu_head); + ies = (void *)rcu_access_pointer(bss->pub.proberesp_ies); + if (ies) + kfree_rcu(ies, rcu_head); kfree(bss); } @@ -288,7 +291,7 @@ const u8 *cfg80211_find_vendor_ie(unsigned int oui, u8 oui_type, } EXPORT_SYMBOL(cfg80211_find_vendor_ie); -static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2) +static int cmp_ies(u8 num, const u8 *ies1, int len1, const u8 *ies2, int len2) { const u8 *ie1 = cfg80211_find_ie(num, ies1, len1); const u8 *ie2 = cfg80211_find_ie(num, ies2, len2); @@ -311,6 +314,7 @@ static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2) static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, const u8 *ssid, size_t ssid_len) { + const struct cfg80211_bss_ies *ies; const u8 *ssidie; if (bssid && !ether_addr_equal(a->bssid, bssid)) @@ -319,9 +323,10 @@ static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, if (!ssid) return true; - ssidie = cfg80211_find_ie(WLAN_EID_SSID, - a->information_elements, - a->len_information_elements); + ies = rcu_access_pointer(a->ies); + if (!ies) + return false; + ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); if (!ssidie) return false; if (ssidie[1] != ssid_len) @@ -331,20 +336,21 @@ static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, static bool is_mesh_bss(struct cfg80211_bss *a) { + const struct cfg80211_bss_ies *ies; const u8 *ie; if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability)) return false; - ie = cfg80211_find_ie(WLAN_EID_MESH_ID, - a->information_elements, - a->len_information_elements); + ies = rcu_access_pointer(a->ies); + if (!ies) + return false; + + ie = cfg80211_find_ie(WLAN_EID_MESH_ID, ies->data, ies->len); if (!ie) return false; - ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, - a->information_elements, - a->len_information_elements); + ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, ies->data, ies->len); if (!ie) return false; @@ -355,14 +361,17 @@ static bool is_mesh(struct cfg80211_bss *a, const u8 *meshid, size_t meshidlen, const u8 *meshcfg) { + const struct cfg80211_bss_ies *ies; const u8 *ie; if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability)) return false; - ie = cfg80211_find_ie(WLAN_EID_MESH_ID, - a->information_elements, - a->len_information_elements); + ies = rcu_access_pointer(a->ies); + if (!ies) + return false; + + ie = cfg80211_find_ie(WLAN_EID_MESH_ID, ies->data, ies->len); if (!ie) return false; if (ie[1] != meshidlen) @@ -370,9 +379,7 @@ static bool is_mesh(struct cfg80211_bss *a, if (memcmp(ie + 2, meshid, meshidlen)) return false; - ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, - a->information_elements, - a->len_information_elements); + ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, ies->data, ies->len); if (!ie) return false; if (ie[1] != sizeof(struct ieee80211_meshconf_ie)) @@ -389,24 +396,28 @@ static bool is_mesh(struct cfg80211_bss *a, static int cmp_bss_core(struct cfg80211_bss *a, struct cfg80211_bss *b) { + const struct cfg80211_bss_ies *a_ies, *b_ies; int r; if (a->channel != b->channel) return b->channel->center_freq - a->channel->center_freq; if (is_mesh_bss(a) && is_mesh_bss(b)) { + a_ies = rcu_access_pointer(a->ies); + if (!a_ies) + return -1; + b_ies = rcu_access_pointer(b->ies); + if (!b_ies) + return 1; + r = cmp_ies(WLAN_EID_MESH_ID, - a->information_elements, - a->len_information_elements, - b->information_elements, - b->len_information_elements); + a_ies->data, a_ies->len, + b_ies->data, b_ies->len); if (r) return r; return cmp_ies(WLAN_EID_MESH_CONFIG, - a->information_elements, - a->len_information_elements, - b->information_elements, - b->len_information_elements); + a_ies->data, a_ies->len, + b_ies->data, b_ies->len); } /* @@ -419,21 +430,28 @@ static int cmp_bss_core(struct cfg80211_bss *a, struct cfg80211_bss *b) static int cmp_bss(struct cfg80211_bss *a, struct cfg80211_bss *b) { + const struct cfg80211_bss_ies *a_ies, *b_ies; int r; r = cmp_bss_core(a, b); if (r) return r; + a_ies = rcu_access_pointer(a->ies); + if (!a_ies) + return -1; + b_ies = rcu_access_pointer(b->ies); + if (!b_ies) + return 1; + return cmp_ies(WLAN_EID_SSID, - a->information_elements, - a->len_information_elements, - b->information_elements, - b->len_information_elements); + a_ies->data, a_ies->len, + b_ies->data, b_ies->len); } static int cmp_hidden_bss(struct cfg80211_bss *a, struct cfg80211_bss *b) { + const struct cfg80211_bss_ies *a_ies, *b_ies; const u8 *ie1; const u8 *ie2; int i; @@ -443,12 +461,15 @@ static int cmp_hidden_bss(struct cfg80211_bss *a, struct cfg80211_bss *b) if (r) return r; - ie1 = cfg80211_find_ie(WLAN_EID_SSID, - a->information_elements, - a->len_information_elements); - ie2 = cfg80211_find_ie(WLAN_EID_SSID, - b->information_elements, - b->len_information_elements); + a_ies = rcu_access_pointer(a->ies); + if (!a_ies) + return -1; + b_ies = rcu_access_pointer(b->ies); + if (!b_ies) + return 1; + + ie1 = cfg80211_find_ie(WLAN_EID_SSID, a_ies->data, a_ies->len); + ie2 = cfg80211_find_ie(WLAN_EID_SSID, b_ies->data, b_ies->len); /* * Key comparator must use same algorithm in any rb-tree @@ -633,126 +654,84 @@ static void copy_hidden_ies(struct cfg80211_internal_bss *res, struct cfg80211_internal_bss *hidden) { - if (unlikely(res->pub.beacon_ies)) - return; - if (WARN_ON(!hidden->pub.beacon_ies)) + const struct cfg80211_bss_ies *ies; + + if (rcu_access_pointer(res->pub.beacon_ies)) return; - res->pub.beacon_ies = kmalloc(hidden->pub.len_beacon_ies, GFP_ATOMIC); - if (unlikely(!res->pub.beacon_ies)) + ies = rcu_access_pointer(hidden->pub.beacon_ies); + if (WARN_ON(!ies)) return; - res->beacon_ies_allocated = true; - res->pub.len_beacon_ies = hidden->pub.len_beacon_ies; - memcpy(res->pub.beacon_ies, hidden->pub.beacon_ies, - res->pub.len_beacon_ies); + ies = kmemdup(ies, sizeof(*ies) + ies->len, GFP_ATOMIC); + if (unlikely(!ies)) + return; + rcu_assign_pointer(res->pub.beacon_ies, ies); } static struct cfg80211_internal_bss * cfg80211_bss_update(struct cfg80211_registered_device *dev, - struct cfg80211_internal_bss *res) + struct cfg80211_internal_bss *tmp) { struct cfg80211_internal_bss *found = NULL; - /* - * The reference to "res" is donated to this function. - */ - - if (WARN_ON(!res->pub.channel)) { - kref_put(&res->ref, bss_release); + if (WARN_ON(!tmp->pub.channel)) return NULL; - } - res->ts = jiffies; + tmp->ts = jiffies; spin_lock_bh(&dev->bss_lock); - found = rb_find_bss(dev, res); + if (WARN_ON(!rcu_access_pointer(tmp->pub.ies))) { + spin_unlock_bh(&dev->bss_lock); + return NULL; + } + + found = rb_find_bss(dev, tmp); if (found) { - found->pub.beacon_interval = res->pub.beacon_interval; - found->pub.tsf = res->pub.tsf; - found->pub.signal = res->pub.signal; - found->pub.capability = res->pub.capability; - found->ts = res->ts; + found->pub.beacon_interval = tmp->pub.beacon_interval; + found->pub.tsf = tmp->pub.tsf; + found->pub.signal = tmp->pub.signal; + found->pub.capability = tmp->pub.capability; + found->ts = tmp->ts; /* Update IEs */ - if (res->pub.proberesp_ies) { - size_t used = dev->wiphy.bss_priv_size + sizeof(*res); - size_t ielen = res->pub.len_proberesp_ies; - - if (found->pub.proberesp_ies && - !found->proberesp_ies_allocated && - ksize(found) >= used + ielen) { - memcpy(found->pub.proberesp_ies, - res->pub.proberesp_ies, ielen); - found->pub.len_proberesp_ies = ielen; - } else { - u8 *ies = found->pub.proberesp_ies; - - if (found->proberesp_ies_allocated) - ies = krealloc(ies, ielen, GFP_ATOMIC); - else - ies = kmalloc(ielen, GFP_ATOMIC); - - if (ies) { - memcpy(ies, res->pub.proberesp_ies, - ielen); - found->proberesp_ies_allocated = true; - found->pub.proberesp_ies = ies; - found->pub.len_proberesp_ies = ielen; - } - } + if (rcu_access_pointer(tmp->pub.proberesp_ies)) { + const struct cfg80211_bss_ies *old; + + old = rcu_access_pointer(found->pub.proberesp_ies); + rcu_assign_pointer(found->pub.proberesp_ies, + tmp->pub.proberesp_ies); /* Override possible earlier Beacon frame IEs */ - found->pub.information_elements = - found->pub.proberesp_ies; - found->pub.len_information_elements = - found->pub.len_proberesp_ies; - } + rcu_assign_pointer(found->pub.ies, + tmp->pub.proberesp_ies); + if (old) + kfree_rcu((struct cfg80211_bss_ies *)old, + rcu_head); + } else if (rcu_access_pointer(tmp->pub.beacon_ies)) { + const struct cfg80211_bss_ies *old, *ies; - if (res->pub.beacon_ies) { - size_t used = dev->wiphy.bss_priv_size + sizeof(*res); - size_t ielen = res->pub.len_beacon_ies; - bool information_elements_is_beacon_ies = - (found->pub.information_elements == - found->pub.beacon_ies); - - if (found->pub.beacon_ies && - !found->beacon_ies_allocated && - ksize(found) >= used + ielen) { - memcpy(found->pub.beacon_ies, - res->pub.beacon_ies, ielen); - found->pub.len_beacon_ies = ielen; - } else { - u8 *ies = found->pub.beacon_ies; - - if (found->beacon_ies_allocated) - ies = krealloc(ies, ielen, GFP_ATOMIC); - else - ies = kmalloc(ielen, GFP_ATOMIC); - - if (ies) { - memcpy(ies, res->pub.beacon_ies, - ielen); - found->beacon_ies_allocated = true; - found->pub.beacon_ies = ies; - found->pub.len_beacon_ies = ielen; - } - } + old = rcu_access_pointer(found->pub.beacon_ies); + ies = rcu_access_pointer(found->pub.ies); + + rcu_assign_pointer(found->pub.beacon_ies, + tmp->pub.beacon_ies); /* Override IEs if they were from a beacon before */ - if (information_elements_is_beacon_ies) { - found->pub.information_elements = - found->pub.beacon_ies; - found->pub.len_information_elements = - found->pub.len_beacon_ies; - } - } + if (old == ies) + rcu_assign_pointer(found->pub.ies, + tmp->pub.beacon_ies); - kref_put(&res->ref, bss_release); + if (old) + kfree_rcu((struct cfg80211_bss_ies *)old, + rcu_head); + } } else { + struct cfg80211_internal_bss *new; struct cfg80211_internal_bss *hidden; + struct cfg80211_bss_ies *ies; /* First check if the beacon is a probe response from * a hidden bss. If so, copy beacon ies (with nullified @@ -763,14 +742,32 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, /* TODO: The code is not trying to update existing probe * response bss entries when beacon ies are * getting changed. */ - hidden = rb_find_hidden_bss(dev, res); + hidden = rb_find_hidden_bss(dev, tmp); if (hidden) - copy_hidden_ies(res, hidden); - - /* this "consumes" the reference */ - list_add_tail(&res->list, &dev->bss_list); - rb_insert_bss(dev, res); - found = res; + copy_hidden_ies(tmp, hidden); + + /* + * create a copy -- the "res" variable that is passed in + * is allocated on the stack since it's not needed in the + * more common case of an update + */ + new = kzalloc(sizeof(*new) + dev->wiphy.bss_priv_size, + GFP_ATOMIC); + if (!new) { + ies = (void *)rcu_dereference(tmp->pub.beacon_ies); + if (ies) + kfree_rcu(ies, rcu_head); + ies = (void *)rcu_dereference(tmp->pub.proberesp_ies); + if (ies) + kfree_rcu(ies, rcu_head); + spin_unlock_bh(&dev->bss_lock); + return NULL; + } + memcpy(new, tmp, sizeof(*new)); + kref_init(&new->ref); + list_add_tail(&new->list, &dev->bss_list); + rb_insert_bss(dev, new); + found = new; } dev->bss_generation++; @@ -819,14 +816,12 @@ cfg80211_inform_bss(struct wiphy *wiphy, u16 beacon_interval, const u8 *ie, size_t ielen, s32 signal, gfp_t gfp) { - struct cfg80211_internal_bss *res; - size_t privsz; + struct cfg80211_bss_ies *ies; + struct cfg80211_internal_bss tmp = {}, *res; if (WARN_ON(!wiphy)) return NULL; - privsz = wiphy->bss_priv_size; - if (WARN_ON(wiphy->signal_type == CFG80211_SIGNAL_TYPE_UNSPEC && (signal < 0 || signal > 100))) return NULL; @@ -835,36 +830,33 @@ cfg80211_inform_bss(struct wiphy *wiphy, if (!channel) return NULL; - res = kzalloc(sizeof(*res) + privsz + ielen, gfp); - if (!res) - return NULL; - - memcpy(res->pub.bssid, bssid, ETH_ALEN); - res->pub.channel = channel; - res->pub.signal = signal; - res->pub.tsf = tsf; - res->pub.beacon_interval = beacon_interval; - res->pub.capability = capability; + memcpy(tmp.pub.bssid, bssid, ETH_ALEN); + tmp.pub.channel = channel; + tmp.pub.signal = signal; + tmp.pub.tsf = tsf; + tmp.pub.beacon_interval = beacon_interval; + tmp.pub.capability = capability; /* * Since we do not know here whether the IEs are from a Beacon or Probe * Response frame, we need to pick one of the options and only use it * with the driver that does not provide the full Beacon/Probe Response * frame. Use Beacon frame pointer to avoid indicating that this should - * override the information_elements pointer should we have received an - * earlier indication of Probe Response data. + * override the iies pointer should we have received an earlier + * indication of Probe Response data. * * The initial buffer for the IEs is allocated with the BSS entry and * is located after the private area. */ - res->pub.beacon_ies = (u8 *)res + sizeof(*res) + privsz; - memcpy(res->pub.beacon_ies, ie, ielen); - res->pub.len_beacon_ies = ielen; - res->pub.information_elements = res->pub.beacon_ies; - res->pub.len_information_elements = res->pub.len_beacon_ies; + ies = kmalloc(sizeof(*ies) + ielen, gfp); + if (!ies) + return NULL; + ies->len = ielen; + memcpy(ies->data, ie, ielen); - kref_init(&res->ref); + rcu_assign_pointer(tmp.pub.beacon_ies, ies); + rcu_assign_pointer(tmp.pub.ies, ies); - res = cfg80211_bss_update(wiphy_to_dev(wiphy), res); + res = cfg80211_bss_update(wiphy_to_dev(wiphy), &tmp); if (!res) return NULL; @@ -883,10 +875,10 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy, struct ieee80211_mgmt *mgmt, size_t len, s32 signal, gfp_t gfp) { - struct cfg80211_internal_bss *res; + struct cfg80211_internal_bss tmp = {}, *res; + struct cfg80211_bss_ies *ies; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); - size_t privsz; BUILD_BUG_ON(offsetof(struct ieee80211_mgmt, u.probe_resp.variable) != offsetof(struct ieee80211_mgmt, u.beacon.variable)); @@ -906,45 +898,31 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy, if (WARN_ON(len < offsetof(struct ieee80211_mgmt, u.probe_resp.variable))) return NULL; - privsz = wiphy->bss_priv_size; - channel = cfg80211_get_bss_channel(wiphy, mgmt->u.beacon.variable, ielen, channel); if (!channel) return NULL; - res = kzalloc(sizeof(*res) + privsz + ielen, gfp); - if (!res) + ies = kmalloc(sizeof(*ies) + ielen, gfp); + if (!ies) return NULL; + ies->len = ielen; + memcpy(ies->data, mgmt->u.probe_resp.variable, ielen); - memcpy(res->pub.bssid, mgmt->bssid, ETH_ALEN); - res->pub.channel = channel; - res->pub.signal = signal; - res->pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); - res->pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); - res->pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); - /* - * The initial buffer for the IEs is allocated with the BSS entry and - * is located after the private area. - */ - if (ieee80211_is_probe_resp(mgmt->frame_control)) { - res->pub.proberesp_ies = (u8 *) res + sizeof(*res) + privsz; - memcpy(res->pub.proberesp_ies, mgmt->u.probe_resp.variable, - ielen); - res->pub.len_proberesp_ies = ielen; - res->pub.information_elements = res->pub.proberesp_ies; - res->pub.len_information_elements = res->pub.len_proberesp_ies; - } else { - res->pub.beacon_ies = (u8 *) res + sizeof(*res) + privsz; - memcpy(res->pub.beacon_ies, mgmt->u.beacon.variable, ielen); - res->pub.len_beacon_ies = ielen; - res->pub.information_elements = res->pub.beacon_ies; - res->pub.len_information_elements = res->pub.len_beacon_ies; - } - - kref_init(&res->ref); - - res = cfg80211_bss_update(wiphy_to_dev(wiphy), res); + if (ieee80211_is_probe_resp(mgmt->frame_control)) + rcu_assign_pointer(tmp.pub.proberesp_ies, ies); + else + rcu_assign_pointer(tmp.pub.beacon_ies, ies); + rcu_assign_pointer(tmp.pub.ies, ies); + + memcpy(tmp.pub.bssid, mgmt->bssid, ETH_ALEN); + tmp.pub.channel = channel; + tmp.pub.signal = signal; + tmp.pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); + tmp.pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); + tmp.pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); + + res = cfg80211_bss_update(wiphy_to_dev(wiphy), &tmp); if (!res) return NULL; @@ -1136,22 +1114,21 @@ int cfg80211_wext_siwscan(struct net_device *dev, EXPORT_SYMBOL_GPL(cfg80211_wext_siwscan); static void ieee80211_scan_add_ies(struct iw_request_info *info, - struct cfg80211_bss *bss, + const struct cfg80211_bss_ies *ies, char **current_ev, char *end_buf) { - u8 *pos, *end, *next; + const u8 *pos, *end, *next; struct iw_event iwe; - if (!bss->information_elements || - !bss->len_information_elements) + if (!ies) return; /* * If needed, fragment the IEs buffer (at IE boundaries) into short * enough fragments to fit into IW_GENERIC_IE_MAX octet messages. */ - pos = bss->information_elements; - end = pos + bss->len_information_elements; + pos = ies->data; + end = pos + ies->len; while (end - pos > IW_GENERIC_IE_MAX) { next = pos + 2 + pos[1]; @@ -1162,7 +1139,8 @@ static void ieee80211_scan_add_ies(struct iw_request_info *info, iwe.cmd = IWEVGENIE; iwe.u.data.length = next - pos; *current_ev = iwe_stream_add_point(info, *current_ev, - end_buf, &iwe, pos); + end_buf, &iwe, + (void *)pos); pos = next; } @@ -1172,7 +1150,8 @@ static void ieee80211_scan_add_ies(struct iw_request_info *info, iwe.cmd = IWEVGENIE; iwe.u.data.length = end - pos; *current_ev = iwe_stream_add_point(info, *current_ev, - end_buf, &iwe, pos); + end_buf, &iwe, + (void *)pos); } } @@ -1191,10 +1170,11 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, struct cfg80211_internal_bss *bss, char *current_ev, char *end_buf) { + const struct cfg80211_bss_ies *ies; struct iw_event iwe; + const u8 *ie; u8 *buf, *cfg, *p; - u8 *ie = bss->pub.information_elements; - int rem = bss->pub.len_information_elements, i, sig; + int rem, i, sig; bool ismesh = false; memset(&iwe, 0, sizeof(iwe)); @@ -1259,7 +1239,17 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, current_ev = iwe_stream_add_point(info, current_ev, end_buf, &iwe, ""); - while (rem >= 2) { + rcu_read_lock(); + ies = rcu_dereference(bss->pub.ies); + if (ies) { + rem = ies->len; + ie = ies->data; + } else { + rem = 0; + ie = NULL; + } + + while (ies && rem >= 2) { /* invalid data */ if (ie[1] > rem - 2) break; @@ -1271,7 +1261,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, iwe.u.data.length = ie[1]; iwe.u.data.flags = 1; current_ev = iwe_stream_add_point(info, current_ev, end_buf, - &iwe, ie + 2); + &iwe, (u8 *)ie + 2); break; case WLAN_EID_MESH_ID: memset(&iwe, 0, sizeof(iwe)); @@ -1279,7 +1269,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, iwe.u.data.length = ie[1]; iwe.u.data.flags = 1; current_ev = iwe_stream_add_point(info, current_ev, end_buf, - &iwe, ie + 2); + &iwe, (u8 *)ie + 2); break; case WLAN_EID_MESH_CONFIG: ismesh = true; @@ -1288,7 +1278,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, buf = kmalloc(50, GFP_ATOMIC); if (!buf) break; - cfg = ie + 2; + cfg = (u8 *)ie + 2; memset(&iwe, 0, sizeof(iwe)); iwe.cmd = IWEVCUSTOM; sprintf(buf, "Mesh Network Path Selection Protocol ID: " @@ -1386,7 +1376,8 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, kfree(buf); } - ieee80211_scan_add_ies(info, &bss->pub, ¤t_ev, end_buf); + ieee80211_scan_add_ies(info, ies, ¤t_ev, end_buf); + rcu_read_unlock(); return current_ev; } diff --git a/net/wireless/sme.c b/net/wireless/sme.c index c7490027237d..f2431e41a373 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -417,7 +417,7 @@ void __cfg80211_connect_result(struct net_device *dev, const u8 *bssid, struct cfg80211_bss *bss) { struct wireless_dev *wdev = dev->ieee80211_ptr; - u8 *country_ie; + const u8 *country_ie; #ifdef CONFIG_CFG80211_WEXT union iwreq_data wrqu; #endif @@ -501,7 +501,15 @@ void __cfg80211_connect_result(struct net_device *dev, const u8 *bssid, wdev->sme_state = CFG80211_SME_CONNECTED; cfg80211_upload_connect_keys(wdev); - country_ie = (u8 *) ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); + rcu_read_lock(); + country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); + if (!country_ie) { + rcu_read_unlock(); + return; + } + + country_ie = kmemdup(country_ie, 2 + country_ie[1], GFP_ATOMIC); + rcu_read_unlock(); if (!country_ie) return; @@ -515,6 +523,7 @@ void __cfg80211_connect_result(struct net_device *dev, const u8 *bssid, bss->channel->band, country_ie + 2, country_ie[1]); + kfree(country_ie); } void cfg80211_connect_result(struct net_device *dev, const u8 *bssid, diff --git a/net/wireless/util.c b/net/wireless/util.c index 3cce6e486219..16d76a807c2f 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -688,10 +688,13 @@ EXPORT_SYMBOL(cfg80211_classify8021d); const u8 *ieee80211_bss_get_ie(struct cfg80211_bss *bss, u8 ie) { - if (bss->information_elements == NULL) + const struct cfg80211_bss_ies *ies; + + ies = rcu_dereference(bss->ies); + if (!ies) return NULL; - return cfg80211_find_ie(ie, bss->information_elements, - bss->len_information_elements); + + return cfg80211_find_ie(ie, ies->data, ies->len); } EXPORT_SYMBOL(ieee80211_bss_get_ie); diff --git a/net/wireless/wext-sme.c b/net/wireless/wext-sme.c index 873af63187c0..fb9622f6d99c 100644 --- a/net/wireless/wext-sme.c +++ b/net/wireless/wext-sme.c @@ -242,13 +242,17 @@ int cfg80211_mgd_wext_giwessid(struct net_device *dev, wdev_lock(wdev); if (wdev->current_bss) { - const u8 *ie = ieee80211_bss_get_ie(&wdev->current_bss->pub, - WLAN_EID_SSID); + const u8 *ie; + + rcu_read_lock(); + ie = ieee80211_bss_get_ie(&wdev->current_bss->pub, + WLAN_EID_SSID); if (ie) { data->flags = 1; data->length = ie[1]; memcpy(ssid, ie + 2, data->length); } + rcu_read_unlock(); } else if (wdev->wext.connect.ssid && wdev->wext.connect.ssid_len) { data->flags = 1; data->length = wdev->wext.connect.ssid_len; -- cgit v1.2.3-59-g8ed1b From 0362063b7be97f6f8e2c644b970f5726489aacdf Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Tue, 27 Nov 2012 00:31:55 +0100 Subject: ssb: extif: fix compile errors If CONFIG_SSB_EMBEDDED or CONFIG_SSB_DRIVER_MIPS is set and CONFIG_SSB_DRIVER_EXTIF is not set, it will cause compile problems because of missing functions. This patch fixes these problems. The mips driver now also uses ssb_chipco_available() instead of checking bus->chipco.dev manually. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/ssb/driver_mipscore.c | 14 ++++++------ include/linux/ssb/ssb_driver_extif.h | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/ssb/driver_mipscore.c b/drivers/ssb/driver_mipscore.c index b918ba922306..5bd05b136d22 100644 --- a/drivers/ssb/driver_mipscore.c +++ b/drivers/ssb/driver_mipscore.c @@ -178,9 +178,9 @@ static void ssb_mips_serial_init(struct ssb_mipscore *mcore) { struct ssb_bus *bus = mcore->dev->bus; - if (bus->extif.dev) + if (ssb_extif_available(&bus->extif)) mcore->nr_serial_ports = ssb_extif_serial_init(&bus->extif, mcore->serial_ports); - else if (bus->chipco.dev) + else if (ssb_chipco_available(&bus->chipco)) mcore->nr_serial_ports = ssb_chipco_serial_init(&bus->chipco, mcore->serial_ports); else mcore->nr_serial_ports = 0; @@ -191,7 +191,7 @@ static void ssb_mips_flash_detect(struct ssb_mipscore *mcore) struct ssb_bus *bus = mcore->dev->bus; /* When there is no chipcommon on the bus there is 4MB flash */ - if (!bus->chipco.dev) { + if (!ssb_chipco_available(&bus->chipco)) { mcore->pflash.present = true; mcore->pflash.buswidth = 2; mcore->pflash.window = SSB_FLASH1; @@ -227,9 +227,9 @@ u32 ssb_cpu_clock(struct ssb_mipscore *mcore) if (bus->chipco.capabilities & SSB_CHIPCO_CAP_PMU) return ssb_pmu_get_cpu_clock(&bus->chipco); - if (bus->extif.dev) { + if (ssb_extif_available(&bus->extif)) { ssb_extif_get_clockcontrol(&bus->extif, &pll_type, &n, &m); - } else if (bus->chipco.dev) { + } else if (ssb_chipco_available(&bus->chipco)) { ssb_chipco_get_clockcpu(&bus->chipco, &pll_type, &n, &m); } else return 0; @@ -265,9 +265,9 @@ void ssb_mipscore_init(struct ssb_mipscore *mcore) hz = 100000000; ns = 1000000000 / hz; - if (bus->extif.dev) + if (ssb_extif_available(&bus->extif)) ssb_extif_timing_init(&bus->extif, ns); - else if (bus->chipco.dev) + else if (ssb_chipco_available(&bus->chipco)) ssb_chipco_timing_init(&bus->chipco, ns); /* Assign IRQs to all cores on the bus, start with irq line 2, because serial usually takes 1 */ diff --git a/include/linux/ssb/ssb_driver_extif.h b/include/linux/ssb/ssb_driver_extif.h index 91161f0aa22b..2604efa7dc4d 100644 --- a/include/linux/ssb/ssb_driver_extif.h +++ b/include/linux/ssb/ssb_driver_extif.h @@ -204,11 +204,53 @@ void ssb_extif_get_clockcontrol(struct ssb_extif *extif, { } +static inline +void ssb_extif_timing_init(struct ssb_extif *extif, unsigned long ns) +{ +} + static inline void ssb_extif_watchdog_timer_set(struct ssb_extif *extif, u32 ticks) { } +static inline u32 ssb_extif_gpio_in(struct ssb_extif *extif, u32 mask) +{ + return 0; +} + +static inline u32 ssb_extif_gpio_out(struct ssb_extif *extif, u32 mask, + u32 value) +{ + return 0; +} + +static inline u32 ssb_extif_gpio_outen(struct ssb_extif *extif, u32 mask, + u32 value) +{ + return 0; +} + +static inline u32 ssb_extif_gpio_polarity(struct ssb_extif *extif, u32 mask, + u32 value) +{ + return 0; +} + +static inline u32 ssb_extif_gpio_intmask(struct ssb_extif *extif, u32 mask, + u32 value) +{ + return 0; +} + +#ifdef CONFIG_SSB_SERIAL +static inline int ssb_extif_serial_init(struct ssb_extif *extif, + struct ssb_serial_port *ports) +{ + return 0; +} +#endif /* CONFIG_SSB_SERIAL */ + #endif /* CONFIG_SSB_DRIVER_EXTIF */ #endif /* LINUX_SSB_EXTIFCORE_H_ */ -- cgit v1.2.3-59-g8ed1b From 5d7fad48ca763f6b20c2d4daf7df9fd7aa2cb242 Mon Sep 17 00:00:00 2001 From: Simon Wunderlich Date: Fri, 30 Nov 2012 19:17:28 +0100 Subject: mac80211: Fix typo in mac80211.h Signed-off-by: Simon Wunderlich Signed-off-by: Johannes Berg --- include/net/mac80211.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 0472d806fb41..1c02fb3b3817 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -164,7 +164,7 @@ enum ieee80211_chanctx_change { * active on the channel to receive MIMO transmissions * @rx_chains_dynamic: The number of RX chains that must be enabled * after RTS/CTS handshake to receive SMPS MIMO transmissions; - * this will always be >= @rx_chains_always. + * this will always be >= @rx_chains_static. * @drv_priv: data area for driver use, will always be aligned to * sizeof(void *), size is determined in hw information. */ -- cgit v1.2.3-59-g8ed1b From 20714bfef84d3e690c9c6f8e9cd46543b5ae1eed Mon Sep 17 00:00:00 2001 From: Frédéric Dalleau Date: Wed, 21 Nov 2012 10:51:12 +0100 Subject: Bluetooth: Implement deferred sco socket setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to authenticate and configure an incoming SCO connection, the BT_DEFER_SETUP option was added. This option is intended to defer reply to Connect Request on SCO sockets. When a connection is requested, the listening socket is unblocked but the effective connection setup happens only on first recv. Any send between accept and recv fails with -ENOTCONN. Signed-off-by: Frédéric Dalleau Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 9 ++++--- net/bluetooth/hci_event.c | 52 ++++++++++++++++++++++++++++++++++++---- net/bluetooth/sco.c | 35 ++++++++++++++++++++++++--- 3 files changed, 86 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index ef5b85dac3f7..76891a914e75 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -376,7 +376,7 @@ extern int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt); extern int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags); -extern int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr); +extern int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags); extern void sco_connect_cfm(struct hci_conn *hcon, __u8 status); extern void sco_disconn_cfm(struct hci_conn *hcon, __u8 reason); extern int sco_recv_scodata(struct hci_conn *hcon, struct sk_buff *skb); @@ -577,6 +577,7 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst); int hci_conn_del(struct hci_conn *conn); void hci_conn_hash_flush(struct hci_dev *hdev); void hci_conn_check_pending(struct hci_dev *hdev); +void hci_conn_accept(struct hci_conn *conn, int mask); struct hci_chan *hci_chan_create(struct hci_conn *conn); void hci_chan_del(struct hci_chan *chan); @@ -779,8 +780,10 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define lmp_host_le_br_capable(dev) ((dev)->host_features[0] & LMP_HOST_LE_BREDR) /* ----- HCI protocols ----- */ +#define HCI_PROTO_DEFER 0x01 + static inline int hci_proto_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, - __u8 type) + __u8 type, __u8 *flags) { switch (type) { case ACL_LINK: @@ -788,7 +791,7 @@ static inline int hci_proto_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, case SCO_LINK: case ESCO_LINK: - return sco_connect_ind(hdev, bdaddr); + return sco_connect_ind(hdev, bdaddr, flags); default: BT_ERR("unknown link type %d", type); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 9f5c5f244502..3843f1897c86 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -2047,15 +2047,53 @@ unlock: hci_conn_check_pending(hdev); } +void hci_conn_accept(struct hci_conn *conn, int mask) +{ + struct hci_dev *hdev = conn->hdev; + + BT_DBG("conn %p", conn); + + conn->state = BT_CONFIG; + + if (!lmp_esco_capable(hdev)) { + struct hci_cp_accept_conn_req cp; + + bacpy(&cp.bdaddr, &conn->dst); + + if (lmp_rswitch_capable(hdev) && (mask & HCI_LM_MASTER)) + cp.role = 0x00; /* Become master */ + else + cp.role = 0x01; /* Remain slave */ + + hci_send_cmd(hdev, HCI_OP_ACCEPT_CONN_REQ, sizeof(cp), &cp); + } else /* lmp_esco_capable(hdev)) */ { + struct hci_cp_accept_sync_conn_req cp; + + bacpy(&cp.bdaddr, &conn->dst); + cp.pkt_type = cpu_to_le16(conn->pkt_type); + + cp.tx_bandwidth = __constant_cpu_to_le32(0x00001f40); + cp.rx_bandwidth = __constant_cpu_to_le32(0x00001f40); + cp.max_latency = __constant_cpu_to_le16(0xffff); + cp.content_format = cpu_to_le16(hdev->voice_setting); + cp.retrans_effort = 0xff; + + hci_send_cmd(hdev, HCI_OP_ACCEPT_SYNC_CONN_REQ, + sizeof(cp), &cp); + } +} + static void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_ev_conn_request *ev = (void *) skb->data; int mask = hdev->link_mode; + __u8 flags = 0; BT_DBG("%s bdaddr %pMR type 0x%x", hdev->name, &ev->bdaddr, ev->link_type); - mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, ev->link_type); + mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, ev->link_type, + &flags); if ((mask & HCI_LM_ACCEPT) && !hci_blacklist_lookup(hdev, &ev->bdaddr)) { @@ -2081,12 +2119,13 @@ static void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) } memcpy(conn->dev_class, ev->dev_class, 3); - conn->state = BT_CONNECT; hci_dev_unlock(hdev); - if (ev->link_type == ACL_LINK || !lmp_esco_capable(hdev)) { + if (ev->link_type == ACL_LINK || + (!(flags & HCI_PROTO_DEFER) && !lmp_esco_capable(hdev))) { struct hci_cp_accept_conn_req cp; + conn->state = BT_CONNECT; bacpy(&cp.bdaddr, &ev->bdaddr); @@ -2097,8 +2136,9 @@ static void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) hci_send_cmd(hdev, HCI_OP_ACCEPT_CONN_REQ, sizeof(cp), &cp); - } else { + } else if (!(flags & HCI_PROTO_DEFER)) { struct hci_cp_accept_sync_conn_req cp; + conn->state = BT_CONNECT; bacpy(&cp.bdaddr, &ev->bdaddr); cp.pkt_type = cpu_to_le16(conn->pkt_type); @@ -2111,6 +2151,10 @@ static void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) hci_send_cmd(hdev, HCI_OP_ACCEPT_SYNC_CONN_REQ, sizeof(cp), &cp); + } else { + conn->state = BT_CONNECT2; + hci_proto_connect_cfm(conn, 0); + hci_conn_put(conn); } } else { /* Connection rejected */ diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index c6678f2bffc9..eea17cdcaf7f 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -397,6 +397,7 @@ static void sco_sock_init(struct sock *sk, struct sock *parent) if (parent) { sk->sk_type = parent->sk_type; + bt_sk(sk)->flags = bt_sk(parent)->flags; security_sk_clone(parent, sk); } } @@ -662,6 +663,28 @@ static int sco_sock_sendmsg(struct kiocb *iocb, struct socket *sock, return err; } +static int sco_sock_recvmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t len, int flags) +{ + struct sock *sk = sock->sk; + struct sco_pinfo *pi = sco_pi(sk); + + lock_sock(sk); + + if (sk->sk_state == BT_CONNECT2 && + test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { + hci_conn_accept(pi->conn->hcon, 0); + sk->sk_state = BT_CONFIG; + + release_sock(sk); + return 0; + } + + release_sock(sk); + + return bt_sock_recvmsg(iocb, sock, msg, len, flags); +} + static int sco_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen) { struct sock *sk = sock->sk; @@ -906,7 +929,10 @@ static void sco_conn_ready(struct sco_conn *conn) hci_conn_hold(conn->hcon); __sco_chan_add(conn, sk, parent); - sk->sk_state = BT_CONNECTED; + if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(parent)->flags)) + sk->sk_state = BT_CONNECT2; + else + sk->sk_state = BT_CONNECTED; /* Wake up parent */ parent->sk_data_ready(parent, 1); @@ -919,7 +945,7 @@ done: } /* ----- SCO interface with lower layer (HCI) ----- */ -int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr) +int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) { struct sock *sk; struct hlist_node *node; @@ -936,6 +962,9 @@ int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr) if (!bacmp(&bt_sk(sk)->src, &hdev->bdaddr) || !bacmp(&bt_sk(sk)->src, BDADDR_ANY)) { lm |= HCI_LM_ACCEPT; + + if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) + *flags |= HCI_PROTO_DEFER; break; } } @@ -1024,7 +1053,7 @@ static const struct proto_ops sco_sock_ops = { .accept = sco_sock_accept, .getname = sco_sock_getname, .sendmsg = sco_sock_sendmsg, - .recvmsg = bt_sock_recvmsg, + .recvmsg = sco_sock_recvmsg, .poll = bt_sock_poll, .ioctl = bt_sock_ioctl, .mmap = sock_no_mmap, -- cgit v1.2.3-59-g8ed1b From ffa88e02bc67a1496fae762ad899e8f49136e7a1 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Fri, 23 Nov 2012 16:50:51 -0200 Subject: Bluetooth: Move double negation to macros Some comparisons needs to double negation(!!) in order to make the value of the field boolean. Add it to the macro makes the code more readable. Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 6 +++--- net/bluetooth/hci_event.c | 4 ++-- net/bluetooth/mgmt.c | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 76891a914e75..2f2b743f5b10 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -767,7 +767,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define lmp_sniffsubr_capable(dev) ((dev)->features[5] & LMP_SNIFF_SUBR) #define lmp_pause_enc_capable(dev) ((dev)->features[5] & LMP_PAUSE_ENC) #define lmp_ext_inq_capable(dev) ((dev)->features[6] & LMP_EXT_INQ) -#define lmp_le_br_capable(dev) ((dev)->features[6] & LMP_SIMUL_LE_BR) +#define lmp_le_br_capable(dev) !!((dev)->features[6] & LMP_SIMUL_LE_BR) #define lmp_ssp_capable(dev) ((dev)->features[6] & LMP_SIMPLE_PAIR) #define lmp_no_flush_capable(dev) ((dev)->features[6] & LMP_NO_FLUSH) #define lmp_lsto_capable(dev) ((dev)->features[7] & LMP_LSTO) @@ -776,8 +776,8 @@ void hci_conn_del_sysfs(struct hci_conn *conn); /* ----- Extended LMP capabilities ----- */ #define lmp_host_ssp_capable(dev) ((dev)->host_features[0] & LMP_HOST_SSP) -#define lmp_host_le_capable(dev) ((dev)->host_features[0] & LMP_HOST_LE) -#define lmp_host_le_br_capable(dev) ((dev)->host_features[0] & LMP_HOST_LE_BREDR) +#define lmp_host_le_capable(dev) !!((dev)->host_features[0] & LMP_HOST_LE) +#define lmp_host_le_br_capable(dev) !!((dev)->host_features[0] & LMP_HOST_LE_BREDR) /* ----- HCI protocols ----- */ #define HCI_PROTO_DEFER 0x01 diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 3843f1897c86..705078a0cc39 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -794,10 +794,10 @@ static void hci_set_le_support(struct hci_dev *hdev) if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) { cp.le = 1; - cp.simul = !!lmp_le_br_capable(hdev); + cp.simul = lmp_le_br_capable(hdev); } - if (cp.le != !!lmp_host_le_capable(hdev)) + if (cp.le != lmp_host_le_capable(hdev)) hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(cp), &cp); } diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index dedbb1d8b2d2..5d0ef759ef49 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1225,7 +1225,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) } val = !!cp->val; - enabled = !!lmp_host_le_capable(hdev); + enabled = lmp_host_le_capable(hdev); if (!hdev_is_powered(hdev) || val == enabled) { bool changed = false; @@ -1261,7 +1261,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) if (val) { hci_cp.le = val; - hci_cp.simul = !!lmp_le_br_capable(hdev); + hci_cp.simul = lmp_le_br_capable(hdev); } err = hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp), @@ -2924,13 +2924,13 @@ int mgmt_powered(struct hci_dev *hdev, u8 powered) struct hci_cp_write_le_host_supported cp; cp.le = 1; - cp.simul = !!lmp_le_br_capable(hdev); + cp.simul = lmp_le_br_capable(hdev); /* Check first if we already have the right * host state (host features set) */ - if (cp.le != !!lmp_host_le_capable(hdev) || - cp.simul != !!lmp_host_le_br_capable(hdev)) + if (cp.le != lmp_host_le_capable(hdev) || + cp.simul != lmp_host_le_br_capable(hdev)) hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(cp), &cp); -- cgit v1.2.3-59-g8ed1b From 5d05416e0907b0dd30b62b002bed3b85e6efbd61 Mon Sep 17 00:00:00 2001 From: Andrei Emeltchenko Date: Thu, 29 Nov 2012 17:46:05 +0200 Subject: Bluetooth: AMP: Check that AMP is present and active Before starting quering remote AMP controllers make sure that there is local active AMP controller. Signed-off-by: Andrei Emeltchenko Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 16 ++++++++++++++++ net/bluetooth/l2cap_core.c | 1 + 2 files changed, 17 insertions(+) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 2f2b743f5b10..014a2eaa5389 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -779,6 +779,22 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define lmp_host_le_capable(dev) !!((dev)->host_features[0] & LMP_HOST_LE) #define lmp_host_le_br_capable(dev) !!((dev)->host_features[0] & LMP_HOST_LE_BREDR) +/* returns true if at least one AMP active */ +static inline bool hci_amp_capable(void) +{ + struct hci_dev *hdev; + bool ret = false; + + read_lock(&hci_dev_list_lock); + list_for_each_entry(hdev, &hci_dev_list, list) + if (hdev->amp_type == HCI_AMP && + test_bit(HCI_UP, &hdev->flags)) + ret = true; + read_unlock(&hci_dev_list_lock); + + return ret; +} + /* ----- HCI protocols ----- */ #define HCI_PROTO_DEFER 0x01 diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index f7ee037c7934..d8cffdbf0d37 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1013,6 +1013,7 @@ static bool __amp_capable(struct l2cap_chan *chan) struct l2cap_conn *conn = chan->conn; if (enable_hs && + hci_amp_capable() && chan->chan_policy == BT_CHANNEL_POLICY_AMP_PREFERRED && conn->fixed_chan_mask & L2CAP_FC_A2MP) return true; -- cgit v1.2.3-59-g8ed1b From f2592d3ee3c5817981f343b90bfb9c5612f38d23 Mon Sep 17 00:00:00 2001 From: Andrei Emeltchenko Date: Thu, 29 Nov 2012 17:46:08 +0200 Subject: Bluetooth: trivial: Change NO_FCS_RECV to RECV_NO_FCS Make code more readable by changing CONF_NO_FCS_RECV which is read as "No L2CAP FCS option received" to CONF_RECV_NO_FCS which means "Received L2CAP option NO_FCS". This flag really means that we have received L2CAP FRAME CHECK SEQUENCE (FCS) OPTION with value "No FCS". Signed-off-by: Andrei Emeltchenko Signed-off-by: Gustavo Padovan --- include/net/bluetooth/l2cap.h | 2 +- net/bluetooth/l2cap_core.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index f57fab04e7c5..7588ef44ebaf 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -611,7 +611,7 @@ enum { CONF_MTU_DONE, CONF_MODE_DONE, CONF_CONNECT_PEND, - CONF_NO_FCS_RECV, + CONF_RECV_NO_FCS, CONF_STATE2_DEVICE, CONF_EWS_RECV, CONF_LOC_CONF_PEND, diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 8c432771d6f9..2c78208d793e 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -3112,7 +3112,7 @@ done: if (chan->conn->feat_mask & L2CAP_FEAT_FCS) if (chan->fcs == L2CAP_FCS_NONE || - test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) { + test_bit(CONF_RECV_NO_FCS, &chan->conf_state)) { chan->fcs = L2CAP_FCS_NONE; l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, chan->fcs); @@ -3140,7 +3140,7 @@ done: if (chan->conn->feat_mask & L2CAP_FEAT_FCS) if (chan->fcs == L2CAP_FCS_NONE || - test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) { + test_bit(CONF_RECV_NO_FCS, &chan->conf_state)) { chan->fcs = L2CAP_FCS_NONE; l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, chan->fcs); @@ -3196,7 +3196,7 @@ static int l2cap_parse_conf_req(struct l2cap_chan *chan, void *data) case L2CAP_CONF_FCS: if (val == L2CAP_FCS_NONE) - set_bit(CONF_NO_FCS_RECV, &chan->conf_state); + set_bit(CONF_RECV_NO_FCS, &chan->conf_state); break; case L2CAP_CONF_EFS: @@ -3435,7 +3435,7 @@ static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, case L2CAP_CONF_FCS: if (*result == L2CAP_CONF_PENDING) if (val == L2CAP_FCS_NONE) - set_bit(CONF_NO_FCS_RECV, + set_bit(CONF_RECV_NO_FCS, &chan->conf_state); break; } @@ -3807,7 +3807,7 @@ static inline void set_default_fcs(struct l2cap_chan *chan) */ if (chan->mode != L2CAP_MODE_ERTM && chan->mode != L2CAP_MODE_STREAMING) chan->fcs = L2CAP_FCS_NONE; - else if (!test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) + else if (!test_bit(CONF_RECV_NO_FCS, &chan->conf_state)) chan->fcs = L2CAP_FCS_CRC16; } -- cgit v1.2.3-59-g8ed1b From bc245cc36c5687dd3fbf6d4a1b3c13d41f9cb189 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 5 Dec 2012 18:45:58 +0100 Subject: ssb/bcma: add common header for watchdog This adds a common header for watchdog functions, so a watchdog driver just needs to use this and could provide watchdog functionality for ssb and bcma based SoCs. Patches for a watchdog driver using this interface will be send later. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- include/linux/bcm47xx_wdt.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 include/linux/bcm47xx_wdt.h (limited to 'include') diff --git a/include/linux/bcm47xx_wdt.h b/include/linux/bcm47xx_wdt.h new file mode 100644 index 000000000000..e5dfc256485b --- /dev/null +++ b/include/linux/bcm47xx_wdt.h @@ -0,0 +1,19 @@ +#ifndef LINUX_BCM47XX_WDT_H_ +#define LINUX_BCM47XX_WDT_H_ + +#include + + +struct bcm47xx_wdt { + u32 (*timer_set)(struct bcm47xx_wdt *, u32); + u32 (*timer_set_ms)(struct bcm47xx_wdt *, u32); + u32 max_timer_ms; + + void *driver_data; +}; + +static inline void *bcm47xx_wdt_get_drvdata(struct bcm47xx_wdt *wdt) +{ + return wdt->driver_data; +} +#endif /* LINUX_BCM47XX_WDT_H_ */ -- cgit v1.2.3-59-g8ed1b From a22a3114a820ca3eadee6af53d90736e5ca68fa1 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 5 Dec 2012 18:46:01 +0100 Subject: bcma: add methods for watchdog driver The watchdog driver wants to set the watchdog timeout in ms and not in ticks, which is depending on the SoC type and the clock. Calculate the number of ticks per millisecond and provide two functions for the watchdog driver. Also return the ticks or millisecond the timer was set to in case the provided value was bigger than the max allowed value. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/bcma/driver_chipcommon.c | 38 ++++++++++++++++++++++++++++- include/linux/bcma/bcma_driver_chipcommon.h | 4 +-- 2 files changed, 39 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/bcma/driver_chipcommon.c b/drivers/bcma/driver_chipcommon.c index 7c132e5eceb0..117222697e1f 100644 --- a/drivers/bcma/driver_chipcommon.c +++ b/drivers/bcma/driver_chipcommon.c @@ -10,6 +10,7 @@ */ #include "bcma_private.h" +#include #include #include @@ -52,6 +53,39 @@ static u32 bcma_chipco_watchdog_get_max_timer(struct bcma_drv_cc *cc) return (1 << nb) - 1; } +static u32 bcma_chipco_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, + u32 ticks) +{ + struct bcma_drv_cc *cc = bcm47xx_wdt_get_drvdata(wdt); + + return bcma_chipco_watchdog_timer_set(cc, ticks); +} + +static u32 bcma_chipco_watchdog_timer_set_ms_wdt(struct bcm47xx_wdt *wdt, + u32 ms) +{ + struct bcma_drv_cc *cc = bcm47xx_wdt_get_drvdata(wdt); + u32 ticks; + + ticks = bcma_chipco_watchdog_timer_set(cc, cc->ticks_per_ms * ms); + return ticks / cc->ticks_per_ms; +} + +static int bcma_chipco_watchdog_ticks_per_ms(struct bcma_drv_cc *cc) +{ + struct bcma_bus *bus = cc->core->bus; + + if (cc->capabilities & BCMA_CC_CAP_PMU) { + if (bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) + /* 4706 CC and PMU watchdogs are clocked at 1/4 of ALP clock */ + return bcma_chipco_alp_clock(cc) / 4000; + else + /* based on 32KHz ILP clock */ + return 32; + } else { + return bcma_chipco_alp_clock(cc) / 1000; + } +} void bcma_core_chipcommon_early_init(struct bcma_drv_cc *cc) { @@ -100,12 +134,13 @@ void bcma_core_chipcommon_init(struct bcma_drv_cc *cc) ((leddc_on << BCMA_CC_GPIOTIMER_ONTIME_SHIFT) | (leddc_off << BCMA_CC_GPIOTIMER_OFFTIME_SHIFT))); } + cc->ticks_per_ms = bcma_chipco_watchdog_ticks_per_ms(cc); cc->setup_done = true; } /* Set chip watchdog reset timer to fire in 'ticks' backplane cycles */ -void bcma_chipco_watchdog_timer_set(struct bcma_drv_cc *cc, u32 ticks) +u32 bcma_chipco_watchdog_timer_set(struct bcma_drv_cc *cc, u32 ticks) { u32 maxt; enum bcma_clkmode clkmode; @@ -125,6 +160,7 @@ void bcma_chipco_watchdog_timer_set(struct bcma_drv_cc *cc, u32 ticks) /* instant NMI */ bcma_cc_write32(cc, BCMA_CC_WATCHDOG, ticks); } + return ticks; } void bcma_chipco_irq_mask(struct bcma_drv_cc *cc, u32 mask, u32 value) diff --git a/include/linux/bcma/bcma_driver_chipcommon.h b/include/linux/bcma/bcma_driver_chipcommon.h index 145f3c56227f..2f9b01493a90 100644 --- a/include/linux/bcma/bcma_driver_chipcommon.h +++ b/include/linux/bcma/bcma_driver_chipcommon.h @@ -570,6 +570,7 @@ struct bcma_drv_cc { int nr_serial_ports; struct bcma_serial_port serial_ports[4]; #endif /* CONFIG_BCMA_DRIVER_MIPS */ + u32 ticks_per_ms; }; /* Register access */ @@ -593,8 +594,7 @@ extern void bcma_chipco_resume(struct bcma_drv_cc *cc); void bcma_chipco_bcm4331_ext_pa_lines_ctl(struct bcma_drv_cc *cc, bool enable); -extern void bcma_chipco_watchdog_timer_set(struct bcma_drv_cc *cc, - u32 ticks); +extern u32 bcma_chipco_watchdog_timer_set(struct bcma_drv_cc *cc, u32 ticks); void bcma_chipco_irq_mask(struct bcma_drv_cc *cc, u32 mask, u32 value); -- cgit v1.2.3-59-g8ed1b From a4855f39d4eb3f550ca5f4aac79bd999da42dc54 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 5 Dec 2012 18:46:02 +0100 Subject: bcma: register watchdog driver Register the watchdog driver to the system if this is a SoC. Using the watchdog on a non SoC device, like a PCIe card, will make the PCIe card die when the timeout expired, but starting it again is not supported by bcma. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/bcma/bcma_private.h | 2 ++ drivers/bcma/driver_chipcommon.c | 22 ++++++++++++++++++++++ drivers/bcma/main.c | 8 ++++++++ include/linux/bcma/bcma_driver_chipcommon.h | 3 +++ 4 files changed, 35 insertions(+) (limited to 'include') diff --git a/drivers/bcma/bcma_private.h b/drivers/bcma/bcma_private.h index 169fc58427d3..bcb830ec4bb4 100644 --- a/drivers/bcma/bcma_private.h +++ b/drivers/bcma/bcma_private.h @@ -84,6 +84,8 @@ extern void __exit bcma_host_pci_exit(void); /* driver_pci.c */ u32 bcma_pcie_read(struct bcma_drv_pci *pc, u32 address); +extern int bcma_chipco_watchdog_register(struct bcma_drv_cc *cc); + #ifdef CONFIG_BCMA_DRIVER_PCI_HOSTMODE bool __devinit bcma_core_pci_is_in_hostmode(struct bcma_drv_pci *pc); void __devinit bcma_core_pci_hostmode_init(struct bcma_drv_pci *pc); diff --git a/drivers/bcma/driver_chipcommon.c b/drivers/bcma/driver_chipcommon.c index 117222697e1f..d017f2512275 100644 --- a/drivers/bcma/driver_chipcommon.c +++ b/drivers/bcma/driver_chipcommon.c @@ -12,6 +12,7 @@ #include "bcma_private.h" #include #include +#include #include static inline u32 bcma_cc_write32_masked(struct bcma_drv_cc *cc, u16 offset, @@ -87,6 +88,27 @@ static int bcma_chipco_watchdog_ticks_per_ms(struct bcma_drv_cc *cc) } } +int bcma_chipco_watchdog_register(struct bcma_drv_cc *cc) +{ + struct bcm47xx_wdt wdt = {}; + struct platform_device *pdev; + + wdt.driver_data = cc; + wdt.timer_set = bcma_chipco_watchdog_timer_set_wdt; + wdt.timer_set_ms = bcma_chipco_watchdog_timer_set_ms_wdt; + wdt.max_timer_ms = bcma_chipco_watchdog_get_max_timer(cc) / cc->ticks_per_ms; + + pdev = platform_device_register_data(NULL, "bcm47xx-wdt", + cc->core->bus->num, &wdt, + sizeof(wdt)); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + cc->watchdog = pdev; + + return 0; +} + void bcma_core_chipcommon_early_init(struct bcma_drv_cc *cc) { if (cc->early_setup_done) diff --git a/drivers/bcma/main.c b/drivers/bcma/main.c index a9718893000b..debd4f142f93 100644 --- a/drivers/bcma/main.c +++ b/drivers/bcma/main.c @@ -165,6 +165,12 @@ static int bcma_register_cores(struct bcma_bus *bus) } #endif + if (bus->hosttype == BCMA_HOSTTYPE_SOC) { + err = bcma_chipco_watchdog_register(&bus->drv_cc); + if (err) + bcma_err(bus, "Error registering watchdog driver\n"); + } + return 0; } @@ -177,6 +183,8 @@ static void bcma_unregister_cores(struct bcma_bus *bus) if (core->dev_registered) device_unregister(&core->dev); } + if (bus->hosttype == BCMA_HOSTTYPE_SOC) + platform_device_unregister(bus->drv_cc.watchdog); } int __devinit bcma_bus_register(struct bcma_bus *bus) diff --git a/include/linux/bcma/bcma_driver_chipcommon.h b/include/linux/bcma/bcma_driver_chipcommon.h index 2f9b01493a90..e51359180b6f 100644 --- a/include/linux/bcma/bcma_driver_chipcommon.h +++ b/include/linux/bcma/bcma_driver_chipcommon.h @@ -1,6 +1,8 @@ #ifndef LINUX_BCMA_DRIVER_CC_H_ #define LINUX_BCMA_DRIVER_CC_H_ +#include + /** ChipCommon core registers. **/ #define BCMA_CC_ID 0x0000 #define BCMA_CC_ID_ID 0x0000FFFF @@ -571,6 +573,7 @@ struct bcma_drv_cc { struct bcma_serial_port serial_ports[4]; #endif /* CONFIG_BCMA_DRIVER_MIPS */ u32 ticks_per_ms; + struct platform_device *watchdog; }; /* Register access */ -- cgit v1.2.3-59-g8ed1b From 7ffbffe37de3979d43c1105e38eb2918bf5d35fe Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 5 Dec 2012 18:46:05 +0100 Subject: ssb: add methods for watchdog driver The watchdog driver wants to set the watchdog timeout in ms and not in ticks, which is depending on the SoC type and the clock. Calculate the number of ticks per millisecond and provide two functions for the watchdog driver. Also return the ticks or millisecond the timer was set to in case the provided value was bigger than the max allowed value. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/ssb/driver_chipcommon.c | 47 ++++++++++++++++++++++++++++++- drivers/ssb/ssb_private.h | 5 ++++ include/linux/ssb/ssb_driver_chipcommon.h | 5 ++-- 3 files changed, 54 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/ssb/driver_chipcommon.c b/drivers/ssb/driver_chipcommon.c index 6e080f6a07a5..95c33a05f434 100644 --- a/drivers/ssb/driver_chipcommon.c +++ b/drivers/ssb/driver_chipcommon.c @@ -4,6 +4,7 @@ * * Copyright 2005, Broadcom Corporation * Copyright 2006, 2007, Michael Buesch + * Copyright 2012, Hauke Mehrtens * * Licensed under the GNU/GPL. See COPYING for details. */ @@ -12,6 +13,7 @@ #include #include #include +#include #include "ssb_private.h" @@ -306,6 +308,43 @@ static u32 ssb_chipco_watchdog_get_max_timer(struct ssb_chipcommon *cc) return (1 << nb) - 1; } +u32 ssb_chipco_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, u32 ticks) +{ + struct ssb_chipcommon *cc = bcm47xx_wdt_get_drvdata(wdt); + + if (cc->dev->bus->bustype != SSB_BUSTYPE_SSB) + return 0; + + return ssb_chipco_watchdog_timer_set(cc, ticks); +} + +u32 ssb_chipco_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms) +{ + struct ssb_chipcommon *cc = bcm47xx_wdt_get_drvdata(wdt); + u32 ticks; + + if (cc->dev->bus->bustype != SSB_BUSTYPE_SSB) + return 0; + + ticks = ssb_chipco_watchdog_timer_set(cc, cc->ticks_per_ms * ms); + return ticks / cc->ticks_per_ms; +} + +static int ssb_chipco_watchdog_ticks_per_ms(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + + if (cc->capabilities & SSB_CHIPCO_CAP_PMU) { + /* based on 32KHz ILP clock */ + return 32; + } else { + if (cc->dev->id.revision < 18) + return ssb_clockspeed(bus) / 1000; + else + return ssb_chipco_alp_clock(cc) / 1000; + } +} + void ssb_chipcommon_init(struct ssb_chipcommon *cc) { if (!cc->dev) @@ -323,6 +362,11 @@ void ssb_chipcommon_init(struct ssb_chipcommon *cc) chipco_powercontrol_init(cc); ssb_chipco_set_clockmode(cc, SSB_CLKMODE_FAST); calc_fast_powerup_delay(cc); + + if (cc->dev->bus->bustype == SSB_BUSTYPE_SSB) { + cc->ticks_per_ms = ssb_chipco_watchdog_ticks_per_ms(cc); + cc->max_timer_ms = ssb_chipco_watchdog_get_max_timer(cc) / cc->ticks_per_ms; + } } void ssb_chipco_suspend(struct ssb_chipcommon *cc) @@ -421,7 +465,7 @@ void ssb_chipco_timing_init(struct ssb_chipcommon *cc, } /* Set chip watchdog reset timer to fire in 'ticks' backplane cycles */ -void ssb_chipco_watchdog_timer_set(struct ssb_chipcommon *cc, u32 ticks) +u32 ssb_chipco_watchdog_timer_set(struct ssb_chipcommon *cc, u32 ticks) { u32 maxt; enum ssb_clkmode clkmode; @@ -441,6 +485,7 @@ void ssb_chipco_watchdog_timer_set(struct ssb_chipcommon *cc, u32 ticks) /* instant NMI */ chipco_write32(cc, SSB_CHIPCO_WATCHDOG, ticks); } + return ticks; } void ssb_chipco_irq_mask(struct ssb_chipcommon *cc, u32 mask, u32 value) diff --git a/drivers/ssb/ssb_private.h b/drivers/ssb/ssb_private.h index 98b2915cd30b..03cc40a71510 100644 --- a/drivers/ssb/ssb_private.h +++ b/drivers/ssb/ssb_private.h @@ -3,6 +3,7 @@ #include #include +#include #define PFX "ssb: " @@ -212,4 +213,8 @@ extern u32 ssb_pmu_get_cpu_clock(struct ssb_chipcommon *cc); extern u32 ssb_pmu_get_controlclock(struct ssb_chipcommon *cc); extern u32 ssb_pmu_get_alp_clock(struct ssb_chipcommon *cc); +extern u32 ssb_chipco_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, + u32 ticks); +extern u32 ssb_chipco_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms); + #endif /* LINUX_SSB_PRIVATE_H_ */ diff --git a/include/linux/ssb/ssb_driver_chipcommon.h b/include/linux/ssb/ssb_driver_chipcommon.h index c2b02a5c86ae..38339fd68a5f 100644 --- a/include/linux/ssb/ssb_driver_chipcommon.h +++ b/include/linux/ssb/ssb_driver_chipcommon.h @@ -591,6 +591,8 @@ struct ssb_chipcommon { /* Fast Powerup Delay constant */ u16 fast_pwrup_delay; struct ssb_chipcommon_pmu pmu; + u32 ticks_per_ms; + u32 max_timer_ms; }; static inline bool ssb_chipco_available(struct ssb_chipcommon *cc) @@ -630,8 +632,7 @@ enum ssb_clkmode { extern void ssb_chipco_set_clockmode(struct ssb_chipcommon *cc, enum ssb_clkmode mode); -extern void ssb_chipco_watchdog_timer_set(struct ssb_chipcommon *cc, - u32 ticks); +extern u32 ssb_chipco_watchdog_timer_set(struct ssb_chipcommon *cc, u32 ticks); void ssb_chipco_irq_mask(struct ssb_chipcommon *cc, u32 mask, u32 value); -- cgit v1.2.3-59-g8ed1b From 7280b51a29f8e6cc7d449d565182d1e1b6183907 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 5 Dec 2012 18:46:06 +0100 Subject: ssb: extif: add check for max value before setting watchdog register Prevent the watchdog register on the extif core to be set to a too high value. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/ssb/driver_extif.c | 5 +++-- include/linux/ssb/ssb_driver_extif.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/ssb/driver_extif.c b/drivers/ssb/driver_extif.c index dc47f30e9cf7..0aa4c2a85774 100644 --- a/drivers/ssb/driver_extif.c +++ b/drivers/ssb/driver_extif.c @@ -112,9 +112,10 @@ void ssb_extif_get_clockcontrol(struct ssb_extif *extif, *m = extif_read32(extif, SSB_EXTIF_CLOCK_SB); } -void ssb_extif_watchdog_timer_set(struct ssb_extif *extif, - u32 ticks) +void ssb_extif_watchdog_timer_set(struct ssb_extif *extif, u32 ticks) { + if (ticks > SSB_EXTIF_WATCHDOG_MAX_TIMER) + ticks = SSB_EXTIF_WATCHDOG_MAX_TIMER; extif_write32(extif, SSB_EXTIF_WATCHDOG, ticks); } diff --git a/include/linux/ssb/ssb_driver_extif.h b/include/linux/ssb/ssb_driver_extif.h index 2604efa7dc4d..b618188939d2 100644 --- a/include/linux/ssb/ssb_driver_extif.h +++ b/include/linux/ssb/ssb_driver_extif.h @@ -152,6 +152,7 @@ /* watchdog */ #define SSB_EXTIF_WATCHDOG_CLK 48000000 /* Hz */ +#define SSB_EXTIF_WATCHDOG_MAX_TIMER ((1 << 28) - 1) #ifdef CONFIG_SSB_DRIVER_EXTIF -- cgit v1.2.3-59-g8ed1b From 9f640a6376e54fa9ae834c32cbe92cefeec970dc Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 5 Dec 2012 18:46:07 +0100 Subject: ssb: extif: add methods for watchdog driver The watchdog driver wants to set the watchdog timeout in ms and not in ticks, add a method converting ms to ticks before setting the watchdog register. Return the ticks or millisecond the timer was set to in case the provided value was bigger than the max allowed value. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/ssb/driver_extif.c | 21 ++++++++++++++++++++- drivers/ssb/ssb_private.h | 15 +++++++++++++++ include/linux/ssb/ssb_driver_extif.h | 9 +++++---- 3 files changed, 40 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/ssb/driver_extif.c b/drivers/ssb/driver_extif.c index 0aa4c2a85774..553227a3062d 100644 --- a/drivers/ssb/driver_extif.c +++ b/drivers/ssb/driver_extif.c @@ -112,11 +112,30 @@ void ssb_extif_get_clockcontrol(struct ssb_extif *extif, *m = extif_read32(extif, SSB_EXTIF_CLOCK_SB); } -void ssb_extif_watchdog_timer_set(struct ssb_extif *extif, u32 ticks) +u32 ssb_extif_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, u32 ticks) +{ + struct ssb_extif *extif = bcm47xx_wdt_get_drvdata(wdt); + + return ssb_extif_watchdog_timer_set(extif, ticks); +} + +u32 ssb_extif_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms) +{ + struct ssb_extif *extif = bcm47xx_wdt_get_drvdata(wdt); + u32 ticks = (SSB_EXTIF_WATCHDOG_CLK / 1000) * ms; + + ticks = ssb_extif_watchdog_timer_set(extif, ticks); + + return (ticks * 1000) / SSB_EXTIF_WATCHDOG_CLK; +} + +u32 ssb_extif_watchdog_timer_set(struct ssb_extif *extif, u32 ticks) { if (ticks > SSB_EXTIF_WATCHDOG_MAX_TIMER) ticks = SSB_EXTIF_WATCHDOG_MAX_TIMER; extif_write32(extif, SSB_EXTIF_WATCHDOG, ticks); + + return ticks; } u32 ssb_extif_gpio_in(struct ssb_extif *extif, u32 mask) diff --git a/drivers/ssb/ssb_private.h b/drivers/ssb/ssb_private.h index 03cc40a71510..50ea02877777 100644 --- a/drivers/ssb/ssb_private.h +++ b/drivers/ssb/ssb_private.h @@ -217,4 +217,19 @@ extern u32 ssb_chipco_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, u32 ticks); extern u32 ssb_chipco_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms); +#ifdef CONFIG_SSB_DRIVER_EXTIF +extern u32 ssb_extif_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, u32 ticks); +extern u32 ssb_extif_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms); +#else +static inline u32 ssb_extif_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, + u32 ticks) +{ + return 0; +} +static inline u32 ssb_extif_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, + u32 ms) +{ + return 0; +} +#endif #endif /* LINUX_SSB_PRIVATE_H_ */ diff --git a/include/linux/ssb/ssb_driver_extif.h b/include/linux/ssb/ssb_driver_extif.h index b618188939d2..99511d0e931d 100644 --- a/include/linux/ssb/ssb_driver_extif.h +++ b/include/linux/ssb/ssb_driver_extif.h @@ -153,6 +153,8 @@ #define SSB_EXTIF_WATCHDOG_CLK 48000000 /* Hz */ #define SSB_EXTIF_WATCHDOG_MAX_TIMER ((1 << 28) - 1) +#define SSB_EXTIF_WATCHDOG_MAX_TIMER_MS (SSB_EXTIF_WATCHDOG_MAX_TIMER \ + / (SSB_EXTIF_WATCHDOG_CLK / 1000)) #ifdef CONFIG_SSB_DRIVER_EXTIF @@ -172,8 +174,7 @@ extern void ssb_extif_get_clockcontrol(struct ssb_extif *extif, extern void ssb_extif_timing_init(struct ssb_extif *extif, unsigned long ns); -extern void ssb_extif_watchdog_timer_set(struct ssb_extif *extif, - u32 ticks); +extern u32 ssb_extif_watchdog_timer_set(struct ssb_extif *extif, u32 ticks); /* Extif GPIO pin access */ u32 ssb_extif_gpio_in(struct ssb_extif *extif, u32 mask); @@ -211,9 +212,9 @@ void ssb_extif_timing_init(struct ssb_extif *extif, unsigned long ns) } static inline -void ssb_extif_watchdog_timer_set(struct ssb_extif *extif, - u32 ticks) +u32 ssb_extif_watchdog_timer_set(struct ssb_extif *extif, u32 ticks) { + return 0; } static inline u32 ssb_extif_gpio_in(struct ssb_extif *extif, u32 mask) -- cgit v1.2.3-59-g8ed1b From bde327eff8a722df1198df9b14464f84f1adfb65 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 5 Dec 2012 18:46:08 +0100 Subject: ssb: register watchdog driver Register the watchdog driver to the system if it is a SoC. Using the watchdog on a non SoC device, like a PCI card, will make the PCI card die when the timeout expired, but starting it again is not supported by ssb. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/ssb/embedded.c | 35 +++++++++++++++++++++++++++++++++++ drivers/ssb/main.c | 8 ++++++++ drivers/ssb/ssb_private.h | 10 ++++++++++ include/linux/ssb/ssb.h | 2 ++ 4 files changed, 55 insertions(+) (limited to 'include') diff --git a/drivers/ssb/embedded.c b/drivers/ssb/embedded.c index 9ef124f9ee2d..bb18d76f9f2c 100644 --- a/drivers/ssb/embedded.c +++ b/drivers/ssb/embedded.c @@ -4,11 +4,13 @@ * * Copyright 2005-2008, Broadcom Corporation * Copyright 2006-2008, Michael Buesch + * Copyright 2012, Hauke Mehrtens * * Licensed under the GNU/GPL. See COPYING for details. */ #include +#include #include #include #include @@ -32,6 +34,39 @@ int ssb_watchdog_timer_set(struct ssb_bus *bus, u32 ticks) } EXPORT_SYMBOL(ssb_watchdog_timer_set); +int ssb_watchdog_register(struct ssb_bus *bus) +{ + struct bcm47xx_wdt wdt = {}; + struct platform_device *pdev; + + if (ssb_chipco_available(&bus->chipco)) { + wdt.driver_data = &bus->chipco; + wdt.timer_set = ssb_chipco_watchdog_timer_set_wdt; + wdt.timer_set_ms = ssb_chipco_watchdog_timer_set_ms; + wdt.max_timer_ms = bus->chipco.max_timer_ms; + } else if (ssb_extif_available(&bus->extif)) { + wdt.driver_data = &bus->extif; + wdt.timer_set = ssb_extif_watchdog_timer_set_wdt; + wdt.timer_set_ms = ssb_extif_watchdog_timer_set_ms; + wdt.max_timer_ms = SSB_EXTIF_WATCHDOG_MAX_TIMER_MS; + } else { + return -ENODEV; + } + + pdev = platform_device_register_data(NULL, "bcm47xx-wdt", + bus->busnumber, &wdt, + sizeof(wdt)); + if (IS_ERR(pdev)) { + ssb_dprintk(KERN_INFO PFX + "can not register watchdog device, err: %li\n", + PTR_ERR(pdev)); + return PTR_ERR(pdev); + } + + bus->watchdog = pdev; + return 0; +} + u32 ssb_gpio_in(struct ssb_bus *bus, u32 mask) { unsigned long flags; diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c index df0f145c22fc..58c7da29a37c 100644 --- a/drivers/ssb/main.c +++ b/drivers/ssb/main.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -433,6 +434,11 @@ static void ssb_devices_unregister(struct ssb_bus *bus) if (sdev->dev) device_unregister(sdev->dev); } + +#ifdef CONFIG_SSB_EMBEDDED + if (bus->bustype == SSB_BUSTYPE_SSB) + platform_device_unregister(bus->watchdog); +#endif } void ssb_bus_unregister(struct ssb_bus *bus) @@ -561,6 +567,8 @@ static int __devinit ssb_attach_queued_buses(void) if (err) goto error; ssb_pcicore_init(&bus->pcicore); + if (bus->bustype == SSB_BUSTYPE_SSB) + ssb_watchdog_register(bus); ssb_bus_may_powerdown(bus); err = ssb_devices_register(bus); diff --git a/drivers/ssb/ssb_private.h b/drivers/ssb/ssb_private.h index 50ea02877777..8942db1d855a 100644 --- a/drivers/ssb/ssb_private.h +++ b/drivers/ssb/ssb_private.h @@ -232,4 +232,14 @@ static inline u32 ssb_extif_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, return 0; } #endif + +#ifdef CONFIG_SSB_EMBEDDED +extern int ssb_watchdog_register(struct ssb_bus *bus); +#else /* CONFIG_SSB_EMBEDDED */ +static inline int ssb_watchdog_register(struct ssb_bus *bus) +{ + return 0; +} +#endif /* CONFIG_SSB_EMBEDDED */ + #endif /* LINUX_SSB_PRIVATE_H_ */ diff --git a/include/linux/ssb/ssb.h b/include/linux/ssb/ssb.h index bb674c02f306..1f64e3f1f22b 100644 --- a/include/linux/ssb/ssb.h +++ b/include/linux/ssb/ssb.h @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -432,6 +433,7 @@ struct ssb_bus { #ifdef CONFIG_SSB_EMBEDDED /* Lock for GPIO register access. */ spinlock_t gpio_lock; + struct platform_device *watchdog; #endif /* EMBEDDED */ /* Internal-only stuff follows. Do not touch. */ -- cgit v1.2.3-59-g8ed1b