| /* |
| * 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. |
| */ |
| #if defined(CONFIG_DEBUG_FS) && (CONFIG_DEBUG_FS == 1) |
| |
| #include <linux/debugfs.h> |
| #include <linux/semaphore.h> |
| #include <linux/slab.h> |
| #include <linux/skbuff.h> |
| #include <linux/kref.h> |
| #include <linux/semaphore.h> |
| #include <linux/compiler.h> |
| |
| #include "btmtk_usb_dbgfs.h" |
| #include "btmtk_usb_main.h" |
| |
| /** |
| * Directory define., |
| * If this functionality enabled you should find specific file in |
| * /sys/kernel/debug/BTDBG_ROOT/ |
| */ |
| #define BTDBG_ROOT "mtkbt" |
| #define BTDBG_DEV "bt_dev" |
| #define BTDBG_TRACE "log_lvl" |
| |
| #define FS_BUFFER_SIZE 2048 |
| #define MTKBT_MAX_HCI_FRAME_SIZE 1028 /* (BT_MAX_ACL_SIZE + 4) */ |
| |
| struct BTDbg_Func_t { |
| const char *name; |
| int (*fp)(const char *value, void *dbg_filp); |
| const char *doc; |
| const char *help; |
| }; |
| |
| struct BTDbg_FW_Func_t { |
| const char *major; |
| const char *minor[DBG_FW_MAX_CMD_COUNT]; |
| const char *cmd[DBG_FW_MAX_CMD_COUNT]; |
| const char *doc[DBG_FW_MAX_CMD_COUNT]; |
| }; |
| |
| /** |
| * Global variable |
| */ |
| static struct mtkdbg_data_t *g_mtkbt_dbg; |
| u8 btmtk_log_lvl = BTMTK_LOG_LEVEL_DEFAULT; |
| |
| /** |
| * Function prototype |
| */ |
| static int btmtk_dbgfs_data_alloc(void *dbg_filp); |
| static void btmtk_dbgfs_data_free(void *dbg_filp); |
| static int btmtk_dbgfs_fw_func_main(const char *value, void *dbg_filp); |
| static int btmtk_dbgfs_tx_frame(void *drv_data, const unsigned char *buffer, const unsigned int length); |
| |
| static int strtol_access_ok(char ch) |
| { |
| if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') |
| || (ch >= 'a' && ch <= 'f')) |
| return 0; |
| |
| BTUSB_ERR("wrong char_%c_", ch); |
| |
| return -1; |
| } |
| |
| static int str_to_u8(const char *ptr) |
| { |
| u8 ret = 0; |
| u8 temp_str[3] = { 0 }; |
| long res = 0; |
| |
| temp_str[0] = *ptr; |
| temp_str[1] = *(ptr + 1); |
| |
| ret = (u8) (kstrtol((char *)temp_str, 16, &res) & 0xff); |
| |
| return res; |
| } |
| |
| static int str_to_bytes(u8 *cmd_buf, const char *ptr) |
| { |
| int i = 0, j = 0, len = 0; |
| |
| len = strlen(ptr); |
| |
| while (i < len) { |
| if (ptr[i] == ' ' || ptr[i] == '\t' || ptr[i] == '\r' |
| || ptr[i] == '\n') { |
| i++; |
| continue; |
| } |
| |
| if ((ptr[i] == '0' && ptr[i + 1] == 'x') |
| || (ptr[0] == '0' && ptr[i + 1] == 'X')) { |
| i += 2; |
| continue; |
| } |
| |
| if (strtol_access_ok(ptr[i]) != 0) |
| break; |
| |
| cmd_buf[j++] = str_to_u8(&ptr[i]); |
| i += 2; |
| } |
| |
| return j; |
| } |
| |
| static int btmtk_dbgfs_tx_frame(void *drv_data, const unsigned char *buffer, const unsigned int length) |
| { |
| int retval = -1; |
| |
| BTUSB_DBG("%s: hci cmd", __func__); |
| |
| if (unlikely(buffer == NULL || length <= 0)) { |
| BTUSB_ERR("%s: [hci_buf = %p][length = 0x%x]", __func__, buffer, length); |
| return -EFAULT; |
| } |
| |
| if (drv_data != NULL) |
| BTUSB_DBG("%s: [drv_data=%p]", __func__, drv_data); |
| |
| |
| switch (buffer[0]) { |
| case MTK_HCI_COMMAND_PKT: |
| retval = btmtk_usb_meta_send_data(buffer, length); |
| break; |
| |
| case MTK_HCI_ACLDATA_PKT: |
| case MTK_HCI_SCODATA_PKT: |
| retval = btmtk_usb_send_data(buffer, length); |
| break; |
| |
| default: |
| BTUSB_ERR("%s: [INVALID PACKAGE TYPE = %d]", __func__, buffer[0]); |
| return -EILSEQ; |
| } |
| |
| BTUSB_DBG("%s: retval = %d", __func__, retval); |
| |
| return retval; |
| } |
| |
| static int btmtk_dbgfs_tx_hci_data(const char *value, void *dbg_filp) |
| { |
| int length = 0, ret = 0; |
| u8 *hci_cmd = NULL; |
| struct mtkdbg_data_t *mtkdbg_filp = (struct mtkdbg_data_t *)dbg_filp; |
| |
| if (!mtkdbg_filp->hci_buf) { |
| |
| BTUSB_ERR("%s: hci buff is null", __func__); |
| btmtk_dbgfs_data_alloc(mtkdbg_filp); |
| |
| if (!mtkdbg_filp->hci_buf) |
| return -1; |
| } |
| |
| hci_cmd = mtkdbg_filp->hci_buf; |
| |
| length = str_to_bytes(hci_cmd, value); |
| |
| BTUSB_DBG_RAW(hci_cmd, length, "%s: dump cmd:", __func__); |
| |
| if (mtkdbg_filp->drv_data) |
| BTUSB_DBG("%s: [mtkdbg_filp->drv_data = %p]", __func__, |
| mtkdbg_filp->drv_data); |
| |
| ret = btmtk_dbgfs_tx_frame(mtkdbg_filp->drv_data, hci_cmd, length); |
| |
| BTUSB_DBG("%s: ret = %d", __func__, ret); |
| |
| return length; |
| } |
| |
| static struct BTDbg_Func_t dbgfs_func_list[] = { |
| {"HCI", btmtk_dbgfs_tx_hci_data, "tx hci data with type only", |
| "01 6F FC 05 01 02 01 00 08"}, |
| {"FW", btmtk_dbgfs_fw_func_main, "set fw to do something", "DUMP"}, |
| {"", NULL, NULL, NULL} |
| }; |
| |
| static struct BTDbg_FW_Func_t dbgfs_fw_func_list[] = { |
| { "DUMP", {"1", "2"}, |
| {"01 6F FC 05 01 02 01 00 08", "02 6F FC 05 01 02 01 00 08"}, |
| {"tx fw assert cmd from endpoint 0", "tx fw assert cmd from endpoint 2"} }, |
| { NULL, {NULL, NULL}, {NULL, NULL}, {NULL, NULL} } |
| }; |
| |
| static void btmtk_dbgfs_func_usage(struct BTDbg_Func_t *funclp) |
| { |
| int i; |
| |
| BTUSB_DBG("Usage:\n\t <command>= value01 value02 ... "); |
| for (i = 0; funclp[i].fp; i++) |
| BTUSB_DBG("[%s]example:\n \t \t <%s>=<%s>", funclp[i].doc, funclp[i].name, funclp[i].help); |
| |
| BTUSB_DBG("<command> should be less than DBG_TAG_MAX_LEN(0x%x) bytes", |
| DBG_TAG_MAX_LEN); |
| BTUSB_DBG("<value> should be less than FS_BUFFER_SIZE(0x%x) bytes", |
| FS_BUFFER_SIZE); |
| } |
| |
| static void btmtk_dbgfs_fw_func_usage(struct BTDbg_FW_Func_t *funclp) |
| { |
| int i, j; |
| |
| BTUSB_DBG("Usage:\n\t <FW>= value01 value02 ..."); |
| for (i = 0; funclp[i].major; i++) { |
| for (j = 0; j < DBG_FW_MAX_CMD_COUNT; j++) { |
| BTUSB_DBG("%s: example:\n \t \t \t \t <FW>=<%s><%s>", |
| __func__, funclp[i].major, |
| funclp[i].minor[j]); |
| BTUSB_DBG("%s: means:[%s]", __func__, |
| funclp[i].doc[j]); |
| BTUSB_DBG("%s: tx fw cmd [%s]", __func__, |
| funclp[i].cmd[j]); |
| } |
| } |
| } |
| |
| static int btmtk_dbgfs_fw_func_main(const char *value, void *dbg_filp) |
| { |
| int i; |
| u32 index = 0; |
| const char *s2 = value; |
| |
| BTUSB_DBG("%s: [FW=%s]", __func__, value); |
| |
| while (1) { |
| if (dbgfs_fw_func_list[index].major == NULL || value == NULL) |
| goto fw_exit; |
| |
| s2 = strstr(value, dbgfs_fw_func_list[index].major); |
| |
| if (s2 != NULL) { |
| for (i = 0; i < DBG_FW_MAX_CMD_COUNT; i++) { |
| if (dbgfs_fw_func_list[index].minor[i] == |
| NULL) |
| continue; |
| |
| if (strstr(s2, dbgfs_fw_func_list[index].minor[i]) != NULL) |
| break; |
| } |
| |
| if (i < DBG_FW_MAX_CMD_COUNT) |
| break; |
| } |
| index++; |
| } |
| |
| BTUSB_DBG("%s: match [cmd =%s]", __func__, |
| dbgfs_fw_func_list[index].cmd[i]); |
| btmtk_dbgfs_tx_hci_data(dbgfs_fw_func_list[index].cmd[i], dbg_filp); |
| |
| return 0; |
| |
| fw_exit: |
| BTUSB_DBG("%s: [FW=Wrong cmd!] Fail", __func__); |
| btmtk_dbgfs_fw_func_usage(dbgfs_fw_func_list); |
| return -1; |
| } |
| |
| static int btmtk_dbgfs_get_option(char *option) |
| { |
| u32 index = 0; |
| |
| while (1) { |
| if (strcmp(option, dbgfs_func_list[index].name) == 0) |
| return index; |
| |
| if (dbgfs_func_list[index].fp == NULL) |
| return -1; |
| |
| index++; |
| } |
| } |
| |
| int BTDbg_Func_Entry(void *dbg_filp, u8 *tag, const char *value) |
| { |
| int index; |
| int ret = 0; |
| |
| if (tag == NULL) |
| goto exit; |
| |
| index = btmtk_dbgfs_get_option(tag); |
| |
| if (index == -1) |
| goto exit; |
| |
| ret = dbgfs_func_list[index].fp(value, dbg_filp); |
| if (ret < 0) { |
| BTUSB_ERR("[tag = %s][value = %s], fail", tag, value); |
| return -1; |
| } |
| |
| return 0; |
| exit: |
| btmtk_dbgfs_func_usage(dbgfs_func_list); |
| return -1; |
| } |
| |
| int btmtk_dbgfs_func_main(const char *value, void *dbg_filp) |
| { |
| static int count_f; |
| int val_len = 0, i; |
| u8 Tag_param[DBG_TAG_MAX_LEN] = { 0 }; |
| const char *val_param = NULL; |
| |
| if (unlikely(dbg_filp == NULL || value == NULL)) { |
| BTUSB_ERR("%s: [flp =0x%p][value = 0x%p] fail", __func__, |
| dbg_filp, value); |
| return -1; |
| } |
| |
| count_f++; |
| |
| BTUSB_DBG("%s: in count = %d", __func__, count_f); |
| |
| val_len = strlen(value); |
| |
| val_len = val_len < DBG_TAG_MAX_LEN ? val_len : DBG_TAG_MAX_LEN; |
| |
| for (i = 0; i < val_len; i++) { |
| if (value[i] != '=') |
| Tag_param[i] = value[i]; |
| else |
| break; |
| } |
| |
| if (i < val_len) { |
| val_param = &value[i + 1]; |
| BTDbg_Func_Entry(dbg_filp, Tag_param, val_param); |
| |
| } else { |
| btmtk_dbgfs_func_usage(dbgfs_func_list); |
| } |
| |
| BTUSB_DBG("%s: out = %d", __func__, count_f); |
| return 0; |
| } |
| |
| static int BTDbg_dev_open(struct inode *inode, struct file *file) |
| { |
| struct mtkdbg_data_t *mtkdbg_filp = NULL; |
| |
| BTUSB_DBG("%s: %s/%s open", __func__, BTDBG_ROOT, BTDBG_DEV); |
| |
| file->private_data = inode->i_private; |
| |
| mtkdbg_filp = (struct mtkdbg_data_t *)file->private_data; |
| |
| if (mtkdbg_filp) |
| sema_init(&mtkdbg_filp->wr_lock, 1); |
| |
| return 0; |
| } |
| |
| static int BTDbg_dev_close(struct inode *inode, struct file *filp) |
| { |
| BTUSB_DBG("[%s][%s]close", BTDBG_ROOT, BTDBG_DEV); |
| |
| filp->private_data = NULL; |
| |
| return 0; |
| } |
| |
| ssize_t BTDbg_dev_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| struct mtkdbg_data_t *mtkdbg_filp = (struct mtkdbg_data_t *)filp->private_data; |
| |
| if (mtkdbg_filp == NULL) { |
| |
| BTUSB_ERR("%s: no mtkbt dgb data", __func__); |
| return -ENODEV; |
| } |
| |
| down(&mtkdbg_filp->wr_lock); |
| |
| if (mtkdbg_filp->w_buf == NULL) { |
| BTUSB_ERR("%s: no w_buf", __func__); |
| |
| btmtk_dbgfs_data_alloc((void *)mtkdbg_filp); |
| |
| if (mtkdbg_filp->w_buf == NULL) |
| goto wt_exit; |
| } |
| |
| memset(mtkdbg_filp->w_buf, 0, FS_BUFFER_SIZE); |
| |
| if (unlikely(count > FS_BUFFER_SIZE)) { |
| BTUSB_WARN("%s: w count(%zu) > FS_BUFFER_SIZE(%d)", __func__, count, FS_BUFFER_SIZE); |
| count = FS_BUFFER_SIZE - 1; |
| } |
| |
| if (count < FS_BUFFER_SIZE) |
| mtkdbg_filp->w_buf[count] = 0; |
| |
| if (copy_from_user(mtkdbg_filp->w_buf, buf, count) != 0) { |
| BTUSB_ERR("[%s][%s]copy_from_user fail", BTDBG_ROOT, |
| BTDBG_DEV); |
| goto wt_exit; |
| } |
| |
| BTUSB_DBG("%s: w_buf: %s", __func__, mtkdbg_filp->w_buf); |
| |
| #if 1 |
| mtkdbg_filp->w_buf[count] = 0; |
| btmtk_dbgfs_func_main(mtkdbg_filp->w_buf, mtkdbg_filp); |
| #else |
| |
| io_len = strlen(mtkdbg_filp->w_buf); |
| |
| if (io_len != count) |
| BTUSB_WARN("WARN:io_len != count"); |
| |
| target_len = io_len < 512 ? io_len : 512; |
| |
| for (i = 0; i < 512; i++) { |
| if (mtkdbg_filp->w_buf[i] != '=') |
| parm_tag[i] = mtkdbg_filp->w_buf[i]; |
| else |
| break; |
| } |
| if (i < 512) { |
| pValue = &mtkdbg_filp->w_buf[i + 1]; |
| BTDbg_Func_Entry(parm_tag, pValue); |
| } else { |
| BTUSB_WARN("tag len should be less than 512"); |
| goto wt_exit; |
| } |
| #endif |
| wt_exit: |
| up(&mtkdbg_filp->wr_lock); |
| |
| return count; |
| } |
| |
| const struct file_operations BTDbg_dev_fops = { |
| .open = BTDbg_dev_open, |
| .release = BTDbg_dev_close, |
| .write = BTDbg_dev_write |
| }; |
| |
| static int btmtk_dbgfs_data_alloc(void *dbg_filp) |
| { |
| struct mtkdbg_data_t *mtkdbg_filp = (struct mtkdbg_data_t *)dbg_filp; |
| |
| if (!mtkdbg_filp) { |
| BTUSB_ERR("%s: fail to alloc data for dbg file is null", |
| __func__); |
| return -1; |
| } |
| |
| if (!mtkdbg_filp->btmtk_root_entery) { |
| |
| BTUSB_ERR("%s: btmtk_root_entery is null", __func__); |
| return -1; |
| } |
| |
| if (!mtkdbg_filp->btmtk_bt_dev_entry) |
| mtkdbg_filp->btmtk_bt_dev_entry = |
| debugfs_create_file(BTDBG_DEV, 0644, |
| mtkdbg_filp->btmtk_root_entery, |
| (void *)mtkdbg_filp, &BTDbg_dev_fops); |
| |
| if (!mtkdbg_filp->btmtk_trace_entry) |
| mtkdbg_filp->btmtk_trace_entry = |
| debugfs_create_u8(BTDBG_TRACE, 0644, |
| mtkdbg_filp->btmtk_root_entery, |
| &btmtk_log_lvl); |
| |
| if (mtkdbg_filp->btmtk_bt_dev_entry == NULL |
| || mtkdbg_filp->btmtk_trace_entry == NULL) { |
| |
| BTUSB_WARN("[%s = %p], [%s = %p]", BTDBG_DEV, |
| mtkdbg_filp->btmtk_bt_dev_entry, BTDBG_TRACE, |
| mtkdbg_filp->btmtk_trace_entry); |
| |
| } |
| if (!mtkdbg_filp->w_buf) |
| mtkdbg_filp->w_buf = kmalloc(FS_BUFFER_SIZE, GFP_KERNEL); |
| |
| if (!mtkdbg_filp->hci_buf) |
| mtkdbg_filp->hci_buf = |
| kmalloc(MTKBT_MAX_HCI_FRAME_SIZE, GFP_KERNEL); |
| |
| if (mtkdbg_filp->w_buf == NULL || mtkdbg_filp->hci_buf == NULL) { |
| BTUSB_WARN("%s: [w_buf = %p][hci_buf = %p]", __func__, |
| mtkdbg_filp->w_buf, mtkdbg_filp->hci_buf); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void btmtk_dbgfs_data_free(void *dbg_filp) |
| { |
| struct mtkdbg_data_t *mtkdbg_filp = (struct mtkdbg_data_t *)dbg_filp; |
| |
| if (!mtkdbg_filp) { |
| BTUSB_ERR("%s: fail to free data for dbg file is null", |
| __func__); |
| return; |
| } |
| |
| if (!mtkdbg_filp->btmtk_root_entery) { |
| BTUSB_ERR("%s: fail to free for root entry is null", __func__); |
| return; |
| } |
| |
| debugfs_remove(mtkdbg_filp->btmtk_bt_dev_entry); |
| mtkdbg_filp->btmtk_bt_dev_entry = NULL; |
| |
| debugfs_remove(mtkdbg_filp->btmtk_trace_entry); |
| mtkdbg_filp->btmtk_trace_entry = NULL; |
| |
| kfree(mtkdbg_filp->w_buf); |
| mtkdbg_filp->w_buf = NULL; |
| |
| kfree(mtkdbg_filp->hci_buf); |
| mtkdbg_filp->hci_buf = NULL; |
| } |
| |
| void btmtk_dbgfs_set_drvdata(void *drv_data) |
| { |
| struct mtkdbg_data_t *mtkdbg_filp = g_mtkbt_dbg; |
| |
| BTUSB_DBG("%s: enter", __func__); |
| |
| if (unlikely(drv_data == NULL)) { |
| |
| BTUSB_ERR("%s: fail for drv_data is null", __func__); |
| return; |
| } |
| |
| if (unlikely(mtkdbg_filp == NULL)) { |
| BTUSB_ERR("%s: no mtkbt dgb data", __func__); |
| return; |
| } |
| |
| mtkdbg_filp->drv_data = drv_data; |
| } |
| |
| void btmtk_dbgfs_remove_drvdata(void) |
| { |
| struct mtkdbg_data_t *mtkdbg_filp = g_mtkbt_dbg; |
| |
| BTUSB_DBG("%s: enter", __func__); |
| |
| if (unlikely(mtkdbg_filp == NULL)) { |
| BTUSB_ERR("%s: no mtkbt dgb data", __func__); |
| return; |
| } |
| |
| mtkdbg_filp->drv_data = NULL; |
| } |
| |
| int btmtk_dbgfs_init(void) |
| { |
| struct mtkdbg_data_t *mtkdbg_filp; |
| |
| UNUSED(btmtk_usb_table); /* clear warning */ |
| BTUSB_DBG("%s", __func__); |
| |
| if (g_mtkbt_dbg != NULL) |
| BTUSB_WARN("%s: mtkbt dbg is not null, memory leak !", __func__); |
| |
| mtkdbg_filp = kzalloc(sizeof(struct mtkdbg_data_t), GFP_KERNEL); |
| |
| if (!mtkdbg_filp) { |
| BTUSB_ERR("%s: alloc mtk dbg file fail!", __func__); |
| return -1; |
| } |
| |
| mtkdbg_filp->btmtk_root_entery = debugfs_create_dir(BTDBG_ROOT, NULL); |
| |
| if (mtkdbg_filp->btmtk_root_entery == NULL) { |
| BTUSB_ERR("%s: fail to create dbg root dir !", __func__); |
| kfree(mtkdbg_filp); |
| return -1; |
| } |
| |
| sema_init(&mtkdbg_filp->wr_lock, 1); |
| |
| g_mtkbt_dbg = mtkdbg_filp; |
| |
| btmtk_dbgfs_data_alloc((void *)g_mtkbt_dbg); |
| |
| BTUSB_DBG("%s: success", __func__); |
| return 0; |
| } |
| |
| void btmtk_dbgfs_exit(void) |
| { |
| |
| if (!g_mtkbt_dbg) { |
| BTUSB_ERR("%s: fail to exit dbg file is null", __func__); |
| return; |
| } |
| |
| btmtk_dbgfs_data_free((void *)g_mtkbt_dbg); |
| |
| debugfs_remove(g_mtkbt_dbg->btmtk_root_entery); /*(bt_dev_entry); */ |
| g_mtkbt_dbg->btmtk_root_entery = NULL; |
| |
| kfree(g_mtkbt_dbg); |
| g_mtkbt_dbg = NULL; |
| |
| } |
| |
| #else /* CONFIG_DEBUG_FS */ |
| void btmtk_dbgfs_set_drvdata(void *drv_data) |
| { |
| } |
| |
| void btmtk_dbgfs_remove_drvdata(void) |
| { |
| } |
| |
| int btmtk_dbgfs_init(void) |
| { |
| BTUSB_INFO("%s: debugfs is not enable now", __func__); |
| return 0; |
| } |
| |
| void btmtk_dbgfs_exit(void) |
| { |
| } |
| |
| #endif /* CONFIG_DEBUG_FS */ |