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);
/* 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];
};
/*
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,
}
}
+ 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 */
/*
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__ */
#define __iwl_fw_file_h__
#include <linux/netdevice.h>
+#include <linux/nl80211.h>
/* v1/v2 uCode file layout */
struct iwl_ucode_header {
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 {
} __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 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__ */
#include <net/mac80211.h>
#include "iwl-fw-file.h"
+#include "iwl-fw-error-dump.h"
/**
* enum iwl_ucode_type
* @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 {
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;
};
}
}
-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__ */