/* * Copyright (c) 2022 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: defines driver functions interfacing with linux kernel */ #include #include #include #include #include #include #include #include #define COAP_MATCH_DATA_BYTES_MAX 16 #define COAP_MSG_BYTES_MAX 1152 #define COAP_OFFLOAD_REPLY_CACHE_EXPTIME_MS 40000 #define COAP_OFFLOAD_CACHE_GET_TIMEOUT_MS 2000 #define COAP_ATTR(_name) QCA_WLAN_VENDOR_ATTR_COAP_OFFLOAD_ ## _name static const struct nla_policy coap_offload_filter_policy[COAP_ATTR(FILTER_MAX) + 1] = { [COAP_ATTR(FILTER_DEST_IPV4)] = {.type = NLA_U32}, [COAP_ATTR(FILTER_DEST_IPV4_IS_BC)] = {.type = NLA_FLAG}, [COAP_ATTR(FILTER_DEST_PORT)] = {.type = NLA_U16}, [COAP_ATTR(FILTER_MATCH_OFFSET)] = {.type = NLA_U32}, [COAP_ATTR(FILTER_MATCH_DATA)] = { .type = NLA_BINARY, .len = COAP_MATCH_DATA_BYTES_MAX}, }; static const struct nla_policy coap_offload_tx_ipv4_policy[COAP_ATTR(TX_IPV4_MAX) + 1] = { [COAP_ATTR(TX_IPV4_SRC_ADDR)] = {.type = NLA_U32}, [COAP_ATTR(TX_IPV4_SRC_PORT)] = {.type = NLA_U16}, [COAP_ATTR(TX_IPV4_DEST_ADDR)] = {.type = NLA_U32}, [COAP_ATTR(TX_IPV4_DEST_IS_BC)] = {.type = NLA_FLAG}, [COAP_ATTR(TX_IPV4_DEST_PORT)] = {.type = NLA_U16}, }; static const struct nla_policy coap_offload_reply_policy[COAP_ATTR(REPLY_MAX) + 1] = { [COAP_ATTR(REPLY_SRC_IPV4)] = {.type = NLA_U32}, [COAP_ATTR(REPLY_FILTER)] = VENDOR_NLA_POLICY_NESTED(coap_offload_filter_policy), [COAP_ATTR(REPLY_MSG)] = { .type = NLA_BINARY, .len = COAP_MSG_BYTES_MAX}, [COAP_ATTR(REPLY_CACHE_EXPTIME)] = {.type = NLA_U32}, }; static const struct nla_policy coap_offload_periodic_tx_policy[COAP_ATTR(PERIODIC_TX_MAX) + 1] = { [COAP_ATTR(PERIODIC_TX_IPV4)] = VENDOR_NLA_POLICY_NESTED(coap_offload_tx_ipv4_policy), [COAP_ATTR(PERIODIC_TX_PERIOD)] = {.type = NLA_U32}, [COAP_ATTR(PERIODIC_TX_MSG)] = { .type = NLA_BINARY, .len = COAP_MSG_BYTES_MAX}, }; const struct nla_policy coap_offload_policy[COAP_ATTR(MAX) + 1] = { [COAP_ATTR(ACTION)] = {.type = NLA_U32 }, [COAP_ATTR(REQ_ID)] = {.type = NLA_U32 }, [COAP_ATTR(REPLY)] = VENDOR_NLA_POLICY_NESTED(coap_offload_reply_policy), [COAP_ATTR(PERIODIC_TX)] = VENDOR_NLA_POLICY_NESTED(coap_offload_periodic_tx_policy), }; /** * wlan_cfg80211_coap_offload_reply_fill_filter() - fill filter for CoAP * offload reply. * @attr_filter: pointer to filter attribute * @params: pointer to parameters for CoAP offload reply * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_reply_fill_filter(struct nlattr *attr_filter, struct coap_offload_reply_param *params) { struct nlattr *tb[COAP_ATTR(FILTER_MAX) + 1]; if (!attr_filter) { coap_err("No ATTR filter"); return -EINVAL; } if (!nla_data(attr_filter)) { coap_err("Invalid filter"); return -EINVAL; } if (wlan_cfg80211_nla_parse_nested(tb, COAP_ATTR(FILTER_MAX), attr_filter, coap_offload_filter_policy)) { coap_err("Invalid ATTR"); return -EINVAL; } if (!tb[COAP_ATTR(FILTER_DEST_IPV4)]) { coap_err("no ATTR dest IPv4"); return -EINVAL; } params->dest_ip_v4 = nla_get_u32(tb[COAP_ATTR(FILTER_DEST_IPV4)]); params->dest_ip_v4_is_bc = nla_get_flag(tb[COAP_ATTR(FILTER_DEST_IPV4_IS_BC)]); if (!tb[COAP_ATTR(FILTER_DEST_PORT)]) { coap_err("no ATTR dest IPv4 port"); return -EINVAL; } params->dest_udp_port = nla_get_u16(tb[COAP_ATTR(FILTER_DEST_PORT)]); if (!tb[COAP_ATTR(FILTER_MATCH_OFFSET)]) { coap_err("no ATTR match offset"); return -EINVAL; } params->verify_offset = nla_get_u32(tb[COAP_ATTR(FILTER_MATCH_OFFSET)]); if (!tb[COAP_ATTR(FILTER_MATCH_DATA)]) { coap_err("no ATTR match data"); return -EINVAL; } params->verify_len = nla_len(tb[COAP_ATTR(FILTER_MATCH_DATA)]); if (!params->verify_len) { coap_err("invalid match data len"); return -EINVAL; } params->verify = nla_data(tb[COAP_ATTR(FILTER_MATCH_DATA)]); return 0; } /** * wlan_cfg80211_coap_offload_reply_enable() - enable CoAP offload reply * @vdev: pointer to vdev object * @req_id: request id * @attr_reply: pointer to CoAP offload reply attribute * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_reply_enable(struct wlan_objmgr_vdev *vdev, uint32_t req_id, struct nlattr *attr_reply) { struct nlattr *tb[COAP_ATTR(REPLY_MAX) + 1]; struct coap_offload_reply_param params = {0}; struct nlattr *attr; QDF_STATUS status; int ret; if (!attr_reply) { coap_err("No ATTR reply"); return -EINVAL; } if (wlan_cfg80211_nla_parse_nested(tb, COAP_ATTR(REPLY_MAX), attr_reply, coap_offload_reply_policy)) { coap_err("Invalid ATTR"); return -EINVAL; } attr = tb[COAP_ATTR(REPLY_SRC_IPV4)]; if (!attr) { coap_err("No ATTR IPv4"); return -EINVAL; } params.pattern_id = req_id; params.vdev_id = wlan_vdev_get_id(vdev); params.src_ip_v4 = nla_get_u32(attr); attr = tb[COAP_ATTR(REPLY_FILTER)]; ret = wlan_cfg80211_coap_offload_reply_fill_filter(attr, ¶ms); if (ret) return ret; attr = tb[COAP_ATTR(REPLY_MSG)]; if (!attr) { coap_err("No ATTR msg"); return -EINVAL; } params.coapmsg_len = nla_len(attr); params.coapmsg = nla_data(attr); attr = tb[COAP_ATTR(REPLY_CACHE_EXPTIME)]; if (!attr) params.cache_timeout = COAP_OFFLOAD_REPLY_CACHE_EXPTIME_MS; else params.cache_timeout = nla_get_u32(attr); status = ucfg_coap_offload_reply_enable(vdev, ¶ms); ret = qdf_status_to_os_return(status); return ret; } /** * wlan_cfg80211_coap_offload_fill_tx_ipv4() - fill IPv4 source/destination * address/port for offload transmitting. * @attr_ipv4: pointer to TX IPv4 attribute * @params: pointer to parameters for CoAP offload reply * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_fill_tx_ipv4(struct nlattr *attr_ipv4, struct coap_offload_periodic_tx_param *params) { struct nlattr *tb[COAP_ATTR(TX_IPV4_MAX) + 1]; if (!attr_ipv4) { coap_err("No ATTR TX IPv4"); return -EINVAL; } if (wlan_cfg80211_nla_parse_nested(tb, COAP_ATTR(TX_IPV4_MAX), attr_ipv4, coap_offload_tx_ipv4_policy)) { coap_err("Invalid ATTR"); return -EINVAL; } if (!tb[COAP_ATTR(TX_IPV4_SRC_ADDR)]) { coap_err("no ATTR src addr"); return -EINVAL; } params->src_ip_v4 = nla_get_u32(tb[COAP_ATTR(TX_IPV4_SRC_ADDR)]); if (tb[COAP_ATTR(TX_IPV4_SRC_PORT)]) params->src_udp_port = nla_get_u32(tb[COAP_ATTR(TX_IPV4_SRC_PORT)]); if (!tb[COAP_ATTR(TX_IPV4_DEST_ADDR)]) { coap_err("no ATTR IPv4 dest addr"); return -EINVAL; } params->dest_ip_v4 = nla_get_u32(tb[COAP_ATTR(TX_IPV4_DEST_ADDR)]); params->dest_ip_v4_is_bc = nla_get_flag(tb[COAP_ATTR(TX_IPV4_DEST_IS_BC)]); if (!tb[COAP_ATTR(TX_IPV4_DEST_PORT)]) { coap_err("no ATTR dest IPv4 port"); return -EINVAL; } params->dest_udp_port = nla_get_u32(tb[COAP_ATTR(TX_IPV4_DEST_PORT)]); return 0; } /** * wlan_cfg80211_coap_offload_periodic_tx_enable() - enable CoAP offload * periodic transmitting * @vdev: pointer to vdev object * @req_id: request id * @attr_periodic_tx: pointer to CoAP offload periodic TX attribute * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_periodic_tx_enable(struct wlan_objmgr_vdev *vdev, uint32_t req_id, struct nlattr *attr_periodic_tx) { struct nlattr *tb[COAP_ATTR(PERIODIC_TX_MAX) + 1]; struct coap_offload_periodic_tx_param param = {0}; struct nlattr *attr_ipv4; QDF_STATUS status; int ret; if (!attr_periodic_tx) { coap_err("No ATTR periodic tx"); return -EINVAL; } if (wlan_cfg80211_nla_parse_nested(tb, COAP_ATTR(PERIODIC_TX_MAX), attr_periodic_tx, coap_offload_periodic_tx_policy)) { coap_err("Invalid ATTR"); return -EINVAL; } if (!tb[COAP_ATTR(PERIODIC_TX_PERIOD)]) { coap_err("no ATTR period"); return -EINVAL; } param.timeout = nla_get_u32(tb[COAP_ATTR(PERIODIC_TX_PERIOD)]); attr_ipv4 = tb[COAP_ATTR(PERIODIC_TX_IPV4)]; ret = wlan_cfg80211_coap_offload_fill_tx_ipv4(attr_ipv4, ¶m); if (ret) return ret; param.vdev_id = wlan_vdev_get_id(vdev); param.pattern_id = req_id; if (!tb[COAP_ATTR(PERIODIC_TX_MSG)]) { coap_err("no ATTR msg"); return -EINVAL; } param.coapmsg_len = nla_len(tb[COAP_ATTR(PERIODIC_TX_MSG)]); param.coapmsg = nla_data(tb[COAP_ATTR(PERIODIC_TX_MSG)]); status = ucfg_coap_offload_periodic_tx_enable(vdev, ¶m); return qdf_status_to_os_return(status); } /** * wlan_cfg80211_coap_offload_periodic_tx_disable() - disable CoAP offload * periodic transmitting * @vdev: pointer to vdev object * @req_id: request id * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_periodic_tx_disable(struct wlan_objmgr_vdev *vdev, uint32_t req_id) { QDF_STATUS status; status = ucfg_coap_offload_periodic_tx_disable(vdev, req_id); return qdf_status_to_os_return(status); } /** * wlan_cfg80211_dealloc_coap_buf_info() - Callback to free priv * allocations for CoAP buffer info * @priv: Pointer to priv data statucture * * Return: None */ static void wlan_cfg80211_dealloc_coap_buf_info(void *priv) { struct coap_buf_info *info = priv; struct coap_buf_node *cur, *next; if (!info) return; qdf_list_for_each_del(&info->info_list, cur, next, node) { qdf_list_remove_node(&info->info_list, &cur->node); qdf_mem_free(cur->payload); qdf_mem_free(cur); } qdf_list_destroy(&info->info_list); } static void wlan_cfg80211_coap_cache_get_cbk(void *context, struct coap_buf_info *info) { struct osif_request *request; struct coap_buf_info *priv_info; if (!context || !info) return; request = osif_request_get(context); if (!request) return; priv_info = osif_request_priv(request); if (info->req_id == priv_info->req_id) { qdf_list_join(&priv_info->info_list, &info->info_list); if (!info->more_info) osif_request_complete(request); } osif_request_put(request); } /** * wlan_cfg80211_coap_fill_buf_info() - Fill cache get response buffer * @reply_skb : pointer to reply_skb * @info : information of cached CoAP messages * @index : attribute type index for nla_next_start() * * Return : 0 on success and errno on failure */ static int wlan_cfg80211_coap_fill_buf_info(struct sk_buff *reply_skb, struct coap_buf_node *info, int index) { struct nlattr *attr; attr = nla_nest_start(reply_skb, index); if (!attr) { coap_err("nla_nest_start failed"); return -EINVAL; } if (hdd_wlan_nla_put_u64(reply_skb, COAP_ATTR(CACHE_INFO_TS), info->tsf) || nla_put_u32(reply_skb, COAP_ATTR(CACHE_INFO_SRC_IPV4), info->src_ip) || nla_put(reply_skb, COAP_ATTR(CACHE_INFO_MSG), info->len, info->payload)) { coap_err("nla_put failed"); return -EINVAL; } nla_nest_end(reply_skb, attr); return 0; } /** * wlan_cfg80211_coap_offload_cache_deliver() - deliver cached CoAP messages * @wiphy: pointer to wireless wiphy structure. * @cache_list: list of cached CoAP messages * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_cache_deliver(struct wiphy *wiphy, qdf_list_t *cache_list) { struct sk_buff *skb; uint32_t skb_len = NLMSG_HDRLEN; struct coap_buf_node *cur, *next; struct nlattr *attr; int i = 0, ret; /* QCA_WLAN_VENDOR_ATTR_COAP_OFFLOAD_CACHES */ skb_len += nla_total_size(0); qdf_list_for_each_del(cache_list, cur, next, node) { if (!cur->len || !cur->payload) continue; /* nest attribute */ skb_len += nla_total_size(0); /* QCA_WLAN_VENDOR_ATTR_COAP_OFFLOAD_CACHE_INFO_TS */ skb_len += nla_total_size(sizeof(uint64_t)); /* QCA_WLAN_VENDOR_ATTR_COAP_OFFLOAD_CACHE_INFO_SRC_IPV4 */ skb_len += nla_total_size(sizeof(uint32_t)); /* QCA_WLAN_VENDOR_ATTR_COAP_OFFLOAD_CACHE_INFO_MSG */ skb_len += nla_total_size(cur->len); } skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(wiphy, skb_len); attr = nla_nest_start(skb, COAP_ATTR(CACHES)); if (!attr) { hdd_err("nla_nest_start failed"); wlan_cfg80211_vendor_free_skb(skb); return -EINVAL; } qdf_list_for_each_del(cache_list, cur, next, node) { if (!cur->len || !cur->payload) continue; qdf_list_remove_node(cache_list, &cur->node); ret = wlan_cfg80211_coap_fill_buf_info(skb, cur, i++); if (ret) { wlan_cfg80211_vendor_free_skb(skb); return -EINVAL; } qdf_mem_free(cur->payload); qdf_mem_free(cur); } nla_nest_end(skb, attr); return wlan_cfg80211_vendor_cmd_reply(skb); } /** * wlan_cfg80211_coap_offload_cache_get() - get cached CoAP messages * @wiphy: pointer to wireless wiphy structure. * @vdev: pointer to vdev object * @req_id: request id * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_cache_get(struct wiphy *wiphy, struct wlan_objmgr_vdev *vdev, uint32_t req_id) { void *cookie; QDF_STATUS status; struct osif_request *request; struct coap_buf_info *buf_info; int ret; static const struct osif_request_params params = { .priv_size = sizeof(*buf_info), .timeout_ms = COAP_OFFLOAD_CACHE_GET_TIMEOUT_MS, .dealloc = wlan_cfg80211_dealloc_coap_buf_info, }; request = osif_request_alloc(¶ms); if (!request) { coap_err("Request allocation failure"); status = QDF_STATUS_E_NOMEM; goto out; } buf_info = osif_request_priv(request); qdf_list_create(&buf_info->info_list, 0); buf_info->req_id = req_id; buf_info->vdev_id = wlan_vdev_get_id(vdev); cookie = osif_request_cookie(request); status = ucfg_coap_offload_cache_get(vdev, req_id, wlan_cfg80211_coap_cache_get_cbk, cookie); if (QDF_IS_STATUS_ERROR(status)) { coap_err("Unable to get cache"); goto out; } ret = osif_request_wait_for_response(request); if (ret) { coap_err("Target response timed out"); status = qdf_status_from_os_return(ret); goto out; } ret = wlan_cfg80211_coap_offload_cache_deliver(wiphy, &buf_info->info_list); if (ret) { coap_err("Failed to deliver buf info"); status = qdf_status_from_os_return(ret); goto out; } out: if (request) osif_request_put(request); return qdf_status_to_os_return(status); } /** * wlan_cfg80211_coap_offload_reply_disable() - disable CoAP offload reply * @wiphy: pointer to wireless wiphy structure. * @vdev: pointer to vdev object * @req_id: request id * * Return: 0 on success; error number otherwise */ static int wlan_cfg80211_coap_offload_reply_disable(struct wiphy *wiphy, struct wlan_objmgr_vdev *vdev, uint32_t req_id) { void *cookie; QDF_STATUS status; struct osif_request *request; struct coap_buf_info *buf_info; int ret; static const struct osif_request_params params = { .priv_size = sizeof(*buf_info), .timeout_ms = COAP_OFFLOAD_CACHE_GET_TIMEOUT_MS, .dealloc = wlan_cfg80211_dealloc_coap_buf_info, }; request = osif_request_alloc(¶ms); if (!request) { coap_err("Request allocation failure"); status = QDF_STATUS_E_NOMEM; goto out; } buf_info = osif_request_priv(request); qdf_list_create(&buf_info->info_list, 0); buf_info->req_id = req_id; buf_info->vdev_id = wlan_vdev_get_id(vdev); cookie = osif_request_cookie(request); status = ucfg_coap_offload_reply_disable(vdev, req_id, wlan_cfg80211_coap_cache_get_cbk, cookie); if (QDF_IS_STATUS_ERROR(status)) { coap_err("Failed to disable offload reply"); goto out; } ret = osif_request_wait_for_response(request); if (ret) { coap_err("Target response timed out"); status = qdf_status_from_os_return(ret); goto out; } ret = wlan_cfg80211_coap_offload_cache_deliver(wiphy, &buf_info->info_list); if (ret) { coap_err("Failed to deliver buf info"); status = qdf_status_from_os_return(ret); goto out; } out: if (request) osif_request_put(request); return qdf_status_to_os_return(status); } int wlan_cfg80211_coap_offload(struct wiphy *wiphy, struct wlan_objmgr_vdev *vdev, const void *data, int data_len) { struct nlattr *tb[COAP_ATTR(MAX) + 1]; struct nlattr *attr; uint32_t action, req_id; int ret; if (wlan_cfg80211_nla_parse(tb, COAP_ATTR(MAX), data, data_len, coap_offload_policy)) { coap_err("Invalid ATTR"); return -EINVAL; } if (!tb[COAP_ATTR(ACTION)]) { coap_err("no attr action"); return -EINVAL; } if (!tb[COAP_ATTR(REQ_ID)]) { coap_err("no attr req id"); return -EINVAL; } action = nla_get_u32(tb[COAP_ATTR(ACTION)]); req_id = nla_get_u32(tb[COAP_ATTR(REQ_ID)]); switch (action) { case QCA_WLAN_VENDOR_COAP_OFFLOAD_ACTION_REPLY_ENABLE: attr = tb[COAP_ATTR(REPLY)]; ret = wlan_cfg80211_coap_offload_reply_enable(vdev, req_id, attr); break; case QCA_WLAN_VENDOR_COAP_OFFLOAD_ACTION_REPLY_DISABLE: ret = wlan_cfg80211_coap_offload_reply_disable(wiphy, vdev, req_id); break; case QCA_WLAN_VENDOR_COAP_OFFLOAD_ACTION_PERIODIC_TX_ENABLE: attr = tb[COAP_ATTR(PERIODIC_TX)]; ret = wlan_cfg80211_coap_offload_periodic_tx_enable(vdev, req_id, attr); break; case QCA_WLAN_VENDOR_COAP_OFFLOAD_ACTION_PERIODIC_TX_DISABLE: ret = wlan_cfg80211_coap_offload_periodic_tx_disable(vdev, req_id); break; case QCA_WLAN_VENDOR_COAP_OFFLOAD_ACTION_CACHE_GET: ret = wlan_cfg80211_coap_offload_cache_get(wiphy, vdev, req_id); break; default: ret = -EINVAL; break; } coap_debug("vdev_id %u action %u req id %u ret %d", wlan_vdev_get_id(vdev), action, req_id, ret); return ret; }