| /* |
| * Copyright (c) 2019 The Linux Foundation. All rights reserved. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /** |
| * DOC: wlan_hdd_bcn_recv.c |
| * Feature for receiving beacons of connected AP and sending select |
| * params to upper layer via vendor event |
| */ |
| |
| #include <wlan_hdd_includes.h> |
| #include <net/cfg80211.h> |
| #include "wlan_osif_priv.h" |
| #include "qdf_trace.h" |
| #include "wlan_hdd_main.h" |
| #include "osif_sync.h" |
| #include "wlan_hdd_bcn_recv.h" |
| #include <linux/limits.h> |
| #include <wlan_hdd_object_manager.h> |
| |
| #define SET_BIT(value, mask) ((value) |= (1 << (mask))) |
| |
| #define BOOTTIME QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_BOOTTIME_WHEN_RECEIVED |
| |
| #ifndef CHAR_BIT |
| #define CHAR_BIT 8 /* Normally in <limits.h> */ |
| #endif |
| |
| static const struct nla_policy |
| beacon_reporting_params[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_MAX + 1] = { |
| [QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE] = {.type = NLA_U8}, |
| [QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_ACTIVE_REPORTING] = {.type = |
| NLA_FLAG}, |
| [QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PERIOD] = {.type = NLA_U8}, |
| [QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_DO_NOT_RESUME] = {.type = |
| NLA_FLAG}, |
| }; |
| |
| /** |
| * get_beacon_report_data_len() - Calculate length for beacon |
| * report to allocate skb buffer |
| * @report: beacon report structure |
| * |
| * Return: skb buffer length |
| */ |
| static |
| int get_beacon_report_data_len(struct wlan_beacon_report *report) |
| { |
| uint32_t data_len = NLMSG_HDRLEN; |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE */ |
| data_len += nla_total_size(sizeof(u32)); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_SSID */ |
| data_len += nla_total_size(report->ssid.length); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_BSSID */ |
| data_len += nla_total_size(ETH_ALEN); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_FREQ */ |
| data_len += nla_total_size(sizeof(u32)); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_BI */ |
| data_len += nla_total_size(sizeof(u16)); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_TSF */ |
| data_len += nla_total_size(sizeof(uint64_t)); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_BOOTTIME_WHEN_RECEIVED */ |
| data_len += nla_total_size(sizeof(uint64_t)); |
| |
| return data_len; |
| } |
| |
| /** |
| * get_pause_ind_data_len() - Calculate skb buffer length |
| * @is_disconnected: Connection state |
| * |
| * Calculate length for pause indication to allocate skb buffer |
| * |
| * Return: skb buffer length |
| */ |
| static int get_pause_ind_data_len(bool is_disconnected) |
| { |
| uint32_t data_len = NLMSG_HDRLEN; |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE */ |
| data_len += nla_total_size(sizeof(u32)); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PAUSE_REASON */ |
| data_len += nla_total_size(sizeof(u32)); |
| |
| /* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_AUTO_RESUMES */ |
| if (!is_disconnected) |
| data_len += nla_total_size(sizeof(u8)); |
| |
| return data_len; |
| } |
| |
| /** |
| * hdd_send_bcn_recv_info() - Send beacon info to userspace for |
| * connected AP |
| * @hdd_handle: hdd_handle to get hdd_adapter |
| * @beacon_report: Required beacon report |
| * |
| * Send beacon info to userspace for connected AP through a vendor event: |
| * QCA_NL80211_VENDOR_SUBCMD_BEACON_REPORTING. |
| */ |
| static QDF_STATUS hdd_send_bcn_recv_info(hdd_handle_t hdd_handle, |
| struct wlan_beacon_report |
| *beacon_report) |
| { |
| struct sk_buff *vendor_event; |
| struct hdd_context *hdd_ctx = hdd_handle_to_context(hdd_handle); |
| uint32_t data_len; |
| int flags = cds_get_gfp_flags(); |
| struct hdd_adapter *adapter; |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return QDF_STATUS_E_FAILURE; |
| |
| data_len = get_beacon_report_data_len(beacon_report); |
| |
| adapter = hdd_get_adapter_by_vdev(hdd_ctx, beacon_report->vdev_id); |
| if (hdd_validate_adapter(adapter)) |
| return QDF_STATUS_E_FAILURE; |
| |
| vendor_event = |
| cfg80211_vendor_event_alloc( |
| hdd_ctx->wiphy, &(adapter->wdev), |
| data_len, |
| QCA_NL80211_VENDOR_SUBCMD_BEACON_REPORTING_INDEX, |
| flags); |
| if (!vendor_event) { |
| hdd_err("cfg80211_vendor_event_alloc failed"); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| if (nla_put_u32(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE, |
| QCA_WLAN_VENDOR_BEACON_REPORTING_OP_BEACON_INFO) || |
| nla_put(vendor_event, QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_SSID, |
| beacon_report->ssid.length, beacon_report->ssid.ssid) || |
| nla_put(vendor_event, QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_BSSID, |
| ETH_ALEN, beacon_report->bssid.bytes) || |
| nla_put_u32(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_FREQ, |
| beacon_report->frequency) || |
| nla_put_u16(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_BI, |
| beacon_report->beacon_interval) || |
| wlan_cfg80211_nla_put_u64(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_TSF, |
| beacon_report->time_stamp) || |
| wlan_cfg80211_nla_put_u64(vendor_event, BOOTTIME, |
| beacon_report->boot_time)) { |
| hdd_err("QCA_WLAN_VENDOR_ATTR put fail"); |
| kfree_skb(vendor_event); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| cfg80211_vendor_event(vendor_event, flags); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * hdd_handle_beacon_reporting_start_op() - Process bcn recv start op |
| * @hdd_ctx: Pointer to hdd context |
| * @adapter: Pointer to network adapter |
| * @active_report: Active reporting flag |
| * @nth_value: Beacon report period |
| * @do_not_resume: beacon reporting resume after a pause is completed |
| * |
| * This function process beacon reporting start operation. |
| */ |
| static int hdd_handle_beacon_reporting_start_op(struct hdd_context *hdd_ctx, |
| struct hdd_adapter *adapter, |
| bool active_report, |
| uint32_t nth_value, |
| bool do_not_resume) |
| { |
| QDF_STATUS qdf_status = QDF_STATUS_SUCCESS; |
| int errno; |
| uint32_t mask = 0; |
| |
| if (active_report) { |
| /* Register beacon report callback */ |
| qdf_status = |
| sme_register_bcn_report_pe_cb(hdd_ctx->mac_handle, |
| hdd_send_bcn_recv_info); |
| if (QDF_IS_STATUS_ERROR(qdf_status)) { |
| hdd_err("bcn recv info cb reg failed = %d", qdf_status); |
| errno = qdf_status_to_os_return(qdf_status); |
| return errno; |
| } |
| |
| /* Register pause indication callback */ |
| qdf_status = |
| sme_register_bcn_recv_pause_ind_cb(hdd_ctx->mac_handle, |
| hdd_beacon_recv_pause_indication); |
| if (QDF_IS_STATUS_ERROR(qdf_status)) { |
| hdd_err("pause_ind_cb reg failed = %d", qdf_status); |
| errno = qdf_status_to_os_return(qdf_status); |
| return errno; |
| } |
| /* Update Beacon report period in case of active reporting */ |
| nth_value = 1; |
| /* |
| * Set MSB which indicates fw to don't wakeup host in wow |
| * mode in case of active beacon report. |
| */ |
| mask = (sizeof(uint32_t) * CHAR_BIT) - 1; |
| SET_BIT(nth_value, mask); |
| } |
| /* Handle beacon receive start indication */ |
| qdf_status = sme_handle_bcn_recv_start(hdd_ctx->mac_handle, |
| adapter->vdev_id, nth_value, |
| do_not_resume); |
| if (QDF_IS_STATUS_ERROR(qdf_status)) { |
| hdd_err("bcn rcv start failed with status=%d", qdf_status); |
| if (sme_register_bcn_report_pe_cb(hdd_ctx->mac_handle, NULL)) |
| hdd_err("bcn report cb deregistration failed"); |
| if (sme_register_bcn_recv_pause_ind_cb(hdd_ctx->mac_handle, |
| NULL)) |
| hdd_err("bcn pause ind cb deregistration failed"); |
| errno = qdf_status_to_os_return(qdf_status); |
| return errno; |
| } |
| |
| errno = qdf_status_to_os_return(qdf_status); |
| |
| return errno; |
| } |
| |
| /** |
| * hdd_handle_beacon_reporting_stop_op() - Process bcn recv stop op |
| * @hdd_ctx: Pointer to hdd context |
| * @adapter: Pointer to network adapter |
| * |
| * This function process beacon reporting stop operation. |
| */ |
| static int hdd_handle_beacon_reporting_stop_op(struct hdd_context *hdd_ctx, |
| struct hdd_adapter *adapter) |
| { |
| QDF_STATUS qdf_status = QDF_STATUS_SUCCESS; |
| int errno; |
| |
| /* Reset bcn recv start flag */ |
| sme_stop_beacon_report(hdd_ctx->mac_handle, adapter->vdev_id); |
| |
| /* Deregister beacon report callback */ |
| qdf_status = sme_register_bcn_report_pe_cb(hdd_ctx->mac_handle, NULL); |
| if (QDF_IS_STATUS_ERROR(qdf_status)) { |
| hdd_err("Callback de-registration failed = %d", qdf_status); |
| errno = qdf_status_to_os_return(qdf_status); |
| return errno; |
| } |
| |
| /* Deregister pause indication callback */ |
| qdf_status = sme_register_bcn_recv_pause_ind_cb(hdd_ctx->mac_handle, |
| NULL); |
| if (QDF_IS_STATUS_ERROR(qdf_status)) { |
| hdd_err("scan even deregister failed = %d", qdf_status); |
| errno = qdf_status_to_os_return(qdf_status); |
| return errno; |
| } |
| |
| if (hdd_adapter_is_connected_sta(adapter)) |
| /* Add beacon filter */ |
| if (hdd_add_beacon_filter(adapter)) { |
| hdd_err("Beacon filter addition failed"); |
| return -EINVAL; |
| } |
| |
| errno = qdf_status_to_os_return(qdf_status); |
| |
| return errno; |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_bcn_rcv_op() - enable/disable beacon reporting |
| * indication |
| * @wiphy: Pointer to wireless phy |
| * @wdev: Pointer to wireless device |
| * @data: Pointer to data |
| * @data_len: Length of @data |
| * |
| * This function is used to enable/disable asynchronous beacon |
| * reporting feature using vendor commands. |
| * |
| * Return: 0 on success, negative errno on failure |
| */ |
| static int __wlan_hdd_cfg80211_bcn_rcv_op(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int data_len) |
| { |
| struct hdd_context *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_MAX + 1]; |
| uint32_t bcn_report, nth_value = 1; |
| int errno; |
| bool active_report, do_not_resume; |
| struct wlan_objmgr_vdev *vdev; |
| enum scm_scan_status scan_req_status; |
| |
| hdd_enter_dev(dev); |
| |
| errno = hdd_validate_adapter(adapter); |
| if (errno) |
| return errno; |
| |
| if (adapter->device_mode != QDF_STA_MODE) { |
| hdd_err("Command not allowed as device not in STA mode"); |
| return -EINVAL; |
| } |
| |
| if (!hdd_conn_is_connected(WLAN_HDD_GET_STATION_CTX_PTR(adapter))) { |
| hdd_err("STA not in connected state"); |
| return -EINVAL; |
| } |
| |
| vdev = hdd_objmgr_get_vdev(adapter); |
| if (!vdev) |
| return -EINVAL; |
| |
| scan_req_status = ucfg_scan_get_pdev_status(wlan_vdev_get_pdev(vdev)); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_OSIF_ID); |
| |
| if (scan_req_status != SCAN_NOT_IN_PROGRESS) { |
| hdd_debug("Scan in progress: %d, bcn rpt start OP not allowed", |
| scan_req_status); |
| return -EBUSY; |
| } |
| |
| errno = |
| wlan_cfg80211_nla_parse(tb, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_MAX, |
| data, data_len, beacon_reporting_params); |
| if (errno) { |
| hdd_err("Failed to parse the beacon reporting params %d", |
| errno); |
| return errno; |
| } |
| |
| /* Parse and fetch OP Type */ |
| if (!tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE]) { |
| hdd_err("attr beacon report OP type failed"); |
| return -EINVAL; |
| } |
| bcn_report = |
| nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE]); |
| hdd_debug("Bcn Report: OP type:%d", bcn_report); |
| |
| switch (bcn_report) { |
| case QCA_WLAN_VENDOR_BEACON_REPORTING_OP_START: |
| active_report = |
| !!tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_ACTIVE_REPORTING]; |
| hdd_debug("attr active_report %d", active_report); |
| |
| do_not_resume = |
| !!tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_DO_NOT_RESUME]; |
| hdd_debug("Attr beacon report do not resume %d", do_not_resume); |
| |
| if (tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PERIOD]) |
| nth_value = |
| nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PERIOD]); |
| hdd_debug("Beacon Report: Period: %d", nth_value); |
| |
| if (sme_is_beacon_report_started(hdd_ctx->mac_handle, |
| adapter->vdev_id)) { |
| hdd_debug("Start cmd already in progress, issue the stop to FW, before new start"); |
| if (hdd_handle_beacon_reporting_stop_op(hdd_ctx, |
| adapter)) { |
| hdd_err("Failed to stop the beacon reporting before starting new start"); |
| return -EAGAIN; |
| } |
| } |
| errno = hdd_handle_beacon_reporting_start_op(hdd_ctx, |
| adapter, |
| active_report, |
| nth_value, |
| do_not_resume); |
| if (errno) { |
| hdd_err("Failed to start beacon reporting %d,", errno); |
| break; |
| } |
| break; |
| case QCA_WLAN_VENDOR_BEACON_REPORTING_OP_STOP: |
| if (sme_is_beacon_report_started(hdd_ctx->mac_handle, |
| adapter->vdev_id)) { |
| errno = hdd_handle_beacon_reporting_stop_op(hdd_ctx, |
| adapter); |
| if (errno) { |
| hdd_err("Failed to stop the beacon report, %d", |
| errno); |
| } |
| } else { |
| hdd_err_rl("BCN_RCV_STOP rej as no START CMD active"); |
| errno = -EINVAL; |
| } |
| break; |
| default: |
| hdd_debug("Invalid bcn report type %d", bcn_report); |
| } |
| |
| return errno; |
| } |
| |
| void hdd_beacon_recv_pause_indication(hdd_handle_t hdd_handle, |
| uint8_t vdev_id, |
| enum scan_event_type type, |
| bool is_disconnected) |
| { |
| struct hdd_context *hdd_ctx = hdd_handle_to_context(hdd_handle); |
| struct hdd_adapter *adapter; |
| struct sk_buff *vendor_event; |
| uint32_t data_len; |
| int flags; |
| uint32_t abort_reason; |
| bool do_not_resume; |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return; |
| |
| adapter = hdd_get_adapter_by_vdev(hdd_ctx, vdev_id); |
| if (hdd_validate_adapter(adapter)) |
| return; |
| |
| data_len = get_pause_ind_data_len(is_disconnected); |
| flags = cds_get_gfp_flags(); |
| |
| vendor_event = |
| cfg80211_vendor_event_alloc( |
| hdd_ctx->wiphy, &(adapter->wdev), |
| data_len, |
| QCA_NL80211_VENDOR_SUBCMD_BEACON_REPORTING_INDEX, |
| flags); |
| if (!vendor_event) { |
| hdd_err("cfg80211_vendor_event_alloc failed"); |
| return; |
| } |
| |
| do_not_resume = |
| sme_is_beacon_reporting_do_not_resume(hdd_ctx->mac_handle, |
| adapter->vdev_id); |
| |
| if (is_disconnected) { |
| abort_reason = |
| QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_DISCONNECTED; |
| /* Deregister callbacks and Reset bcn recv start flag */ |
| if (sme_is_beacon_report_started(hdd_ctx->mac_handle, |
| adapter->vdev_id)) |
| hdd_handle_beacon_reporting_stop_op(hdd_ctx, |
| adapter); |
| } else { |
| /* |
| * In case of scan, Check that auto resume of beacon reporting |
| * is allowed or not. |
| * If not allowed: |
| * Deregister callbacks and Reset bcn recv start flag in order |
| * to make sure host should not send beacon report to userspace |
| * further. |
| * If Auto resume allowed: |
| * Send pause indication to userspace and continue sending |
| * connected AP's beacon to userspace. |
| */ |
| if (do_not_resume) |
| hdd_handle_beacon_reporting_stop_op(hdd_ctx, |
| adapter); |
| |
| switch (type) { |
| case SCAN_EVENT_TYPE_FOREIGN_CHANNEL: |
| abort_reason = |
| QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_SCAN_STARTED; |
| break; |
| default: |
| abort_reason = |
| QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_UNSPECIFIED; |
| } |
| } |
| /* Send vendor event to user space to inform ABORT */ |
| if (nla_put_u32(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE, |
| QCA_WLAN_VENDOR_BEACON_REPORTING_OP_PAUSE) || |
| nla_put_u32(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PAUSE_REASON, |
| abort_reason)) { |
| hdd_err("QCA_WLAN_VENDOR_ATTR put fail"); |
| kfree_skb(vendor_event); |
| return; |
| } |
| |
| /* |
| * Send auto resume flag to user space to specify the driver will |
| * automatically resume reporting beacon events only in case of |
| * pause indication due to scan started. |
| * If do_not_resume flag is set in the recent |
| * QCA_WLAN_VENDOR_BEACON_REPORTING_OP_START command, then in the |
| * subsequent QCA_WLAN_VENDOR_BEACON_REPORTING_OP_PAUSE event (if any) |
| * the QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_AUTO_RESUMES shall not be |
| * set by the driver. |
| */ |
| if (!is_disconnected && !do_not_resume) |
| if (nla_put_flag(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_AUTO_RESUMES)) { |
| hdd_err("QCA_WLAN_VENDOR_ATTR put fail"); |
| kfree_skb(vendor_event); |
| return; |
| } |
| |
| cfg80211_vendor_event(vendor_event, flags); |
| } |
| |
| int wlan_hdd_cfg80211_bcn_rcv_op(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int data_len) |
| { |
| int errno; |
| struct osif_vdev_sync *vdev_sync; |
| |
| errno = osif_vdev_sync_op_start(wdev->netdev, &vdev_sync); |
| if (errno) |
| return errno; |
| |
| errno = __wlan_hdd_cfg80211_bcn_rcv_op(wiphy, wdev, |
| data, data_len); |
| |
| osif_vdev_sync_op_stop(vdev_sync); |
| |
| return errno; |
| } |