Commit 703f261d authored by Alan Liu's avatar Alan Liu Committed by Kalle Valo

ath10k: add memory dump support for QCA6174/QCA9377

Add memory dump to the firmware crash data file which is provided to user space
via devcoredump interface. This makes it easier for firmware engineers to debug
firmware crashes.

Due to increased memory consumption the memory dump is disabled by default. To
enable it make sure that bit 3 is set in coredump_mask module parameter:

modprobe ath10k_core coredump_mask=0xffffffff

When RAMDUMP is enabled a buffer for the dump is allocated with vmalloc during
device probe. The actual memory layout is different in hardware versions and
the layouts are defined in coredump.c. The memory is split to regions and, to
get even finegrained control of what to copy, the region can split to smaller
sections as not all registers are readable (which could cause the whole system
to stall).
Signed-off-by: default avatarAlan Liu <alanliu@qca.qualcomm.com>
[kvalo@qca.qualcomm.com: refactoring and cleanup]
Signed-off-by: default avatarKalle Valo <kvalo@qca.qualcomm.com>
parent 5c9d0a20
......@@ -2563,10 +2563,16 @@ static void ath10k_core_register_work(struct work_struct *work)
goto err_release_fw;
}
status = ath10k_coredump_register(ar);
if (status) {
ath10k_err(ar, "unable to register coredump\n");
goto err_unregister_mac;
}
status = ath10k_debug_register(ar);
if (status) {
ath10k_err(ar, "unable to initialize debugfs\n");
goto err_unregister_mac;
goto err_unregister_coredump;
}
status = ath10k_spectral_create(ar);
......@@ -2589,6 +2595,8 @@ static void ath10k_core_register_work(struct work_struct *work)
ath10k_spectral_destroy(ar);
err_debug_destroy:
ath10k_debug_destroy(ar);
err_unregister_coredump:
ath10k_coredump_unregister(ar);
err_unregister_mac:
ath10k_mac_unregister(ar);
err_release_fw:
......
......@@ -460,12 +460,17 @@ struct ath10k_ce_crash_hdr {
struct ath10k_ce_crash_data entries[];
};
#define MAX_MEM_DUMP_TYPE 5
/* used for crash-dump storage, protected by data-lock */
struct ath10k_fw_crash_data {
guid_t guid;
struct timespec64 timestamp;
__le32 registers[REG_DUMP_COUNT_QCA988X];
struct ath10k_ce_crash_data ce_crash_data[CE_COUNT_MAX];
u8 *ramdump_buf;
size_t ramdump_buf_len;
};
struct ath10k_debug {
......
This diff is collapsed.
......@@ -29,6 +29,9 @@ enum ath10k_fw_crash_dump_type {
ATH10K_FW_CRASH_DUMP_REGISTERS = 0,
ATH10K_FW_CRASH_DUMP_CE_DATA = 1,
/* contains multiple struct ath10k_dump_ram_data_hdr */
ATH10K_FW_CRASH_DUMP_RAM_DATA = 2,
ATH10K_FW_CRASH_DUMP_MAX,
};
......@@ -99,13 +102,88 @@ struct ath10k_dump_file_data {
u8 data[0];
} __packed;
struct ath10k_dump_ram_data_hdr {
/* enum ath10k_mem_region_type */
__le32 region_type;
__le32 start;
/* length of payload data, not including this header */
__le32 length;
u8 data[0];
};
/* magic number to fill the holes not copied due to sections in regions */
#define ATH10K_MAGIC_NOT_COPIED 0xAA
/* part of user space ABI */
enum ath10k_mem_region_type {
ATH10K_MEM_REGION_TYPE_REG = 1,
ATH10K_MEM_REGION_TYPE_DRAM = 2,
ATH10K_MEM_REGION_TYPE_AXI = 3,
ATH10K_MEM_REGION_TYPE_IRAM1 = 4,
ATH10K_MEM_REGION_TYPE_IRAM2 = 5,
};
/* Define a section of the region which should be copied. As not all parts
* of the memory is possible to copy, for example some of the registers can
* be like that, sections can be used to define what is safe to copy.
*
* To minimize the size of the array, the list must obey the format:
* '{start0,stop0},{start1,stop1},{start2,stop2}....' The values below must
* also obey to 'start0 < stop0 < start1 < stop1 < start2 < ...', otherwise
* we may encouter error in the dump processing.
*/
struct ath10k_mem_section {
u32 start;
u32 end;
};
/* One region of a memory layout. If the sections field is null entire
* region is copied. If sections is non-null only the areas specified in
* sections are copied and rest of the areas are filled with
* ATH10K_MAGIC_NOT_COPIED.
*/
struct ath10k_mem_region {
enum ath10k_mem_region_type type;
u32 start;
u32 len;
const char *name;
struct {
const struct ath10k_mem_section *sections;
u32 size;
} section_table;
};
/* Contains the memory layout of a hardware version identified with the
* hardware id, split into regions.
*/
struct ath10k_hw_mem_layout {
u32 hw_id;
struct {
const struct ath10k_mem_region *regions;
int size;
} region_table;
};
/* FIXME: where to put this? */
extern unsigned long ath10k_coredump_mask;
#ifdef CONFIG_DEV_COREDUMP
int ath10k_coredump_submit(struct ath10k *ar);
struct ath10k_fw_crash_data *ath10k_coredump_new(struct ath10k *ar);
int ath10k_coredump_create(struct ath10k *ar);
int ath10k_coredump_register(struct ath10k *ar);
void ath10k_coredump_unregister(struct ath10k *ar);
void ath10k_coredump_destroy(struct ath10k *ar);
const struct ath10k_hw_mem_layout *ath10k_coredump_get_mem_layout(struct ath10k *ar);
#else /* CONFIG_DEV_COREDUMP */
static inline int ath10k_coredump_submit(struct ath10k *ar)
......@@ -123,10 +201,25 @@ static inline int ath10k_coredump_create(struct ath10k *ar)
return 0;
}
static inline int ath10k_coredump_register(struct ath10k *ar)
{
return 0;
}
static inline void ath10k_coredump_unregister(struct ath10k *ar)
{
}
static inline void ath10k_coredump_destroy(struct ath10k *ar)
{
}
static inline const struct ath10k_hw_mem_layout *
ath10k_coredump_get_mem_layout(struct ath10k *ar)
{
return NULL;
}
#endif /* CONFIG_DEV_COREDUMP */
#endif /* _COREDUMP_H_ */
......@@ -888,6 +888,7 @@ ath10k_rx_desc_get_l3_pad_bytes(struct ath10k_hw_params *hw,
#define PCIE_INTR_CLR_ADDRESS ar->regs->pcie_intr_clr_address
#define SCRATCH_3_ADDRESS ar->regs->scratch_3_address
#define CPU_INTR_ADDRESS 0x0010
#define FW_RAM_CONFIG_ADDRESS 0x0018
#define CCNT_TO_MSEC(ar, x) ((x) / ar->hw_params.channel_counters_freq_hz)
......
......@@ -52,6 +52,11 @@ MODULE_PARM_DESC(reset_mode, "0: auto, 1: warm only (default: 0)");
#define ATH10K_PCI_TARGET_WAIT 3000
#define ATH10K_PCI_NUM_WARM_RESET_ATTEMPTS 3
/* Maximum number of bytes that can be handled atomically by
* diag read and write.
*/
#define ATH10K_DIAG_TRANSFER_LIMIT 0x5000
static const struct pci_device_id ath10k_pci_id_table[] = {
{ PCI_VDEVICE(ATHEROS, QCA988X_2_0_DEVICE_ID) }, /* PCI-E QCA988X V2 */
{ PCI_VDEVICE(ATHEROS, QCA6164_2_1_DEVICE_ID) }, /* PCI-E QCA6164 V2.1 */
......@@ -1462,6 +1467,218 @@ static void ath10k_pci_dump_registers(struct ath10k *ar,
crash_data->registers[i] = reg_dump_values[i];
}
static int ath10k_pci_dump_memory_section(struct ath10k *ar,
const struct ath10k_mem_region *mem_region,
u8 *buf, size_t buf_len)
{
const struct ath10k_mem_section *cur_section, *next_section;
unsigned int count, section_size, skip_size;
int ret, i, j;
if (!mem_region || !buf)
return 0;
if (mem_region->section_table.size < 0)
return 0;
cur_section = &mem_region->section_table.sections[0];
if (mem_region->start > cur_section->start) {
ath10k_warn(ar, "incorrect memdump region 0x%x with section start addrress 0x%x.\n",
mem_region->start, cur_section->start);
return 0;
}
skip_size = cur_section->start - mem_region->start;
/* fill the gap between the first register section and register
* start address
*/
for (i = 0; i < skip_size; i++) {
*buf = ATH10K_MAGIC_NOT_COPIED;
buf++;
}
count = 0;
for (i = 0; cur_section != NULL; i++) {
section_size = cur_section->end - cur_section->start;
if (section_size <= 0) {
ath10k_warn(ar, "incorrect ramdump format with start address 0x%x and stop address 0x%x\n",
cur_section->start,
cur_section->end);
break;
}
if ((i + 1) == mem_region->section_table.size) {
/* last section */
next_section = NULL;
skip_size = 0;
} else {
next_section = cur_section + 1;
if (cur_section->end > next_section->start) {
ath10k_warn(ar, "next ramdump section 0x%x is smaller than current end address 0x%x\n",
next_section->start,
cur_section->end);
break;
}
skip_size = next_section->start - cur_section->end;
}
if (buf_len < (skip_size + section_size)) {
ath10k_warn(ar, "ramdump buffer is too small: %zu\n", buf_len);
break;
}
buf_len -= skip_size + section_size;
/* read section to dest memory */
ret = ath10k_pci_diag_read_mem(ar, cur_section->start,
buf, section_size);
if (ret) {
ath10k_warn(ar, "failed to read ramdump from section 0x%x: %d\n",
cur_section->start, ret);
break;
}
buf += section_size;
count += section_size;
/* fill in the gap between this section and the next */
for (j = 0; j < skip_size; j++) {
*buf = ATH10K_MAGIC_NOT_COPIED;
buf++;
}
count += skip_size;
if (!next_section)
/* this was the last section */
break;
cur_section = next_section;
}
return count;
}
static int ath10k_pci_set_ram_config(struct ath10k *ar, u32 config)
{
u32 val;
ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS +
FW_RAM_CONFIG_ADDRESS, config);
val = ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS +
FW_RAM_CONFIG_ADDRESS);
if (val != config) {
ath10k_warn(ar, "failed to set RAM config from 0x%x to 0x%x\n",
val, config);
return -EIO;
}
return 0;
}
static void ath10k_pci_dump_memory(struct ath10k *ar,
struct ath10k_fw_crash_data *crash_data)
{
const struct ath10k_hw_mem_layout *mem_layout;
const struct ath10k_mem_region *current_region;
struct ath10k_dump_ram_data_hdr *hdr;
u32 count, shift;
size_t buf_len;
int ret, i;
u8 *buf;
lockdep_assert_held(&ar->data_lock);
if (!crash_data)
return;
mem_layout = ath10k_coredump_get_mem_layout(ar);
if (!mem_layout)
return;
current_region = &mem_layout->region_table.regions[0];
buf = crash_data->ramdump_buf;
buf_len = crash_data->ramdump_buf_len;
memset(buf, 0, buf_len);
for (i = 0; i < mem_layout->region_table.size; i++) {
count = 0;
if (current_region->len > buf_len) {
ath10k_warn(ar, "memory region %s size %d is larger that remaining ramdump buffer size %zu\n",
current_region->name,
current_region->len,
buf_len);
break;
}
/* To get IRAM dump, the host driver needs to switch target
* ram config from DRAM to IRAM.
*/
if (current_region->type == ATH10K_MEM_REGION_TYPE_IRAM1 ||
current_region->type == ATH10K_MEM_REGION_TYPE_IRAM2) {
shift = current_region->start >> 20;
ret = ath10k_pci_set_ram_config(ar, shift);
if (ret) {
ath10k_warn(ar, "failed to switch ram config to IRAM for section %s: %d\n",
current_region->name, ret);
break;
}
}
/* Reserve space for the header. */
hdr = (void *)buf;
buf += sizeof(*hdr);
buf_len -= sizeof(*hdr);
if (current_region->section_table.size > 0) {
/* Copy each section individually. */
count = ath10k_pci_dump_memory_section(ar,
current_region,
buf,
current_region->len);
} else {
/* No individiual memory sections defined so we can
* copy the entire memory region.
*/
ret = ath10k_pci_diag_read_mem(ar,
current_region->start,
buf,
current_region->len);
if (ret) {
ath10k_warn(ar, "failed to copy ramdump region %s: %d\n",
current_region->name, ret);
break;
}
count = current_region->len;
}
hdr->region_type = cpu_to_le32(current_region->type);
hdr->start = cpu_to_le32(current_region->start);
hdr->length = cpu_to_le32(count);
if (count == 0)
/* Note: the header remains, just with zero length. */
break;
buf += count;
buf_len -= count;
current_region++;
}
}
static void ath10k_pci_fw_crashed_dump(struct ath10k *ar)
{
struct ath10k_fw_crash_data *crash_data;
......@@ -1482,6 +1699,7 @@ static void ath10k_pci_fw_crashed_dump(struct ath10k *ar)
ath10k_print_driver_info(ar);
ath10k_pci_dump_registers(ar, crash_data);
ath10k_ce_dump_registers(ar, crash_data);
ath10k_pci_dump_memory(ar, crash_data);
spin_unlock_bh(&ar->data_lock);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment