Skip to content

Commit

Permalink
iwlwifi: mvm: add framework for triggers for fw dump
Browse files Browse the repository at this point in the history
Most of the time, the issues we want to debug with the
firmware dump mechanism are transient. It is then very
hard to stop the recording on time and get meaningful
data.
In order to solve this, I add here an infrastucture
of triggers. The user will supply a list of triggers
that will start / stop the recording. We have two types
of triggers: start and stop. Start triggers can start a
specific configuration. The stop triggers will be able to
kick the collection of the data with the currently running
configuration. These triggers are given to the driver by
the .ucode file - just like the configuration.

In the next patches, I'll add triggers in the code.

Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
  • Loading branch information
Emmanuel Grumbach committed Mar 2, 2015
1 parent ef17708 commit d2709ad
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 80 deletions.
46 changes: 44 additions & 2 deletions drivers/net/wireless/iwlwifi/iwl-drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ static void iwl_dealloc_ucode(struct iwl_drv *drv)
kfree(drv->fw.dbg_dest_tlv);
for (i = 0; i < ARRAY_SIZE(drv->fw.dbg_conf_tlv); i++)
kfree(drv->fw.dbg_conf_tlv[i]);
for (i = 0; i < ARRAY_SIZE(drv->fw.dbg_trigger_tlv); i++)
kfree(drv->fw.dbg_trigger_tlv[i]);

for (i = 0; i < IWL_UCODE_TYPE_MAX; i++)
iwl_free_fw_img(drv, drv->fw.img + i);
Expand Down Expand Up @@ -293,8 +295,10 @@ struct iwl_firmware_pieces {

/* FW debug data parsed for driver usage */
struct iwl_fw_dbg_dest_tlv *dbg_dest_tlv;
struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_MAX];
size_t dbg_conf_tlv_len[FW_DBG_MAX];
struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_CONF_MAX];
size_t dbg_conf_tlv_len[FW_DBG_CONF_MAX];
struct iwl_fw_dbg_trigger_tlv *dbg_trigger_tlv[FW_DBG_TRIGGER_MAX];
size_t dbg_trigger_tlv_len[FW_DBG_TRIGGER_MAX];
};

/*
Expand Down Expand Up @@ -914,6 +918,31 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv,
pieces->dbg_conf_tlv_len[conf->id] = tlv_len;
break;
}
case IWL_UCODE_TLV_FW_DBG_TRIGGER: {
struct iwl_fw_dbg_trigger_tlv *trigger =
(void *)tlv_data;
u32 trigger_id = le32_to_cpu(trigger->id);

if (trigger_id >= ARRAY_SIZE(drv->fw.dbg_trigger_tlv)) {
IWL_ERR(drv,
"Skip unknown trigger: %u\n",
trigger->id);
break;
}

if (pieces->dbg_trigger_tlv[trigger_id]) {
IWL_ERR(drv,
"Ignore duplicate dbg trigger %u\n",
trigger->id);
break;
}

IWL_INFO(drv, "Found debug trigger: %u\n", trigger->id);

pieces->dbg_trigger_tlv[trigger_id] = trigger;
pieces->dbg_trigger_tlv_len[trigger_id] = tlv_len;
break;
}
case IWL_UCODE_TLV_SEC_RT_USNIFFER:
usniffer_images = true;
iwl_store_ucode_sec(pieces, tlv_data,
Expand Down Expand Up @@ -1198,6 +1227,19 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
}
}

for (i = 0; i < ARRAY_SIZE(drv->fw.dbg_trigger_tlv); i++) {
if (pieces->dbg_trigger_tlv[i]) {
drv->fw.dbg_trigger_tlv_len[i] =
pieces->dbg_trigger_tlv_len[i];
drv->fw.dbg_trigger_tlv[i] =
kmemdup(pieces->dbg_trigger_tlv[i],
drv->fw.dbg_trigger_tlv_len[i],
GFP_KERNEL);
if (!drv->fw.dbg_trigger_tlv[i])
goto out_free_fw;
}
}

/* Now that we can no longer fail, copy information */

/*
Expand Down
18 changes: 18 additions & 0 deletions drivers/net/wireless/iwlwifi/iwl-fw-error-dump.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,22 @@ iwl_fw_error_next_data(struct iwl_fw_error_dump_data *data)
return (void *)(data->data + le32_to_cpu(data->len));
}

/**
* enum iwl_fw_dbg_trigger - triggers available
*
* @FW_DBG_TRIGGER_USER: trigger log collection by user
* This should not be defined as a trigger to the driver, but a value the
* driver should set to indicate that the trigger was initiated by the
* user.
* @FW_DBG_TRIGGER_FW_ASSERT: trigger log collection when the firmware asserts
*/
enum iwl_fw_dbg_trigger {
FW_DBG_TRIGGER_INVALID = 0,
FW_DBG_TRIGGER_USER,
FW_DBG_TRIGGER_FW_ASSERT,

/* must be last */
FW_DBG_TRIGGER_MAX,
};

#endif /* __fw_error_dump_h__ */
88 changes: 61 additions & 27 deletions drivers/net/wireless/iwlwifi/iwl-fw-file.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
#define __iwl_fw_file_h__

#include <linux/netdevice.h>
#include <linux/nl80211.h>

/* v1/v2 uCode file layout */
struct iwl_ucode_header {
Expand Down Expand Up @@ -136,6 +137,7 @@ enum iwl_ucode_tlv_type {
IWL_UCODE_TLV_FW_VERSION = 36,
IWL_UCODE_TLV_FW_DBG_DEST = 38,
IWL_UCODE_TLV_FW_DBG_CONF = 39,
IWL_UCODE_TLV_FW_DBG_TRIGGER = 40,
};

struct iwl_ucode_tlv {
Expand Down Expand Up @@ -458,53 +460,85 @@ struct iwl_fw_dbg_conf_hcmd {
} __packed;

/**
* struct iwl_fw_dbg_trigger - a TLV that describes a debug configuration
* enum iwl_fw_dbg_trigger_mode - triggers functionalities
*
* @enabled: is this trigger enabled
* @reserved:
* @len: length, in bytes, of the %trigger field
* @trigger: pointer to a trigger struct
* @IWL_FW_DBG_TRIGGER_START: when trigger occurs re-conf the dbg mechanism
* @IWL_FW_DBG_TRIGGER_STOP: when trigger occurs pull the dbg data
*/
struct iwl_fw_dbg_trigger {
u8 enabled;
u8 reserved;
u8 len;
u8 trigger[0];
} __packed;
enum iwl_fw_dbg_trigger_mode {
IWL_FW_DBG_TRIGGER_START = BIT(0),
IWL_FW_DBG_TRIGGER_STOP = BIT(1),
};

/**
* enum iwl_fw_dbg_conf - configurations available
*
* @FW_DBG_CUSTOM: take this configuration from alive
* Note that the trigger is NO-OP for this configuration
* enum iwl_fw_dbg_trigger_vif_type - define the VIF type for a trigger
* @IWL_FW_DBG_CONF_VIF_ANY: any vif type
* @IWL_FW_DBG_CONF_VIF_IBSS: IBSS mode
* @IWL_FW_DBG_CONF_VIF_STATION: BSS mode
* @IWL_FW_DBG_CONF_VIF_AP: AP mode
* @IWL_FW_DBG_CONF_VIF_P2P_CLIENT: P2P Client mode
* @IWL_FW_DBG_CONF_VIF_P2P_GO: P2P GO mode
* @IWL_FW_DBG_CONF_VIF_P2P_DEVICE: P2P device
*/
enum iwl_fw_dbg_conf {
FW_DBG_CUSTOM = 0,

/* must be last */
FW_DBG_MAX,
FW_DBG_INVALID = 0xff,
enum iwl_fw_dbg_trigger_vif_type {
IWL_FW_DBG_CONF_VIF_ANY = NL80211_IFTYPE_UNSPECIFIED,
IWL_FW_DBG_CONF_VIF_IBSS = NL80211_IFTYPE_ADHOC,
IWL_FW_DBG_CONF_VIF_STATION = NL80211_IFTYPE_STATION,
IWL_FW_DBG_CONF_VIF_AP = NL80211_IFTYPE_AP,
IWL_FW_DBG_CONF_VIF_P2P_CLIENT = NL80211_IFTYPE_P2P_CLIENT,
IWL_FW_DBG_CONF_VIF_P2P_GO = NL80211_IFTYPE_P2P_GO,
IWL_FW_DBG_CONF_VIF_P2P_DEVICE = NL80211_IFTYPE_P2P_DEVICE,
};

/**
* struct iwl_fw_dbg_conf_tlv - a TLV that describes a debug configuration
*
* @id: %enum iwl_fw_dbg_conf
* struct iwl_fw_dbg_trigger_tlv - a TLV that describes the trigger
* @id: %enum iwl_fw_dbg_trigger
* @vif_type: %enum iwl_fw_dbg_trigger_vif_type
* @stop_conf_ids: bitmap of configurations this trigger relates to.
* if the mode is %IWL_FW_DBG_TRIGGER_STOP, then if the bit corresponding
* to the currently running configuration is set, the data should be
* collected.
* @stop_delay: how many milliseconds to wait before collecting the data
* after the STOP trigger fires.
* @mode: %enum iwl_fw_dbg_trigger_mode - can be stop / start of both
* @start_conf_id: if mode is %IWL_FW_DBG_TRIGGER_START, this defines what
* configuration should be applied when the triggers kicks in.
* @occurrences: number of occurrences. 0 means the trigger will never fire.
*/
struct iwl_fw_dbg_trigger_tlv {
__le32 id;
__le32 vif_type;
__le32 stop_conf_ids;
__le32 stop_delay;
u8 mode;
u8 start_conf_id;
__le16 occurrences;
__le32 reserved[2];

u8 data[0];
} __packed;

#define FW_DBG_START_FROM_ALIVE 0
#define FW_DBG_CONF_MAX 32
#define FW_DBG_INVALID 0xff

/**
* struct iwl_fw_dbg_conf_tlv - a TLV that describes a debug configuration.
* @id: conf id
* @usniffer: should the uSniffer image be used
* @num_of_hcmds: how many HCMDs to send are present here
* @hcmd: a variable length host command to be sent to apply the configuration.
* If there is more than one HCMD to send, they will appear one after the
* other and be sent in the order that they appear in.
* This parses IWL_UCODE_TLV_FW_DBG_CONF
* This parses IWL_UCODE_TLV_FW_DBG_CONF. The user can add up-to
* %FW_DBG_CONF_MAX configuration per run.
*/
struct iwl_fw_dbg_conf_tlv {
u8 id;
u8 usniffer;
u8 reserved;
u8 num_of_hcmds;
struct iwl_fw_dbg_conf_hcmd hcmd;

/* struct iwl_fw_dbg_trigger sits after all variable length hcmds */
} __packed;

#endif /* __iwl_fw_file_h__ */
53 changes: 20 additions & 33 deletions drivers/net/wireless/iwlwifi/iwl-fw.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include <net/mac80211.h>

#include "iwl-fw-file.h"
#include "iwl-fw-error-dump.h"

/**
* enum iwl_ucode_type
Expand Down Expand Up @@ -157,6 +158,8 @@ struct iwl_fw_cscheme_list {
* @dbg_dest_tlv: points to the destination TLV for debug
* @dbg_conf_tlv: array of pointers to configuration TLVs for debug
* @dbg_conf_tlv_len: lengths of the @dbg_conf_tlv entries
* @dbg_trigger_tlv: array of pointers to triggers TLVs
* @dbg_trigger_tlv_len: lengths of the @dbg_trigger_tlv entries
* @dbg_dest_reg_num: num of reg_ops in %dbg_dest_tlv
*/
struct iwl_fw {
Expand Down Expand Up @@ -186,9 +189,10 @@ struct iwl_fw {
u32 sdio_adma_addr;

struct iwl_fw_dbg_dest_tlv *dbg_dest_tlv;
struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_MAX];
size_t dbg_conf_tlv_len[FW_DBG_MAX];

struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_CONF_MAX];
size_t dbg_conf_tlv_len[FW_DBG_CONF_MAX];
struct iwl_fw_dbg_trigger_tlv *dbg_trigger_tlv[FW_DBG_TRIGGER_MAX];
size_t dbg_trigger_tlv_len[FW_DBG_TRIGGER_MAX];
u8 dbg_dest_reg_num;
};

Expand All @@ -206,46 +210,29 @@ static inline const char *get_fw_dbg_mode_string(int mode)
}
}

static inline const struct iwl_fw_dbg_trigger *
iwl_fw_dbg_conf_get_trigger(const struct iwl_fw *fw, u8 id)
static inline bool
iwl_fw_dbg_conf_usniffer(const struct iwl_fw *fw, u8 id)
{
const struct iwl_fw_dbg_conf_tlv *conf_tlv = fw->dbg_conf_tlv[id];
u8 *ptr;
int i;

if (!conf_tlv)
return NULL;

ptr = (void *)&conf_tlv->hcmd;
for (i = 0; i < conf_tlv->num_of_hcmds; i++) {
ptr += sizeof(conf_tlv->hcmd);
ptr += le16_to_cpu(conf_tlv->hcmd.len);
}

return (const struct iwl_fw_dbg_trigger *)ptr;
}

static inline bool
iwl_fw_dbg_conf_enabled(const struct iwl_fw *fw, u8 id)
{
const struct iwl_fw_dbg_trigger *trigger =
iwl_fw_dbg_conf_get_trigger(fw, id);

if (!trigger)
return false;

return trigger->enabled;
return conf_tlv->usniffer;
}

static inline bool
iwl_fw_dbg_conf_usniffer(const struct iwl_fw *fw, u8 id)
{
const struct iwl_fw_dbg_conf_tlv *conf_tlv = fw->dbg_conf_tlv[id];
#define iwl_fw_dbg_trigger_enabled(fw, id) ({ \
void *__dbg_trigger = (fw)->dbg_trigger_tlv[(id)]; \
unlikely(__dbg_trigger); \
})

if (!conf_tlv)
return false;
static inline struct iwl_fw_dbg_trigger_tlv*
iwl_fw_dbg_get_trigger(const struct iwl_fw *fw, u8 id)
{
if (WARN_ON(id >= ARRAY_SIZE(fw->dbg_trigger_tlv)))
return NULL;

return conf_tlv->usniffer;
return fw->dbg_trigger_tlv[id];
}

#endif /* __iwl_fw_h__ */
4 changes: 3 additions & 1 deletion drivers/net/wireless/iwlwifi/iwl-trans.h
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ enum iwl_d0i3_mode {
* @dflt_pwr_limit: default power limit fetched from the platform (ACPI)
* @dbg_dest_tlv: points to the destination TLV for debug
* @dbg_conf_tlv: array of pointers to configuration TLVs for debug
* @dbg_trigger_tlv: array of pointers to triggers TLVs for debug
* @dbg_dest_reg_num: num of reg_ops in %dbg_dest_tlv
*/
struct iwl_trans {
Expand Down Expand Up @@ -628,7 +629,8 @@ struct iwl_trans {
u64 dflt_pwr_limit;

const struct iwl_fw_dbg_dest_tlv *dbg_dest_tlv;
const struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_MAX];
const struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_CONF_MAX];
struct iwl_fw_dbg_trigger_tlv * const *dbg_trigger_tlv;
u8 dbg_dest_reg_num;

enum iwl_d0i3_mode d0i3_mode;
Expand Down
6 changes: 3 additions & 3 deletions drivers/net/wireless/iwlwifi/mvm/debugfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ static ssize_t iwl_dbgfs_fw_dbg_conf_read(struct file *file,
size_t count, loff_t *ppos)
{
struct iwl_mvm *mvm = file->private_data;
enum iwl_fw_dbg_conf conf;
int conf;
char buf[8];
const size_t bufsz = sizeof(buf);
int pos = 0;
Expand All @@ -966,7 +966,7 @@ static ssize_t iwl_dbgfs_fw_dbg_conf_write(struct iwl_mvm *mvm,
if (ret)
return ret;

if (WARN_ON(conf_id >= FW_DBG_MAX))
if (WARN_ON(conf_id >= FW_DBG_CONF_MAX))
return -EINVAL;

mutex_lock(&mvm->mutex);
Expand All @@ -985,7 +985,7 @@ static ssize_t iwl_dbgfs_fw_dbg_collect_write(struct iwl_mvm *mvm,
if (ret)
return ret;

iwl_mvm_fw_dbg_collect(mvm);
iwl_mvm_fw_dbg_collect(mvm, FW_DBG_TRIGGER_USER, 0);

iwl_mvm_unref(mvm, IWL_MVM_REF_PRPH_WRITE);

Expand Down
Loading

0 comments on commit d2709ad

Please sign in to comment.