From: Jerome Pouiller <Jerome.Pouiller@silabs.com> To: "devel@driverdev.osuosl.org" <devel@driverdev.osuosl.org>, "linux-wireless@vger.kernel.org" <linux-wireless@vger.kernel.org> Cc: "netdev@vger.kernel.org" <netdev@vger.kernel.org>, "linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Kalle Valo <kvalo@codeaurora.org>, "David S . Miller" <davem@davemloft.net>, David Le Goff <David.Legoff@silabs.com>, Jerome Pouiller <Jerome.Pouiller@silabs.com> Subject: [PATCH v3 18/20] staging: wfx: allow to scan networks Date: Thu, 19 Sep 2019 14:25:47 +0000 [thread overview] Message-ID: <20190919142527.31797-19-Jerome.Pouiller@silabs.com> (raw) In-Reply-To: <20190919142527.31797-1-Jerome.Pouiller@silabs.com> From: Jérôme Pouiller <jerome.pouiller@silabs.com> Chip can make foreground scan or background, but both can't be mixed in same request. So, we need to split each mac80211 requests into multiple HIF requests. Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com> --- drivers/staging/wfx/Makefile | 1 + drivers/staging/wfx/bh.c | 2 +- drivers/staging/wfx/hif_rx.c | 13 ++ drivers/staging/wfx/main.c | 5 + drivers/staging/wfx/scan.c | 249 +++++++++++++++++++++++++++++++++++ drivers/staging/wfx/scan.h | 42 ++++++ drivers/staging/wfx/sta.c | 23 +++- drivers/staging/wfx/sta.h | 4 + drivers/staging/wfx/wfx.h | 11 ++ 9 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 drivers/staging/wfx/scan.c create mode 100644 drivers/staging/wfx/scan.h diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile index d9e21515d08e..2b8a5fa86fac 100644 --- a/drivers/staging/wfx/Makefile +++ b/drivers/staging/wfx/Makefile @@ -12,6 +12,7 @@ wfx-y := \ queue.o \ data_tx.o \ data_rx.o \ + scan.o \ sta.o \ main.o \ sta.o \ diff --git a/drivers/staging/wfx/bh.c b/drivers/staging/wfx/bh.c index ed81c3924d98..6000c03bb658 100644 --- a/drivers/staging/wfx/bh.c +++ b/drivers/staging/wfx/bh.c @@ -268,7 +268,7 @@ static void bh_work(struct work_struct *work) if (last_op_is_rx) ack_sdio_data(wdev); - if (!wdev->hif.tx_buffers_used && !work_pending(work)) { + if (!wdev->hif.tx_buffers_used && !work_pending(work) && !atomic_read(&wdev->scan_in_progress)) { device_release(wdev); release_chip = true; } diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c index c07984b0535d..d386fab0a90f 100644 --- a/drivers/staging/wfx/hif_rx.c +++ b/drivers/staging/wfx/hif_rx.c @@ -11,6 +11,7 @@ #include "hif_rx.h" #include "wfx.h" +#include "scan.h" #include "data_rx.h" #include "secure_link.h" #include "hif_api_cmd.h" @@ -143,6 +144,17 @@ static int hif_receive_indication(struct wfx_dev *wdev, struct hif_msg *hif, voi return 0; } +static int hif_scan_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_scan_cmpl *body = buf; + + WARN_ON(!wvif); + wfx_scan_complete_cb(wvif, body); + + return 0; +} + static int hif_join_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) { struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); @@ -230,6 +242,7 @@ static const struct { { HIF_IND_ID_STARTUP, hif_startup_indication }, { HIF_IND_ID_WAKEUP, hif_wakeup_indication }, { HIF_IND_ID_JOIN_COMPLETE, hif_join_complete_indication }, + { HIF_IND_ID_SCAN_CMPL, hif_scan_complete_indication }, { HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication }, { HIF_IND_ID_GENERIC, hif_generic_indication }, { HIF_IND_ID_ERROR, hif_error_indication }, diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c index cce4e30dd94a..06220bac5b75 100644 --- a/drivers/staging/wfx/main.c +++ b/drivers/staging/wfx/main.c @@ -55,6 +55,7 @@ static const struct ieee80211_ops wfx_ops = { .add_interface = wfx_add_interface, .remove_interface = wfx_remove_interface, .tx = wfx_tx, + .hw_scan = wfx_hw_scan, }; bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor) @@ -203,6 +204,8 @@ struct wfx_dev *wfx_init_common(struct device *dev, hw->extra_tx_headroom = sizeof(struct hif_sl_msg_hdr) + sizeof(struct hif_msg) + sizeof(struct hif_req_tx) + 4 /* alignment */ + 8 /* TKIP IV */; + hw->wiphy->max_scan_ssids = 2; + hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; wdev = hw->priv; wdev->hw = hw; @@ -214,6 +217,7 @@ struct wfx_dev *wfx_init_common(struct device *dev, wdev->pdata.gpio_wakeup = wfx_get_gpio(dev, gpio_wakeup, "wakeup"); wfx_fill_sl_key(dev, &wdev->pdata); + mutex_init(&wdev->conf_mutex); mutex_init(&wdev->rx_stats_lock); init_completion(&wdev->firmware_ready); wfx_init_hif_cmd(&wdev->hif_cmd); @@ -225,6 +229,7 @@ struct wfx_dev *wfx_init_common(struct device *dev, void wfx_free_common(struct wfx_dev *wdev) { mutex_destroy(&wdev->rx_stats_lock); + mutex_destroy(&wdev->conf_mutex); wfx_tx_queues_deinit(wdev); ieee80211_free_hw(wdev->hw); } diff --git a/drivers/staging/wfx/scan.c b/drivers/staging/wfx/scan.c new file mode 100644 index 000000000000..207b26ebc9fd --- /dev/null +++ b/drivers/staging/wfx/scan.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Scan related functions. + * + * Copyright (c) 2017-2019, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + */ +#include <net/mac80211.h> + +#include "scan.h" +#include "wfx.h" +#include "sta.h" +#include "hif_tx_mib.h" + +static void __ieee80211_scan_completed_compat(struct ieee80211_hw *hw, bool aborted) +{ + struct cfg80211_scan_info info = { + .aborted = aborted ? 1 : 0, + }; + + ieee80211_scan_completed(hw, &info); +} + +static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan) +{ + int ret; + int tmo = 500; + + tmo += scan->scan_req.num_of_channels * + ((20 * (scan->scan_req.max_channel_time)) + 10); + atomic_set(&wvif->scan.in_progress, 1); + atomic_set(&wvif->wdev->scan_in_progress, 1); + + schedule_delayed_work(&wvif->scan.timeout, msecs_to_jiffies(tmo)); + ret = hif_scan(wvif, scan); + if (ret) { + wfx_scan_failed_cb(wvif); + atomic_set(&wvif->scan.in_progress, 0); + atomic_set(&wvif->wdev->scan_in_progress, 0); + cancel_delayed_work_sync(&wvif->scan.timeout); + } + return ret; +} + +int wfx_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + struct wfx_dev *wdev = hw->priv; + struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv; + struct cfg80211_scan_request *req = &hw_req->req; + struct sk_buff *skb; + int i, ret; + struct hif_mib_template_frame *p; + + if (!wvif) + return -EINVAL; + + if (req->n_ssids == 1 && !req->ssids[0].ssid_len) + req->n_ssids = 0; + + if (req->n_ssids > HIF_API_MAX_NB_SSIDS) + return -EINVAL; + + skb = ieee80211_probereq_get(hw, wvif->vif->addr, NULL, 0, req->ie_len); + if (!skb) + return -ENOMEM; + + if (req->ie_len) + memcpy(skb_put(skb, req->ie_len), req->ie, req->ie_len); + + mutex_lock(&wdev->conf_mutex); + + p = (struct hif_mib_template_frame *)skb_push(skb, 4); + p->frame_type = HIF_TMPLT_PRBREQ; + p->frame_length = cpu_to_le16(skb->len - 4); + ret = hif_set_template_frame(wvif, p); + skb_pull(skb, 4); + + if (!ret) + /* Host want to be the probe responder. */ + ret = wfx_fwd_probe_req(wvif, true); + if (ret) { + mutex_unlock(&wdev->conf_mutex); + dev_kfree_skb(skb); + return ret; + } + + wfx_tx_lock_flush(wdev); + + BUG_ON(wvif->scan.req); + wvif->scan.req = req; + wvif->scan.n_ssids = 0; + wvif->scan.status = 0; + wvif->scan.begin = &req->channels[0]; + wvif->scan.curr = wvif->scan.begin; + wvif->scan.end = &req->channels[req->n_channels]; + wvif->scan.output_power = wdev->output_power; + + for (i = 0; i < req->n_ssids; ++i) { + struct hif_ssid_def *dst = &wvif->scan.ssids[wvif->scan.n_ssids]; + + memcpy(&dst->ssid[0], req->ssids[i].ssid, sizeof(dst->ssid)); + dst->ssid_length = req->ssids[i].ssid_len; + ++wvif->scan.n_ssids; + } + + mutex_unlock(&wdev->conf_mutex); + + if (skb) + dev_kfree_skb(skb); + schedule_work(&wvif->scan.work); + return 0; +} + +void wfx_scan_work(struct work_struct *work) +{ + struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan.work); + struct ieee80211_channel **it; + struct wfx_scan_params scan = { + .scan_req.scan_type.type = 0, /* Foreground */ + }; + struct ieee80211_channel *first; + int i; + + down(&wvif->scan.lock); + mutex_lock(&wvif->wdev->conf_mutex); + + if (!wvif->scan.req || wvif->scan.curr == wvif->scan.end) { + if (wvif->scan.output_power != wvif->wdev->output_power) + hif_set_output_power(wvif, wvif->wdev->output_power * 10); + + if (wvif->scan.status < 0) + dev_warn(wvif->wdev->dev, "scan failed\n"); + else if (wvif->scan.req) + dev_dbg(wvif->wdev->dev, "scan completed\n"); + else + dev_dbg(wvif->wdev->dev, "scan canceled\n"); + + wvif->scan.req = NULL; + wfx_tx_unlock(wvif->wdev); + mutex_unlock(&wvif->wdev->conf_mutex); + __ieee80211_scan_completed_compat(wvif->wdev->hw, wvif->scan.status ? 1 : 0); + up(&wvif->scan.lock); + return; + } + first = *wvif->scan.curr; + + for (it = wvif->scan.curr + 1, i = 1; + it != wvif->scan.end && i < HIF_API_MAX_NB_CHANNELS; + ++it, ++i) { + if ((*it)->band != first->band) + break; + if (((*it)->flags ^ first->flags) & + IEEE80211_CHAN_NO_IR) + break; + if (!(first->flags & IEEE80211_CHAN_NO_IR) && + (*it)->max_power != first->max_power) + break; + } + scan.scan_req.band = first->band; + + if (wvif->scan.req->no_cck) + scan.scan_req.max_transmit_rate = API_RATE_INDEX_G_6MBPS; + else + scan.scan_req.max_transmit_rate = API_RATE_INDEX_B_1MBPS; + scan.scan_req.num_of_probe_requests = + (first->flags & IEEE80211_CHAN_NO_IR) ? 0 : 2; + scan.scan_req.num_of_ssi_ds = wvif->scan.n_ssids; + scan.ssids = &wvif->scan.ssids[0]; + scan.scan_req.num_of_channels = it - wvif->scan.curr; + scan.scan_req.probe_delay = 100; + + scan.ch = kcalloc(scan.scan_req.num_of_channels, sizeof(u8), GFP_KERNEL); + + if (!scan.ch) { + wvif->scan.status = -ENOMEM; + goto fail; + } + for (i = 0; i < scan.scan_req.num_of_channels; ++i) + scan.ch[i] = wvif->scan.curr[i]->hw_value; + + if (wvif->scan.curr[0]->flags & IEEE80211_CHAN_NO_IR) { + scan.scan_req.min_channel_time = 50; + scan.scan_req.max_channel_time = 150; + } else { + scan.scan_req.min_channel_time = 10; + scan.scan_req.max_channel_time = 50; + } + if (!(first->flags & IEEE80211_CHAN_NO_IR) && + wvif->scan.output_power != first->max_power) { + wvif->scan.output_power = first->max_power; + hif_set_output_power(wvif, wvif->scan.output_power * 10); + } + wvif->scan.status = wfx_scan_start(wvif, &scan); + kfree(scan.ch); + if (wvif->scan.status) + goto fail; + wvif->scan.curr = it; + mutex_unlock(&wvif->wdev->conf_mutex); + return; + +fail: + wvif->scan.curr = wvif->scan.end; + mutex_unlock(&wvif->wdev->conf_mutex); + up(&wvif->scan.lock); + schedule_work(&wvif->scan.work); +} + +static void wfx_scan_complete(struct wfx_vif *wvif) +{ + up(&wvif->scan.lock); + atomic_set(&wvif->wdev->scan_in_progress, 0); + + wfx_scan_work(&wvif->scan.work); +} + +void wfx_scan_failed_cb(struct wfx_vif *wvif) +{ + if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) { + wvif->scan.status = -EIO; + schedule_work(&wvif->scan.timeout.work); + } +} + +void wfx_scan_complete_cb(struct wfx_vif *wvif, struct hif_ind_scan_cmpl *arg) +{ + if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) { + wvif->scan.status = 1; + schedule_work(&wvif->scan.timeout.work); + } +} + +void wfx_scan_timeout(struct work_struct *work) +{ + struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan.timeout.work); + + if (atomic_xchg(&wvif->scan.in_progress, 0)) { + if (wvif->scan.status > 0) { + wvif->scan.status = 0; + } else if (!wvif->scan.status) { + dev_warn(wvif->wdev->dev, "timeout waiting for scan complete notification\n"); + wvif->scan.status = -ETIMEDOUT; + wvif->scan.curr = wvif->scan.end; + hif_stop_scan(wvif); + } + wfx_scan_complete(wvif); + } +} diff --git a/drivers/staging/wfx/scan.h b/drivers/staging/wfx/scan.h new file mode 100644 index 000000000000..b4ddd0771a9b --- /dev/null +++ b/drivers/staging/wfx/scan.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Scan related functions. + * + * Copyright (c) 2017-2019, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + */ +#ifndef WFX_SCAN_H +#define WFX_SCAN_H + +#include <linux/semaphore.h> +#include <linux/workqueue.h> +#include <net/mac80211.h> + +#include "hif_api_cmd.h" + +struct wfx_dev; +struct wfx_vif; + +struct wfx_scan { + struct semaphore lock; + struct work_struct work; + struct delayed_work timeout; + struct cfg80211_scan_request *req; + struct ieee80211_channel **begin; + struct ieee80211_channel **curr; + struct ieee80211_channel **end; + struct hif_ssid_def ssids[HIF_API_MAX_NB_SSIDS]; + int output_power; + int n_ssids; + int status; + atomic_t in_progress; +}; + +int wfx_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_scan_request *req); +void wfx_scan_work(struct work_struct *work); +void wfx_scan_timeout(struct work_struct *work); +void wfx_scan_complete_cb(struct wfx_vif *wvif, struct hif_ind_scan_cmpl *arg); +void wfx_scan_failed_cb(struct wfx_vif *wvif); + +#endif /* WFX_SCAN_H */ diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c index 5714aba1432c..5a8140100e97 100644 --- a/drivers/staging/wfx/sta.c +++ b/drivers/staging/wfx/sta.c @@ -9,9 +9,18 @@ #include "sta.h" #include "wfx.h" +#include "scan.h" +#include "hif_tx_mib.h" #define TXOP_UNIT 32 +int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable) +{ + wvif->fwd_probe_req = enable; + return hif_set_rx_filter(wvif, wvif->filter_bssid, + wvif->fwd_probe_req); +} + static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set) { struct sk_buff *skb; @@ -128,6 +137,8 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) default_edca_params[IEEE80211_AC_BK].queue_id = HIF_QUEUE_ID_BESTEFFORT; } + mutex_lock(&wdev->conf_mutex); + for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) { if (!wdev->vif[i]) { wdev->vif[i] = vif; @@ -135,8 +146,10 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) break; } } - if (i == ARRAY_SIZE(wdev->vif)) + if (i == ARRAY_SIZE(wdev->vif)) { + mutex_unlock(&wdev->conf_mutex); return -EOPNOTSUPP; + } wvif->vif = vif; wvif->wdev = wdev; @@ -148,6 +161,12 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) INIT_WORK(&wvif->mcast_start_work, wfx_mcast_start_work); INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work); timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0); + + sema_init(&wvif->scan.lock, 1); + INIT_WORK(&wvif->scan.work, wfx_scan_work); + INIT_DELAYED_WORK(&wvif->scan.timeout, wfx_scan_timeout); + + mutex_unlock(&wdev->conf_mutex); BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params)); for (i = 0; i < IEEE80211_NUM_ACS; i++) memcpy(&wvif->edca.params[i], &default_edca_params[i], sizeof(default_edca_params[i])); @@ -175,7 +194,9 @@ void wfx_stop(struct ieee80211_hw *hw) struct wfx_dev *wdev = hw->priv; wfx_tx_lock_flush(wdev); + mutex_lock(&wdev->conf_mutex); wfx_tx_queues_clear(wdev); + mutex_unlock(&wdev->conf_mutex); wfx_tx_unlock(wdev); WARN(atomic_read(&wdev->tx_lock), "tx_lock is locked"); } diff --git a/drivers/staging/wfx/sta.h b/drivers/staging/wfx/sta.h index f36d94f907c7..dd1b6b3fc2f1 100644 --- a/drivers/staging/wfx/sta.h +++ b/drivers/staging/wfx/sta.h @@ -12,6 +12,8 @@ #include "hif_api_cmd.h" +struct wfx_vif; + struct wfx_edca_params { /* NOTE: index is a linux queue id. */ struct hif_req_edca_queue_params params[IEEE80211_NUM_ACS]; @@ -29,4 +31,6 @@ void wfx_stop(struct ieee80211_hw *hw); int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable); + #endif /* WFX_STA_H */ diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h index 11775b1e06ef..50c0d9c0e528 100644 --- a/drivers/staging/wfx/wfx.h +++ b/drivers/staging/wfx/wfx.h @@ -19,6 +19,7 @@ #include "queue.h" #include "secure_link.h" #include "sta.h" +#include "scan.h" #include "hif_tx.h" #include "hif_api_general.h" @@ -39,6 +40,7 @@ struct wfx_dev { struct wfx_hif hif; struct sl_context sl; int chip_frozen; + struct mutex conf_mutex; struct wfx_hif_cmd hif_cmd; struct wfx_queue tx_queue[4]; @@ -48,6 +50,9 @@ struct wfx_dev { struct hif_rx_stats rx_stats; struct mutex rx_stats_lock; + + int output_power; + atomic_t scan_in_progress; }; struct wfx_vif { @@ -71,11 +76,17 @@ struct wfx_vif { struct tx_policy_cache tx_policy_cache; struct work_struct tx_policy_upload_work; + u32 sta_asleep_mask; u32 pspoll_mask; spinlock_t ps_state_lock; + bool filter_bssid; + bool fwd_probe_req; + struct wfx_edca_params edca; + + struct wfx_scan scan; }; static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id) -- 2.20.1
WARNING: multiple messages have this Message-ID (diff)
From: Jerome Pouiller <Jerome.Pouiller@silabs.com> To: "devel@driverdev.osuosl.org" <devel@driverdev.osuosl.org>, "linux-wireless@vger.kernel.org" <linux-wireless@vger.kernel.org> Cc: "netdev@vger.kernel.org" <netdev@vger.kernel.org>, "linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, David Le Goff <David.Legoff@silabs.com>, "David S . Miller" <davem@davemloft.net>, Kalle Valo <kvalo@codeaurora.org> Subject: [PATCH v3 18/20] staging: wfx: allow to scan networks Date: Thu, 19 Sep 2019 14:25:47 +0000 [thread overview] Message-ID: <20190919142527.31797-19-Jerome.Pouiller@silabs.com> (raw) In-Reply-To: <20190919142527.31797-1-Jerome.Pouiller@silabs.com> From: Jérôme Pouiller <jerome.pouiller@silabs.com> Chip can make foreground scan or background, but both can't be mixed in same request. So, we need to split each mac80211 requests into multiple HIF requests. Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com> --- drivers/staging/wfx/Makefile | 1 + drivers/staging/wfx/bh.c | 2 +- drivers/staging/wfx/hif_rx.c | 13 ++ drivers/staging/wfx/main.c | 5 + drivers/staging/wfx/scan.c | 249 +++++++++++++++++++++++++++++++++++ drivers/staging/wfx/scan.h | 42 ++++++ drivers/staging/wfx/sta.c | 23 +++- drivers/staging/wfx/sta.h | 4 + drivers/staging/wfx/wfx.h | 11 ++ 9 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 drivers/staging/wfx/scan.c create mode 100644 drivers/staging/wfx/scan.h diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile index d9e21515d08e..2b8a5fa86fac 100644 --- a/drivers/staging/wfx/Makefile +++ b/drivers/staging/wfx/Makefile @@ -12,6 +12,7 @@ wfx-y := \ queue.o \ data_tx.o \ data_rx.o \ + scan.o \ sta.o \ main.o \ sta.o \ diff --git a/drivers/staging/wfx/bh.c b/drivers/staging/wfx/bh.c index ed81c3924d98..6000c03bb658 100644 --- a/drivers/staging/wfx/bh.c +++ b/drivers/staging/wfx/bh.c @@ -268,7 +268,7 @@ static void bh_work(struct work_struct *work) if (last_op_is_rx) ack_sdio_data(wdev); - if (!wdev->hif.tx_buffers_used && !work_pending(work)) { + if (!wdev->hif.tx_buffers_used && !work_pending(work) && !atomic_read(&wdev->scan_in_progress)) { device_release(wdev); release_chip = true; } diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c index c07984b0535d..d386fab0a90f 100644 --- a/drivers/staging/wfx/hif_rx.c +++ b/drivers/staging/wfx/hif_rx.c @@ -11,6 +11,7 @@ #include "hif_rx.h" #include "wfx.h" +#include "scan.h" #include "data_rx.h" #include "secure_link.h" #include "hif_api_cmd.h" @@ -143,6 +144,17 @@ static int hif_receive_indication(struct wfx_dev *wdev, struct hif_msg *hif, voi return 0; } +static int hif_scan_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_scan_cmpl *body = buf; + + WARN_ON(!wvif); + wfx_scan_complete_cb(wvif, body); + + return 0; +} + static int hif_join_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) { struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); @@ -230,6 +242,7 @@ static const struct { { HIF_IND_ID_STARTUP, hif_startup_indication }, { HIF_IND_ID_WAKEUP, hif_wakeup_indication }, { HIF_IND_ID_JOIN_COMPLETE, hif_join_complete_indication }, + { HIF_IND_ID_SCAN_CMPL, hif_scan_complete_indication }, { HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication }, { HIF_IND_ID_GENERIC, hif_generic_indication }, { HIF_IND_ID_ERROR, hif_error_indication }, diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c index cce4e30dd94a..06220bac5b75 100644 --- a/drivers/staging/wfx/main.c +++ b/drivers/staging/wfx/main.c @@ -55,6 +55,7 @@ static const struct ieee80211_ops wfx_ops = { .add_interface = wfx_add_interface, .remove_interface = wfx_remove_interface, .tx = wfx_tx, + .hw_scan = wfx_hw_scan, }; bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor) @@ -203,6 +204,8 @@ struct wfx_dev *wfx_init_common(struct device *dev, hw->extra_tx_headroom = sizeof(struct hif_sl_msg_hdr) + sizeof(struct hif_msg) + sizeof(struct hif_req_tx) + 4 /* alignment */ + 8 /* TKIP IV */; + hw->wiphy->max_scan_ssids = 2; + hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; wdev = hw->priv; wdev->hw = hw; @@ -214,6 +217,7 @@ struct wfx_dev *wfx_init_common(struct device *dev, wdev->pdata.gpio_wakeup = wfx_get_gpio(dev, gpio_wakeup, "wakeup"); wfx_fill_sl_key(dev, &wdev->pdata); + mutex_init(&wdev->conf_mutex); mutex_init(&wdev->rx_stats_lock); init_completion(&wdev->firmware_ready); wfx_init_hif_cmd(&wdev->hif_cmd); @@ -225,6 +229,7 @@ struct wfx_dev *wfx_init_common(struct device *dev, void wfx_free_common(struct wfx_dev *wdev) { mutex_destroy(&wdev->rx_stats_lock); + mutex_destroy(&wdev->conf_mutex); wfx_tx_queues_deinit(wdev); ieee80211_free_hw(wdev->hw); } diff --git a/drivers/staging/wfx/scan.c b/drivers/staging/wfx/scan.c new file mode 100644 index 000000000000..207b26ebc9fd --- /dev/null +++ b/drivers/staging/wfx/scan.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Scan related functions. + * + * Copyright (c) 2017-2019, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + */ +#include <net/mac80211.h> + +#include "scan.h" +#include "wfx.h" +#include "sta.h" +#include "hif_tx_mib.h" + +static void __ieee80211_scan_completed_compat(struct ieee80211_hw *hw, bool aborted) +{ + struct cfg80211_scan_info info = { + .aborted = aborted ? 1 : 0, + }; + + ieee80211_scan_completed(hw, &info); +} + +static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan) +{ + int ret; + int tmo = 500; + + tmo += scan->scan_req.num_of_channels * + ((20 * (scan->scan_req.max_channel_time)) + 10); + atomic_set(&wvif->scan.in_progress, 1); + atomic_set(&wvif->wdev->scan_in_progress, 1); + + schedule_delayed_work(&wvif->scan.timeout, msecs_to_jiffies(tmo)); + ret = hif_scan(wvif, scan); + if (ret) { + wfx_scan_failed_cb(wvif); + atomic_set(&wvif->scan.in_progress, 0); + atomic_set(&wvif->wdev->scan_in_progress, 0); + cancel_delayed_work_sync(&wvif->scan.timeout); + } + return ret; +} + +int wfx_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + struct wfx_dev *wdev = hw->priv; + struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv; + struct cfg80211_scan_request *req = &hw_req->req; + struct sk_buff *skb; + int i, ret; + struct hif_mib_template_frame *p; + + if (!wvif) + return -EINVAL; + + if (req->n_ssids == 1 && !req->ssids[0].ssid_len) + req->n_ssids = 0; + + if (req->n_ssids > HIF_API_MAX_NB_SSIDS) + return -EINVAL; + + skb = ieee80211_probereq_get(hw, wvif->vif->addr, NULL, 0, req->ie_len); + if (!skb) + return -ENOMEM; + + if (req->ie_len) + memcpy(skb_put(skb, req->ie_len), req->ie, req->ie_len); + + mutex_lock(&wdev->conf_mutex); + + p = (struct hif_mib_template_frame *)skb_push(skb, 4); + p->frame_type = HIF_TMPLT_PRBREQ; + p->frame_length = cpu_to_le16(skb->len - 4); + ret = hif_set_template_frame(wvif, p); + skb_pull(skb, 4); + + if (!ret) + /* Host want to be the probe responder. */ + ret = wfx_fwd_probe_req(wvif, true); + if (ret) { + mutex_unlock(&wdev->conf_mutex); + dev_kfree_skb(skb); + return ret; + } + + wfx_tx_lock_flush(wdev); + + BUG_ON(wvif->scan.req); + wvif->scan.req = req; + wvif->scan.n_ssids = 0; + wvif->scan.status = 0; + wvif->scan.begin = &req->channels[0]; + wvif->scan.curr = wvif->scan.begin; + wvif->scan.end = &req->channels[req->n_channels]; + wvif->scan.output_power = wdev->output_power; + + for (i = 0; i < req->n_ssids; ++i) { + struct hif_ssid_def *dst = &wvif->scan.ssids[wvif->scan.n_ssids]; + + memcpy(&dst->ssid[0], req->ssids[i].ssid, sizeof(dst->ssid)); + dst->ssid_length = req->ssids[i].ssid_len; + ++wvif->scan.n_ssids; + } + + mutex_unlock(&wdev->conf_mutex); + + if (skb) + dev_kfree_skb(skb); + schedule_work(&wvif->scan.work); + return 0; +} + +void wfx_scan_work(struct work_struct *work) +{ + struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan.work); + struct ieee80211_channel **it; + struct wfx_scan_params scan = { + .scan_req.scan_type.type = 0, /* Foreground */ + }; + struct ieee80211_channel *first; + int i; + + down(&wvif->scan.lock); + mutex_lock(&wvif->wdev->conf_mutex); + + if (!wvif->scan.req || wvif->scan.curr == wvif->scan.end) { + if (wvif->scan.output_power != wvif->wdev->output_power) + hif_set_output_power(wvif, wvif->wdev->output_power * 10); + + if (wvif->scan.status < 0) + dev_warn(wvif->wdev->dev, "scan failed\n"); + else if (wvif->scan.req) + dev_dbg(wvif->wdev->dev, "scan completed\n"); + else + dev_dbg(wvif->wdev->dev, "scan canceled\n"); + + wvif->scan.req = NULL; + wfx_tx_unlock(wvif->wdev); + mutex_unlock(&wvif->wdev->conf_mutex); + __ieee80211_scan_completed_compat(wvif->wdev->hw, wvif->scan.status ? 1 : 0); + up(&wvif->scan.lock); + return; + } + first = *wvif->scan.curr; + + for (it = wvif->scan.curr + 1, i = 1; + it != wvif->scan.end && i < HIF_API_MAX_NB_CHANNELS; + ++it, ++i) { + if ((*it)->band != first->band) + break; + if (((*it)->flags ^ first->flags) & + IEEE80211_CHAN_NO_IR) + break; + if (!(first->flags & IEEE80211_CHAN_NO_IR) && + (*it)->max_power != first->max_power) + break; + } + scan.scan_req.band = first->band; + + if (wvif->scan.req->no_cck) + scan.scan_req.max_transmit_rate = API_RATE_INDEX_G_6MBPS; + else + scan.scan_req.max_transmit_rate = API_RATE_INDEX_B_1MBPS; + scan.scan_req.num_of_probe_requests = + (first->flags & IEEE80211_CHAN_NO_IR) ? 0 : 2; + scan.scan_req.num_of_ssi_ds = wvif->scan.n_ssids; + scan.ssids = &wvif->scan.ssids[0]; + scan.scan_req.num_of_channels = it - wvif->scan.curr; + scan.scan_req.probe_delay = 100; + + scan.ch = kcalloc(scan.scan_req.num_of_channels, sizeof(u8), GFP_KERNEL); + + if (!scan.ch) { + wvif->scan.status = -ENOMEM; + goto fail; + } + for (i = 0; i < scan.scan_req.num_of_channels; ++i) + scan.ch[i] = wvif->scan.curr[i]->hw_value; + + if (wvif->scan.curr[0]->flags & IEEE80211_CHAN_NO_IR) { + scan.scan_req.min_channel_time = 50; + scan.scan_req.max_channel_time = 150; + } else { + scan.scan_req.min_channel_time = 10; + scan.scan_req.max_channel_time = 50; + } + if (!(first->flags & IEEE80211_CHAN_NO_IR) && + wvif->scan.output_power != first->max_power) { + wvif->scan.output_power = first->max_power; + hif_set_output_power(wvif, wvif->scan.output_power * 10); + } + wvif->scan.status = wfx_scan_start(wvif, &scan); + kfree(scan.ch); + if (wvif->scan.status) + goto fail; + wvif->scan.curr = it; + mutex_unlock(&wvif->wdev->conf_mutex); + return; + +fail: + wvif->scan.curr = wvif->scan.end; + mutex_unlock(&wvif->wdev->conf_mutex); + up(&wvif->scan.lock); + schedule_work(&wvif->scan.work); +} + +static void wfx_scan_complete(struct wfx_vif *wvif) +{ + up(&wvif->scan.lock); + atomic_set(&wvif->wdev->scan_in_progress, 0); + + wfx_scan_work(&wvif->scan.work); +} + +void wfx_scan_failed_cb(struct wfx_vif *wvif) +{ + if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) { + wvif->scan.status = -EIO; + schedule_work(&wvif->scan.timeout.work); + } +} + +void wfx_scan_complete_cb(struct wfx_vif *wvif, struct hif_ind_scan_cmpl *arg) +{ + if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) { + wvif->scan.status = 1; + schedule_work(&wvif->scan.timeout.work); + } +} + +void wfx_scan_timeout(struct work_struct *work) +{ + struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan.timeout.work); + + if (atomic_xchg(&wvif->scan.in_progress, 0)) { + if (wvif->scan.status > 0) { + wvif->scan.status = 0; + } else if (!wvif->scan.status) { + dev_warn(wvif->wdev->dev, "timeout waiting for scan complete notification\n"); + wvif->scan.status = -ETIMEDOUT; + wvif->scan.curr = wvif->scan.end; + hif_stop_scan(wvif); + } + wfx_scan_complete(wvif); + } +} diff --git a/drivers/staging/wfx/scan.h b/drivers/staging/wfx/scan.h new file mode 100644 index 000000000000..b4ddd0771a9b --- /dev/null +++ b/drivers/staging/wfx/scan.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Scan related functions. + * + * Copyright (c) 2017-2019, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + */ +#ifndef WFX_SCAN_H +#define WFX_SCAN_H + +#include <linux/semaphore.h> +#include <linux/workqueue.h> +#include <net/mac80211.h> + +#include "hif_api_cmd.h" + +struct wfx_dev; +struct wfx_vif; + +struct wfx_scan { + struct semaphore lock; + struct work_struct work; + struct delayed_work timeout; + struct cfg80211_scan_request *req; + struct ieee80211_channel **begin; + struct ieee80211_channel **curr; + struct ieee80211_channel **end; + struct hif_ssid_def ssids[HIF_API_MAX_NB_SSIDS]; + int output_power; + int n_ssids; + int status; + atomic_t in_progress; +}; + +int wfx_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_scan_request *req); +void wfx_scan_work(struct work_struct *work); +void wfx_scan_timeout(struct work_struct *work); +void wfx_scan_complete_cb(struct wfx_vif *wvif, struct hif_ind_scan_cmpl *arg); +void wfx_scan_failed_cb(struct wfx_vif *wvif); + +#endif /* WFX_SCAN_H */ diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c index 5714aba1432c..5a8140100e97 100644 --- a/drivers/staging/wfx/sta.c +++ b/drivers/staging/wfx/sta.c @@ -9,9 +9,18 @@ #include "sta.h" #include "wfx.h" +#include "scan.h" +#include "hif_tx_mib.h" #define TXOP_UNIT 32 +int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable) +{ + wvif->fwd_probe_req = enable; + return hif_set_rx_filter(wvif, wvif->filter_bssid, + wvif->fwd_probe_req); +} + static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set) { struct sk_buff *skb; @@ -128,6 +137,8 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) default_edca_params[IEEE80211_AC_BK].queue_id = HIF_QUEUE_ID_BESTEFFORT; } + mutex_lock(&wdev->conf_mutex); + for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) { if (!wdev->vif[i]) { wdev->vif[i] = vif; @@ -135,8 +146,10 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) break; } } - if (i == ARRAY_SIZE(wdev->vif)) + if (i == ARRAY_SIZE(wdev->vif)) { + mutex_unlock(&wdev->conf_mutex); return -EOPNOTSUPP; + } wvif->vif = vif; wvif->wdev = wdev; @@ -148,6 +161,12 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) INIT_WORK(&wvif->mcast_start_work, wfx_mcast_start_work); INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work); timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0); + + sema_init(&wvif->scan.lock, 1); + INIT_WORK(&wvif->scan.work, wfx_scan_work); + INIT_DELAYED_WORK(&wvif->scan.timeout, wfx_scan_timeout); + + mutex_unlock(&wdev->conf_mutex); BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params)); for (i = 0; i < IEEE80211_NUM_ACS; i++) memcpy(&wvif->edca.params[i], &default_edca_params[i], sizeof(default_edca_params[i])); @@ -175,7 +194,9 @@ void wfx_stop(struct ieee80211_hw *hw) struct wfx_dev *wdev = hw->priv; wfx_tx_lock_flush(wdev); + mutex_lock(&wdev->conf_mutex); wfx_tx_queues_clear(wdev); + mutex_unlock(&wdev->conf_mutex); wfx_tx_unlock(wdev); WARN(atomic_read(&wdev->tx_lock), "tx_lock is locked"); } diff --git a/drivers/staging/wfx/sta.h b/drivers/staging/wfx/sta.h index f36d94f907c7..dd1b6b3fc2f1 100644 --- a/drivers/staging/wfx/sta.h +++ b/drivers/staging/wfx/sta.h @@ -12,6 +12,8 @@ #include "hif_api_cmd.h" +struct wfx_vif; + struct wfx_edca_params { /* NOTE: index is a linux queue id. */ struct hif_req_edca_queue_params params[IEEE80211_NUM_ACS]; @@ -29,4 +31,6 @@ void wfx_stop(struct ieee80211_hw *hw); int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable); + #endif /* WFX_STA_H */ diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h index 11775b1e06ef..50c0d9c0e528 100644 --- a/drivers/staging/wfx/wfx.h +++ b/drivers/staging/wfx/wfx.h @@ -19,6 +19,7 @@ #include "queue.h" #include "secure_link.h" #include "sta.h" +#include "scan.h" #include "hif_tx.h" #include "hif_api_general.h" @@ -39,6 +40,7 @@ struct wfx_dev { struct wfx_hif hif; struct sl_context sl; int chip_frozen; + struct mutex conf_mutex; struct wfx_hif_cmd hif_cmd; struct wfx_queue tx_queue[4]; @@ -48,6 +50,9 @@ struct wfx_dev { struct hif_rx_stats rx_stats; struct mutex rx_stats_lock; + + int output_power; + atomic_t scan_in_progress; }; struct wfx_vif { @@ -71,11 +76,17 @@ struct wfx_vif { struct tx_policy_cache tx_policy_cache; struct work_struct tx_policy_upload_work; + u32 sta_asleep_mask; u32 pspoll_mask; spinlock_t ps_state_lock; + bool filter_bssid; + bool fwd_probe_req; + struct wfx_edca_params edca; + + struct wfx_scan scan; }; static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id) -- 2.20.1 _______________________________________________ devel mailing list devel@linuxdriverproject.org http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel
next prev parent reply other threads:[~2019-09-19 14:26 UTC|newest] Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-09-19 14:25 [PATCH v3 00/20] Add support for Silicon Labs WiFi chip WF200 and further Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 01/20] staging: wfx: add infrastructure for new driver Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-10-04 8:23 ` Greg Kroah-Hartman 2019-10-04 8:23 ` Greg Kroah-Hartman 2019-09-19 14:25 ` [PATCH v3 02/20] staging: wfx: add support for I/O access Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 03/20] staging: wfx: add I/O API Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 05/20] staging: wfx: load firmware Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 04/20] staging: wfx: add tracepoints for I/O access Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 06/20] staging: wfx: import HIF API headers Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 07/20] staging: wfx: add IRQ handling Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 08/20] staging: wfx: add tracepoints for HIF Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 09/20] staging: wfx: add support for start-up indication Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 10/20] staging: wfx: instantiate mac80211 data Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 11/20] staging: wfx: allow to send commands to chip Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 12/20] staging: wfx: add HIF commands helpers Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 13/20] staging: wfx: introduce "secure link" Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 14/20] staging: wfx: setup initial chip configuration Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 15/20] staging: wfx: add debug files and trace debug events Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 16/20] staging: wfx: allow to send 802.11 frames Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 17/20] staging: wfx: allow to receive " Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller [this message] 2019-09-19 14:25 ` [PATCH v3 18/20] staging: wfx: allow to scan networks Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 19/20] staging: wfx: implement 802.11 key handling Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:25 ` [PATCH v3 20/20] staging: wfx: implement the rest of mac80211 API Jerome Pouiller 2019-09-19 14:25 ` Jerome Pouiller 2019-09-19 14:41 ` [PATCH v3 00/20] Add support for Silicon Labs WiFi chip WF200 and further Greg Kroah-Hartman 2019-09-19 14:41 ` Greg Kroah-Hartman
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20190919142527.31797-19-Jerome.Pouiller@silabs.com \ --to=jerome.pouiller@silabs.com \ --cc=David.Legoff@silabs.com \ --cc=davem@davemloft.net \ --cc=devel@driverdev.osuosl.org \ --cc=gregkh@linuxfoundation.org \ --cc=kvalo@codeaurora.org \ --cc=linux-kernel@vger.kernel.org \ --cc=linux-wireless@vger.kernel.org \ --cc=netdev@vger.kernel.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.