| /* Define for proce node */ |
| #include <linux/proc_fs.h> |
| #include <linux/seq_file.h> |
| |
| #include "btmtk_fw_log.h" |
| |
| #if (USE_DEVICE_NODE == 1) |
| #include "connv3_debug_utility.h" |
| #include "connv3_mcu_log.h" |
| #include "connv3.h" |
| #endif |
| |
| /* |
| * BT Logger Tool will turn on/off Firmware Picus log, and set 3 log levels (Low, SQC and Debug) |
| * For extention capability, driver does not check the value range. |
| * |
| * Combine log state and log level to below settings: |
| * - 0x00: OFF |
| * - 0x01: Low Power |
| * - 0x02: SQC |
| * - 0x03: Debug |
| */ |
| #define BT_FWLOG_DEFAULT_LEVEL 0x02 |
| #define CONNV3_XML_SIZE 1024 /* according to connv3_dump_test.c */ |
| |
| /* CTD BT log function and log status */ |
| static wait_queue_head_t BT_log_wq; |
| static struct semaphore ioctl_mtx; |
| static uint8_t g_bt_on = BT_FWLOG_OFF; |
| static uint8_t g_log_on = BT_FWLOG_OFF; |
| static uint8_t g_log_level = BT_FWLOG_DEFAULT_LEVEL; |
| static uint8_t g_log_current = BT_FWLOG_OFF; |
| /* For fwlog dev node setting */ |
| static struct btmtk_fops_fwlog *g_fwlog; |
| |
| const struct file_operations BT_fopsfwlog = { |
| .open = btmtk_fops_openfwlog, |
| .release = btmtk_fops_closefwlog, |
| .read = btmtk_fops_readfwlog, |
| .write = btmtk_fops_writefwlog, |
| .poll = btmtk_fops_pollfwlog, |
| .unlocked_ioctl = btmtk_fops_unlocked_ioctlfwlog, |
| .compat_ioctl = btmtk_fops_compat_ioctlfwlog |
| }; |
| |
| /** read_write for proc */ |
| static int btmtk_proc_show(struct seq_file *m, void *v); |
| static int btmtk_proc_open(struct inode *inode, struct file *file); |
| static int btmtk_proc_chip_reset_count_open(struct inode *inode, struct file *file); |
| static int btmtk_proc_chip_reset_count_show(struct seq_file *m, void *v); |
| |
| #if (KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE) |
| static const struct file_operations BT_proc_fops = { |
| .open = btmtk_proc_open, |
| .read = seq_read, |
| .release = single_release, |
| }; |
| |
| static const struct file_operations BT_proc_chip_reset_count_fops = { |
| .open = btmtk_proc_chip_reset_count_open, |
| .read = seq_read, |
| .release = single_release, |
| }; |
| #else |
| static const struct proc_ops BT_proc_fops = { |
| .proc_open = btmtk_proc_open, |
| .proc_read = seq_read, |
| .proc_release = single_release, |
| }; |
| |
| static const struct proc_ops BT_proc_chip_reset_count_fops = { |
| .proc_open = btmtk_proc_chip_reset_count_open, |
| .proc_read = seq_read, |
| .proc_release = single_release, |
| }; |
| #endif |
| __weak int32_t btmtk_intcmd_wmt_utc_sync(void) |
| { |
| BTMTK_ERR("weak function %s not implement", __func__); |
| return -1; |
| } |
| |
| __weak int32_t btmtk_intcmd_set_fw_log(uint8_t flag) |
| { |
| BTMTK_ERR("weak function %s not implement", __func__); |
| return -1; |
| } |
| |
| void fw_log_bt_state_cb(uint8_t state) |
| { |
| uint8_t on_off; |
| /* sp use BTMTK_FOPS_STATE_OPENED to judge state */ |
| on_off = (state == FUNC_ON) ? BT_FWLOG_ON : BT_FWLOG_OFF; |
| BTMTK_INFO("bt_on(0x%x) state(%d) on_off(0x%x)", g_bt_on, state, on_off); |
| |
| if (g_bt_on != on_off) { |
| // changed |
| if (on_off == BT_FWLOG_OFF) { // should turn off |
| g_bt_on = BT_FWLOG_OFF; |
| BTMTK_INFO("BT func off, no need to send hci cmd"); |
| } else { |
| g_bt_on = BT_FWLOG_ON; |
| if (g_log_current) { |
| btmtk_intcmd_set_fw_log(g_log_current); |
| btmtk_intcmd_wmt_utc_sync(); |
| } |
| } |
| } |
| } |
| |
| void fw_log_bt_event_cb(void) |
| { |
| wake_up_interruptible(&BT_log_wq); |
| } |
| |
| static int btmtk_proc_show(struct seq_file *m, void *v) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| (void)seq_printf(m, "patch version:%s driver version:%s\n", bmain_info->fw_version_str, VERSION); |
| return 0; |
| } |
| |
| static int btmtk_proc_chip_reset_count_show(struct seq_file *m, void *v) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| (void)seq_printf(m, "whole_reset_count=%d subsys_reset_count=%d\n", |
| atomic_read(&bmain_info->whole_reset_count), |
| atomic_read(&bmain_info->subsys_reset_count)); |
| return 0; |
| } |
| |
| static int btmtk_proc_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, btmtk_proc_show, NULL); |
| } |
| |
| static int btmtk_proc_chip_reset_count_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, btmtk_proc_chip_reset_count_show, NULL); |
| } |
| |
| static void btmtk_proc_create_new_entry(void) |
| { |
| struct proc_dir_entry *proc_show_entry = NULL; |
| struct proc_dir_entry *proc_show_chip_reset_count_entry = NULL; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| BTMTK_INFO("%s, proc initialized", __func__); |
| |
| bmain_info->proc_dir = proc_mkdir("stpbt", NULL); |
| if (bmain_info->proc_dir == NULL) { |
| BTMTK_ERR("Unable to creat dir"); |
| return; |
| } |
| proc_show_entry = proc_create("bt_fw_version", 0640, bmain_info->proc_dir, &BT_proc_fops); |
| if (proc_show_entry == NULL) { |
| BTMTK_ERR("Unable to creat bt_fw_version node"); |
| remove_proc_entry("stpbt", NULL); |
| } |
| |
| proc_show_chip_reset_count_entry = proc_create(PROC_BT_CHIP_RESET_COUNT, 0640, |
| bmain_info->proc_dir, &BT_proc_chip_reset_count_fops); |
| if (proc_show_chip_reset_count_entry == NULL) { |
| BTMTK_ERR("Unable to creat %s node", PROC_BT_CHIP_RESET_COUNT); |
| remove_proc_entry(PROC_ROOT_DIR, NULL); |
| } |
| |
| } |
| |
| static void btmtk_proc_delete_entry(void) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| if (bmain_info->proc_dir == NULL) |
| return; |
| |
| remove_proc_entry("bt_fw_version", bmain_info->proc_dir); |
| BTMTK_INFO("%s, proc device node and folder removed!!", __func__); |
| remove_proc_entry(PROC_BT_CHIP_RESET_COUNT, bmain_info->proc_dir); |
| BTMTK_INFO("%s, proc device node and folder %s removed!!", __func__, PROC_BT_CHIP_RESET_COUNT); |
| |
| remove_proc_entry(PROC_ROOT_DIR, NULL); |
| bmain_info->proc_dir = NULL; |
| } |
| |
| static int btmtk_fops_initfwlog(void) |
| { |
| #ifdef STATIC_REGISTER_FWLOG_NODE |
| static int BT_majorfwlog = FIXED_STPBT_MAJOR_DEV_ID + 1; |
| dev_t devIDfwlog = MKDEV(BT_majorfwlog, 1); |
| #else |
| static int BT_majorfwlog; |
| dev_t devIDfwlog = MKDEV(BT_majorfwlog, 0); |
| #endif |
| int ret = 0; |
| int cdevErr = 0; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| BTMTK_INFO("%s: Start %s", __func__, BT_FWLOG_DEV_NODE); |
| |
| if (g_fwlog == NULL) { |
| g_fwlog = kzalloc(sizeof(*g_fwlog), GFP_KERNEL); |
| if (!g_fwlog) { |
| BTMTK_ERR("%s: alloc memory fail (g_data)", __func__); |
| return -1; |
| } |
| } |
| |
| #ifdef STATIC_REGISTER_FWLOG_NODE |
| ret = register_chrdev_region(devIDfwlog, 1, "BT_chrdevfwlog"); |
| if (ret) { |
| BTMTK_ERR("%s: fail to register chrdev(%x)", __func__, devIDfwlog); |
| goto alloc_error; |
| } |
| #else |
| ret = alloc_chrdev_region(&devIDfwlog, 0, 1, "BT_chrdevfwlog"); |
| if (ret) { |
| BTMTK_ERR("%s: fail to allocate chrdev", __func__); |
| goto alloc_error; |
| } |
| #endif |
| BT_majorfwlog = MAJOR(devIDfwlog); |
| |
| cdev_init(&g_fwlog->BT_cdevfwlog, &BT_fopsfwlog); |
| g_fwlog->BT_cdevfwlog.owner = THIS_MODULE; |
| |
| cdevErr = cdev_add(&g_fwlog->BT_cdevfwlog, devIDfwlog, 1); |
| if (cdevErr) |
| goto cdv_error; |
| |
| g_fwlog->pBTClass = class_create(THIS_MODULE, BT_FWLOG_DEV_NODE); |
| if (IS_ERR(g_fwlog->pBTClass)) { |
| BTMTK_ERR("%s: class create fail, error code(%ld)\n", __func__, PTR_ERR(g_fwlog->pBTClass)); |
| goto create_node_error; |
| } |
| |
| g_fwlog->pBTDevfwlog = device_create(g_fwlog->pBTClass, NULL, devIDfwlog, NULL, |
| "%s", BT_FWLOG_DEV_NODE); |
| if (IS_ERR(g_fwlog->pBTDevfwlog)) { |
| BTMTK_ERR("%s: device(stpbtfwlog) create fail, error code(%ld)", __func__, |
| PTR_ERR(g_fwlog->pBTDevfwlog)); |
| goto create_node_error; |
| } |
| BTMTK_INFO("%s: BT_majorfwlog %d, devIDfwlog %d", __func__, BT_majorfwlog, devIDfwlog); |
| |
| g_fwlog->g_devIDfwlog = devIDfwlog; |
| sema_init(&ioctl_mtx, 1); |
| |
| //if (is_mt66xx(g_sbdev->chip_id)) { |
| if (bmain_info->hif_hook.log_init) { |
| bmain_info->hif_hook.log_init(fw_log_bt_event_cb); |
| //bmain_info->hif_hook.log_register_cb(fw_log_bt_event_cb); |
| init_waitqueue_head(&BT_log_wq); |
| } else { |
| spin_lock_init(&g_fwlog->fwlog_lock); |
| skb_queue_head_init(&g_fwlog->fwlog_queue); |
| skb_queue_head_init(&g_fwlog->usr_opcode_queue);//opcode |
| init_waitqueue_head(&(g_fwlog->fw_log_inq)); |
| } |
| |
| atomic_set(&bmain_info->fwlog_ref_cnt, 0); |
| BTMTK_INFO("%s: End", __func__); |
| return 0; |
| |
| create_node_error: |
| if (g_fwlog->pBTClass) { |
| class_destroy(g_fwlog->pBTClass); |
| g_fwlog->pBTClass = NULL; |
| } |
| |
| cdv_error: |
| if (cdevErr == 0) |
| cdev_del(&g_fwlog->BT_cdevfwlog); |
| |
| if (ret == 0) |
| unregister_chrdev_region(devIDfwlog, 1); |
| |
| alloc_error: |
| kfree(g_fwlog); |
| g_fwlog = NULL; |
| |
| return -1; |
| } |
| |
| static int btmtk_fops_exitfwlog(void) |
| { |
| dev_t devIDfwlog = g_fwlog->g_devIDfwlog; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| BTMTK_INFO("%s: Start\n", __func__); |
| |
| //if (is_mt66xx(g_sbdev->chip_id)) |
| if (bmain_info->hif_hook.log_deinit) |
| bmain_info->hif_hook.log_deinit(); |
| |
| if (g_fwlog->pBTDevfwlog) { |
| device_destroy(g_fwlog->pBTClass, devIDfwlog); |
| g_fwlog->pBTDevfwlog = NULL; |
| } |
| |
| if (g_fwlog->pBTClass) { |
| class_destroy(g_fwlog->pBTClass); |
| g_fwlog->pBTClass = NULL; |
| } |
| BTMTK_INFO("%s: pBTDevfwlog, pBTClass done\n", __func__); |
| |
| cdev_del(&g_fwlog->BT_cdevfwlog); |
| unregister_chrdev_region(devIDfwlog, 1); |
| BTMTK_INFO("%s: BT_chrdevfwlog driver removed.\n", __func__); |
| |
| kfree(g_fwlog); |
| |
| return 0; |
| } |
| static int flag; |
| void btmtk_init_node(void) |
| { |
| if (flag == 1) |
| return; |
| |
| flag = 1; |
| btmtk_proc_create_new_entry(); |
| if (btmtk_fops_initfwlog() < 0) |
| BTMTK_ERR("%s: create stpbtfwlog failed.\n", __func__); |
| } |
| |
| void btmtk_deinit_node(void) |
| { |
| if (flag != 1) |
| return; |
| |
| flag = 0; |
| btmtk_proc_delete_entry(); |
| if (btmtk_fops_exitfwlog() < 0) |
| BTMTK_ERR("%s: release stpbtfwlog failed.\n", __func__); |
| } |
| |
| ssize_t btmtk_fops_readfwlog(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) |
| { |
| int copyLen = 0; |
| ulong flags = 0; |
| struct sk_buff *skb = NULL; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| static unsigned int fwlog_count; |
| |
| //if (is_mt66xx(g_sbdev->chip_id)) { |
| if (bmain_info->hif_hook.log_read_to_user) { |
| copyLen = bmain_info->hif_hook.log_read_to_user(buf, count); |
| BTMTK_DBG_LIMITTED("%s: fw log counter[%d]", __func__, fwlog_count++); |
| return copyLen; |
| } |
| |
| /* picus read a queue, it may occur performace issue */ |
| spin_lock_irqsave(&g_fwlog->fwlog_lock, flags); |
| if (skb_queue_len(&g_fwlog->fwlog_queue)) |
| skb = skb_dequeue(&g_fwlog->fwlog_queue); |
| |
| spin_unlock_irqrestore(&g_fwlog->fwlog_lock, flags); |
| if (skb == NULL) |
| return 0; |
| |
| if (skb->len <= count) { |
| if (copy_to_user(buf, skb->data, skb->len)) |
| BTMTK_ERR("%s: copy_to_user failed!", __func__); |
| copyLen = skb->len; |
| } else { |
| BTMTK_DBG("%s: socket buffer length error(count: %d, skb.len: %d)", |
| __func__, (int)count, skb->len); |
| } |
| kfree_skb(skb); |
| |
| return copyLen; |
| } |
| ssize_t btmtk_fops_writefwlog(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) |
| { |
| int i = 0, len = 0, ret = -1; |
| int hci_idx = 0; |
| int vlen = 0, index = 3; |
| struct sk_buff *skb = NULL; |
| #if (USE_DEVICE_NODE == 0) |
| struct sk_buff *skb_opcode = NULL; |
| #endif |
| int state = BTMTK_STATE_INIT; |
| unsigned char fstate = BTMTK_FOPS_STATE_INIT; |
| u8 *i_fwlog_buf = NULL; |
| u8 *o_fwlog_buf = NULL; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| struct btmtk_dev **pp_bdev = btmtk_get_pp_bdev(); |
| |
| /* only 7xxx will use writefwlog, 66xx not used */ |
| /*if (is_mt66xx(bdev->chip_id)) { |
| * BTMTK_WARN("%s: not implement!", __func__); |
| * return 0; |
| * } |
| */ |
| |
| i_fwlog_buf = kmalloc(HCI_MAX_COMMAND_BUF_SIZE, GFP_KERNEL); |
| if (!i_fwlog_buf) { |
| BTMTK_ERR("%s: alloc i_fwlog_buf failed", __func__); |
| return -ENOMEM; |
| } |
| |
| o_fwlog_buf = kmalloc(HCI_MAX_COMMAND_SIZE, GFP_KERNEL); |
| if (!o_fwlog_buf) { |
| BTMTK_ERR("%s: alloc o_fwlog_buf failed", __func__); |
| ret = -ENOMEM; |
| kfree(i_fwlog_buf); |
| return -ENOMEM; |
| } |
| |
| if (count > HCI_MAX_COMMAND_BUF_SIZE) { |
| BTMTK_ERR("%s: your command is larger than maximum length, count = %zd", |
| __func__, count); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| memset(i_fwlog_buf, 0, HCI_MAX_COMMAND_BUF_SIZE); |
| memset(o_fwlog_buf, 0, HCI_MAX_COMMAND_SIZE); |
| |
| if (buf == NULL || count == 0) { |
| BTMTK_ERR("%s: worng input data", __func__); |
| ret = -ENODATA; |
| goto exit; |
| } |
| |
| if (copy_from_user(i_fwlog_buf, buf, count) != 0) { |
| BTMTK_ERR("%s: Failed to copy data", __func__); |
| ret = -ENODATA; |
| goto exit; |
| } |
| |
| /* For log level, EX: echo log_lvl=1 > /dev/stpbtfwlog */ |
| if (strncmp(i_fwlog_buf, "log_lvl=", strlen("log_lvl=")) == 0) { |
| u8 val = *(i_fwlog_buf + strlen("log_lvl=")) - '0'; |
| |
| if (val > BTMTK_LOG_LVL_MAX || val <= 0) { |
| BTMTK_ERR("Got incorrect value for log level(%d)", val); |
| ret = -EINVAL; |
| goto exit; |
| } |
| btmtk_log_lvl = val; |
| BTMTK_INFO("btmtk_log_lvl = %d", btmtk_log_lvl); |
| ret = count; |
| goto exit; |
| } |
| |
| /* For bperf, EX: echo bperf=1 > /dev/stpbtfwlog */ |
| if (strncmp(i_fwlog_buf, "bperf=", strlen("bperf=")) == 0) { |
| u8 val = *(i_fwlog_buf + strlen("bperf=")) - '0'; |
| |
| g_fwlog->btmtk_bluetooth_kpi = val; |
| BTMTK_INFO("%s: set bluetooth KPI feature(bperf) to %d", __func__, g_fwlog->btmtk_bluetooth_kpi); |
| ret = count; |
| goto exit; |
| } |
| |
| if (strncmp(i_fwlog_buf, "set_para=", strlen("set_para=")) == 0) { |
| u8 val = *(i_fwlog_buf + strlen("set_para=")) - '0'; |
| |
| if (bmain_info->hif_hook.set_para) |
| bmain_info->hif_hook.set_para(pp_bdev[hci_idx], val); |
| else |
| BTMTK_WARN("%s: not support set_para", __func__); |
| ret = count; |
| goto exit; |
| } |
| |
| if (strncmp(i_fwlog_buf, "chip_reset=", strlen("chip_reset=")) == 0) { |
| u8 val = *(i_fwlog_buf + strlen("chip_reset=")) - '0'; |
| |
| bmain_info->chip_reset_flag = val; |
| BTMTK_INFO("%s: set chip reset flag to %d", __func__, bmain_info->chip_reset_flag); |
| ret = count; |
| goto exit; |
| } |
| if (strncmp(i_fwlog_buf, "whole chip reset", strlen("whole chip reset")) == 0) { |
| BTMTK_INFO("whole chip reset start"); |
| bmain_info->chip_reset_flag = 1; |
| btmtk_reset_trigger(pp_bdev[hci_idx]); |
| ret = count; |
| goto exit; |
| } |
| if (strncmp(i_fwlog_buf, "subsys chip reset", strlen("subsys chip reset")) == 0) { |
| BTMTK_INFO("subsys chip reset"); |
| bmain_info->chip_reset_flag = 0; |
| btmtk_reset_trigger(pp_bdev[hci_idx]); |
| ret = count; |
| goto exit; |
| } |
| if (strncmp(i_fwlog_buf, "dump chip reset", strlen("dump chip reset")) == 0) { |
| BTMTK_INFO("subsys chip reset = %d", atomic_read(&bmain_info->subsys_reset_count)); |
| BTMTK_INFO("whole chip reset = %d", atomic_read(&bmain_info->whole_reset_count)); |
| ret = count; |
| goto exit; |
| } |
| |
| if (strncmp(i_fwlog_buf, "dump btsnoop", strlen("dump btsnoop")) == 0) { |
| btmtk_hci_snoop_print_to_log(); |
| ret = count; |
| goto exit; |
| } |
| |
| #ifdef BTMTK_DEBUG_SOP |
| if (strncmp(i_fwlog_buf, "dump test", strlen("dump test")) == 0) { |
| btmtk_load_debug_sop_register(pp_bdev[hci_idx]->debug_sop_file_name, |
| pp_bdev[hci_idx]->intf_dev, pp_bdev[hci_idx]); |
| ret = count; |
| goto exit; |
| } |
| |
| if (strncmp(i_fwlog_buf, "dump clean", strlen("dump clean")) == 0) { |
| btmtk_clean_debug_reg_file(pp_bdev[hci_idx]); |
| ret = count; |
| goto exit; |
| } |
| #endif |
| |
| if (strncmp(i_fwlog_buf, "dump_debug=", strlen("dump_debug")) == 0) { |
| u8 val = *(i_fwlog_buf + strlen("dump_debug=")) - '0'; |
| |
| if (bmain_info->hif_hook.dump_debug_sop) { |
| BTMTK_INFO("%s: dump_debug(%s)", __func__, |
| (val == 0) ? "SLEEP" : |
| ((val == 1) ? "WAKEUP" : |
| ((val == 2) ? "NO_RESPONSE" : "ERROR"))); |
| if (fstate != BTMTK_FOPS_STATE_OPENED) { |
| ret = bmain_info->hif_hook.open(pp_bdev[hci_idx]->hdev); |
| if (ret < 0) { |
| BTMTK_ERR("%s, cif_open failed", __func__); |
| ret = count; |
| goto exit; |
| } |
| } |
| bmain_info->hif_hook.dump_debug_sop(pp_bdev[hci_idx]); |
| if (fstate != BTMTK_FOPS_STATE_OPENED) { |
| ret = bmain_info->hif_hook.close(pp_bdev[hci_idx]->hdev); |
| if (ret < 0) { |
| BTMTK_ERR("%s, cif_close failed", __func__); |
| ret = count; |
| goto exit; |
| } |
| } |
| } else { |
| BTMTK_INFO("%s: not support", __func__); |
| } |
| ret = count; |
| goto exit; |
| } |
| |
| /* hci input command format : echo 01 be fc 01 05 > /dev/stpbtfwlog */ |
| /* We take the data from index three to end. */ |
| for (i = 0; i < count; i++) { |
| char *pos = i_fwlog_buf + i; |
| char temp_str[3] = {'\0'}; |
| long res = 0; |
| |
| if (*pos == ' ' || *pos == '\t' || *pos == '\r' || *pos == '\n') { |
| continue; |
| } else if (*pos == '0' && (*(pos + 1) == 'x' || *(pos + 1) == 'X')) { |
| i++; |
| continue; |
| } else if (!(*pos >= '0' && *pos <= '9') && !(*pos >= 'A' && *pos <= 'F') |
| && !(*pos >= 'a' && *pos <= 'f')) { |
| BTMTK_ERR("%s: There is an invalid input(%c)", __func__, *pos); |
| ret = -EINVAL; |
| goto exit; |
| } |
| temp_str[0] = *pos; |
| temp_str[1] = *(pos + 1); |
| i++; |
| ret = kstrtol(temp_str, 16, &res); |
| if (ret == 0) |
| o_fwlog_buf[len++] = (u8)res; |
| else |
| BTMTK_ERR("%s: Convert %s failed(%d)", __func__, temp_str, ret); |
| } |
| |
| if (o_fwlog_buf[0] != HCI_COMMAND_PKT && o_fwlog_buf[0] != FWLOG_TYPE) { |
| BTMTK_ERR("%s: Not support 0x%02X yet", __func__, o_fwlog_buf[0]); |
| ret = -EPROTONOSUPPORT; |
| goto exit; |
| } |
| /* check HCI command length */ |
| if (len > HCI_MAX_COMMAND_SIZE) { |
| BTMTK_ERR("%s: command is larger than max buf size, length = %d", __func__, len); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| skb = alloc_skb(count + BT_SKB_RESERVE, GFP_ATOMIC); |
| if (!skb) { |
| BTMTK_ERR("%s allocate skb failed!!", __func__); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| /* send HCI command */ |
| bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; |
| |
| /* format */ |
| /* 0xF0 XX XX 00 01 AA 10 BB CC CC CC CC ... */ |
| /* XX XX total length */ |
| /* 00 : hci index setting type */ |
| /* AA hci index to indicate which hci send following command*/ |
| /* 10 : raw data type*/ |
| /* BB command length */ |
| /* CC command */ |
| if (o_fwlog_buf[0] == FWLOG_TYPE) { |
| while (index < ((o_fwlog_buf[2] << 8) + o_fwlog_buf[1])) { |
| switch (o_fwlog_buf[index]) { |
| case FWLOG_HCI_IDX: /* hci index */ |
| vlen = o_fwlog_buf[index + 1]; |
| hci_idx = o_fwlog_buf[index + 2]; |
| BTMTK_DBG("%s: send to hci%d", __func__, hci_idx); |
| index += (FWLOG_ATTR_TL_SIZE + vlen); |
| break; |
| case FWLOG_TX: /* tx raw data */ |
| vlen = o_fwlog_buf[index + 1]; |
| memcpy(skb->data, o_fwlog_buf + index + FWLOG_ATTR_TL_SIZE, vlen); |
| skb->len = vlen; |
| index = index + FWLOG_ATTR_TL_SIZE + vlen; |
| break; |
| default: |
| BTMTK_WARN("%s: Invalid opcode", __func__); |
| ret = -1; |
| goto free_skb; |
| } |
| } |
| } else { |
| memcpy(skb->data, o_fwlog_buf, len); |
| skb->len = len; |
| #if defined(DRV_RETURN_SPECIFIC_HCE_ONLY) && (DRV_RETURN_SPECIFIC_HCE_ONLY == 1) && (USE_DEVICE_NODE == 0) |
| // 0xFC26 is get link & profile information command. |
| if (*(uint16_t *)(o_fwlog_buf + 1) != 0xFC26) { |
| skb_opcode = alloc_skb(len + FWLOG_PRSV_LEN, GFP_ATOMIC); |
| if (!skb_opcode) { |
| BTMTK_ERR("%s allocate skb failed!!", __func__); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| memcpy(skb_opcode->data, (o_fwlog_buf + 1), 2); |
| BTMTK_INFO("opcode is %02x,%02x", skb_opcode->data[0], skb_opcode->data[1]); |
| skb_queue_tail(&g_fwlog->usr_opcode_queue, skb_opcode); |
| } |
| #endif |
| } |
| |
| /* won't send command if g_bdev not define */ |
| if (pp_bdev[hci_idx]->hdev == NULL) { |
| BTMTK_DBG("%s: pp_bdev[%d] not define", __func__, hci_idx); |
| ret = count; |
| goto free_skb; |
| } |
| |
| state = btmtk_get_chip_state(pp_bdev[hci_idx]); |
| if (state != BTMTK_STATE_WORKING) { |
| BTMTK_WARN("%s: current is in suspend/resume/standby/dump/disconnect (%d).", |
| __func__, state); |
| ret = -EBADFD; |
| goto free_skb; |
| } |
| |
| fstate = btmtk_fops_get_state(pp_bdev[hci_idx]); |
| if (fstate != BTMTK_FOPS_STATE_OPENED) { |
| BTMTK_WARN("%s: fops is not open yet(%d)!", __func__, fstate); |
| ret = -ENODEV; |
| goto free_skb; |
| } |
| |
| if (pp_bdev[hci_idx]->power_state == BTMTK_DONGLE_STATE_POWER_OFF) { |
| BTMTK_WARN("%s: dongle state already power off, do not write", __func__); |
| ret = -EFAULT; |
| goto free_skb; |
| } |
| |
| #if (USE_DEVICE_NODE == 0) |
| /* clean fwlog queue before enable picus log */ |
| if (skb_queue_len(&g_fwlog->fwlog_queue) && skb->data[0] == 0x01 |
| && skb->data[1] == 0x5d && skb->data[2] == 0xfc && skb->data[4] == 0x00) { |
| skb_queue_purge(&g_fwlog->fwlog_queue); |
| BTMTK_INFO("clean fwlog_queue, skb_queue_len = %d", skb_queue_len(&g_fwlog->fwlog_queue)); |
| } |
| |
| btmtk_dispatch_fwlog_bluetooth_kpi(pp_bdev[hci_idx], skb->data, skb->len, KPI_WITHOUT_TYPE); |
| #endif |
| ret = bmain_info->hif_hook.send_cmd(pp_bdev[hci_idx], skb, 0, 0, (int)BTMTK_TX_PKT_FROM_HOST); |
| if (ret < 0) { |
| BTMTK_ERR("%s failed!!", __func__); |
| goto free_skb; |
| } else |
| BTMTK_INFO("%s: OK", __func__); |
| |
| BTMTK_INFO("%s: Write end(len: %d)", __func__, len); |
| ret = count; |
| goto exit; |
| |
| free_skb: |
| kfree_skb(skb); |
| skb = NULL; |
| #if (USE_DEVICE_NODE == 0) |
| /* clean opcode queue if bt is disable */ |
| skb_queue_purge(&g_fwlog->usr_opcode_queue); |
| #endif |
| exit: |
| kfree(i_fwlog_buf); |
| kfree(o_fwlog_buf); |
| |
| return ret; /* If input is correct should return the same length */ |
| } |
| |
| int btmtk_fops_openfwlog(struct inode *inode, struct file *file) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| atomic_inc(&bmain_info->fwlog_ref_cnt); |
| BTMTK_INFO("%s: Start.", __func__); |
| |
| return 0; |
| } |
| |
| int btmtk_fops_closefwlog(struct inode *inode, struct file *file) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| atomic_dec(&bmain_info->fwlog_ref_cnt); |
| BTMTK_INFO("%s: Start.", __func__); |
| |
| return 0; |
| } |
| |
| long btmtk_fops_unlocked_ioctlfwlog(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| long retval = 0; |
| uint8_t log_tmp = BT_FWLOG_OFF; |
| |
| /* only 66xx will use ioctlfwlog, 76xx not used */ |
| /* if (!is_mt66xx(g_sbdev->chip_id)) { |
| * BTMTK_WARN("%s: not implement!", __func__); |
| * return 0; |
| *} |
| */ |
| |
| down(&ioctl_mtx); |
| switch (cmd) { |
| case BT_FWLOG_IOC_ON_OFF: |
| /* Connsyslogger daemon dynamically enable/disable Picus log */ |
| BTMTK_INFO("[ON_OFF]arg(%lu) bt_on(0x%x) log_on(0x%x) level(0x%x) log_cur(0x%x)", |
| arg, g_bt_on, g_log_on, g_log_level, g_log_current); |
| |
| log_tmp = (arg == 0) ? BT_FWLOG_OFF : BT_FWLOG_ON; |
| if (log_tmp != g_log_on) { // changed |
| g_log_on = log_tmp; |
| g_log_current = g_log_on & g_log_level; |
| if (g_bt_on) { |
| retval = btmtk_intcmd_set_fw_log(g_log_current); |
| btmtk_intcmd_wmt_utc_sync(); |
| } |
| } |
| break; |
| case BT_FWLOG_IOC_SET_LEVEL: |
| /* Connsyslogger daemon dynamically set Picus log level */ |
| BTMTK_INFO("[SET_LEVEL]arg(%lu) bt_on(0x%x) log_on(0x%x) level(0x%x) log_cur(0x%x)", |
| arg, g_bt_on, g_log_on, g_log_level, g_log_current); |
| |
| log_tmp = (uint8_t)arg; |
| if (log_tmp != g_log_level) { |
| g_log_level = log_tmp; |
| g_log_current = g_log_on & g_log_level; |
| if (g_bt_on & g_log_on) { |
| // driver on and log on |
| retval = btmtk_intcmd_set_fw_log(g_log_current); |
| btmtk_intcmd_wmt_utc_sync(); |
| } |
| } |
| break; |
| case BT_FWLOG_IOC_GET_LEVEL: |
| retval = g_log_level; |
| BTMTK_INFO("[GET_LEVEL]return %ld", retval); |
| break; |
| default: |
| BTMTK_ERR("Unknown cmd: 0x%08x", cmd); |
| retval = -EOPNOTSUPP; |
| break; |
| } |
| |
| up(&ioctl_mtx); |
| return retval; |
| } |
| |
| long btmtk_fops_compat_ioctlfwlog(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| BTMTK_DBG("%s: Start.", __func__); |
| return btmtk_fops_unlocked_ioctlfwlog(filp, cmd, arg); |
| } |
| |
| unsigned int btmtk_fops_pollfwlog(struct file *file, poll_table *wait) |
| { |
| unsigned int mask = 0; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| //if (is_mt66xx(g_sbdev->chip_id)) { |
| if (bmain_info->hif_hook.log_get_buf_size) { |
| poll_wait(file, &BT_log_wq, wait); |
| if (bmain_info->hif_hook.log_get_buf_size() > 0) |
| mask = (POLLIN | POLLRDNORM); |
| } else { |
| poll_wait(file, &g_fwlog->fw_log_inq, wait); |
| if (skb_queue_len(&g_fwlog->fwlog_queue) > 0) |
| mask |= POLLIN | POLLRDNORM; /* readable */ |
| } |
| return mask; |
| } |
| |
| static void btmtk_fwdump_wake_lock(void) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| BTMTK_INFO("%s: enter", __func__); |
| __pm_stay_awake(bmain_info->fwdump_ws); |
| BTMTK_INFO("%s: exit", __func__); |
| } |
| |
| void btmtk_fwdump_wake_unlock(void) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| BTMTK_INFO("%s: enter", __func__); |
| __pm_relax(bmain_info->fwdump_ws); |
| BTMTK_INFO("%s: exit", __func__); |
| } |
| |
| static int btmtk_skb_enq_fwlog(struct btmtk_dev *bdev, void *src, u32 len, u8 type, struct sk_buff_head *queue) |
| { |
| struct sk_buff *skb_tmp = NULL; |
| ulong flags = 0; |
| int retry = 10, index = FWLOG_TL_SIZE; |
| |
| do { |
| skb_tmp = alloc_skb(len + FWLOG_PRSV_LEN, GFP_ATOMIC); |
| if (skb_tmp != NULL) |
| break; |
| else if (retry <= 0) { |
| pr_err("%s: alloc_skb return 0, error", __func__); |
| return -ENOMEM; |
| } |
| pr_err("%s: alloc_skb return 0, error, retry = %d", __func__, retry); |
| } while (retry-- > 0); |
| |
| if (type) { |
| skb_tmp->data[0] = FWLOG_TYPE; |
| /* 01 for dongle index */ |
| skb_tmp->data[index] = FWLOG_DONGLE_IDX; |
| skb_tmp->data[index + 1] = sizeof(bdev->dongle_index); |
| skb_tmp->data[index + 2] = bdev->dongle_index; |
| index += (FWLOG_ATTR_RX_LEN_LEN + FWLOG_ATTR_TYPE_LEN); |
| /* 11 for rx data*/ |
| skb_tmp->data[index] = FWLOG_RX; |
| if (type == HCI_ACLDATA_PKT || type == HCI_EVENT_PKT || type == HCI_COMMAND_PKT) { |
| skb_tmp->data[index + 1] = len & 0x00FF; |
| skb_tmp->data[index + 2] = (len & 0xFF00) >> 8; |
| skb_tmp->data[index + 3] = type; |
| index += (HCI_TYPE_SIZE + FWLOG_ATTR_RX_LEN_LEN + FWLOG_ATTR_TYPE_LEN); |
| } else { |
| skb_tmp->data[index + 1] = len & 0x00FF; |
| skb_tmp->data[index + 2] = (len & 0xFF00) >> 8; |
| index += (FWLOG_ATTR_RX_LEN_LEN + FWLOG_ATTR_TYPE_LEN); |
| } |
| memcpy(&skb_tmp->data[index], src, len); |
| skb_tmp->data[1] = (len + index - FWLOG_TL_SIZE) & 0x00FF; |
| skb_tmp->data[2] = ((len + index - FWLOG_TL_SIZE) & 0xFF00) >> 8; |
| skb_tmp->len = len + index; |
| } else { |
| memcpy(skb_tmp->data, src, len); |
| skb_tmp->len = len; |
| } |
| |
| |
| spin_lock_irqsave(&g_fwlog->fwlog_lock, flags); |
| skb_queue_tail(queue, skb_tmp); |
| spin_unlock_irqrestore(&g_fwlog->fwlog_lock, flags); |
| return 0; |
| } |
| |
| int btmtk_dispatch_fwlog_bluetooth_kpi(struct btmtk_dev *bdev, u8 *buf, int len, u8 type) |
| { |
| static u8 fwlog_blocking_warn; |
| int ret = 0; |
| |
| if (g_fwlog->btmtk_bluetooth_kpi && |
| skb_queue_len(&g_fwlog->fwlog_queue) < FWLOG_BLUETOOTH_KPI_QUEUE_COUNT) { |
| /* sent event to queue, picus tool will log it for bluetooth KPI feature */ |
| if (btmtk_skb_enq_fwlog(bdev, buf, len, type, &g_fwlog->fwlog_queue) == 0) { |
| wake_up_interruptible(&g_fwlog->fw_log_inq); |
| fwlog_blocking_warn = 0; |
| } |
| } else { |
| if (fwlog_blocking_warn == 0) { |
| fwlog_blocking_warn = 1; |
| pr_warn("btmtk_usb fwlog queue size is full(bluetooth_kpi)"); |
| } |
| } |
| return ret; |
| } |
| |
| /* if modify the common part, please sync to another btmtk_dispatch_fwlog */ |
| #if (USE_DEVICE_NODE == 0) |
| int btmtk_dispatch_fwlog(struct btmtk_dev *bdev, struct sk_buff *skb) |
| { |
| static u8 fwlog_picus_blocking_warn; |
| static u8 fwlog_fwdump_blocking_warn; |
| int state = BTMTK_STATE_INIT; |
| u8 hci_reset_event[HCI_RESET_EVT_LEN] = { 0x04, 0x0E, 0x04, 0x01, 0x03, 0x0c, 0x00 }; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| struct sk_buff *skb_opcode = NULL; |
| |
| if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) && |
| skb->data[0] == 0x6f && |
| skb->data[1] == 0xfc) { |
| static int dump_data_counter; |
| static int dump_data_length; |
| |
| state = btmtk_get_chip_state(bdev); |
| if (state != BTMTK_STATE_FW_DUMP) { |
| BTMTK_INFO("%s: FW dump begin", __func__); |
| DUMP_TIME_STAMP("FW_dump_start"); |
| btmtk_hci_snoop_print_to_log(); |
| /* Print too much log, it may cause kernel panic. */ |
| dump_data_counter = 0; |
| dump_data_length = 0; |
| btmtk_set_chip_state(bdev, BTMTK_STATE_FW_DUMP); |
| btmtk_fwdump_wake_lock(); |
| } |
| |
| dump_data_counter++; |
| dump_data_length += skb->len; |
| |
| /* coredump */ |
| /* print dump data to console */ |
| if (dump_data_counter % 1000 == 0) { |
| BTMTK_INFO("%s: FW dump on-going, total_packet = %d, total_length = %d", |
| __func__, dump_data_counter, dump_data_length); |
| } |
| |
| /* print dump data to console */ |
| if (dump_data_counter < 20) |
| BTMTK_INFO("%s: FW dump data (%d): %s", |
| __func__, dump_data_counter, &skb->data[4]); |
| |
| /* In the new generation, we will check the keyword of coredump (; coredump end) |
| * Such as : 79xx |
| */ |
| if (skb->data[skb->len - 4] == 'e' && |
| skb->data[skb->len - 3] == 'n' && |
| skb->data[skb->len - 2] == 'd') { |
| /* This is the latest coredump packet. */ |
| BTMTK_INFO("%s: FW dump end, dump_data_counter = %d", __func__, dump_data_counter); |
| /* TODO: Chip reset*/ |
| bmain_info->reset_stack_flag = HW_ERR_CODE_CORE_DUMP; |
| btmtk_fwdump_wake_unlock(); |
| DUMP_TIME_STAMP("FW_dump_end"); |
| if (bmain_info->hif_hook.waker_notify) |
| bmain_info->hif_hook.waker_notify(bdev); |
| } |
| |
| if (skb_queue_len(&g_fwlog->fwlog_queue) < FWLOG_ASSERT_QUEUE_COUNT) { |
| /* sent picus data to queue, picus tool will log it */ |
| if (btmtk_skb_enq_fwlog(bdev, skb->data, skb->len, 0, &g_fwlog->fwlog_queue) == 0) { |
| wake_up_interruptible(&g_fwlog->fw_log_inq); |
| fwlog_fwdump_blocking_warn = 0; |
| } |
| } else { |
| if (fwlog_fwdump_blocking_warn == 0) { |
| fwlog_fwdump_blocking_warn = 1; |
| pr_warn("btmtk fwlog queue size is full(coredump)"); |
| } |
| } |
| |
| /* change coredump's ACL handle to FF F0 */ |
| skb->data[0] = 0xFF; |
| skb->data[1] = 0xF0; |
| } else if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) && |
| (skb->data[0] == 0xff || skb->data[0] == 0xfe) && |
| skb->data[1] == 0x05 && |
| !bdev->bt_cfg.support_picus_to_host) { |
| /* picus or syslog */ |
| if (skb_queue_len(&g_fwlog->fwlog_queue) < FWLOG_QUEUE_COUNT) { |
| if (btmtk_skb_enq_fwlog(bdev, skb->data, skb->len, |
| FWLOG_TYPE, &g_fwlog->fwlog_queue) == 0) { |
| wake_up_interruptible(&g_fwlog->fw_log_inq); |
| fwlog_picus_blocking_warn = 0; |
| } |
| } else { |
| if (fwlog_picus_blocking_warn == 0) { |
| fwlog_picus_blocking_warn = 1; |
| pr_warn("btmtk fwlog queue size is full(picus)"); |
| } |
| } |
| return 1; |
| } else if (memcmp(skb->data, &hci_reset_event[1], HCI_RESET_EVT_LEN - 1) == 0) { |
| BTMTK_INFO("%s: Get RESET_EVENT", __func__); |
| bdev->get_hci_reset = 1; |
| atomic_set(&bmain_info->subsys_reset_conti_count, 0); |
| } |
| |
| |
| /* filter event from usr cmd */ |
| if ((bt_cb(skb)->pkt_type == HCI_EVENT_PKT) && |
| skb->data[0] == 0x0E) { |
| if (skb_queue_len(&g_fwlog->usr_opcode_queue)) { |
| BTMTK_INFO("%s: opcode queue len is %d", __func__, |
| skb_queue_len(&g_fwlog->usr_opcode_queue)); |
| skb_opcode = skb_dequeue(&g_fwlog->usr_opcode_queue); |
| } |
| |
| if (skb_opcode == NULL) |
| return 0; |
| |
| if (skb_opcode->data[0] == skb->data[3] && |
| skb_opcode->data[1] == skb->data[4]) { |
| BTMTK_INFO_RAW(skb->data, skb->len, "%s: Discard event from user hci command - ", __func__); |
| #if defined(DRV_RETURN_SPECIFIC_HCE_ONLY) && (DRV_RETURN_SPECIFIC_HCE_ONLY == 0) |
| // should return to upper layer tool |
| if (btmtk_skb_enq_fwlog(bdev, skb->data, skb->len, FWLOG_TYPE, |
| &g_fwlog->fwlog_queue) == 0) { |
| wake_up_interruptible(&g_fwlog->fw_log_inq); |
| } |
| kfree_skb(skb_opcode); |
| #endif |
| return 1; |
| } |
| |
| BTMTK_INFO("%s: check opcode fail!", __func__); |
| skb_queue_head(&g_fwlog->usr_opcode_queue, skb_opcode); |
| } |
| |
| return 0; |
| } |
| |
| #else // #if (USE_DEVICE_NODE == 0) |
| |
| /* if modify the common part, please sync to another btmtk_dispatch_fwlog*/ |
| int btmtk_dispatch_fwlog(struct btmtk_dev *bdev, struct sk_buff *skb) |
| { |
| char xml_log[CONNV3_XML_SIZE] = {0}; |
| int state = BTMTK_STATE_INIT; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| struct connv3_issue_info issue_info; |
| int ret = 0, line = 0; |
| |
| if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) && |
| skb->data[0] == 0x6f && |
| skb->data[1] == 0xfc) { |
| static int dump_data_counter; |
| static int dump_data_length; |
| //static int ori_log_lvl; |
| |
| /* remove acl header 6F FC LL LL */ |
| skb_pull(skb, 4); |
| |
| state = btmtk_get_chip_state(bdev); |
| /* coredump info*/ |
| if (state != BTMTK_STATE_FW_DUMP) { |
| BTMTK_INFO("%s: msg: %s len[%d]", __func__, skb->data, skb->len); |
| /* drop "Disable Cache" */ |
| if (skb->len > 6 && |
| skb->data[skb->len - 6] == 'C' && |
| skb->data[skb->len - 5] == 'a' && |
| skb->data[skb->len - 4] == 'c' && |
| skb->data[skb->len - 3] == 'h' && |
| skb->data[skb->len - 2] == 'e') { |
| BTMTK_INFO("%s: drop Cache", __func__, skb->data, skb->len); |
| return 1; |
| } |
| |
| /* drop "bt radio off" */ |
| if (skb->len > 10 && |
| skb->data[skb->len - 10] == 'r' && |
| skb->data[skb->len - 9] == 'a' && |
| skb->data[skb->len - 8] == 'd' && |
| skb->data[skb->len - 7] == 'i' && |
| skb->data[skb->len - 6] == 'o' && |
| skb->data[skb->len - 5] == ' ' && |
| skb->data[skb->len - 4] == 'o' && |
| skb->data[skb->len - 3] == 'f' && |
| skb->data[skb->len - 2] == 'f') { |
| BTMTK_INFO("%s: drop radio off", __func__, skb->data, skb->len); |
| return 1; |
| } |
| |
| //ori_log_lvl = btmtk_log_lvl; |
| //btmtk_log_lvl = BTMTK_LOG_LVL_INFO; |
| //BTMTK_INFO("%s: FW dump begin, change log level [%d]->[%d]", __func__, ori_log_lvl, btmtk_log_lvl); |
| DUMP_TIME_STAMP("FW_dump_start"); |
| /* Print too much log, it may cause kernel panic. */ |
| dump_data_counter = 0; |
| dump_data_length = 0; |
| if (bmain_info->hif_hook.coredump_handler == NULL) { |
| BTMTK_ERR("%s: coredump_handler is NULL", __func__); |
| goto coredump_fail; |
| } |
| btmtk_set_chip_state(bdev, BTMTK_STATE_FW_DUMP); |
| reinit_completion(&bdev->dump_comp); |
| btmtk_fwdump_wake_lock(); |
| line = __LINE__; |
| ret = connv3_coredump_start( |
| bmain_info->hif_hook.coredump_handler, CONNV3_DRV_TYPE_BT, |
| "BT exception test", skb->data, bmain_info->fw_version_str); |
| if (ret == CONNV3_COREDUMP_ERR_WRONG_STATUS) { |
| BTMTK_ERR("%s: BT previous not end", __func__); |
| connv3_coredump_end(bmain_info->hif_hook.coredump_handler, "BT previous not end"); |
| ret = connv3_coredump_start( |
| bmain_info->hif_hook.coredump_handler, CONNV3_DRV_TYPE_BT, |
| "BT exception test", skb->data, bmain_info->fw_version_str); |
| } |
| if (ret) |
| goto coredump_fail_unlock; |
| line = __LINE__; |
| ret = connv3_coredump_get_issue_info(bmain_info->hif_hook.coredump_handler, |
| &issue_info, xml_log, CONNV3_XML_SIZE); |
| if (ret) |
| goto coredump_fail_unlock; |
| BTMTK_INFO("%s: xml_log: %s, assert_info: %s ", __func__, xml_log, issue_info.assert_info); |
| line = __LINE__; |
| ret = connv3_coredump_send(bmain_info->hif_hook.coredump_handler, |
| "INFO", xml_log, strlen(xml_log)); |
| if (ret) |
| goto coredump_fail_unlock; |
| } |
| |
| dump_data_counter++; |
| dump_data_length += skb->len; |
| |
| /* coredump content*/ |
| /* print dump data to console */ |
| if (dump_data_counter % 1000 == 0) { |
| BTMTK_INFO("%s: FW dump on-going, total_packet = %d, total_length = %d", |
| __func__, dump_data_counter, dump_data_length); |
| } |
| |
| /* print dump data to console */ |
| if (dump_data_counter < 20) |
| BTMTK_INFO("%s: FW dump data (%d): %s", |
| __func__, dump_data_counter, skb->data); |
| line = __LINE__; |
| ret = connv3_coredump_send(bmain_info->hif_hook.coredump_handler, "[M]", skb->data, skb->len); |
| if (ret) |
| goto coredump_fail_unlock; |
| |
| /* In the new generation, we will check the keyword of coredump (; coredump end) |
| * Such as : 79xx |
| */ |
| if (skb->len > 6 && |
| skb->data[skb->len - 6] == 'p' && |
| skb->data[skb->len - 5] == ' ' && |
| skb->data[skb->len - 4] == 'e' && |
| skb->data[skb->len - 3] == 'n' && |
| skb->data[skb->len - 2] == 'd') { |
| /* TODO: Chip reset*/ |
| bmain_info->reset_stack_flag = HW_ERR_CODE_CORE_DUMP; |
| btmtk_fwdump_wake_unlock(); |
| DUMP_TIME_STAMP("FW_dump_end"); |
| line = __LINE__; |
| /* This is the latest coredump packet. */ |
| BTMTK_INFO("%s: FW dump end, dump_data_counter[%d], dump_data_length[%d]", |
| __func__, dump_data_counter, dump_data_length); |
| //btmtk_log_lvl = ori_log_lvl; |
| ret = connv3_coredump_end(bmain_info->hif_hook.coredump_handler, "BT assert"); |
| if (bmain_info->hif_hook.waker_notify) |
| bmain_info->hif_hook.waker_notify(bdev); |
| BTMTK_DBG("%s: connv3_coredump_end", __func__); |
| if (ret) |
| goto coredump_fail; |
| |
| } |
| return 1; |
| coredump_fail_unlock: |
| btmtk_fwdump_wake_unlock(); |
| coredump_fail: |
| BTMTK_ERR("%s: coredump fail ret[%d] line[%d]", __func__, ret, line); |
| connv3_coredump_end(bmain_info->hif_hook.coredump_handler, "BT coredump fail"); |
| return 1; |
| } else if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) && |
| (skb->data[0] == 0xff || skb->data[0] == 0xfe) && |
| skb->data[1] == 0x05 && bmain_info->hif_hook.log_handler) { |
| /* picus or syslog */ |
| /* remove acl header (FF 05 LL LL)*/ |
| skb_pull(skb, 4); |
| bmain_info->hif_hook.log_handler(skb->data, skb->len); |
| return 1; |
| } |
| |
| return 0; |
| } |
| #endif // #else (USE_DEVICE_NODE == 1) |
| |