Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions drivers/net/wireless/ath/ath10k/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1259,9 +1259,13 @@ struct ath10k {
struct {
/* protected by conf_mutex */
struct ath10k_fw_components utf_mode_fw;
u8 ftm_msgref;

/* protected by data_lock */
bool utf_monitor;
u32 data_pos;
u32 expected_seq;
u8 *eventdata;
} testmode;

struct {
Expand Down
253 changes: 223 additions & 30 deletions drivers/net/wireless/ath/ath10k/testmode.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014-2017 Qualcomm Atheros, Inc.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/

#include "testmode.h"
Expand All @@ -10,12 +11,17 @@

#include "debug.h"
#include "wmi.h"
#include "wmi-tlv.h"
#include "hif.h"
#include "hw.h"
#include "core.h"

#include "testmode_i.h"

#define ATH10K_FTM_SEG_NONE ((u32)-1)
#define ATH10K_FTM_SEGHDR_CURRENT_SEQ GENMASK(3, 0)
#define ATH10K_FTM_SEGHDR_TOTAL_SEGMENTS GENMASK(7, 4)

static const struct nla_policy ath10k_tm_policy[ATH10K_TM_ATTR_MAX + 1] = {
[ATH10K_TM_ATTR_CMD] = { .type = NLA_U32 },
[ATH10K_TM_ATTR_DATA] = { .type = NLA_BINARY,
Expand All @@ -25,41 +31,19 @@ static const struct nla_policy ath10k_tm_policy[ATH10K_TM_ATTR_MAX + 1] = {
[ATH10K_TM_ATTR_VERSION_MINOR] = { .type = NLA_U32 },
};

/* Returns true if callee consumes the skb and the skb should be discarded.
* Returns false if skb is not used. Does not sleep.
*/
bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb)
static void ath10k_tm_event_unsegmented(struct ath10k *ar, u32 cmd_id,
struct sk_buff *skb)
{
struct sk_buff *nl_skb;
bool consumed;
int ret;

ath10k_dbg(ar, ATH10K_DBG_TESTMODE,
"testmode event wmi cmd_id %d skb %p skb->len %d\n",
cmd_id, skb, skb->len);

ath10k_dbg_dump(ar, ATH10K_DBG_TESTMODE, NULL, "", skb->data, skb->len);

spin_lock_bh(&ar->data_lock);

if (!ar->testmode.utf_monitor) {
consumed = false;
goto out;
}

/* Only testmode.c should be handling events from utf firmware,
* otherwise all sort of problems will arise as mac80211 operations
* are not initialised.
*/
consumed = true;

nl_skb = cfg80211_testmode_alloc_event_skb(ar->hw->wiphy,
2 * sizeof(u32) + skb->len,
GFP_ATOMIC);
if (!nl_skb) {
ath10k_warn(ar,
"failed to allocate skb for testmode wmi event\n");
goto out;
return;
}

ret = nla_put_u32(nl_skb, ATH10K_TM_ATTR_CMD, ATH10K_TM_CMD_WMI);
Expand All @@ -68,7 +52,7 @@ bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb)
"failed to put testmode wmi event cmd attribute: %d\n",
ret);
kfree_skb(nl_skb);
goto out;
return;
}

ret = nla_put_u32(nl_skb, ATH10K_TM_ATTR_WMI_CMDID, cmd_id);
Expand All @@ -77,7 +61,7 @@ bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb)
"failed to put testmode wmi event cmd_id: %d\n",
ret);
kfree_skb(nl_skb);
goto out;
return;
}

ret = nla_put(nl_skb, ATH10K_TM_ATTR_DATA, skb->len, skb->data);
Expand All @@ -86,10 +70,122 @@ bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb)
"failed to copy skb to testmode wmi event: %d\n",
ret);
kfree_skb(nl_skb);
goto out;
return;
}

cfg80211_testmode_event(nl_skb, GFP_ATOMIC);
}

static void ath10k_tm_event_segmented(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb)
{
struct wmi_ftm_cmd *ftm = (struct wmi_ftm_cmd *)skb->data;
u8 total_segments, current_seq;
struct sk_buff *nl_skb;
u8 const *buf_pos;
u16 datalen;
u32 data_pos;
int ret;

if (skb->len < sizeof(*ftm)) {
ath10k_warn(ar, "Invalid ftm event length: %d\n", skb->len);
return;
}

current_seq = FIELD_GET(ATH10K_FTM_SEGHDR_CURRENT_SEQ,
__le32_to_cpu(ftm->seg_hdr.segmentinfo));
total_segments = FIELD_GET(ATH10K_FTM_SEGHDR_TOTAL_SEGMENTS,
__le32_to_cpu(ftm->seg_hdr.segmentinfo));
datalen = skb->len - sizeof(*ftm);
buf_pos = ftm->data;

if (current_seq == 0) {
ar->testmode.expected_seq = 0;
ar->testmode.data_pos = 0;
}

data_pos = ar->testmode.data_pos;

if ((data_pos + datalen) > ATH_FTM_EVENT_MAX_BUF_LENGTH) {
ath10k_warn(ar, "Invalid ftm event length at %u: %u\n",
data_pos, datalen);
ret = -EINVAL;
return;
}

memcpy(&ar->testmode.eventdata[data_pos], buf_pos, datalen);
data_pos += datalen;

if (++ar->testmode.expected_seq != total_segments) {
ar->testmode.data_pos = data_pos;
ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "partial data received %u/%u\n",
current_seq + 1, total_segments);
return;
}

ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "total data length %u\n", data_pos);

nl_skb = cfg80211_testmode_alloc_event_skb(ar->hw->wiphy,
2 * sizeof(u32) + data_pos,
GFP_ATOMIC);
if (!nl_skb) {
ath10k_warn(ar, "failed to allocate skb for testmode wmi event\n");
return;
}

ret = nla_put_u32(nl_skb, ATH10K_TM_ATTR_CMD, ATH10K_TM_CMD_TLV);
if (ret) {
ath10k_warn(ar, "failed to put testmode wmi event attribute: %d\n", ret);
kfree_skb(nl_skb);
return;
}

ret = nla_put_u32(nl_skb, ATH10K_TM_ATTR_WMI_CMDID, cmd_id);
if (ret) {
ath10k_warn(ar, "failed to put testmode wmi event cmd_id: %d\n", ret);
kfree_skb(nl_skb);
return;
}

ret = nla_put(nl_skb, ATH10K_TM_ATTR_DATA, data_pos, &ar->testmode.eventdata[0]);
if (ret) {
ath10k_warn(ar, "failed to copy skb to testmode wmi event: %d\n", ret);
kfree_skb(nl_skb);
return;
}

cfg80211_testmode_event(nl_skb, GFP_ATOMIC);
}

/* Returns true if callee consumes the skb and the skb should be discarded.
* Returns false if skb is not used. Does not sleep.
*/
bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb)
{
bool consumed;

ath10k_dbg(ar, ATH10K_DBG_TESTMODE,
"testmode event wmi cmd_id %d skb %p skb->len %d\n",
cmd_id, skb, skb->len);

ath10k_dbg_dump(ar, ATH10K_DBG_TESTMODE, NULL, "", skb->data, skb->len);

spin_lock_bh(&ar->data_lock);

if (!ar->testmode.utf_monitor) {
consumed = false;
goto out;
}

/* Only testmode.c should be handling events from utf firmware,
* otherwise all sort of problems will arise as mac80211 operations
* are not initialised.
*/
consumed = true;

if (ar->testmode.expected_seq != ATH10K_FTM_SEG_NONE)
ath10k_tm_event_segmented(ar, cmd_id, skb);
else
ath10k_tm_event_unsegmented(ar, cmd_id, skb);

out:
spin_unlock_bh(&ar->data_lock);
Expand Down Expand Up @@ -281,12 +377,18 @@ static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[])
goto err_release_utf_mode_fw;
}

ar->testmode.eventdata = kzalloc(ATH_FTM_EVENT_MAX_BUF_LENGTH, GFP_KERNEL);
if (!ar->testmode.eventdata) {
ret = -ENOMEM;
goto err_power_down;
}

ret = ath10k_core_start(ar, ATH10K_FIRMWARE_MODE_UTF,
&ar->testmode.utf_mode_fw);
if (ret) {
ath10k_err(ar, "failed to start core (testmode): %d\n", ret);
ar->state = ATH10K_STATE_OFF;
goto err_power_down;
goto err_release_eventdata;
}

ar->state = ATH10K_STATE_UTF;
Expand All @@ -302,6 +404,10 @@ static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[])

return 0;

err_release_eventdata:
kfree(ar->testmode.eventdata);
ar->testmode.eventdata = NULL;

err_power_down:
ath10k_hif_power_down(ar);

Expand Down Expand Up @@ -341,6 +447,9 @@ static void __ath10k_tm_cmd_utf_stop(struct ath10k *ar)
release_firmware(ar->testmode.utf_mode_fw.fw_file.firmware);
ar->testmode.utf_mode_fw.fw_file.firmware = NULL;

kfree(ar->testmode.eventdata);
ar->testmode.eventdata = NULL;

ar->state = ATH10K_STATE_OFF;
}

Expand Down Expand Up @@ -424,6 +533,85 @@ static int ath10k_tm_cmd_wmi(struct ath10k *ar, struct nlattr *tb[])
return ret;
}

static int ath10k_tm_cmd_tlv(struct ath10k *ar, struct nlattr *tb[])
{
u16 total_bytes, num_segments;
u32 cmd_id, buf_len;
u8 segnumber = 0;
u8 *bufpos;
void *buf;
int ret;

mutex_lock(&ar->conf_mutex);

if (ar->state != ATH10K_STATE_UTF) {
ret = -ENETDOWN;
goto out;
}

buf = nla_data(tb[ATH10K_TM_ATTR_DATA]);
buf_len = nla_len(tb[ATH10K_TM_ATTR_DATA]);
cmd_id = WMI_PDEV_UTF_CMDID;

ath10k_dbg(ar, ATH10K_DBG_TESTMODE,
"cmd wmi ftm cmd_id %d buffer length %d\n",
cmd_id, buf_len);
ath10k_dbg_dump(ar, ATH10K_DBG_TESTMODE, NULL, "", buf, buf_len);

bufpos = buf;
total_bytes = buf_len;
num_segments = total_bytes / MAX_WMI_UTF_LEN;
ar->testmode.expected_seq = 0;

if (buf_len - (num_segments * MAX_WMI_UTF_LEN))
num_segments++;

while (buf_len) {
u16 chunk_len = min_t(u16, buf_len, MAX_WMI_UTF_LEN);
struct wmi_ftm_cmd *ftm_cmd;
struct sk_buff *skb;
u32 hdr_info;
u8 seginfo;

skb = ath10k_wmi_alloc_skb(ar, (chunk_len +
sizeof(struct wmi_ftm_cmd)));
if (!skb) {
ret = -ENOMEM;
goto out;
}

ftm_cmd = (struct wmi_ftm_cmd *)skb->data;
hdr_info = FIELD_PREP(WMI_TLV_TAG, WMI_TLV_TAG_ARRAY_BYTE) |
FIELD_PREP(WMI_TLV_LEN, (chunk_len +
sizeof(struct wmi_ftm_seg_hdr)));
ftm_cmd->tlv_header = __cpu_to_le32(hdr_info);
ftm_cmd->seg_hdr.len = __cpu_to_le32(total_bytes);
ftm_cmd->seg_hdr.msgref = __cpu_to_le32(ar->testmode.ftm_msgref);
seginfo = FIELD_PREP(ATH10K_FTM_SEGHDR_TOTAL_SEGMENTS, num_segments) |
FIELD_PREP(ATH10K_FTM_SEGHDR_CURRENT_SEQ, segnumber);
ftm_cmd->seg_hdr.segmentinfo = __cpu_to_le32(seginfo);
segnumber++;

memcpy(&ftm_cmd->data, bufpos, chunk_len);

ret = ath10k_wmi_cmd_send(ar, skb, cmd_id);
if (ret) {
ath10k_warn(ar, "failed to send wmi ftm command: %d\n", ret);
goto out;
}

buf_len -= chunk_len;
bufpos += chunk_len;
}

ar->testmode.ftm_msgref++;
ret = 0;

out:
mutex_unlock(&ar->conf_mutex);
return ret;
}

int ath10k_tm_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
void *data, int len)
{
Expand All @@ -439,9 +627,14 @@ int ath10k_tm_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
if (!tb[ATH10K_TM_ATTR_CMD])
return -EINVAL;

ar->testmode.expected_seq = ATH10K_FTM_SEG_NONE;

switch (nla_get_u32(tb[ATH10K_TM_ATTR_CMD])) {
case ATH10K_TM_CMD_GET_VERSION:
return ath10k_tm_cmd_get_version(ar, tb);
if (!tb[ATH10K_TM_ATTR_DATA])
return ath10k_tm_cmd_get_version(ar, tb);
else /* ATH10K_TM_CMD_TLV */
return ath10k_tm_cmd_tlv(ar, tb);
case ATH10K_TM_CMD_UTF_START:
return ath10k_tm_cmd_utf_start(ar, tb);
case ATH10K_TM_CMD_UTF_STOP:
Expand Down
Loading