blob: 6170a79f6c8dadfc69bfe145056ceaa098962e9f [file] [log] [blame]
/*
* Copyright (c) 2016 MediaTek Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "btmtk_define.h"
#if ENABLE_BT_FIFO_THREAD
#include <linux/kthread.h>
#include <asm/uaccess.h>
#include "btmtk_usb_fifo.h"
#include "btmtk_usb_main.h"
/**
* Definition
*/
#define FW_DUMP_SIZE (1024 * 16)
#define SYS_LOG_SIZE (1024 * 16)
#define KFIO_D(pData, t) (((struct btmtk_fifo_data_t *)(pData))->fifo_l[t].kfifo.data)
#define pKFIO(pData, t) (&((struct btmtk_fifo_data_t *)(pData))->fifo_l[t].kfifo)
#define pFIO(pData, t) (&((struct btmtk_fifo_data_t *)(pData))->fifo_l[t])
static struct btmtk_fifo_data_t g_fifo_data;
static struct btmtk_fifo_t btmtk_fifo_list[] = {
{ FIFO_SYSLOG, SYS_LOG_SIZE, SYS_LOG_FILE_NAME, {0}, NULL, {0} },
{ FIFO_COREDUMP, FW_DUMP_SIZE, FW_DUMP_FILE_NAME, {0}, NULL, {0} },
{ FIFO_END, 0, NULL, {0}, NULL, {0} },
{ FIFO_BTBUF, 0, NULL, {0}, NULL, {0} }
};
static int fifo_alloc(struct btmtk_fifo_t *bt_fifo)
{
int ret = 0;
struct __kfifo *kfio = &bt_fifo->kfifo;
ret = __kfifo_alloc(kfio, bt_fifo->size, 1, GFP_KERNEL);
if (ret == 0)
bt_fifo->size = kfio->mask + 1;
return ret;
}
static int fifo_init(struct btmtk_fifo_t *bt_fifo)
{
int ret = 0;
struct __kfifo *kfio = &bt_fifo->kfifo;
BTUSB_INFO("%s: %d", __func__, bt_fifo->type);
ret = __kfifo_init(kfio, kfio->data, bt_fifo->size, kfio->esize);
if (bt_fifo->filp) {
BTUSB_INFO("%s: call filp_close", __func__);
vfs_fsync(bt_fifo->filp, 0);
filp_close(bt_fifo->filp, NULL);
bt_fifo->filp = NULL;
BTUSB_WARN("%s: FW dump file closed, rx=0x%X, tx =0x%x",
__func__, bt_fifo->stat.rx, bt_fifo->stat.tx);
}
bt_fifo->stat.rx = 0;
bt_fifo->stat.tx = 0;
return ret;
}
static void fifo_out_info(struct btmtk_fifo_data_t *data_p)
{
struct __kfifo *fifo = NULL;
if (data_p == NULL) {
BTUSB_ERR("%s: data_p == NULL return", __func__);
return;
}
fifo = pKFIO(data_p, FIFO_SYSLOG);
if (fifo->data != NULL) {
if (fifo->in > fifo->out + fifo->mask)
BTUSB_WARN("sys_log buffer full");
if (fifo->in == fifo->out + fifo->mask)
BTUSB_WARN("sys_log buffer empty");
BTUSB_DBG("[syslog][fifo in - fifo out = 0x%x]",
fifo->in - fifo->out);
}
fifo = pKFIO(data_p, FIFO_COREDUMP);
if (fifo->data != NULL) {
if (fifo->in > fifo->out + fifo->mask)
BTUSB_WARN("coredump buffer full");
if (fifo->in == fifo->out + fifo->mask)
BTUSB_WARN("coredump buffer empty");
}
}
static u32 fifo_out_accessible(struct btmtk_fifo_data_t *data_p)
{
int i;
struct btmtk_fifo_t *fio_p = NULL;
if (data_p == NULL)
return 0;
if (test_bit(TSK_SHOULD_STOP, &data_p->tsk_flag)) {
BTUSB_WARN("%s: task should stop", __func__);
return 1;
}
for (i = 0; i < FIFO_END; i++) {
fio_p = &data_p->fifo_l[i];
if (fio_p->type != i)
continue;
if (fio_p->kfifo.data != NULL) {
if (fio_p->kfifo.in != fio_p->kfifo.out)
return 1;
}
}
return 0;
}
static struct file *fifo_filp_open(const char *file, int flags, umode_t mode, unsigned int type)
{
struct file *f = NULL;
/* Retry to open following path */
static const char * const sys[] = {
"/sdcard/"SYSLOG_FNAME, "/data/misc/bluedroid/"SYSLOG_FNAME, "/tmp/"SYSLOG_FNAME};
static const char * const fw[] = {
"/sdcard/"FWDUMP_FNAME, "/data/misc/bluedroid/"FWDUMP_FNAME, "/tmp/"SYSLOG_FNAME};
u8 i = 0;
f = filp_open(file, flags, mode);
if (IS_ERR(f)) {
const char *p = NULL;
u8 count = ARRAY_SIZE(sys);
f = NULL;
BTUSB_ERR("%s: open file error: %s, try to open others default path",
__func__, file);
if (type != FIFO_SYSLOG && type != FIFO_COREDUMP) {
BTUSB_ERR("%s: Incorrect type: %d", __func__, type);
return NULL;
}
for (i = 0; i < count; ++i) {
if (type == FIFO_SYSLOG)
p = sys[i];
else
p = fw[i];
if (memcmp(file, p, MIN(strlen(file), strlen(p))) != 0) {
BTUSB_INFO("%s: Try to open %s", __func__, p);
f = filp_open(p, flags, mode);
if (IS_ERR(f)) {
f = NULL;
continue;
}
} else {
continue;
}
BTUSB_INFO("%s: %s opened", __func__, p);
break;
}
}
return f;
}
static u32 fifo_out_to_file(struct btmtk_fifo_t *fio, u32 len, u32 off, u8 *end)
{
struct __kfifo *fifo = &fio->kfifo;
unsigned int size = fifo->mask + 1;
unsigned int esize = fifo->esize;
unsigned int l;
unsigned char *dump_end = NULL;
mm_segment_t old_fs;
off &= fifo->mask;
if (esize != 1) {
off *= esize;
size *= esize;
len *= esize;
}
l = min(len, size - off);
if (l > 0 && fifo->data != NULL) {
old_fs = get_fs();
set_fs(KERNEL_DS);
if (fio->filp == NULL) {
BTUSB_WARN("%s: FW Dump started, open file %s", __func__,
fio->folder_name);
fio->filp = fifo_filp_open(fio->folder_name, O_RDWR | O_CREAT, 0644, fio->type);
if (IS_ERR(fio->filp)) {
set_fs(old_fs);
fio->filp = NULL;
return 0;
}
}
if (fio->filp != NULL) {
dump_end = strstr(fifo->data, FW_DUMP_END_EVENT);
fio->filp->f_op->write(fio->filp, fifo->data + off, l,
&fio->filp->f_pos);
fio->filp->f_op->write(fio->filp, fifo->data, len - l,
&fio->filp->f_pos);
fio->stat.tx += len;
if (dump_end && end) {
*end = 1;
BTUSB_INFO("%s: FW Dump finished(success)", __func__);
}
}
set_fs(old_fs);
}
/*
* make sure that the data is copied before
* incrementing the fifo->out index counter
*/
smp_wmb();
return len;
}
static u32 fifo_out_peek_fs(struct btmtk_fifo_t *fio, u32 len, u8 *end)
{
unsigned int l;
struct __kfifo *fifo = &fio->kfifo;
l = fifo->in - fifo->out;
if (len > l)
len = l;
fifo_out_to_file(fio, len, fifo->out, end);
return len;
}
static u32 fifo_out_fs(void *fifo_d)
{
u8 end = 0;
u32 len = 0;
struct btmtk_fifo_t *syslog_p = NULL;
struct btmtk_fifo_t *coredump_p = NULL;
struct btmtk_fifo_data_t *data_p = (void *)fifo_d;
if (fifo_d == NULL) {
BTUSB_ERR("[%s: fifo data is null]", __func__);
return len;
}
if (KFIO_D(fifo_d, FIFO_SYSLOG) != NULL) {
syslog_p = pFIO(fifo_d, FIFO_SYSLOG);
len = fifo_out_peek_fs(syslog_p, SYS_LOG_SIZE, &end);
syslog_p->kfifo.out += len;
}
if (KFIO_D(fifo_d, FIFO_COREDUMP) != NULL) {
coredump_p = pFIO(fifo_d, FIFO_COREDUMP);
len = fifo_out_peek_fs(coredump_p, FW_DUMP_SIZE, &end);
coredump_p->kfifo.out += len;
}
if (end) {
set_bit(TSK_SHOULD_STOP, &data_p->tsk_flag);
BTUSB_INFO("%s: [call btmtk_usb_toggle_rst_pinl]", __func__);
btmtk_usb_toggle_rst_pin();
}
return len;
}
static int btmtk_fifo_thread(void *ptr)
{
struct btmtk_fifo_data_t *data_p = (struct btmtk_fifo_data_t *)ptr;
if (data_p == NULL) {
BTUSB_ERR("%s: [FIFO Data is null]", __func__);
return -1;
}
while (!kthread_should_stop() || test_bit(TSK_START, &data_p->tsk_flag)) {
wait_event_interruptible(data_p->rx_wait_q,
fifo_out_accessible(data_p));
if (test_bit(TSK_SHOULD_STOP, &data_p->tsk_flag))
break;
fifo_out_info(data_p);
fifo_out_fs(data_p);
fifo_out_info(data_p);
}
set_bit(TSK_EXIT, &data_p->tsk_flag);
BTUSB_INFO("%s: end: down != 0", __func__);
return 0;
}
void *btmtk_fifo_init(void)
{
int i;
struct btmtk_fifo_data_t *data_p = &g_fifo_data;
struct btmtk_fifo_t *fio_p = NULL;
UNUSED(btmtk_usb_table); /* clear warning */
data_p->fifo_l = btmtk_fifo_list;
while (test_bit(TSK_RUNNING, &data_p->tsk_flag)) {
if (test_bit(TSK_EXIT, &data_p->tsk_flag))
break;
set_bit(TSK_SHOULD_STOP, &data_p->tsk_flag);
msleep(100);
}
data_p->tsk_flag = 0;
for (i = 0; i < FIFO_END; i++) {
fio_p = &(data_p->fifo_l[i]);
if (fio_p->type != i)
continue;
if (fio_p->kfifo.data == NULL)
fifo_alloc(fio_p);
fifo_init(fio_p);
}
init_waitqueue_head(&data_p->rx_wait_q);
set_bit(TSK_INIT, &data_p->tsk_flag);
return (void *)(data_p);
}
u32 btmtk_fifo_in(unsigned int type, void *fifo_d, const void *buf,
unsigned int length)
{
u32 len = 0;
u8 hci_pkt = MTK_HCI_EVENT_PKT;
struct btmtk_fifo_data_t *data_p = (struct btmtk_fifo_data_t *)fifo_d;
struct btmtk_fifo_t *fio_p = NULL;
struct __kfifo *fifo = NULL;
if (fifo_d == NULL) {
BTUSB_ERR("%s: [fifo data is null]", __func__);
return len;
}
if (!test_bit(TSK_START, &data_p->tsk_flag)) {
BTUSB_ERR("%s: [fail task not start ]", __func__);
return 0;
}
fio_p = pFIO(fifo_d, type);
fifo = pKFIO(fifo_d, type);
if (fifo->data != NULL) {
if (type == FIFO_SYSLOG) {
BTUSB_DBG("%s: [type == FIFO_SYSLOG ]", __func__);
len = __kfifo_in(pKFIO(fifo_d, type), (const void *)&hci_pkt,
sizeof(hci_pkt));
if (len != sizeof(hci_pkt))
return len;
}
len = __kfifo_in(pKFIO(fifo_d, type), buf, length);
fio_p->stat.rx += len;
}
if (len != 0)
wake_up_interruptible(&data_p->rx_wait_q);
return len;
}
int btmtk_fifo_start(void *fio_d)
{
struct btmtk_fifo_data_t *data_p = (struct btmtk_fifo_data_t *)fio_d;
int err;
if (data_p == NULL) {
BTUSB_ERR("%s: [fail][fifo data is null]", __func__);
return -1;
}
if (!test_bit(TSK_INIT, &data_p->tsk_flag)) {
BTUSB_ERR("%s: [fail task not init ]", __func__);
return -1;
}
if (!KFIO_D(data_p, FIFO_SYSLOG) && !KFIO_D(data_p, FIFO_COREDUMP))
return -1;
data_p->fifo_tsk = kthread_create(btmtk_fifo_thread, fio_d,
"btmtk_fifo_thread");
if (IS_ERR(data_p->fifo_tsk)) {
BTUSB_ERR("%s: create FIFO thread failed!", __func__);
err = PTR_ERR(data_p->fifo_tsk);
data_p->fifo_tsk = NULL;
return -1;
}
set_bit(TSK_START, &data_p->tsk_flag);
BTUSB_INFO("%s: set TSK_START", __func__);
wake_up_process(data_p->fifo_tsk);
BTUSB_INFO("%s: [ok]", __func__);
return 0;
}
#else /* ENABLE_BT_FIFO_THREAD */
void *btmtk_fifo_init(void)
{
int *p = 0;
UNUSED(btmtk_usb_table); /* clear warning */
return NULL;
}
int btmtk_fifo_start(void *fio_d)
{
return 0;
}
u32 btmtk_fifo_in(unsigned int type, void *fifo_d, const void *buf,
unsigned int length)
{
return 0;
}
#endif /* ENABLE_BT_FIFO_THREAD */