/* * Copyright (c) 2012-2021 The Linux Foundation. All rights reserved. * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. 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_apf.c * * Android Packet Filter support and implementation */ #include "wlan_hdd_apf.h" #include "osif_sync.h" #include "qca_vendor.h" #include "wlan_osif_request_manager.h" /* * define short names for the global vendor params * used by __wlan_hdd_cfg80211_apf_offload() */ #define APF_INVALID \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_INVALID #define APF_SUBCMD \ QCA_WLAN_VENDOR_ATTR_SET_RESET_PACKET_FILTER #define APF_VERSION \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_VERSION #define APF_FILTER_ID \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_ID #define APF_PACKET_SIZE \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_SIZE #define APF_CURRENT_OFFSET \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_CURRENT_OFFSET #define APF_PROGRAM \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_PROGRAM #define APF_PROG_LEN \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_PROG_LENGTH #define APF_MAX \ QCA_WLAN_VENDOR_ATTR_PACKET_FILTER_MAX const struct nla_policy wlan_hdd_apf_offload_policy[APF_MAX + 1] = { [APF_SUBCMD] = {.type = NLA_U32}, [APF_VERSION] = {.type = NLA_U32}, [APF_FILTER_ID] = {.type = NLA_U32}, [APF_PACKET_SIZE] = {.type = NLA_U32}, [APF_CURRENT_OFFSET] = {.type = NLA_U32}, [APF_PROGRAM] = {.type = NLA_BINARY, .len = MAX_APF_MEMORY_LEN}, [APF_PROG_LEN] = {.type = NLA_U32}, }; void hdd_apf_context_init(struct hdd_adapter *adapter) { qdf_event_create(&adapter->apf_context.qdf_apf_event); qdf_spinlock_create(&adapter->apf_context.lock); adapter->apf_context.apf_enabled = true; } void hdd_apf_context_destroy(struct hdd_adapter *adapter) { qdf_event_destroy(&adapter->apf_context.qdf_apf_event); qdf_spinlock_destroy(&adapter->apf_context.lock); qdf_mem_zero(&adapter->apf_context, sizeof(struct hdd_apf_context)); } struct apf_offload_priv { struct sir_apf_get_offload apf_get_offload; }; void hdd_get_apf_capabilities_cb(void *context, struct sir_apf_get_offload *data) { struct osif_request *request; struct apf_offload_priv *priv; hdd_enter(); request = osif_request_get(context); if (!request) { hdd_err("Obsolete request"); return; } priv = osif_request_priv(request); priv->apf_get_offload = *data; osif_request_complete(request); osif_request_put(request); hdd_exit(); } /** * hdd_post_get_apf_capabilities_rsp() - Callback function to APF Offload * @hdd_ctx: hdd_context * @apf_get_offload: struct for get offload * * Return: 0 on success, error number otherwise. */ static int hdd_post_get_apf_capabilities_rsp(struct hdd_context *hdd_ctx, struct sir_apf_get_offload *apf_get_offload) { struct sk_buff *skb; uint32_t nl_buf_len; hdd_enter(); nl_buf_len = NLMSG_HDRLEN; nl_buf_len += (sizeof(apf_get_offload->max_bytes_for_apf_inst) + NLA_HDRLEN) + (sizeof(apf_get_offload->apf_version) + NLA_HDRLEN); skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy, nl_buf_len); if (!skb) { hdd_err("wlan_cfg80211_vendor_cmd_alloc_reply_skb failed"); return -ENOMEM; } hdd_ctx->apf_version = apf_get_offload->apf_version; hdd_debug("APF Version: %u APF max bytes: %u", apf_get_offload->apf_version, apf_get_offload->max_bytes_for_apf_inst); if (nla_put_u32(skb, APF_PACKET_SIZE, apf_get_offload->max_bytes_for_apf_inst) || nla_put_u32(skb, APF_VERSION, apf_get_offload->apf_version)) { hdd_err("nla put failure"); goto nla_put_failure; } wlan_cfg80211_vendor_cmd_reply(skb); hdd_exit(); return 0; nla_put_failure: wlan_cfg80211_vendor_free_skb(skb); return -EINVAL; } /** * hdd_get_apf_capabilities - Get APF offload Capabilities * @hdd_ctx: Hdd context * * Return: 0 on success, errno on failure */ static int hdd_get_apf_capabilities(struct hdd_context *hdd_ctx) { QDF_STATUS status; int ret; void *cookie; struct osif_request *request; struct apf_offload_priv *priv; static const struct osif_request_params params = { .priv_size = sizeof(*priv), .timeout_ms = WLAN_WAIT_TIME_APF, }; hdd_enter(); request = osif_request_alloc(¶ms); if (!request) { hdd_err("Unable to allocate request"); return -EINVAL; } cookie = osif_request_cookie(request); status = sme_get_apf_capabilities(hdd_ctx->mac_handle, hdd_get_apf_capabilities_cb, cookie); if (!QDF_IS_STATUS_SUCCESS(status)) { hdd_err("Unable to retrieve APF caps"); ret = qdf_status_to_os_return(status); goto cleanup; } ret = osif_request_wait_for_response(request); if (ret) { hdd_err("Target response timed out"); goto cleanup; } priv = osif_request_priv(request); ret = hdd_post_get_apf_capabilities_rsp(hdd_ctx, &priv->apf_get_offload); if (ret) hdd_err("Failed to post get apf capabilities"); cleanup: /* * either we never sent a request to SME, we sent a request to * SME and timed out, or we sent a request to SME, received a * response from SME, and posted the response to userspace. * regardless we are done with the request. */ osif_request_put(request); hdd_exit(); return ret; } /** * hdd_set_reset_apf_offload - Post set/reset apf to SME * @hdd_ctx: Hdd context * @tb: Length of @data * @adapter: pointer to adapter struct * * Return: 0 on success; errno on failure */ static int hdd_set_reset_apf_offload(struct hdd_context *hdd_ctx, struct nlattr **tb, struct hdd_adapter *adapter) { struct sir_apf_set_offload apf_set_offload = {0}; QDF_STATUS status; int prog_len; int ret = 0; if (!hdd_cm_is_vdev_associated(adapter->deflink)) { hdd_err("Not in Connected state!"); return -ENOTSUPP; } /* Parse and fetch apf packet size */ if (!tb[APF_PACKET_SIZE]) { hdd_err("attr apf packet size failed"); ret = -EINVAL; goto fail; } apf_set_offload.session_id = adapter->deflink->vdev_id; apf_set_offload.total_length = nla_get_u32(tb[APF_PACKET_SIZE]); if (!apf_set_offload.total_length) { hdd_debug("APF reset packet"); goto post_sme; } /* Parse and fetch apf program */ if (!tb[APF_PROGRAM]) { hdd_err("attr apf program failed"); ret = -EINVAL; goto fail; } prog_len = nla_len(tb[APF_PROGRAM]); apf_set_offload.program = qdf_mem_malloc(sizeof(uint8_t) * prog_len); if (!apf_set_offload.program) { ret = -ENOMEM; goto fail; } apf_set_offload.current_length = prog_len; nla_memcpy(apf_set_offload.program, tb[APF_PROGRAM], prog_len); /* Parse and fetch filter Id */ if (!tb[APF_FILTER_ID]) { hdd_err("attr filter id failed"); ret = -EINVAL; goto fail; } apf_set_offload.filter_id = nla_get_u32(tb[APF_FILTER_ID]); /* Parse and fetch current offset */ if (!tb[APF_CURRENT_OFFSET]) { hdd_err("attr current offset failed"); ret = -EINVAL; goto fail; } apf_set_offload.current_offset = nla_get_u32(tb[APF_CURRENT_OFFSET]); post_sme: hdd_debug("Posting, session_id: %d APF Version: %d filter ID: %d total_len: %d current_len: %d offset: %d", apf_set_offload.session_id, apf_set_offload.version, apf_set_offload.filter_id, apf_set_offload.total_length, apf_set_offload.current_length, apf_set_offload.current_offset); status = sme_set_apf_instructions(hdd_ctx->mac_handle, &apf_set_offload); if (!QDF_IS_STATUS_SUCCESS(status)) { hdd_err("sme_set_apf_instructions failed(err=%d)", status); ret = -EINVAL; goto fail; } hdd_exit(); fail: if (apf_set_offload.current_length) qdf_mem_free(apf_set_offload.program); if (!ret) hdd_ctx->apf_enabled_v2 = true; return ret; } /** * hdd_enable_disable_apf - Enable or Disable the APF interpreter * @adapter: HDD Adapter * @apf_enable: true: Enable APF Int., false: disable APF Int. * * Return: 0 on success, errno on failure */ static int hdd_enable_disable_apf(struct hdd_adapter *adapter, bool apf_enable) { QDF_STATUS status; status = sme_set_apf_enable_disable(hdd_adapter_get_mac_handle(adapter), adapter->deflink->vdev_id, apf_enable); if (!QDF_IS_STATUS_SUCCESS(status)) { hdd_err("Unable to post sme apf enable/disable message (status-%d)", status); return -EINVAL; } adapter->apf_context.apf_enabled = apf_enable; return 0; } /** * hdd_apf_write_memory - Write into the apf work memory * @adapter: HDD Adapter * @tb: list of attributes * * This function writes code/data into the APF work memory and * provides program length that is passed on to the interpreter. * * Return: 0 on success, errno on failure */ static int hdd_apf_write_memory(struct hdd_adapter *adapter, struct nlattr **tb) { struct wmi_apf_write_memory_params write_mem_params = {0}; struct hdd_context *hdd_ctx = WLAN_HDD_GET_CTX(adapter); QDF_STATUS status; int ret = 0; write_mem_params.vdev_id = adapter->deflink->vdev_id; if (adapter->apf_context.apf_enabled) { hdd_err("Cannot get/set when APF interpreter is enabled"); return -EINVAL; } /* Read program length */ if (!tb[APF_PROG_LEN]) { hdd_err("attr program length failed"); return -EINVAL; } write_mem_params.program_len = nla_get_u32(tb[APF_PROG_LEN]); /* Read APF work memory offset */ if (!tb[APF_CURRENT_OFFSET]) { hdd_err("attr apf packet size failed"); return -EINVAL; } write_mem_params.addr_offset = nla_get_u32(tb[APF_CURRENT_OFFSET]); /* Parse and fetch apf program */ if (!tb[APF_PROGRAM]) { hdd_err("attr apf program failed"); return -EINVAL; } write_mem_params.length = nla_len(tb[APF_PROGRAM]); if (!write_mem_params.length) { hdd_err("Program attr with empty data"); return -EINVAL; } write_mem_params.buf = qdf_mem_malloc(sizeof(uint8_t) * write_mem_params.length); if (!write_mem_params.buf) return -EINVAL; nla_memcpy(write_mem_params.buf, tb[APF_PROGRAM], write_mem_params.length); write_mem_params.apf_version = hdd_ctx->apf_version; status = sme_apf_write_work_memory(hdd_adapter_get_mac_handle(adapter), &write_mem_params); if (!QDF_IS_STATUS_SUCCESS(status)) { hdd_err("Unable to retrieve APF caps"); ret = -EINVAL; } hdd_debug("Writing successful into APF work memory from offset 0x%X:", write_mem_params.addr_offset); QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_DEBUG, write_mem_params.buf, write_mem_params.length); if (write_mem_params.buf) qdf_mem_free(write_mem_params.buf); return ret; } /** * hdd_apf_read_memory_callback - HDD Callback for the APF read memory * operation * @hdd_context: Hdd context * @evt: APF read memory event response parameters * * Return: 0 on success, errno on failure */ static void hdd_apf_read_memory_callback(void *hdd_context, struct wmi_apf_read_memory_resp_event_params *evt) { struct hdd_context *hdd_ctx = hdd_context; struct hdd_apf_context *context; uint8_t *buf_ptr; uint32_t pkt_offset; struct wlan_hdd_link_info *link_info; hdd_enter(); if (wlan_hdd_validate_context(hdd_ctx) || !evt) { hdd_err("HDD context is invalid or event buf(%pK) is null", evt); return; } link_info = hdd_get_link_info_by_vdev(hdd_ctx, evt->vdev_id); if (!link_info || hdd_validate_adapter(link_info->adapter)) return; context = &link_info->adapter->apf_context; if (context->magic != APF_CONTEXT_MAGIC) { /* The caller presumably timed out, nothing to do */ hdd_err("Caller timed out or corrupt magic, simply return"); return; } if (evt->offset < context->offset) { hdd_err("Offset in read event(%d) smaller than offset in request(%d)!", evt->offset, context->offset); return; } /* * offset in the event is relative to the APF work memory. * Calculate the packet offset, which gives us the relative * location in the buffer to start copy into. */ pkt_offset = evt->offset - context->offset; if ((pkt_offset > context->buf_len) || (context->buf_len - pkt_offset < evt->length)) { hdd_err("Read chunk exceeding allocated space"); return; } buf_ptr = context->buf + pkt_offset; qdf_mem_copy(buf_ptr, evt->data, evt->length); if (!evt->more_data) { /* Release the caller after last event, clear magic */ context->magic = 0; qdf_event_set(&context->qdf_apf_event); } hdd_exit(); } /** * hdd_apf_read_memory - Read part of the apf work memory * @adapter: HDD Adapter * @tb: list of attributes * * Return: 0 on success, errno on failure */ static int hdd_apf_read_memory(struct hdd_adapter *adapter, struct nlattr **tb) { struct wmi_apf_read_memory_params read_mem_params = {0}; struct hdd_apf_context *context = &adapter->apf_context; struct hdd_context *hdd_ctx = WLAN_HDD_GET_CTX(adapter); QDF_STATUS status; unsigned long nl_buf_len = NLMSG_HDRLEN; int ret = 0; struct sk_buff *skb = NULL; uint8_t *bufptr; mac_handle_t mac_handle; mac_handle = hdd_adapter_get_mac_handle(adapter); if (!mac_handle) { hdd_debug("mac ctx NULL"); return -EINVAL; } if (context->apf_enabled) { hdd_err("Cannot get/set while interpreter is enabled"); return -EINVAL; } read_mem_params.vdev_id = adapter->deflink->vdev_id; /* Read APF work memory offset */ if (!tb[APF_CURRENT_OFFSET]) { hdd_err("attr apf memory offset failed"); return -EINVAL; } read_mem_params.addr_offset = nla_get_u32(tb[APF_CURRENT_OFFSET]); if (read_mem_params.addr_offset > MAX_APF_MEMORY_LEN) { hdd_err("attr apf memory offset should be less than %d", MAX_APF_MEMORY_LEN); return -EINVAL; } /* Read length */ if (!tb[APF_PACKET_SIZE]) { hdd_err("attr apf packet size failed"); return -EINVAL; } read_mem_params.length = nla_get_u32(tb[APF_PACKET_SIZE]); if (!read_mem_params.length) { hdd_err("apf read length cannot be zero!"); return -EINVAL; } bufptr = qdf_mem_malloc(read_mem_params.length); if (!bufptr) return -ENOMEM; qdf_event_reset(&context->qdf_apf_event); context->offset = read_mem_params.addr_offset; context->buf = bufptr; context->buf_len = read_mem_params.length; context->magic = APF_CONTEXT_MAGIC; status = sme_apf_read_work_memory(mac_handle, &read_mem_params, hdd_apf_read_memory_callback); if (QDF_IS_STATUS_ERROR(status)) { hdd_err("Unable to post sme APF read memory message (status-%d)", status); ret = -EINVAL; goto fail; } /* request was sent -- wait for the response */ status = qdf_wait_for_event_completion(&context->qdf_apf_event, WLAN_WAIT_TIME_APF_READ_MEM); if (QDF_IS_STATUS_ERROR(status)) { hdd_err("Target response timed out"); context->magic = 0; ret = -ETIMEDOUT; goto fail; } nl_buf_len += sizeof(uint32_t) + NLA_HDRLEN; nl_buf_len += context->buf_len + NLA_HDRLEN; skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy, nl_buf_len); if (!skb) { hdd_err("wlan_cfg80211_vendor_cmd_alloc_reply_skb failed"); ret = -ENOMEM; goto fail; } if (nla_put_u32(skb, APF_SUBCMD, QCA_WLAN_READ_PACKET_FILTER) || nla_put(skb, APF_PROGRAM, read_mem_params.length, context->buf)) { hdd_err("put fail"); wlan_cfg80211_vendor_free_skb(skb); ret = -EINVAL; goto fail; } wlan_cfg80211_vendor_cmd_reply(skb); hdd_debug("Reading APF work memory from offset 0x%X:", read_mem_params.addr_offset); QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_DEBUG, context->buf, read_mem_params.length); fail: if (context->buf) { qdf_mem_free(context->buf); context->buf = NULL; } return ret; } /** * __wlan_hdd_cfg80211_apf_offload() - Set/Reset to APF Offload * @wiphy: wiphy structure pointer * @wdev: Wireless device structure pointer * @data: Pointer to the data received * @data_len: Length of @data * * Return: 0 on success; errno on failure */ static int __wlan_hdd_cfg80211_apf_offload(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[APF_MAX + 1]; int ret_val = 0, apf_subcmd; struct hdd_apf_context *context; hdd_enter_dev(dev); if (!adapter) { hdd_err("Adapter is null"); return -EINVAL; } context = &adapter->apf_context; ret_val = wlan_hdd_validate_context(hdd_ctx); if (ret_val) return ret_val; if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) { hdd_err("Command not allowed in FTM mode"); return -EINVAL; } if (!ucfg_pmo_is_apf_enabled(hdd_ctx->psoc)) { hdd_err("APF is not supported or disabled through INI"); return -ENOTSUPP; } if (!(adapter->device_mode == QDF_STA_MODE || adapter->device_mode == QDF_P2P_CLIENT_MODE)) { hdd_err("APF only supported in STA or P2P CLI modes!"); return -ENOTSUPP; } if (wlan_cfg80211_nla_parse(tb, APF_MAX, data, data_len, wlan_hdd_apf_offload_policy)) { hdd_err("Invalid ATTR"); return -EINVAL; } if (!tb[APF_SUBCMD]) { hdd_err("attr apf sub-command failed"); return -EINVAL; } apf_subcmd = nla_get_u32(tb[APF_SUBCMD]); /* Do not allow simultaneous new APF commands on the same adapter */ qdf_spin_lock(&context->lock); if (context->cmd_in_progress) { qdf_spin_unlock(&context->lock); hdd_err("Another cmd in progress for same session!"); return -EAGAIN; } context->cmd_in_progress = true; qdf_spin_unlock(&context->lock); switch (apf_subcmd) { /* Legacy APF sub-commands */ case QCA_WLAN_SET_PACKET_FILTER: ret_val = hdd_set_reset_apf_offload(hdd_ctx, tb, adapter); break; case QCA_WLAN_GET_PACKET_FILTER: ret_val = hdd_get_apf_capabilities(hdd_ctx); break; /* APF 3.0 sub-commands */ case QCA_WLAN_WRITE_PACKET_FILTER: ret_val = hdd_apf_write_memory(adapter, tb); break; case QCA_WLAN_READ_PACKET_FILTER: ret_val = hdd_apf_read_memory(adapter, tb); break; case QCA_WLAN_ENABLE_PACKET_FILTER: ret_val = hdd_enable_disable_apf(adapter, true); break; case QCA_WLAN_DISABLE_PACKET_FILTER: ret_val = hdd_enable_disable_apf(adapter, false); break; default: hdd_err("Unknown APF Sub-command: %d", apf_subcmd); ret_val = -ENOTSUPP; } qdf_spin_lock(&context->lock); context->cmd_in_progress = false; qdf_spin_unlock(&context->lock); return ret_val; } int wlan_hdd_cfg80211_apf_offload(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_apf_offload(wiphy, wdev, data, data_len); osif_vdev_sync_op_stop(vdev_sync); return errno; }