From 889cb360b4f48c1334311093161f06f7b4bd77d2 Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Sun, 1 May 2011 09:56:45 +0300 Subject: wl12xx: simplify wl1271_ssid_set() Simplify wl1271_ssid_set by re-using cfg80211_find_ie instead of reimplementing it. Additionally, add a length check to prevent a potential buffer overflow. Signed-off-by: Eliad Peller Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/main.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 6dab6f0c91b..f82e736ba19 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -2376,20 +2376,24 @@ out: static int wl1271_ssid_set(struct wl1271 *wl, struct sk_buff *skb, int offset) { - u8 *ptr = skb->data + offset; + u8 ssid_len; + const u8 *ptr = cfg80211_find_ie(WLAN_EID_SSID, skb->data + offset, + skb->len - offset); - /* find the location of the ssid in the beacon */ - while (ptr < skb->data + skb->len) { - if (ptr[0] == WLAN_EID_SSID) { - wl->ssid_len = ptr[1]; - memcpy(wl->ssid, ptr+2, wl->ssid_len); - return 0; - } - ptr += (ptr[1] + 2); + if (!ptr) { + wl1271_error("No SSID in IEs!"); + return -ENOENT; } - wl1271_error("No SSID in IEs!\n"); - return -ENOENT; + ssid_len = ptr[1]; + if (ssid_len > IEEE80211_MAX_SSID_LEN) { + wl1271_error("SSID is too long!"); + return -EINVAL; + } + + wl->ssid_len = ssid_len; + memcpy(wl->ssid, ptr+2, ssid_len); + return 0; } static int wl1271_bss_erp_info_changed(struct wl1271 *wl, -- cgit v1.2.3-70-g09d2 From bc76b94051983b94c8ba04fbfbc59651b9925fa7 Mon Sep 17 00:00:00 2001 From: Shahar Levi Date: Wed, 11 May 2011 11:14:22 +0300 Subject: wl12xx: Don't filter beacons that include changed HT IEs This patch adds a beacon filter rule to pass up the beacons that contain changed HT information elements. These beacons need to be passed to mac80211 so that it can act on such changes. [Reworded commit log -- Luca.] Signed-off-by: Shahar Levi Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index f82e736ba19..fa6b996d7d4 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -257,12 +257,16 @@ static struct conf_drv_settings default_conf = { .wake_up_event = CONF_WAKE_UP_EVENT_DTIM, .listen_interval = 1, .bcn_filt_mode = CONF_BCN_FILT_MODE_ENABLED, - .bcn_filt_ie_count = 1, + .bcn_filt_ie_count = 2, .bcn_filt_ie = { [0] = { .ie = WLAN_EID_CHANNEL_SWITCH, .rule = CONF_BCN_RULE_PASS_ON_APPEARANCE, - } + }, + [1] = { + .ie = WLAN_EID_HT_INFORMATION, + .rule = CONF_BCN_RULE_PASS_ON_CHANGE, + }, }, .synch_fail_thold = 10, .bss_lose_timeout = 100, -- cgit v1.2.3-70-g09d2 From fcd23b6305e98f5ad3ddd7ff3f5081c75fcd4367 Mon Sep 17 00:00:00 2001 From: Shahar Levi Date: Wed, 11 May 2011 12:12:56 +0300 Subject: wl12xx: add IEEE80211_HW_SPECTRUM_MGMT bit to the hw flags Set the spectrum management bit in the hw flags so that mac80211 will set the WLAN_CAPABILITY_SPECTRUM_MGMT bit in association requests (which in practice means that we support 802.11h spectrum management). [Reworded the commit log -- Luca.] Signed-off-by: Shahar Levi Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index fa6b996d7d4..acfbbfd3b7d 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -3634,6 +3634,7 @@ int wl1271_init_ieee80211(struct wl1271 *wl) IEEE80211_HW_CONNECTION_MONITOR | IEEE80211_HW_SUPPORTS_CQM_RSSI | IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_SPECTRUM_MGMT | IEEE80211_HW_AP_LINK_PS; wl->hw->wiphy->cipher_suites = cipher_suites; -- cgit v1.2.3-70-g09d2 From 3a9d60e5bd72f9533b05d39278fec50b181dbdd2 Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Tue, 10 May 2011 14:06:31 +0300 Subject: wl12xx: add configuration values for scheduled scan Add the structures and values for driver-configured scheduled scan parameters. Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/conf.h | 21 +++++++++++++++++++++ drivers/net/wireless/wl12xx/main.c | 9 +++++++++ 2 files changed, 30 insertions(+) diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index 1f947368f9e..ba558fcc76d 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h @@ -1147,6 +1147,26 @@ struct conf_scan_settings { }; +struct conf_sched_scan_settings { + /* minimum time to wait on the channel for active scans (in TUs) */ + u16 min_dwell_time_active; + + /* maximum time to wait on the channel for active scans (in TUs) */ + u16 max_dwell_time_active; + + /* time to wait on the channel for passive scans (in TUs) */ + u32 dwell_time_passive; + + /* number of probe requests to send on each channel in active scans */ + u8 num_probe_reqs; + + /* RSSI threshold to be used for filtering */ + s8 rssi_threshold; + + /* SNR threshold to be used for filtering */ + s8 snr_threshold; +}; + /* these are number of channels on the band divided by two, rounded up */ #define CONF_TX_PWR_COMPENSATION_LEN_2 7 #define CONF_TX_PWR_COMPENSATION_LEN_5 18 @@ -1234,6 +1254,7 @@ struct conf_drv_settings { struct conf_pm_config_settings pm_config; struct conf_roam_trigger_settings roam_trigger; struct conf_scan_settings scan; + struct conf_sched_scan_settings sched_scan; struct conf_rf_settings rf; struct conf_ht_setting ht; struct conf_memory_settings mem_wl127x; diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index acfbbfd3b7d..88d2e9052a0 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -306,6 +306,15 @@ static struct conf_drv_settings default_conf = { .max_dwell_time_passive = 100000, .num_probe_reqs = 2, }, + .sched_scan = { + /* sched_scan requires dwell times in TU instead of TU/1000 */ + .min_dwell_time_active = 8, + .max_dwell_time_active = 30, + .dwell_time_passive = 100, + .num_probe_reqs = 2, + .rssi_threshold = -90, + .snr_threshold = 0, + }, .rf = { .tx_per_channel_power_compensation_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -- cgit v1.2.3-70-g09d2 From 6394c01b61f8ab66a6af1a24ff05f2429130afcd Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Tue, 10 May 2011 14:28:27 +0300 Subject: wl12xx: listen to scheduled scan events Subscribe and listen to PERIODIC_SCAN_REPORT_EVENT_ID and PERIODIC_SCAN_COMPLETE_EVENT_ID in preparation for the scheduled scan implementation. Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/boot.c | 4 +++- drivers/net/wireless/wl12xx/event.c | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/wl12xx/boot.c b/drivers/net/wireless/wl12xx/boot.c index 2b0cf85788b..b07f8b7e5f1 100644 --- a/drivers/net/wireless/wl12xx/boot.c +++ b/drivers/net/wireless/wl12xx/boot.c @@ -478,7 +478,9 @@ static int wl1271_boot_run_firmware(struct wl1271 *wl) DISCONNECT_EVENT_COMPLETE_ID | RSSI_SNR_TRIGGER_0_EVENT_ID | PSPOLL_DELIVERY_FAILURE_EVENT_ID | - SOFT_GEMINI_SENSE_EVENT_ID; + SOFT_GEMINI_SENSE_EVENT_ID | + PERIODIC_SCAN_REPORT_EVENT_ID | + PERIODIC_SCAN_COMPLETE_EVENT_ID; if (wl->bss_type == BSS_TYPE_AP_BSS) wl->event_mask |= STA_REMOVE_COMPLETE_EVENT_ID; diff --git a/drivers/net/wireless/wl12xx/event.c b/drivers/net/wireless/wl12xx/event.c index ae69330e807..dc110e8a618 100644 --- a/drivers/net/wireless/wl12xx/event.c +++ b/drivers/net/wireless/wl12xx/event.c @@ -188,6 +188,16 @@ static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox) wl1271_scan_stm(wl); } + if (vector & PERIODIC_SCAN_REPORT_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "PERIODIC_SCAN_REPORT_EVENT " + "(status 0x%0x)", mbox->scheduled_scan_status); + } + + if (vector & PERIODIC_SCAN_COMPLETE_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "PERIODIC_SCAN_COMPLETE_EVENT " + "(status 0x%0x)", mbox->scheduled_scan_status); + } + /* disable dynamic PS when requested by the firmware */ if (vector & SOFT_GEMINI_SENSE_EVENT_ID && wl->bss_type == BSS_TYPE_STA_BSS) { -- cgit v1.2.3-70-g09d2 From 95feadca6dca909ae0f6e65665b782c7ca9d5122 Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Tue, 10 May 2011 14:38:59 +0300 Subject: wl12xx: add scheduled scan structures and commands Add firmware command structures, definitions and code to to configure, start and stop scheduled scans. Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/scan.c | 233 +++++++++++++++++++++++++++++++++++++ drivers/net/wireless/wl12xx/scan.h | 114 ++++++++++++++++++ 2 files changed, 347 insertions(+) diff --git a/drivers/net/wireless/wl12xx/scan.c b/drivers/net/wireless/wl12xx/scan.c index 5d0544c8f3f..d78044f0081 100644 --- a/drivers/net/wireless/wl12xx/scan.c +++ b/drivers/net/wireless/wl12xx/scan.c @@ -320,3 +320,236 @@ int wl1271_scan(struct wl1271 *wl, const u8 *ssid, size_t ssid_len, return 0; } + +static int +wl1271_scan_get_sched_scan_channels(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct conn_scan_ch_params *channels, + u32 band, bool radar, bool passive, + int start) +{ + struct conf_sched_scan_settings *c = &wl->conf.sched_scan; + int i, j; + u32 flags; + + for (i = 0, j = start; + i < req->n_channels && j < MAX_CHANNELS_ALL_BANDS; + i++) { + flags = req->channels[i]->flags; + + if (!(flags & IEEE80211_CHAN_DISABLED) && + ((flags & IEEE80211_CHAN_PASSIVE_SCAN) == passive) && + ((flags & IEEE80211_CHAN_RADAR) == radar) && + (req->channels[i]->band == band)) { + wl1271_debug(DEBUG_SCAN, "band %d, center_freq %d ", + req->channels[i]->band, + req->channels[i]->center_freq); + wl1271_debug(DEBUG_SCAN, "hw_value %d, flags %X", + req->channels[i]->hw_value, + req->channels[i]->flags); + wl1271_debug(DEBUG_SCAN, "max_power %d", + req->channels[i]->max_power); + + if (flags & IEEE80211_CHAN_PASSIVE_SCAN) { + channels[j].passive_duration = + cpu_to_le16(c->dwell_time_passive); + } else { + channels[j].min_duration = + cpu_to_le16(c->min_dwell_time_active); + channels[j].max_duration = + cpu_to_le16(c->max_dwell_time_active); + } + channels[j].tx_power_att = req->channels[j]->max_power; + channels[j].channel = req->channels[i]->hw_value; + + j++; + } + } + + return j - start; +} + +static int +wl1271_scan_sched_scan_channels(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct wl1271_cmd_sched_scan_config *cfg) +{ + int idx = 0; + + cfg->passive[0] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_2GHZ, + false, true, idx); + idx += cfg->passive[0]; + + cfg->active[0] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_2GHZ, + false, false, idx); + idx += cfg->active[0]; + + cfg->passive[1] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_5GHZ, + false, true, idx); + idx += cfg->passive[1]; + + cfg->active[1] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_5GHZ, + false, false, 14); + idx += cfg->active[1]; + + cfg->dfs = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_5GHZ, + true, false, idx); + idx += cfg->dfs; + + wl1271_debug(DEBUG_SCAN, " 2.4GHz: active %d passive %d", + cfg->active[0], cfg->passive[0]); + wl1271_debug(DEBUG_SCAN, " 5GHz: active %d passive %d", + cfg->active[1], cfg->passive[1]); + + return idx; +} + +int wl1271_scan_sched_scan_config(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + struct wl1271_cmd_sched_scan_config *cfg = NULL; + struct conf_sched_scan_settings *c = &wl->conf.sched_scan; + int i, total_channels, ret; + + wl1271_debug(DEBUG_CMD, "cmd sched_scan scan config"); + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->rssi_threshold = c->rssi_threshold; + cfg->snr_threshold = c->snr_threshold; + cfg->n_probe_reqs = c->num_probe_reqs; + /* cycles set to 0 it means infinite (until manually stopped) */ + cfg->cycles = 0; + /* report APs when at least 1 is found */ + cfg->report_after = 1; + /* don't stop scanning automatically when something is found */ + cfg->terminate = 0; + cfg->tag = WL1271_SCAN_DEFAULT_TAG; + /* don't filter on BSS type */ + cfg->bss_type = SCAN_BSS_TYPE_ANY; + /* currently NL80211 supports only a single interval */ + for (i = 0; i < SCAN_MAX_CYCLE_INTERVALS; i++) + cfg->intervals[i] = cpu_to_le32(req->interval); + + if (req->ssids[0].ssid_len && req->ssids[0].ssid) { + cfg->filter_type = SCAN_SSID_FILTER_SPECIFIC; + cfg->ssid_len = req->ssids[0].ssid_len; + memcpy(cfg->ssid, req->ssids[0].ssid, + req->ssids[0].ssid_len); + } else { + cfg->filter_type = SCAN_SSID_FILTER_ANY; + cfg->ssid_len = 0; + } + + total_channels = wl1271_scan_sched_scan_channels(wl, req, cfg); + if (total_channels == 0) { + wl1271_error("scan channel list is empty"); + ret = -EINVAL; + goto out; + } + + if (cfg->active[0]) { + ret = wl1271_cmd_build_probe_req(wl, req->ssids[0].ssid, + req->ssids[0].ssid_len, + ies->ie[IEEE80211_BAND_2GHZ], + ies->len[IEEE80211_BAND_2GHZ], + IEEE80211_BAND_2GHZ); + if (ret < 0) { + wl1271_error("2.4GHz PROBE request template failed"); + goto out; + } + } + + if (cfg->active[1]) { + ret = wl1271_cmd_build_probe_req(wl, req->ssids[0].ssid, + req->ssids[0].ssid_len, + ies->ie[IEEE80211_BAND_5GHZ], + ies->len[IEEE80211_BAND_5GHZ], + IEEE80211_BAND_5GHZ); + if (ret < 0) { + wl1271_error("5GHz PROBE request template failed"); + goto out; + } + } + + wl1271_dump(DEBUG_SCAN, "SCAN_CFG: ", cfg, sizeof(*cfg)); + + ret = wl1271_cmd_send(wl, CMD_CONNECTION_SCAN_CFG, cfg, + sizeof(*cfg), 0); + if (ret < 0) { + wl1271_error("SCAN configuration failed"); + goto out; + } +out: + kfree(cfg); + return ret; +} + +int wl1271_scan_sched_scan_start(struct wl1271 *wl) +{ + struct wl1271_cmd_sched_scan_start *start; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd periodic scan start"); + + start = kzalloc(sizeof(*start), GFP_KERNEL); + if (!start) + return -ENOMEM; + + start->tag = WL1271_SCAN_DEFAULT_TAG; + + ret = wl1271_cmd_send(wl, CMD_START_PERIODIC_SCAN, start, + sizeof(*start), 0); + if (ret < 0) { + wl1271_error("failed to send scan start command"); + goto out_free; + } + +out_free: + kfree(start); + return ret; +} + +void wl1271_scan_sched_scan_results(struct wl1271 *wl) +{ + wl1271_debug(DEBUG_SCAN, "got periodic scan results"); + + ieee80211_sched_scan_results(wl->hw); +} + +void wl1271_scan_sched_scan_stop(struct wl1271 *wl) +{ + struct wl1271_cmd_sched_scan_stop *stop; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd periodic scan stop"); + + /* FIXME: what to do if alloc'ing to stop fails? */ + stop = kzalloc(sizeof(*stop), GFP_KERNEL); + if (!stop) { + wl1271_error("failed to alloc memory to send sched scan stop"); + return; + } + + stop->tag = WL1271_SCAN_DEFAULT_TAG; + + ret = wl1271_cmd_send(wl, CMD_STOP_PERIODIC_SCAN, stop, + sizeof(*stop), 0); + if (ret < 0) + wl1271_error("failed to send sched scan stop command"); + + kfree(stop); +} diff --git a/drivers/net/wireless/wl12xx/scan.h b/drivers/net/wireless/wl12xx/scan.h index 421a750add5..c83319579ca 100644 --- a/drivers/net/wireless/wl12xx/scan.h +++ b/drivers/net/wireless/wl12xx/scan.h @@ -33,6 +33,12 @@ int wl1271_scan_build_probe_req(struct wl1271 *wl, const u8 *ie, size_t ie_len, u8 band); void wl1271_scan_stm(struct wl1271 *wl); void wl1271_scan_complete_work(struct work_struct *work); +int wl1271_scan_sched_scan_config(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies); +int wl1271_scan_sched_scan_start(struct wl1271 *wl); +void wl1271_scan_sched_scan_stop(struct wl1271 *wl); +void wl1271_scan_sched_scan_results(struct wl1271 *wl); #define WL1271_SCAN_MAX_CHANNELS 24 #define WL1271_SCAN_DEFAULT_TAG 1 @@ -106,4 +112,112 @@ struct wl1271_cmd_trigger_scan_to { __le32 timeout; } __packed; +#define MAX_CHANNELS_ALL_BANDS 41 +#define SCAN_MAX_CYCLE_INTERVALS 16 +#define SCAN_MAX_BANDS 3 + +enum { + SCAN_CHANNEL_TYPE_2GHZ_PASSIVE, + SCAN_CHANNEL_TYPE_2GHZ_ACTIVE, + SCAN_CHANNEL_TYPE_5GHZ_PASSIVE, + SCAN_CHANNEL_TYPE_5GHZ_ACTIVE, + SCAN_CHANNEL_TYPE_5GHZ_DFS, +}; + +enum { + SCAN_SSID_FILTER_ANY = 0, + SCAN_SSID_FILTER_SPECIFIC = 1, + SCAN_SSID_FILTER_LIST = 2, + SCAN_SSID_FILTER_DISABLED = 3 +}; + +enum { + SCAN_BSS_TYPE_INDEPENDENT, + SCAN_BSS_TYPE_INFRASTRUCTURE, + SCAN_BSS_TYPE_ANY, +}; + +struct conn_scan_ch_params { + __le16 min_duration; + __le16 max_duration; + __le16 passive_duration; + + u8 channel; + u8 tx_power_att; + + /* bit 0: DFS channel; bit 1: DFS enabled */ + u8 flags; + + u8 padding[3]; +} __packed; + +struct wl1271_cmd_sched_scan_config { + struct wl1271_cmd_header header; + + __le32 intervals[SCAN_MAX_CYCLE_INTERVALS]; + + s8 rssi_threshold; /* for filtering (in dBm) */ + s8 snr_threshold; /* for filtering (in dB) */ + + u8 cycles; /* maximum number of scan cycles */ + u8 report_after; /* report when this number of results are received */ + u8 terminate; /* stop scanning after reporting */ + + u8 tag; + u8 bss_type; /* for filtering */ + u8 filter_type; + + u8 ssid_len; /* For SCAN_SSID_FILTER_SPECIFIC */ + u8 ssid[IW_ESSID_MAX_SIZE]; + + u8 n_probe_reqs; /* Number of probes requests per channel */ + + u8 passive[SCAN_MAX_BANDS]; + u8 active[SCAN_MAX_BANDS]; + + u8 dfs; + + u8 padding[3]; + + struct conn_scan_ch_params channels[MAX_CHANNELS_ALL_BANDS]; +} __packed; + + +#define SCHED_SCAN_MAX_SSIDS 8 + +enum { + SCAN_SSID_TYPE_PUBLIC = 0, + SCAN_SSID_TYPE_HIDDEN = 1, +}; + +struct wl1271_ssid { + u8 type; + u8 len; + u8 ssid[IW_ESSID_MAX_SIZE]; + /* u8 padding[2]; */ +} __packed; + +struct wl1271_cmd_sched_scan_ssid_list { + struct wl1271_cmd_header header; + + u8 n_ssids; + struct wl1271_ssid ssids[SCHED_SCAN_MAX_SSIDS]; + u8 padding[3]; +} __packed; + +struct wl1271_cmd_sched_scan_start { + struct wl1271_cmd_header header; + + u8 tag; + u8 padding[3]; +} __packed; + +struct wl1271_cmd_sched_scan_stop { + struct wl1271_cmd_header header; + + u8 tag; + u8 padding[3]; +} __packed; + + #endif /* __WL1271_SCAN_H__ */ -- cgit v1.2.3-70-g09d2 From 33c2c06cd2d766387cf919d0afd432cc5796c369 Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Tue, 10 May 2011 14:46:02 +0300 Subject: wl12xx: implement scheduled scan driver operations and reporting This patch adds the mac80211 operations for scheduled scan and the scheduled scan results reporting. Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/event.c | 6 ++++ drivers/net/wireless/wl12xx/main.c | 70 ++++++++++++++++++++++++++++++++++++ drivers/net/wireless/wl12xx/scan.c | 6 +++- drivers/net/wireless/wl12xx/wl12xx.h | 2 ++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/wl12xx/event.c b/drivers/net/wireless/wl12xx/event.c index dc110e8a618..1e4bd6a2c39 100644 --- a/drivers/net/wireless/wl12xx/event.c +++ b/drivers/net/wireless/wl12xx/event.c @@ -191,11 +191,17 @@ static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox) if (vector & PERIODIC_SCAN_REPORT_EVENT_ID) { wl1271_debug(DEBUG_EVENT, "PERIODIC_SCAN_REPORT_EVENT " "(status 0x%0x)", mbox->scheduled_scan_status); + + wl1271_scan_sched_scan_results(wl); } if (vector & PERIODIC_SCAN_COMPLETE_EVENT_ID) { wl1271_debug(DEBUG_EVENT, "PERIODIC_SCAN_COMPLETE_EVENT " "(status 0x%0x)", mbox->scheduled_scan_status); + if (wl->sched_scanning) { + wl1271_scan_sched_scan_stop(wl); + ieee80211_sched_scan_stopped(wl->hw); + } } /* disable dynamic PS when requested by the firmware */ diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 88d2e9052a0..a14a035aa44 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -988,6 +988,11 @@ static void wl1271_recovery_work(struct work_struct *work) /* Prevent spurious TX during FW restart */ ieee80211_stop_queues(wl->hw); + if (wl->sched_scanning) { + ieee80211_sched_scan_stopped(wl->hw); + wl->sched_scanning = false; + } + /* reboot the chipset */ __wl1271_op_remove_interface(wl, false); ieee80211_restart_hw(wl->hw); @@ -1576,6 +1581,7 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl, memset(wl->ap_hlid_map, 0, sizeof(wl->ap_hlid_map)); wl->ap_fw_ps_map = 0; wl->ap_ps_map = 0; + wl->sched_scanning = false; /* * this is performed after the cancel_work calls and the associated @@ -1778,6 +1784,13 @@ static int wl1271_sta_handle_idle(struct wl1271 *wl, bool idle) wl->session_counter++; if (wl->session_counter >= SESSION_COUNTER_MAX) wl->session_counter = 0; + + /* The current firmware only supports sched_scan in idle */ + if (wl->sched_scanning) { + wl1271_scan_sched_scan_stop(wl); + ieee80211_sched_scan_stopped(wl->hw); + } + ret = wl1271_dummy_join(wl); if (ret < 0) goto out; @@ -2330,6 +2343,60 @@ out: return ret; } +static int wl1271_op_sched_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + struct wl1271 *wl = hw->priv; + int ret; + + wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_start"); + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_scan_sched_scan_config(wl, req, ies); + if (ret < 0) + goto out_sleep; + + ret = wl1271_scan_sched_scan_start(wl); + if (ret < 0) + goto out_sleep; + + wl->sched_scanning = true; + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return ret; +} + +static void wl1271_op_sched_scan_stop(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + int ret; + + wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_stop"); + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_scan_sched_scan_stop(wl); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) { struct wl1271 *wl = hw->priv; @@ -3445,6 +3512,8 @@ static const struct ieee80211_ops wl1271_ops = { .tx = wl1271_op_tx, .set_key = wl1271_op_set_key, .hw_scan = wl1271_op_hw_scan, + .sched_scan_start = wl1271_op_sched_scan_start, + .sched_scan_stop = wl1271_op_sched_scan_stop, .bss_info_changed = wl1271_op_bss_info_changed, .set_frag_threshold = wl1271_op_set_frag_threshold, .set_rts_threshold = wl1271_op_set_rts_threshold, @@ -3765,6 +3834,7 @@ struct ieee80211_hw *wl1271_alloc_hw(void) wl->ap_fw_ps_map = 0; wl->quirks = 0; wl->platform_quirks = 0; + wl->sched_scanning = false; memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); for (i = 0; i < ACX_TX_DESCRIPTORS; i++) diff --git a/drivers/net/wireless/wl12xx/scan.c b/drivers/net/wireless/wl12xx/scan.c index d78044f0081..668ff46a682 100644 --- a/drivers/net/wireless/wl12xx/scan.c +++ b/drivers/net/wireless/wl12xx/scan.c @@ -548,8 +548,12 @@ void wl1271_scan_sched_scan_stop(struct wl1271 *wl) ret = wl1271_cmd_send(wl, CMD_STOP_PERIODIC_SCAN, stop, sizeof(*stop), 0); - if (ret < 0) + if (ret < 0) { wl1271_error("failed to send sched scan stop command"); + goto out_free; + } + wl->sched_scanning = false; +out_free: kfree(stop); } diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index b7601438eca..10f076770fe 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -480,6 +480,8 @@ struct wl1271 { struct wl1271_scan scan; struct delayed_work scan_complete_work; + bool sched_scanning; + /* probe-req template for the current AP */ struct sk_buff *probereq; -- cgit v1.2.3-70-g09d2 From d3eff81de6048d8af8f95f52f0f06625980f2efb Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Tue, 10 May 2011 14:47:45 +0300 Subject: wl12xx: export scheduled scan state in debugfs Add the sched_scanning value to the driver_status debugfs entry. Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/debugfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/wl12xx/debugfs.c b/drivers/net/wireless/wl12xx/debugfs.c index b2f692babed..f1f8df9b6cd 100644 --- a/drivers/net/wireless/wl12xx/debugfs.c +++ b/drivers/net/wireless/wl12xx/debugfs.c @@ -377,6 +377,7 @@ static ssize_t driver_state_read(struct file *file, char __user *user_buf, DRIVER_STATE_PRINT_HEX(platform_quirks); DRIVER_STATE_PRINT_HEX(chip.id); DRIVER_STATE_PRINT_STR(chip.fw_ver_str); + DRIVER_STATE_PRINT_INT(sched_scanning); #undef DRIVER_STATE_PRINT_INT #undef DRIVER_STATE_PRINT_LONG -- cgit v1.2.3-70-g09d2 From 683c002447c12742f5151691083f68524f33b13a Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Thu, 12 May 2011 17:07:55 +0300 Subject: wl12xx: prevent sched_scan when not idle or not in station mode The current firmware only supports scheduled scan in station mode and when idle. To prevent the firmware from crashing, return -EOPNOTSUPP when sched_scan start is called in an invalid state. Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/scan.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/wl12xx/scan.c b/drivers/net/wireless/wl12xx/scan.c index 668ff46a682..f37e5a39197 100644 --- a/drivers/net/wireless/wl12xx/scan.c +++ b/drivers/net/wireless/wl12xx/scan.c @@ -505,6 +505,12 @@ int wl1271_scan_sched_scan_start(struct wl1271 *wl) wl1271_debug(DEBUG_CMD, "cmd periodic scan start"); + if (wl->bss_type != BSS_TYPE_STA_BSS) + return -EOPNOTSUPP; + + if (!test_bit(WL1271_FLAG_IDLE, &wl->flags)) + return -EBUSY; + start = kzalloc(sizeof(*start), GFP_KERNEL); if (!start) return -ENOMEM; -- cgit v1.2.3-70-g09d2 From fe44870bcdf614e4abb35657c68081cda35ba741 Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Thu, 12 May 2011 16:50:41 +0300 Subject: wl12xx: remove unused flag WL1271_FLAG_IDLE_REQUESTED This flag is not used anymore, remove it from the list. Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/wl12xx.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 10f076770fe..ab0c2f155b8 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -351,7 +351,6 @@ enum wl12xx_flags { WL1271_FLAG_PSM_REQUESTED, WL1271_FLAG_IRQ_RUNNING, WL1271_FLAG_IDLE, - WL1271_FLAG_IDLE_REQUESTED, WL1271_FLAG_PSPOLL_FAILURE, WL1271_FLAG_STA_STATE_SENT, WL1271_FLAG_FW_TX_BUSY, -- cgit v1.2.3-70-g09d2 From 2c0f24636c80aa09990c507c0cede39add4b4724 Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Fri, 13 May 2011 11:57:08 +0300 Subject: wl12xx_sdio: set interrupt as wake_up interrupt set the sdio interrupt as wake_up interrupt, so we will be able to wake up the suspended system (Wake-On-Wireless) Signed-off-by: Eliad Peller Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/sdio.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c index bcd4ad7ba90..1298461c45d 100644 --- a/drivers/net/wireless/wl12xx/sdio.c +++ b/drivers/net/wireless/wl12xx/sdio.c @@ -267,6 +267,8 @@ static int __devinit wl1271_probe(struct sdio_func *func, goto out_free; } + enable_irq_wake(wl->irq); + disable_irq(wl->irq); ret = wl1271_init_ieee80211(wl); @@ -303,6 +305,7 @@ static void __devexit wl1271_remove(struct sdio_func *func) pm_runtime_get_noresume(&func->dev); wl1271_unregister_hw(wl); + disable_irq_wake(wl->irq); free_irq(wl->irq, wl); wl1271_free_hw(wl); } -- cgit v1.2.3-70-g09d2 From 402e48616078c1e56f55a69d314b77f1d750d6ad Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Fri, 13 May 2011 11:57:09 +0300 Subject: wl12xx: declare suspend/resume callbacks (for wowlan) Additionally, add wow_enabled field to wl, to indicate whether wowlan was configured. Signed-off-by: Eliad Peller Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/main.c | 19 +++++++++++++++++++ drivers/net/wireless/wl12xx/wl12xx.h | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index a14a035aa44..4b421d80187 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -1350,6 +1350,23 @@ static struct notifier_block wl1271_dev_notifier = { .notifier_call = wl1271_dev_notify, }; +static int wl1271_op_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wow) +{ + struct wl1271 *wl = hw->priv; + wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow); + wl->wow_enabled = !!wow; + return 0; +} + +static int wl1271_op_resume(struct ieee80211_hw *hw) +{ + struct wl1271 *wl = hw->priv; + wl1271_debug(DEBUG_MAC80211, "mac80211 resume wow=%d", + wl->wow_enabled); + return 0; +} + static int wl1271_op_start(struct ieee80211_hw *hw) { wl1271_debug(DEBUG_MAC80211, "mac80211 start"); @@ -3506,6 +3523,8 @@ static const struct ieee80211_ops wl1271_ops = { .stop = wl1271_op_stop, .add_interface = wl1271_op_add_interface, .remove_interface = wl1271_op_remove_interface, + .suspend = wl1271_op_suspend, + .resume = wl1271_op_resume, .config = wl1271_op_config, .prepare_multicast = wl1271_op_prepare_multicast, .configure_filter = wl1271_op_configure_filter, diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index ab0c2f155b8..9629e90d9b5 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -564,6 +564,12 @@ struct wl1271 { int tcxo_clock; + /* + * wowlan trigger was configured during suspend. + * (currently, only "ANY" trigger is supported) + */ + bool wow_enabled; + /* * AP-mode - links indexed by HLID. The global and broadcast links * are always active. -- cgit v1.2.3-70-g09d2 From 039bdb1494d1d514987ce596a4898494021c7af2 Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Fri, 13 May 2011 11:57:10 +0300 Subject: wl12xx_sdio: set MMC_PM_KEEP_POWER flag on suspend if a wow trigger was configured, set the MMC_PM_KEEP_POWER flag on suspend, so our power will be kept while the system is suspended. We needed to set this flag on each suspend attempt (when we want to keep power) Signed-off-by: Eliad Peller Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/sdio.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c index 1298461c45d..5b03fd5ee33 100644 --- a/drivers/net/wireless/wl12xx/sdio.c +++ b/drivers/net/wireless/wl12xx/sdio.c @@ -314,7 +314,34 @@ static int wl1271_suspend(struct device *dev) { /* Tell MMC/SDIO core it's OK to power down the card * (if it isn't already), but not to remove it completely */ - return 0; + struct sdio_func *func = dev_to_sdio_func(dev); + struct wl1271 *wl = sdio_get_drvdata(func); + mmc_pm_flag_t sdio_flags; + int ret = 0; + + wl1271_debug(DEBUG_MAC80211, "wl1271 suspend. wow_enabled: %d", + wl->wow_enabled); + + /* check whether sdio should keep power */ + if (wl->wow_enabled) { + sdio_flags = sdio_get_host_pm_caps(func); + + if (!(sdio_flags & MMC_PM_KEEP_POWER)) { + wl1271_error("can't keep power while host " + "is suspended"); + ret = -EINVAL; + goto out; + } + + /* keep power while host suspended */ + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) { + wl1271_error("error while trying to keep power"); + goto out; + } + } +out: + return ret; } static int wl1271_resume(struct device *dev) -- cgit v1.2.3-70-g09d2 From f44e58681aec420b132a54823d8911293a644d4e Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Fri, 13 May 2011 11:57:11 +0300 Subject: wl12xx: prevent scheduling while suspending (WoW enabled) When WoW is enabled, the interface will stay up and the chip will be powered on, so we have to flush/cancel any remaining work, and prevent the irq handler from scheduling a new work until the system is resumed. Add 2 new flags: * WL1271_FLAG_SUSPENDED - the system is (about to be) suspended. * WL1271_FLAG_PENDING_WORK - there is a pending irq work which should be scheduled when the system is being resumed. In order to wake-up the system while getting an irq, we initialize the device as wakeup device, and calling pm_wakeup_event() upon getting the interrupt (while the system is about to be suspended) Signed-off-by: Eliad Peller Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/main.c | 46 ++++++++++++++++++++++++++++++++++++ drivers/net/wireless/wl12xx/sdio.c | 24 +++++++++++++++++++ drivers/net/wireless/wl12xx/wl12xx.h | 2 ++ 3 files changed, 72 insertions(+) diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 4b421d80187..8f9e6152f3b 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -1356,6 +1356,28 @@ static int wl1271_op_suspend(struct ieee80211_hw *hw, struct wl1271 *wl = hw->priv; wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow); wl->wow_enabled = !!wow; + if (wl->wow_enabled) { + /* flush any remaining work */ + wl1271_debug(DEBUG_MAC80211, "flushing remaining works"); + flush_delayed_work(&wl->scan_complete_work); + + /* + * disable and re-enable interrupts in order to flush + * the threaded_irq + */ + wl1271_disable_interrupts(wl); + + /* + * set suspended flag to avoid triggering a new threaded_irq + * work. no need for spinlock as interrupts are disabled. + */ + set_bit(WL1271_FLAG_SUSPENDED, &wl->flags); + + wl1271_enable_interrupts(wl); + flush_work(&wl->tx_work); + flush_delayed_work(&wl->pspoll_work); + flush_delayed_work(&wl->elp_work); + } return 0; } @@ -1364,6 +1386,30 @@ static int wl1271_op_resume(struct ieee80211_hw *hw) struct wl1271 *wl = hw->priv; wl1271_debug(DEBUG_MAC80211, "mac80211 resume wow=%d", wl->wow_enabled); + + /* + * re-enable irq_work enqueuing, and call irq_work directly if + * there is a pending work. + */ + if (wl->wow_enabled) { + struct wl1271 *wl = hw->priv; + unsigned long flags; + bool run_irq_work = false; + + spin_lock_irqsave(&wl->wl_lock, flags); + clear_bit(WL1271_FLAG_SUSPENDED, &wl->flags); + if (test_and_clear_bit(WL1271_FLAG_PENDING_WORK, &wl->flags)) + run_irq_work = true; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + if (run_irq_work) { + wl1271_debug(DEBUG_MAC80211, + "run postponed irq_work directly"); + wl1271_irq(0, wl); + wl1271_enable_interrupts(wl); + } + } + return 0; } diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c index 5b03fd5ee33..41183db3483 100644 --- a/drivers/net/wireless/wl12xx/sdio.c +++ b/drivers/net/wireless/wl12xx/sdio.c @@ -82,6 +82,16 @@ static irqreturn_t wl1271_hardirq(int irq, void *cookie) complete(wl->elp_compl); wl->elp_compl = NULL; } + + if (test_bit(WL1271_FLAG_SUSPENDED, &wl->flags)) { + /* don't enqueue a work right now. mark it as pending */ + set_bit(WL1271_FLAG_PENDING_WORK, &wl->flags); + wl1271_debug(DEBUG_IRQ, "should not enqueue work"); + disable_irq_nosync(wl->irq); + pm_wakeup_event(wl1271_sdio_wl_to_dev(wl), 0); + spin_unlock_irqrestore(&wl->wl_lock, flags); + return IRQ_HANDLED; + } spin_unlock_irqrestore(&wl->wl_lock, flags); return IRQ_WAKE_THREAD; @@ -268,6 +278,7 @@ static int __devinit wl1271_probe(struct sdio_func *func, } enable_irq_wake(wl->irq); + device_init_wakeup(wl1271_sdio_wl_to_dev(wl), 1); disable_irq(wl->irq); @@ -305,6 +316,7 @@ static void __devexit wl1271_remove(struct sdio_func *func) pm_runtime_get_noresume(&func->dev); wl1271_unregister_hw(wl); + device_init_wakeup(wl1271_sdio_wl_to_dev(wl), 0); disable_irq_wake(wl->irq); free_irq(wl->irq, wl); wl1271_free_hw(wl); @@ -339,6 +351,9 @@ static int wl1271_suspend(struct device *dev) wl1271_error("error while trying to keep power"); goto out; } + + /* release host */ + sdio_release_host(func); } out: return ret; @@ -346,6 +361,15 @@ out: static int wl1271_resume(struct device *dev) { + struct sdio_func *func = dev_to_sdio_func(dev); + struct wl1271 *wl = sdio_get_drvdata(func); + + wl1271_debug(DEBUG_MAC80211, "wl1271 resume"); + if (wl->wow_enabled) { + /* claim back host */ + sdio_claim_host(func); + } + return 0; } diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 9629e90d9b5..2218b9c6384 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -357,6 +357,8 @@ enum wl12xx_flags { WL1271_FLAG_AP_STARTED, WL1271_FLAG_IF_INITIALIZED, WL1271_FLAG_DUMMY_PACKET_PENDING, + WL1271_FLAG_SUSPENDED, + WL1271_FLAG_PENDING_WORK, }; struct wl1271_link { -- cgit v1.2.3-70-g09d2 From f795ea8b2f047409c59e891d6e5e86a925048bf4 Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Fri, 13 May 2011 11:57:12 +0300 Subject: wl12xx_sdio: declare support for NL80211_WOW_TRIGGER_ANYTHING trigger Since wowlan requires the ability to stay awake while the host is suspended, declare support for NL80211_WOW_TRIGGER_ANYTHING if the MMC_PM_KEEP_POWER capability is being supported. Signed-off-by: Eliad Peller Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/sdio.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c index 41183db3483..92d29a860fc 100644 --- a/drivers/net/wireless/wl12xx/sdio.c +++ b/drivers/net/wireless/wl12xx/sdio.c @@ -231,6 +231,7 @@ static int __devinit wl1271_probe(struct sdio_func *func, const struct wl12xx_platform_data *wlan_data; struct wl1271 *wl; unsigned long irqflags; + mmc_pm_flag_t mmcflags; int ret; /* We are only able to handle the wlan function */ @@ -282,6 +283,13 @@ static int __devinit wl1271_probe(struct sdio_func *func, disable_irq(wl->irq); + /* if sdio can keep power while host is suspended, enable wow */ + mmcflags = sdio_get_host_pm_caps(func); + wl1271_debug(DEBUG_SDIO, "sdio PM caps = 0x%x", mmcflags); + + if (mmcflags & MMC_PM_KEEP_POWER) + hw->wiphy->wowlan.flags = WIPHY_WOWLAN_ANY; + ret = wl1271_init_ieee80211(wl); if (ret) goto out_irq; -- cgit v1.2.3-70-g09d2 From 9439064cd9fce8a4db716a748dbf581eb234f9c7 Mon Sep 17 00:00:00 2001 From: Eliad Peller Date: Fri, 13 May 2011 11:57:13 +0300 Subject: wl12xx: enter/exit psm on wowlan suspend/resume When operating as station, enter psm before suspending the device into wowlan state. Add a new completion event to signal when psm was entered successfully. Signed-off-by: Eliad Peller Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/event.c | 7 ++++ drivers/net/wireless/wl12xx/main.c | 81 ++++++++++++++++++++++++++++++++++++ drivers/net/wireless/wl12xx/ps.h | 2 + drivers/net/wireless/wl12xx/wl12xx.h | 1 + 4 files changed, 91 insertions(+) diff --git a/drivers/net/wireless/wl12xx/event.c b/drivers/net/wireless/wl12xx/event.c index 1e4bd6a2c39..c3c554cd658 100644 --- a/drivers/net/wireless/wl12xx/event.c +++ b/drivers/net/wireless/wl12xx/event.c @@ -135,6 +135,13 @@ static int wl1271_event_ps_report(struct wl1271 *wl, /* enable beacon early termination */ ret = wl1271_acx_bet_enable(wl, true); + if (ret < 0) + break; + + if (wl->ps_compl) { + complete(wl->ps_compl); + wl->ps_compl = NULL; + } break; default: break; diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 8f9e6152f3b..610be03a198 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -1350,6 +1350,79 @@ static struct notifier_block wl1271_dev_notifier = { .notifier_call = wl1271_dev_notify, }; +static int wl1271_configure_suspend(struct wl1271 *wl) +{ + int ret; + + if (wl->bss_type != BSS_TYPE_STA_BSS) + return 0; + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_unlock; + + /* enter psm if needed*/ + if (!test_bit(WL1271_FLAG_PSM, &wl->flags)) { + DECLARE_COMPLETION_ONSTACK(compl); + + wl->ps_compl = &compl; + ret = wl1271_ps_set_mode(wl, STATION_POWER_SAVE_MODE, + wl->basic_rate, true); + if (ret < 0) + goto out_sleep; + + /* we must unlock here so we will be able to get events */ + wl1271_ps_elp_sleep(wl); + mutex_unlock(&wl->mutex); + + ret = wait_for_completion_timeout( + &compl, msecs_to_jiffies(WL1271_PS_COMPLETE_TIMEOUT)); + if (ret <= 0) { + wl1271_warning("couldn't enter ps mode!"); + ret = -EBUSY; + goto out; + } + + /* take mutex again, and wakeup */ + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_unlock; + } +out_sleep: + wl1271_ps_elp_sleep(wl); +out_unlock: + mutex_unlock(&wl->mutex); +out: + return ret; + +} + +static void wl1271_configure_resume(struct wl1271 *wl) +{ + int ret; + + if (wl->bss_type != BSS_TYPE_STA_BSS) + return; + + mutex_lock(&wl->mutex); + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* exit psm if it wasn't configured */ + if (!test_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags)) + wl1271_ps_set_mode(wl, STATION_ACTIVE_MODE, + wl->basic_rate, true); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + static int wl1271_op_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wow) { @@ -1357,6 +1430,12 @@ static int wl1271_op_suspend(struct ieee80211_hw *hw, wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow); wl->wow_enabled = !!wow; if (wl->wow_enabled) { + int ret; + ret = wl1271_configure_suspend(wl); + if (ret < 0) { + wl1271_warning("couldn't prepare device to suspend"); + return ret; + } /* flush any remaining work */ wl1271_debug(DEBUG_MAC80211, "flushing remaining works"); flush_delayed_work(&wl->scan_complete_work); @@ -1408,6 +1487,8 @@ static int wl1271_op_resume(struct ieee80211_hw *hw) wl1271_irq(0, wl); wl1271_enable_interrupts(wl); } + + wl1271_configure_resume(wl); } return 0; diff --git a/drivers/net/wireless/wl12xx/ps.h b/drivers/net/wireless/wl12xx/ps.h index c41bd0a711b..25eb9bc9b62 100644 --- a/drivers/net/wireless/wl12xx/ps.h +++ b/drivers/net/wireless/wl12xx/ps.h @@ -35,4 +35,6 @@ void wl1271_elp_work(struct work_struct *work); void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues); void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid); +#define WL1271_PS_COMPLETE_TIMEOUT 500 + #endif /* __WL1271_PS_H__ */ diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 2218b9c6384..fbe8f46d123 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -513,6 +513,7 @@ struct wl1271 { unsigned int rx_filter; struct completion *elp_compl; + struct completion *ps_compl; struct delayed_work elp_work; struct delayed_work pspoll_work; -- cgit v1.2.3-70-g09d2