blob: f95ba53d1a6977f702c4e2df54d30dd766ef3e12 [file] [log] [blame]
/******************************************************************************
*
* Copyright(c) 2014 Intel Corporation. All rights reserved.
* Copyright(c) 2014 Intel Mobile Communications GmbH
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*****************************************************************************/
#include "iwl-dnt-dev-if.h"
#include <linux/export.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include "iwl-csr.h"
#include "iwl-debug.h"
#include "iwl-dnt-cfg.h"
#include "iwl-io.h"
#include "iwl-prph.h"
#include "iwl-tm-gnl.h"
#include "iwl-trans.h"
static void iwl_dnt_dev_if_configure_mipi(struct iwl_trans* trans) {
if (trans->cfg->device_family >= IWL_DEVICE_FAMILY_8000) {
iwl_trans_set_bits_mask(trans, trans->dbg_cfg.dbg_mipi_conf_reg,
trans->dbg_cfg.dbg_mipi_conf_mask, trans->dbg_cfg.dbg_mipi_conf_mask);
return;
}
/* ABB_CguDTClkCtrl - set system trace and mtm clock souce as PLLA */
iowrite32(0x30303, (void __force __iomem*)0xe640110c);
/* ABB_SpcuMemPower - set the power of the trace memory */
iowrite32(0x1, (void __force __iomem*)0xe640201c);
/* set MIPI2 PCL, PCL_26 - PCL_30 */
iowrite32(0x10, (void __force __iomem*)0xe6300274);
iowrite32(0x10, (void __force __iomem*)0xe6300278);
iowrite32(0x10, (void __force __iomem*)0xe630027c);
iowrite32(0x10, (void __force __iomem*)0xe6300280);
iowrite32(0x10, (void __force __iomem*)0xe6300284);
/* ARB0_CNF - enable generic arbiter */
iowrite32(0xc0000000, (void __force __iomem*)0xe6700108);
/* enable WLAN arbiter */
iowrite32(0x80000006, (void __force __iomem*)0xe6700140);
}
static void iwl_dnt_dev_if_configure_marbh(struct iwl_trans* trans) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
uint32_t ret, reg_val = 0;
if (cfg->dbg_marbh_access_type == ACCESS_TYPE_DIRECT) {
iwl_trans_set_bits_mask(trans, cfg->dbg_marbh_conf_reg, cfg->dbg_marbh_conf_mask,
cfg->dbg_marbh_conf_mask);
} else if (cfg->dbg_marbh_access_type == ACCESS_TYPE_INDIRECT) {
ret = iwl_trans_read_mem(trans, cfg->dbg_marbh_conf_reg, &reg_val, 1);
if (ret) {
IWL_ERR(trans, "Failed to read MARBH conf reg\n");
return;
}
reg_val |= cfg->dbg_marbh_conf_mask;
ret = iwl_trans_write_mem(trans, cfg->dbg_marbh_conf_reg, &reg_val, 1);
if (ret) {
IWL_ERR(trans, "Failed to write MARBH conf reg\n");
return;
}
} else {
IWL_ERR(trans, "Invalid MARBH access type\n");
}
}
static void iwl_dnt_dev_if_configure_dbgc_registers(struct iwl_trans* trans, uint32_t base_addr,
uint32_t end_addr) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
switch (trans->tmdev->dnt->cur_mon_type) {
case SMEM:
iwl_write_prph(trans, cfg->dbgc_hb_base_addr, cfg->dbgc_hb_base_val_smem);
iwl_write_prph(trans, cfg->dbgc_hb_end_addr, cfg->dbgc_hb_end_val_smem);
/*
* SMEM requires the same internal configuration as MARBH,
* which preceded it.
*/
iwl_dnt_dev_if_configure_marbh(trans);
break;
case DMA:
default:
/*
* The given addresses are already shifted by 4 places so we
* need to shift by another 4.
* Note that in SfP the end addr points to the last block of
* data that the DBGC can write to, so when setting the end
* register we need to set it to 1 block before.
*/
iwl_write_prph(trans, cfg->dbgc_hb_base_addr, base_addr >> 4);
iwl_write_prph(trans, cfg->dbgc_hb_end_addr, (end_addr >> 4) - 1);
break;
};
}
static void iwl_dnt_dev_if_configure_dbgm_registers(struct iwl_trans* trans, uint32_t base_addr,
uint32_t end_addr) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
/* If we're running a device that supports DBGC - use it */
if (trans->cfg->dbgc_supported) {
iwl_dnt_dev_if_configure_dbgc_registers(trans, base_addr, end_addr);
return;
}
/* configuring monitor */
iwl_write_prph(trans, cfg->dbg_mon_buff_base_addr_reg_addr, base_addr);
iwl_write_prph(trans, cfg->dbg_mon_buff_end_addr_reg_addr, end_addr);
iwl_write_prph(trans, cfg->dbg_mon_data_sel_ctl_addr, cfg->dbg_mon_data_sel_ctl_val);
iwl_write_prph(trans, cfg->dbg_mon_mc_msk_addr, cfg->dbg_mon_mc_msk_val);
iwl_write_prph(trans, cfg->dbg_mon_sample_mask_addr, cfg->dbg_mon_sample_mask_val);
iwl_write_prph(trans, cfg->dbg_mon_start_mask_addr, cfg->dbg_mon_start_mask_val);
iwl_write_prph(trans, cfg->dbg_mon_end_threshold_addr, cfg->dbg_mon_end_threshold_val);
iwl_write_prph(trans, cfg->dbg_mon_end_mask_addr, cfg->dbg_mon_end_mask_val);
iwl_write_prph(trans, cfg->dbg_mon_sample_period_addr, cfg->dbg_mon_sample_period_val);
/* starting monitor */
iwl_write_prph(trans, cfg->dbg_mon_sample_ctl_addr, cfg->dbg_mon_sample_ctl_val);
}
static int iwl_dnt_dev_if_retrieve_dma_monitor_data(struct iwl_dnt* dnt, struct iwl_trans* trans,
void* buffer, uint32_t buffer_size) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
uint32_t wr_ptr, wrap_cnt;
bool dont_reorder = false;
/* FIXME send stop command to FW */
if (WARN_ON_ONCE(!dnt->mon_buf_cpu_addr)) {
IWL_ERR(trans, "Can't retrieve data - DMA wasn't allocated\n");
return -ENOMEM;
}
/* If we're running a device that supports DBGC - use it */
if (trans->cfg->dbgc_supported) {
wr_ptr = iwl_read_prph(trans, cfg->dbgc_dram_wrptr_addr);
} else {
wr_ptr = iwl_read_prph(trans, cfg->dbg_mon_wr_ptr_addr);
}
/* iwl_read_prph returns 0x5a5a5a5a when it fails to grab nic access */
if (wr_ptr == 0x5a5a5a5a) {
IWL_ERR(trans, "Can't read write pointer - not reordering buffer\n");
dont_reorder = true;
}
/* If we're running a device that supports DBGC.... */
if (trans->cfg->dbgc_supported)
/*
* wr_ptr is given relative to the base address, in
* DWORD granularity, and points to the next chunk to
* write to - i.e., the oldest data in the buffer.
*/
{
wr_ptr <<= 2;
} else {
wr_ptr = (wr_ptr << 4) - dnt->mon_base_addr;
}
/* Misunderstanding wr_ptr can cause a page fault, so validate it... */
if (wr_ptr > dnt->mon_buf_size) {
IWL_ERR(trans, "Write pointer DMA monitor register points to invalid data - setting to 0\n");
dont_reorder = true;
}
/* We have a problem with the wr_ptr, so just return the memory as-is */
if (dont_reorder) {
wr_ptr = 0;
}
if (cfg->dbgc_wrap_count_addr) {
wrap_cnt = iwl_read_prph(trans, cfg->dbgc_wrap_count_addr);
} else {
wrap_cnt = 1;
}
if (wrap_cnt) {
memcpy(buffer, dnt->mon_buf_cpu_addr + wr_ptr, dnt->mon_buf_size - wr_ptr);
memcpy(buffer + dnt->mon_buf_size - wr_ptr, dnt->mon_buf_cpu_addr, wr_ptr);
} else {
memcpy(buffer, dnt->mon_buf_cpu_addr, wr_ptr);
memset(buffer + wr_ptr, 0, dnt->mon_buf_size - wr_ptr);
}
return dnt->mon_buf_size;
}
static int iwl_dnt_dev_if_retrieve_marbh_monitor_data(struct iwl_dnt* dnt, struct iwl_trans* trans,
uint8_t* buffer, uint32_t buffer_size) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
int buf_size_in_dwords, buf_index, i;
uint32_t wr_ptr, read_val;
/* FIXME send stop command to FW */
wr_ptr = iwl_read_prph(trans, cfg->dbg_mon_wr_ptr_addr);
/* iwl_read_prph returns 0x5a5a5a5a when it fails to grab nic access */
if (wr_ptr == 0x5a5a5a5a) {
IWL_ERR(trans, "Can't read write pointer\n");
return -ENODEV;
}
read_val = iwl_read_prph(trans, cfg->dbg_mon_buff_base_addr_reg_addr);
if (read_val == 0x5a5a5a5a) {
IWL_ERR(trans, "Can't read monitor base address\n");
return -ENODEV;
}
dnt->mon_base_addr = read_val;
read_val = iwl_read_prph(trans, cfg->dbg_mon_buff_end_addr_reg_addr);
if (read_val == 0x5a5a5a5a) {
IWL_ERR(trans, "Can't read monitor end address\n");
return -ENODEV;
}
dnt->mon_end_addr = read_val;
wr_ptr = wr_ptr - dnt->mon_base_addr;
iwl_write_prph(trans, cfg->dbg_mon_dmarb_rd_ctl_addr, 0x00000001);
/* buf size includes the end_addr as well */
buf_size_in_dwords = dnt->mon_end_addr - dnt->mon_base_addr + 1;
for (i = 0; i < buf_size_in_dwords; i++) {
/* reordering cyclic buffer */
buf_index = (i + (buf_size_in_dwords - wr_ptr)) % buf_size_in_dwords;
read_val = iwl_read_prph(trans, cfg->dbg_mon_dmarb_rd_data_addr);
memcpy(&buffer[buf_index * sizeof(uint32_t)], &read_val, sizeof(uint32_t));
}
iwl_write_prph(trans, cfg->dbg_mon_dmarb_rd_ctl_addr, 0x00000000);
return buf_size_in_dwords * sizeof(uint32_t);
}
static int iwl_dnt_dev_if_retrieve_smem_monitor_data(struct iwl_dnt* dnt, struct iwl_trans* trans,
uint8_t* buffer, uint32_t buffer_size) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
uint32_t i, bytes_to_end, calc_size;
uint32_t base_addr, end_addr, wr_ptr_addr, wr_ptr_shift;
uint32_t base, end, wr_ptr, pos, chunks_num, wr_ptr_offset, wrap_cnt;
uint8_t* temp_buffer;
/* assuming B-step or C-step */
base_addr = cfg->dbg_mon_buff_base_addr_reg_addr_b_step;
end_addr = cfg->dbg_mon_buff_end_addr_reg_addr_b_step;
wr_ptr_addr = cfg->dbg_mon_wr_ptr_addr_b_step;
wr_ptr_shift = 2;
base = iwl_read_prph(trans, base_addr);
/* iwl_read_prph returns 0x5a5a5a5a when it fails to grab nic access */
if (base == 0x5a5a5a5a) {
IWL_ERR(trans, "Can't read base addr\n");
return -ENODEV;
}
end = iwl_read_prph(trans, end_addr);
/* iwl_read_prph returns 0x5a5a5a5a when it fails to grab nic access */
if (end == 0x5a5a5a5a) {
IWL_ERR(trans, "Can't read end addr\n");
return -ENODEV;
}
if (base == end) {
IWL_ERR(trans, "Invalid base and end values\n");
return -ENODEV;
}
wr_ptr = iwl_read_prph(trans, wr_ptr_addr);
/* iwl_read_prph returns 0x5a5a5a5a when it fails to grab nic access */
if (wr_ptr == 0x5a5a5a5a) {
IWL_ERR(trans, "Can't read write pointer, not re-aligning\n");
wr_ptr = base << 8;
}
pos = base << 8;
calc_size = (end - base + 1) << 8;
wr_ptr <<= wr_ptr_shift;
bytes_to_end = ((end + 1) << 8) - wr_ptr;
chunks_num = calc_size / DNT_CHUNK_SIZE;
wr_ptr_offset = wr_ptr - pos;
if (wr_ptr_offset > calc_size) {
IWL_ERR(trans, "Invalid wr_ptr value, not re-aligning\n");
wr_ptr_offset = 0;
}
if (calc_size > buffer_size) {
IWL_ERR(trans, "Invalid buffer size\n");
return -EINVAL;
}
temp_buffer = kzalloc(calc_size, GFP_KERNEL);
if (!temp_buffer) {
return -ENOMEM;
}
for (i = 0; i < chunks_num; i++)
iwl_trans_read_mem(trans, pos + (i * DNT_CHUNK_SIZE), temp_buffer + (i * DNT_CHUNK_SIZE),
DNT_CHUNK_SIZE / sizeof(uint32_t));
if (calc_size % DNT_CHUNK_SIZE)
iwl_trans_read_mem(trans, pos + (chunks_num * DNT_CHUNK_SIZE),
temp_buffer + (chunks_num * DNT_CHUNK_SIZE),
(calc_size - (chunks_num * DNT_CHUNK_SIZE)) / sizeof(uint32_t));
if (cfg->dbgc_wrap_count_addr) {
wrap_cnt = iwl_read_prph(trans, cfg->dbgc_wrap_count_addr);
} else {
wrap_cnt = 1;
}
if (wrap_cnt) {
memcpy(buffer, temp_buffer + wr_ptr_offset, bytes_to_end);
memcpy(buffer + bytes_to_end, temp_buffer, wr_ptr_offset);
} else {
memcpy(buffer, temp_buffer, wr_ptr_offset);
memset(buffer + wr_ptr_offset, 0, bytes_to_end);
}
kfree(temp_buffer);
return calc_size;
}
int iwl_dnt_dev_if_configure_monitor(struct iwl_dnt* dnt, struct iwl_trans* trans) {
uint32_t base_addr, end_addr;
switch (dnt->cur_mon_type) {
case NO_MONITOR:
IWL_INFO(trans, "Monitor is disabled\n");
dnt->iwl_dnt_status &= ~IWL_DNT_STATUS_MON_CONFIGURED;
break;
case MARBH_ADC:
case MARBH_DBG:
iwl_dnt_dev_if_configure_marbh(trans);
dnt->mon_buf_size = DNT_MARBH_BUF_SIZE;
break;
case DMA:
if (!dnt->mon_buf_cpu_addr) {
IWL_ERR(trans, "Can't configure DMA monitor: no cpu addr\n");
return -ENOMEM;
}
base_addr = dnt->mon_base_addr >> 4;
end_addr = dnt->mon_end_addr >> 4;
iwl_dnt_dev_if_configure_dbgm_registers(trans, base_addr, end_addr);
break;
case MIPI:
iwl_dnt_dev_if_configure_mipi(trans);
break;
case SMEM:
base_addr = 0;
end_addr = 0;
iwl_dnt_dev_if_configure_dbgm_registers(trans, base_addr, end_addr);
dnt->mon_buf_size = DNT_SMEM_BUF_SIZE;
break;
case INTERFACE:
base_addr = 0;
end_addr = 0x400;
iwl_dnt_dev_if_configure_dbgm_registers(trans, base_addr, end_addr);
break;
default:
dnt->iwl_dnt_status &= ~IWL_DNT_STATUS_MON_CONFIGURED;
IWL_INFO(trans, "Invalid monitor type\n");
return -EINVAL;
}
dnt->iwl_dnt_status |= IWL_DNT_STATUS_MON_CONFIGURED;
return 0;
}
static int iwl_dnt_dev_if_send_dbgm(struct iwl_dnt* dnt, struct iwl_trans* trans) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
struct iwl_host_cmd host_cmd = {
.id = cfg->dbg_conf_monitor_cmd_id,
.data[0] = cfg->dbg_conf_monitor_host_command.data,
.len[0] = cfg->dbg_conf_monitor_host_command.len,
.dataflags[0] = IWL_HCMD_DFL_NOCOPY,
};
int ret;
ret = iwl_trans_send_cmd(trans, &host_cmd);
if (ret) {
IWL_ERR(trans, "Failed to send monitor command\n");
dnt->iwl_dnt_status |= IWL_DNT_STATUS_FAILED_START_MONITOR;
}
return ret;
}
static int iwl_dnt_dev_if_send_ldbg(struct iwl_dnt* dnt, struct iwl_trans* trans, int cmd_index) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
struct iwl_host_cmd host_cmd = {
.id = cfg->dbg_conf_monitor_cmd_id,
.data[0] = cfg->ldbg_cmd[cmd_index].data,
.len[0] = DNT_LDBG_CMD_SIZE,
.dataflags[0] = IWL_HCMD_DFL_NOCOPY,
};
return iwl_trans_send_cmd(trans, &host_cmd);
}
int iwl_dnt_dev_if_start_monitor(struct iwl_dnt* dnt, struct iwl_trans* trans) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
int i, ret;
switch (cfg->dbgm_enable_mode) {
case DEBUG:
return iwl_dnt_dev_if_send_dbgm(dnt, trans);
case SNIFFER:
ret = 0;
for (i = 0; i < cfg->ldbg_cmd_nums; i++) {
ret = iwl_dnt_dev_if_send_ldbg(dnt, trans, i);
if (ret) {
IWL_ERR(trans, "Failed to send ldbg command\n");
break;
}
}
return ret;
default:
WARN_ONCE(1, "invalid option: %d\n", cfg->dbgm_enable_mode);
return -EINVAL;
}
}
int iwl_dnt_dev_if_set_log_level(struct iwl_dnt* dnt, struct iwl_trans* trans) {
struct iwl_dbg_cfg* cfg = &trans->dbg_cfg;
struct iwl_host_cmd host_cmd = {
.id = cfg->log_level_cmd_id,
.data[0] = cfg->log_level_cmd.data,
.len[0] = cfg->log_level_cmd.len,
.dataflags[0] = IWL_HCMD_DFL_NOCOPY,
};
int ret;
ret = iwl_trans_send_cmd(trans, &host_cmd);
if (ret) {
IWL_ERR(trans, "Failed to send log level cmd\n");
}
return ret;
}
int iwl_dnt_dev_if_retrieve_monitor_data(struct iwl_dnt* dnt, struct iwl_trans* trans,
uint8_t* buffer, uint32_t buffer_size) {
switch (dnt->cur_mon_type) {
case DMA:
return iwl_dnt_dev_if_retrieve_dma_monitor_data(dnt, trans, buffer, buffer_size);
case MARBH_ADC:
case MARBH_DBG:
return iwl_dnt_dev_if_retrieve_marbh_monitor_data(dnt, trans, buffer, buffer_size);
case SMEM:
return iwl_dnt_dev_if_retrieve_smem_monitor_data(dnt, trans, buffer, buffer_size);
case INTERFACE:
default:
WARN_ONCE(1, "invalid option: %d\n", dnt->cur_mon_type);
return -EINVAL;
}
}
int iwl_dnt_dev_if_read_sram(struct iwl_dnt* dnt, struct iwl_trans* trans) {
struct dnt_crash_data* crash = &dnt->dispatch.crash;
int ofs, len = 0;
ofs = dnt->image->sec[IWL_UCODE_SECTION_DATA].offset;
len = dnt->image->sec[IWL_UCODE_SECTION_DATA].len;
crash->sram = vmalloc(len);
if (!crash->sram) {
return -ENOMEM;
}
crash->sram_buf_size = len;
return iwl_trans_read_mem(trans, ofs, crash->sram, len / sizeof(uint32_t));
}
IWL_EXPORT_SYMBOL(iwl_dnt_dev_if_read_sram);
int iwl_dnt_dev_if_read_rx(struct iwl_dnt* dnt, struct iwl_trans* trans) {
struct dnt_crash_data* crash = &dnt->dispatch.crash;
int i, reg_val;
uint32_t buf32_size, offset = 0;
uint32_t* buf32;
unsigned long flags;
/* reading buffer size */
reg_val = iwl_trans_read_prph(trans, RXF_SIZE_ADDR);
crash->rx_buf_size = (reg_val & RXF_SIZE_BYTE_CNT_MSK) >> RXF_SIZE_BYTE_CND_POS;
/* the register holds the value divided by 128 */
crash->rx_buf_size = crash->rx_buf_size << 7;
if (!crash->rx_buf_size) {
return -ENOMEM;
}
buf32_size = crash->rx_buf_size / sizeof(uint32_t);
crash->rx = vmalloc(crash->rx_buf_size);
if (!crash->rx) {
return -ENOMEM;
}
buf32 = (uint32_t*)crash->rx;
if (!iwl_trans_grab_nic_access(trans, &flags)) {
vfree(crash->rx);
return -EBUSY;
}
for (i = 0; i < buf32_size; i++) {
iwl_trans_write_prph(trans, RXF_LD_FENCE_OFFSET_ADDR, offset);
offset += sizeof(uint32_t);
buf32[i] = iwl_trans_read_prph(trans, RXF_FIFO_RD_FENCE_ADDR);
}
iwl_trans_release_nic_access(trans, &flags);
return 0;
}
IWL_EXPORT_SYMBOL(iwl_dnt_dev_if_read_rx);