blob: 97054fe9042f5f15b8a5bd99ac5a3b0689084f26 [file] [log] [blame]
/**
Copyright (c) 2008 - 2013 Quantenna Communications Inc
All Rights Reserved
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <linux/version.h>
#include <linux/syscalls.h>
#include <asm/hardware.h>
#include "qdrv_features.h"
#include "qdrv_debug.h"
#include "qdrv_mac.h"
#include "qdrv_soc.h"
#include "qdrv_muc.h"
#include "qdrv_uc_print.h"
#include "qdrv_comm.h"
#include "qdrv_control.h"
#include <qtn/shared_params.h>
#include <qtn/shared_print_buf.h>
#include <qtn/mproc_sync_base.h>
#if AUC_LHOST_PRINT_FORMAT
#include <auc/auc_print.h>
#endif
#define AUC_PREFIX "AuC"
#define MUC_PREFIX "MuC"
static struct shared_print_consumer muc_printbuf = {0};
static struct shared_print_consumer auc_printbuf = {0};
static struct work_struct uc_print_wq;
static struct qdrv_cb *uc_print_qcb = NULL;
/*
* the uC acts as a producer, writing data into the buffer and updating a count of
* bytes written. This function acts as a sole consumer, reading data from the
* uC's buffer line by line.
*
* The producer is not aware of the consumer(s), so failure to consume bytes quickly enough
* result in lost printouts
*/
static void uc_print(struct shared_print_consumer* shared_buf, const char *prefix)
{
#define LINE_MAX 128
int took_line = 1;
while(took_line) {
u32 chars_to_consume = shared_buf->producer->produced - shared_buf->consumed;
u32 i;
char stackbuf[LINE_MAX];
took_line = 0;
for (i = 0; i < chars_to_consume && i < LINE_MAX - 1; i++) {
char c = shared_buf->buf[(shared_buf->consumed + i) % shared_buf->producer->bufsize];
stackbuf[i] = c;
if (!c || i == LINE_MAX - 2 || c == '\n') {
took_line = 1;
if (c == '\n')
stackbuf[i] = '\0';
stackbuf[i + 1] = '\0';
shared_buf->consumed += i + 1;
if ((uc_print_qcb != NULL) &&
(uc_print_qcb->macs[0].data != NULL)) {
qdrv_control_sysmsg_send(uc_print_qcb->macs[0].data,
stackbuf, i + 1, 0);
}
printk(KERN_INFO "%s: %s\n", prefix, stackbuf);
break;
}
}
}
}
static int uc_print_initialise_shared_data(const char *uc_name, uint32_t addr,
struct shared_print_consumer *printbuf)
{
struct shared_print_producer *prod;
if (!addr) {
return 0;
}
prod = ioremap_nocache(muc_to_lhost(addr), sizeof(*prod));
if (!prod) {
panic("%s to lhost printbuf could not translate metadata\n", uc_name);
}
printbuf->producer = prod;
printbuf->buf = ioremap_nocache(muc_to_lhost((u32)prod->buf), prod->bufsize);
if (!printbuf->buf) {
panic("%s to lhost printbuf could not translate char buffer %p : %u\n",
uc_name, prod->buf, prod->bufsize);
}
return 1;
}
static int muc_print_initialise_shared_data(void)
{
return uc_print_initialise_shared_data(MUC_PREFIX,
qtn_mproc_sync_shared_params_get()->m2l_printbuf_producer,
&muc_printbuf);
}
static int auc_print_initialise_shared_data(void)
{
return uc_print_initialise_shared_data(AUC_PREFIX,
qtn_mproc_sync_shared_params_get()->auc.a2l_printbuf_producer,
&auc_printbuf);
}
void uc_print_schedule_work(void)
{
schedule_work(&uc_print_wq);
}
static void muc_print_irq_handler(void *arg1, void *arg2)
{
uc_print_schedule_work();
}
static void uc_print_work(struct work_struct *work)
{
if (muc_printbuf.producer || muc_print_initialise_shared_data()) {
uc_print(&muc_printbuf, MUC_PREFIX);
}
if (auc_printbuf.producer || auc_print_initialise_shared_data()) {
#if AUC_LHOST_PRINT_FORMAT
if (uc_print_auc_cb)
uc_print_auc_cb(&auc_printbuf);
#else
uc_print(&auc_printbuf, AUC_PREFIX);
#endif
}
}
static int qdrv_uc_set_irq_handler(struct qdrv_cb *qcb, void(*handler)(void*, void*), uint32_t num)
{
struct int_handler int_handler;
int_handler.handler = handler;
int_handler.arg1 = qcb;
int_handler.arg2 = NULL;
if(qdrv_mac_set_handler(&qcb->macs[0], num, &int_handler) != 0) {
DBGPRINTF_E( "Set handler failed\n");
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return -1;
}
qdrv_mac_enable_irq(&qcb->macs[0], num);
return 0;
}
int qdrv_uc_print_init(struct qdrv_cb *qcb)
{
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
uc_print_qcb = qcb;
INIT_WORK(&uc_print_wq, uc_print_work);
if (qdrv_uc_set_irq_handler(qcb, muc_print_irq_handler, RUBY_M2L_IRQ_LO_PRINT) != 0) {
return -1;
}
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return 0;
}
int qdrv_uc_print_exit(struct qdrv_cb *qcb)
{
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "-->Enter\n");
uc_print_qcb = NULL;
qdrv_mac_disable_irq(&qcb->macs[0], RUBY_M2L_IRQ_LO_PRINT);
/* TODO: AuC need to use own IRQ handler, similar to RUBY_M2L_IRQ_LO_PRINT. Need to unregister it here. */
flush_scheduled_work();
if (muc_printbuf.buf) {
iounmap(muc_printbuf.buf);
muc_printbuf.buf = NULL;
}
if (muc_printbuf.producer) {
iounmap((void *)muc_printbuf.producer);
muc_printbuf.producer = NULL;
}
if (auc_printbuf.buf) {
iounmap(auc_printbuf.buf);
auc_printbuf.buf = NULL;
}
if (auc_printbuf.producer) {
iounmap((void *)auc_printbuf.producer);
auc_printbuf.producer = NULL;
}
DBGPRINTF(DBG_LL_ALL, QDRV_LF_TRACE, "<--Exit\n");
return 0;
}