blob: 691a9a712bda98309e1888caa0048fe98ef2d55e [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2015-2020, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
/*
* nss_core.c
* NSS driver core APIs source file.
*/
#include "nss_core.h"
#include "nss_hal.h"
#include "nss_log.h"
#include <linux/kernel.h>
#include <linux/notifier.h> /* for panic_notifier_list */
#include <linux/jiffies.h> /* for time */
#include "nss_tx_rx_common.h"
#if NSS_MAX_CORES > 2 /* see comment in nss_fw_coredump_notify */
#error too many NSS Cores: should be 1 or 2
#endif
static struct delayed_work coredump_queuewait;
static struct workqueue_struct *coredump_workqueue;
/*
* nss_coredump_wait()
* reboot (panic) if all finished coredump interrupts will not come.
* N2H (C2C) interrupt may get lost during trap, as well NSS may start
* only one core; so timeout if less than desird core sends back finished
* coredump interrupt.
*/
static void nss_coredump_wait(struct work_struct *work)
{
panic("did not get all coredump finished signals\n");
}
/*
* nss_coredump_init_delay_work()
* set a wait function in case coredump finish interrupt lost or
* only one NSS core is up.
*/
int nss_coredump_init_delay_work(void)
{
coredump_workqueue = create_singlethread_workqueue("coredump_wait");
if (!coredump_workqueue) {
nss_warning("can't set wait: hopefully all int will come\n");
return -ENOMEM;
}
INIT_DELAYED_WORK(&coredump_queuewait, nss_coredump_wait);
return 0;
}
/*
* nss_panic_handler()
* notification callback register to panic chain
*/
static int nss_panic_handler(struct notifier_block *nb,
unsigned long action, void *data)
{
int dumped, timed;
int i;
for (i = 0; i < nss_top_main.num_nss; i++) {
struct nss_ctx_instance *nss_ctx = &nss_top_main.nss[i];
if (nss_ctx->state & NSS_CORE_STATE_FW_DEAD || !nss_ctx->nmap)
continue;
nss_ctx->state |= NSS_CORE_STATE_PANIC;
nss_hal_send_interrupt(nss_ctx, NSS_H2N_INTR_TRIGGER_COREDUMP);
nss_warning("panic call NSS FW %px to dump %x\n",
nss_ctx->nmap, nss_ctx->state);
}
/*
* wait for FW coredump done: maximum 2 rounds for each core
* 200ms per round -- 16MB * 10 over 200MHz 32-bit memory bus
* panic will take another 3-5 seconds to reboot, so longer enough.
*/
dumped = timed = 0;
do {
mdelay(200);
for (i = 0; i < nss_top_main.num_nss; i++) {
struct nss_ctx_instance *nss_ctx = &nss_top_main.nss[i];
if ((nss_ctx->state & NSS_CORE_STATE_FW_DEAD ||
!nss_ctx->nmap) &&
!(nss_ctx->state & NSS_CORE_STATE_FW_DUMP)) {
nss_ctx->state |= NSS_CORE_STATE_FW_DUMP;
dumped++;
}
}
if (dumped >= nss_top_main.num_nss) {
nss_warning("NSS FW dump completed\n");
break;
}
} while (timed++ < nss_top_main.num_nss * 2);
if (timed >= nss_top_main.num_nss * 2)
nss_warning("might get %d FW dumped", dumped);
return NOTIFY_DONE;
}
static struct notifier_block nss_panic_nb = {
.notifier_call = nss_panic_handler,
};
/*
* nss_coredump_notify_register()
* API for nss_init to register coredump notifier to panic chain
*/
void nss_coredump_notify_register(void)
{
atomic_notifier_chain_register(&panic_notifier_list, &nss_panic_nb);
}
/*
* nss_fw_coredump_notify()
* handler for coredump notification from NSS FW
*/
void nss_fw_coredump_notify(struct nss_ctx_instance *nss_own,
int intr __attribute__ ((unused)))
{
int i, j, curr_index, useful_entries, num_cores_wait;
struct nss_log_descriptor *nld;
struct nss_log_entry *nle_init, *nle_print;
dma_addr_t dma_addr;
uint32_t offset, index;
nss_warning("%px: COREDUMP %x Baddr %px stat %x",
nss_own, intr, nss_own->nmap, nss_own->state);
nss_own->state |= NSS_CORE_STATE_FW_DEAD;
queue_delayed_work(coredump_workqueue, &coredump_queuewait,
msecs_to_jiffies(3456));
/*
* If external log buffer is not set, use the nss initial log buffer.
*/
nld = (struct nss_log_descriptor *)(nss_rbe[nss_own->id].addr);
dma_addr = nss_rbe[nss_own->id].dma_addr;
if (!nld) {
nld = nss_own->meminfo_ctx.logbuffer;
dma_addr = nss_own->meminfo_ctx.logbuffer_dma;
}
dma_sync_single_for_cpu(NULL, dma_addr, sizeof(struct nss_log_descriptor), DMA_FROM_DEVICE);
/*
* If the current entry is smaller than or equal to the number of NSS_LOG_COREDUMP_LINE_NUM,
* only print whatever is in the buffer. Otherwise, dump last NSS_LOG_COREDUMP_LINE_NUM
* to the dmessage.
*/
nss_info_always("%px: Starting NSS-FW logbuffer dump for core %u\n",
nss_own, nss_own->id);
nle_init = nld->log_ring_buffer;
if (nld->current_entry <= NSS_LOG_COREDUMP_LINE_NUM) {
curr_index = 0;
useful_entries = nld->current_entry;
} else {
curr_index = ((nld->current_entry - NSS_LOG_COREDUMP_LINE_NUM) % nld->log_nentries);
useful_entries = NSS_LOG_COREDUMP_LINE_NUM;
}
nle_print = nle_init + curr_index;
for (j = index = curr_index; j < (curr_index + useful_entries); j++, index++) {
if (j == nld->log_nentries) {
nle_print = nle_init;
index = 0;
}
offset = (index * sizeof(struct nss_log_entry))
+ offsetof(struct nss_log_descriptor, log_ring_buffer);
dma_sync_single_for_cpu(NULL, dma_addr + offset,
sizeof(struct nss_log_entry), DMA_FROM_DEVICE);
nss_info_always("%px: %s\n", nss_own, nle_print->message);
nle_print++;
}
if (nss_own->state & NSS_CORE_STATE_PANIC)
return;
/*
* We need to wait until all other cores finish their dump.
*/
num_cores_wait = (nss_top_main.num_nss - 1);
if (!num_cores_wait) {
/*
* nss_cmd_buf.coredump values:
* 0 == normal coredump and panic
* non-zero value is for debug purpose:
* 1 == force coredump and panic
* otherwise coredump but do not panic.
*/
if (!(nss_cmd_buf.coredump & 0xFFFFFFFE)) {
panic("NSS FW coredump: bringing system down\n");
}
nss_info_always("NSS core dump completed & use mdump to collect dump to debug\n");
return;
}
for (i = 0; i < nss_top_main.num_nss; i++) {
struct nss_ctx_instance *nss_ctx = &nss_top_main.nss[i];
/*
* Skip waiting for ourselves to coredump, we already have.
*/
if (nss_ctx == nss_own) {
continue;
}
/*
* Notify any live core to dump.
*/
if (!(nss_ctx->state & NSS_CORE_STATE_FW_DEAD) && nss_ctx->nmap) {
nss_warning("notify NSS FW %px for coredump\n", nss_ctx->nmap);
nss_hal_send_interrupt(nss_ctx, NSS_H2N_INTR_TRIGGER_COREDUMP);
continue;
}
/*
* bit 1 is used for testing coredump. Any other
* bit(s) (value other than 0/1) disable panic
* in order to use mdump utility: see mdump/src/README
* for more info.
*/
if (nss_cmd_buf.coredump & 0xFFFFFFFE) {
nss_info_always("NSS core dump completed and please use mdump to collect dump data\n");
continue;
}
/*
* Ideally we need to unregister ourselves from the panic
* notifier list before calling the panic to prevent infinite calling.
* However, When we tried, we couldn't make it work. Therefore, We just leave the corresponding call here
* if it will be needed in the future.
*
* atomic_notifier_chain_unregister(&panic_notifier_list, &nss_panic_nb);
*/
num_cores_wait--;
if (!num_cores_wait) {
panic("NSS FW coredump: bringing system down\n");
return;
}
}
}