| /* | |
| * Copyright (c) 2018, 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. | |
| */ | |
| #include <net/netlink.h> | |
| #include <linux/firmware.h> | |
| #include <net/cfg80211.h> | |
| #include "wcn36xx.h" | |
| #include "testmode.h" | |
| #include "testmode_i.h" | |
| #include "hal.h" | |
| #include "smd.h" | |
| static const struct nla_policy wcn36xx_tm_policy[WCN36XX_TM_ATTR_MAX + 1] = { | |
| [WCN36XX_TM_ATTR_CMD] = { .type = NLA_U16 }, | |
| [WCN36XX_TM_ATTR_DATA] = { .type = NLA_BINARY, | |
| .len = WCN36XX_TM_DATA_MAX_LEN }, | |
| }; | |
| struct build_release_number { | |
| u16 drv_major; | |
| u16 drv_minor; | |
| u16 drv_patch; | |
| u16 drv_build; | |
| u16 ptt_max; | |
| u16 ptt_min; | |
| u16 fw_ver; | |
| } __packed; | |
| static int wcn36xx_tm_cmd_ptt(struct wcn36xx *wcn, struct ieee80211_vif *vif, | |
| struct nlattr *tb[]) | |
| { | |
| int ret = 0, buf_len; | |
| void *buf; | |
| struct ftm_rsp_msg *msg, *rsp = NULL; | |
| struct sk_buff *skb; | |
| if (!tb[WCN36XX_TM_ATTR_DATA]) | |
| return -EINVAL; | |
| buf = nla_data(tb[WCN36XX_TM_ATTR_DATA]); | |
| buf_len = nla_len(tb[WCN36XX_TM_ATTR_DATA]); | |
| msg = buf; | |
| wcn36xx_dbg(WCN36XX_DBG_TESTMODE, | |
| "testmode cmd wmi msg_id 0x%04X msg_len %d buf %p buf_len %d\n", | |
| msg->msg_id, msg->msg_body_length, | |
| buf, buf_len); | |
| wcn36xx_dbg_dump(WCN36XX_DBG_TESTMODE_DUMP, "REQ ", buf, buf_len); | |
| if (msg->msg_id == MSG_GET_BUILD_RELEASE_NUMBER) { | |
| struct build_release_number *body = | |
| (struct build_release_number *) | |
| msg->msg_response; | |
| body->drv_major = wcn->fw_major; | |
| body->drv_minor = wcn->fw_minor; | |
| body->drv_patch = wcn->fw_version; | |
| body->drv_build = wcn->fw_revision; | |
| body->ptt_max = 10; | |
| body->ptt_min = 0; | |
| rsp = msg; | |
| rsp->resp_status = 0; | |
| } else { | |
| wcn36xx_dbg(WCN36XX_DBG_TESTMODE, | |
| "PPT Request >> HAL size %d\n", | |
| msg->msg_body_length); | |
| msg->resp_status = wcn36xx_smd_process_ptt_msg(wcn, vif, msg, | |
| msg->msg_body_length, (void *)(&rsp)); | |
| wcn36xx_dbg(WCN36XX_DBG_TESTMODE, | |
| "Response status = %d\n", | |
| msg->resp_status); | |
| if (rsp) | |
| wcn36xx_dbg(WCN36XX_DBG_TESTMODE, | |
| "PPT Response << HAL size %d\n", | |
| rsp->msg_body_length); | |
| } | |
| if (!rsp) { | |
| rsp = msg; | |
| wcn36xx_warn("No response! Echoing request with response status %d\n", | |
| rsp->resp_status); | |
| } | |
| wcn36xx_dbg_dump(WCN36XX_DBG_TESTMODE_DUMP, "RSP ", | |
| rsp, rsp->msg_body_length); | |
| skb = cfg80211_testmode_alloc_reply_skb(wcn->hw->wiphy, | |
| nla_total_size(msg->msg_body_length)); | |
| if (!skb) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| ret = nla_put(skb, WCN36XX_TM_ATTR_DATA, rsp->msg_body_length, rsp); | |
| if (ret) { | |
| kfree_skb(skb); | |
| goto out; | |
| } | |
| ret = cfg80211_testmode_reply(skb); | |
| out: | |
| if (rsp != msg) | |
| kfree(rsp); | |
| return ret; | |
| } | |
| int wcn36xx_tm_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif, | |
| void *data, int len) | |
| { | |
| struct wcn36xx *wcn = hw->priv; | |
| struct nlattr *tb[WCN36XX_TM_ATTR_MAX + 1]; | |
| int ret = 0; | |
| unsigned short attr; | |
| wcn36xx_dbg_dump(WCN36XX_DBG_TESTMODE_DUMP, "Data:", data, len); | |
| ret = nla_parse_deprecated(tb, WCN36XX_TM_ATTR_MAX, data, len, | |
| wcn36xx_tm_policy, NULL); | |
| if (ret) | |
| return ret; | |
| if (!tb[WCN36XX_TM_ATTR_CMD]) | |
| return -EINVAL; | |
| attr = nla_get_u16(tb[WCN36XX_TM_ATTR_CMD]); | |
| if (attr != WCN36XX_TM_CMD_PTT) | |
| return -EOPNOTSUPP; | |
| return wcn36xx_tm_cmd_ptt(wcn, vif, tb); | |
| } |