/* * DHD BT WiFi Coex RegON Coordinator * * Copyright (C) 2022, Broadcom. * * Unless you and Broadcom execute a separate written software license * agreement governing use of this software, this software is licensed to you * under the terms of the GNU General Public License version 2 (the "GPL"), * available at http://www.broadcom.com/licenses/GPLv2.php, with the * following added to such license: * * As a special exception, the copyright holders of this software give you * permission to link this software with independent modules, and to copy and * distribute the resulting executable under terms of your choice, provided that * you also meet, for each linked independent module, the terms and conditions of * the license of that module. An independent module is a module which is not * derived from this software. The special exception does not apply to any * modifications of the software. * * * <> * */ #include #include #include #include #include #include #include #include #include #include #include #define DESCRIPTION "Broadcom WiFi BT Regon coordinator Driver" #define AUTHOR "Broadcom Corporation" #define DEVICE_NAME "wbrc" #define CLASS_NAME "bcm" #ifndef TRUE #define TRUE (1) #endif #ifndef FALSE #define FALSE (0) #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) typedef unsigned int __poll_t; #endif /* * 4 byte message to sync with BT stack. * Byte 0 - header * Byte 1 - length of LTV now fixed to 2 * Byte 2 - type * Byte 3 - command value */ #define WBRC_MSG_LEN 4u /* Below defines to be mapped in the user space. */ /* TODO have these as enums and define new structure with members */ /* Byte 0 - Define header for direction of command */ #define HEADER_DIR_WL2BT 0x01 #define HEADER_DIR_BT2WL 0x02 /* * Byte 2 - Define Type of Command (Followed LTV format) * wifi/bt, signal/ack types */ #define TYPE_WIFI_CMD 0x01 #define TYPE_WIFI_ACK 0x02 #define TYPE_BT_CMD 0x03 #define TYPE_BT_ACK 0x04 /* Byte 3 - Define Value field: commands/acks */ #define CMD_RESET_WIFI 0x40 #define CMD_RESET_WIFI_WITH_ACK 0x41 #define CMD_RESET_BT 0x42 #define CMD_RESET_BT_WITH_ACK 0x43 #define ACK_RESET_WIFI_COMPLETE 0x80 #define ACK_RESET_BT_COMPLETE 0x81 struct wbrc_pvt_data { int wbrc_bt_dev_major_number; /* BT char dev major number */ struct class *wbrc_bt_dev_class; /* BT char dev class */ struct device *wbrc_bt_dev_device; /* BT char dev */ struct mutex wbrc_mutex; /* mutex to synchronise */ bool bt_dev_opened; /* To check if bt dev open is called */ wait_queue_head_t bt_reset_waitq; /* waitq to wait till bt reset is done */ unsigned int bt_reset_ack; /* condition variable to be check for bt reset */ wait_queue_head_t wlan_reset_waitq; /* waitq to wait till wlan reset is done */ unsigned int wlan_reset_ack; /* condition variable to be check for wlan reset */ wait_queue_head_t wlan_on_waitq; /* waitq to wait till wlan on is done */ unsigned int wlan_on_ack; /* condition variable to be check for wlan on */ wait_queue_head_t outmsg_waitq; /* wait queue for poll */ char wl2bt_message[WBRC_MSG_LEN]; /* message to communicate with Bt stack */ bool read_data_available; /* condition to check if read data is present */ }; static struct wbrc_pvt_data *g_wbrc_data; #define WBRC_LOCK(wbrc_data) {if (wbrc_data) mutex_lock(&(wbrc_data)->wbrc_mutex);} #define WBRC_UNLOCK(wbrc_data) {if (wbrc_data) mutex_unlock(&(wbrc_data)->wbrc_mutex);} int wbrc_wl2bt_reset(void); int wbrc_bt_reset_ack(struct wbrc_pvt_data *wbrc_data); static int wbrc_bt2wl_reset(void); static int wbrc_wl_reset_ack(struct wbrc_pvt_data *wbrc_data); static int wbrc_bt_dev_open(struct inode *, struct file *); static int wbrc_bt_dev_release(struct inode *, struct file *); static ssize_t wbrc_bt_dev_read(struct file *, char *, size_t, loff_t *); static ssize_t wbrc_bt_dev_write(struct file *, const char *, size_t, loff_t *); static __poll_t wbrc_bt_dev_poll(struct file *filep, poll_table *wait); static struct file_operations wbrc_bt_dev_fops = { .open = wbrc_bt_dev_open, .read = wbrc_bt_dev_read, .write = wbrc_bt_dev_write, .release = wbrc_bt_dev_release, .poll = wbrc_bt_dev_poll, }; static ssize_t wbrc_bt_dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) { struct wbrc_pvt_data *wbrc_data = filep->private_data; int err_count = 0; int ret = 0; WBRC_LOCK(wbrc_data); pr_info("%s\n", __func__); if (wbrc_data->read_data_available == FALSE) { goto exit; } if (len < WBRC_MSG_LEN) { pr_err("%s: invalid length:%d\n", __func__, (int)len); ret = -EFAULT; goto exit; } err_count = copy_to_user(buffer, &wbrc_data->wl2bt_message, sizeof(wbrc_data->wl2bt_message)); if (err_count == 0) { pr_info("Sent %d bytes\n", (int)sizeof(wbrc_data->wl2bt_message)); err_count = sizeof(wbrc_data->wl2bt_message); } else { pr_err("Failed to send %d bytes\n", err_count); ret = -EFAULT; } wbrc_data->read_data_available = FALSE; exit: WBRC_UNLOCK(wbrc_data); return ret; } static ssize_t wbrc_bt_dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { struct wbrc_pvt_data *wbrc_data = filep->private_data; int err_count = 0; int ret = 0; char message[WBRC_MSG_LEN] = {}; WBRC_LOCK(wbrc_data); pr_info("%s Received %zu bytes\n", __func__, len); if (len != WBRC_MSG_LEN) { pr_err("%s: Received malformed packet:%d\n", __func__, (int)len); ret = -EFAULT; goto exit; } err_count = copy_from_user(message, buffer, len); if (err_count) { pr_err("%s: copy_from_user failed:%d\n", __func__, err_count); ret = -EFAULT; goto exit; } if (message[0] != HEADER_DIR_BT2WL) { pr_err("%s: invalid header:%d\n", __func__, message[0]); ret = -EFAULT; goto exit; } if (message[2] == TYPE_BT_CMD) { switch (message[3]) { case CMD_RESET_WIFI: pr_info("RCVD TYPE_BT_CMD: CMD_RESET_WIFI\n"); break; case CMD_RESET_WIFI_WITH_ACK: pr_info("RCVD TYPE_BT_CMD: CMD_RESET_WIFI_WITH_ACK\n"); wbrc_bt2wl_reset(); break; } } if (message[2] == TYPE_BT_ACK && message[3] == ACK_RESET_BT_COMPLETE) { pr_info("RCVD ACK_RESET_BT_COMPLETE"); wbrc_bt_reset_ack(wbrc_data); } exit: WBRC_UNLOCK(wbrc_data); return ret; } static __poll_t wbrc_bt_dev_poll(struct file *filep, poll_table *wait) { struct wbrc_pvt_data *wbrc_data = filep->private_data; __poll_t mask = 0; poll_wait(filep, &wbrc_data->outmsg_waitq, wait); if (wbrc_data->read_data_available) mask |= EPOLLIN | EPOLLRDNORM; if (!wbrc_data->bt_dev_opened) mask |= EPOLLHUP; return mask; } int wbrc_reset_wait_on_condition(wait_queue_head_t *reset_waitq, uint *var, uint condition); static int wbrc_bt_dev_open(struct inode *inodep, struct file *filep) { struct wbrc_pvt_data *wbrc_data = g_wbrc_data; int ret = 0; WBRC_LOCK(wbrc_data); if (wbrc_data->bt_dev_opened) { pr_err("%s already opened\n", __func__); WBRC_UNLOCK(wbrc_data); return -EFAULT; } wbrc_data->bt_dev_opened = TRUE; pr_info("%s Device opened %d time(s)\n", __func__, wbrc_data->bt_dev_opened); filep->private_data = wbrc_data; /* If wlan_on_ack is false means, the dev_open is called during WLAN ON */ if (wbrc_data->wlan_on_ack == FALSE) { /* unlock mutex before wait as wbrc_wlan_on_ack also holds same mutex */ WBRC_UNLOCK(wbrc_data); pr_info("%s: wait for wlan_on_ack\n", __func__); /* Wait till wlan is ON */ wbrc_reset_wait_on_condition(&wbrc_data->wlan_on_waitq, &wbrc_data->wlan_on_ack, TRUE); if (wbrc_data->wlan_on_ack == FALSE) { pr_err("%s: WLAN ON timeout\n", __func__); } else { pr_info("%s: got wlan_on_ack\n", __func__); } } else { pr_info("%s: wlan_on_ack is TRUE\n", __func__); WBRC_UNLOCK(wbrc_data); } pr_info("%s Done\n", __func__); return ret; } static int wbrc_bt_dev_release(struct inode *inodep, struct file *filep) { struct wbrc_pvt_data *wbrc_data = filep->private_data; WBRC_LOCK(wbrc_data); pr_info("%s Device closed %d\n", __func__, wbrc_data->bt_dev_opened); wbrc_data->bt_dev_opened = FALSE; WBRC_UNLOCK(wbrc_data); wake_up_interruptible(&wbrc_data->outmsg_waitq); return 0; } void wbrc_signal_bt_reset(struct wbrc_pvt_data *wbrc_data) { pr_info("%s\n", __func__); /* Below message will be read by userspace using .read */ wbrc_data->wl2bt_message[0] = HEADER_DIR_WL2BT; // Minimal Header wbrc_data->wl2bt_message[1] = 2; // Length wbrc_data->wl2bt_message[2] = TYPE_WIFI_CMD; // Type wbrc_data->wl2bt_message[3] = CMD_RESET_BT_WITH_ACK; // Value wbrc_data->read_data_available = TRUE; smp_wmb(); wake_up_interruptible(&wbrc_data->outmsg_waitq); } int wbrc_init(void) { int err = 0; struct wbrc_pvt_data *wbrc_data; pr_info("%s\n", __func__); wbrc_data = kzalloc(sizeof(struct wbrc_pvt_data), GFP_KERNEL); if (wbrc_data == NULL) { return -ENOMEM; } mutex_init(&wbrc_data->wbrc_mutex); init_waitqueue_head(&wbrc_data->bt_reset_waitq); init_waitqueue_head(&wbrc_data->wlan_reset_waitq); init_waitqueue_head(&wbrc_data->wlan_on_waitq); init_waitqueue_head(&wbrc_data->outmsg_waitq); /* Set wlan_on_ack in bootup so that 1st BT ON without WLAN does not wait */ wbrc_data->wlan_on_ack = TRUE; g_wbrc_data = wbrc_data; wbrc_data->wbrc_bt_dev_major_number = register_chrdev(0, DEVICE_NAME, &wbrc_bt_dev_fops); err = wbrc_data->wbrc_bt_dev_major_number; if (wbrc_data->wbrc_bt_dev_major_number < 0) { pr_alert("wbrc_sequencer failed to register a major number\n"); goto err_register; } wbrc_data->wbrc_bt_dev_class = class_create(THIS_MODULE, CLASS_NAME); err = PTR_ERR(wbrc_data->wbrc_bt_dev_class); if (IS_ERR(wbrc_data->wbrc_bt_dev_class)) { pr_alert("Failed to register device class\n"); goto err_class; } wbrc_data->wbrc_bt_dev_device = device_create( wbrc_data->wbrc_bt_dev_class, NULL, MKDEV(wbrc_data->wbrc_bt_dev_major_number, 0), NULL, DEVICE_NAME); err = PTR_ERR(wbrc_data->wbrc_bt_dev_device); if (IS_ERR(wbrc_data->wbrc_bt_dev_device)) { pr_alert("Failed to create the device\n"); goto err_device; } pr_info("device class created correctly\n"); return 0; err_device: class_destroy(wbrc_data->wbrc_bt_dev_class); err_class: unregister_chrdev(wbrc_data->wbrc_bt_dev_major_number, DEVICE_NAME); err_register: kfree(wbrc_data); g_wbrc_data = NULL; return err; } void wbrc_exit(void) { struct wbrc_pvt_data *wbrc_data = g_wbrc_data; pr_info("%s\n", __func__); wake_up_interruptible(&wbrc_data->outmsg_waitq); device_destroy(wbrc_data->wbrc_bt_dev_class, MKDEV(wbrc_data->wbrc_bt_dev_major_number, 0)); class_destroy(wbrc_data->wbrc_bt_dev_class); unregister_chrdev(wbrc_data->wbrc_bt_dev_major_number, DEVICE_NAME); kfree(wbrc_data); g_wbrc_data = NULL; } #ifndef BCMDHD_MODULAR /* Required only for Built-in DHD */ module_init(wbrc_init); module_exit(wbrc_exit); #endif /* BOARD_MODULAR */ MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION(DESCRIPTION); MODULE_AUTHOR(AUTHOR); /* * Wait until the condition *var == condition is met. * Returns 0 if the @condition evaluated to false after the timeout elapsed * Returns 1 if the @condition evaluated to true */ #define WBRC_RESET_WAIT_TIMEOUT 4000 int wbrc_reset_wait_on_condition(wait_queue_head_t *reset_waitq, uint *var, uint condition) { int timeout; /* Convert timeout in millsecond to jiffies */ timeout = msecs_to_jiffies(WBRC_RESET_WAIT_TIMEOUT); timeout = wait_event_timeout(*reset_waitq, (*var == condition), timeout); return timeout; } int wbrc_wlan_on_ack(void) { struct wbrc_pvt_data *wbrc_data = g_wbrc_data; WBRC_LOCK(wbrc_data); if (wbrc_data->wlan_on_ack == FALSE) { pr_info("%s: set wlan_on_ack\n", __func__); wbrc_data->wlan_on_ack = TRUE; smp_wmb(); wake_up(&wbrc_data->wlan_on_waitq); } WBRC_UNLOCK(wbrc_data); return 0; } int wbrc_wlan_on_started(void) { struct wbrc_pvt_data *wbrc_data = g_wbrc_data; WBRC_LOCK(wbrc_data); pr_info("%s: reset wlan_on_ack\n", __func__); wbrc_data->wlan_on_ack = FALSE; WBRC_UNLOCK(wbrc_data); return 0; } /* WBRC_LOCK should be held from caller */ int wbrc_bt_reset_ack(struct wbrc_pvt_data *wbrc_data) { pr_info("%s\n", __func__); wbrc_data->bt_reset_ack = TRUE; smp_wmb(); wake_up(&wbrc_data->bt_reset_waitq); return 0; } int wbrc_wl2bt_reset(void) { struct wbrc_pvt_data *wbrc_data = g_wbrc_data; pr_info("%s\n", __func__); WBRC_LOCK(wbrc_data); if (!wbrc_data->bt_dev_opened) { pr_info("%s: no BT\n", __func__); WBRC_UNLOCK(wbrc_data); return -1; } /* bt_reset_ack will be set after BT acks for reset */ wbrc_data->bt_reset_ack = FALSE; wbrc_signal_bt_reset(wbrc_data); WBRC_UNLOCK(wbrc_data); /* Wait till BT reset is done */ wbrc_reset_wait_on_condition(&wbrc_data->bt_reset_waitq, &wbrc_data->bt_reset_ack, TRUE); if (wbrc_data->bt_reset_ack == FALSE) { pr_err("%s: BT reset timeout\n", __func__); } else { pr_info("%s: got bt_reset_ack\n", __func__); } return 0; } EXPORT_SYMBOL(wbrc_wl2bt_reset); #ifdef WBRC_BT2WL_RESET extern void dhd_wbrc_wl_trap(void); #endif /* WBRC_BT2WL_RESET */ /* WBRC_LOCK should be held from caller */ static int wbrc_signal_wlan_reset(struct wbrc_pvt_data *wbrc_data) { #ifdef WBRC_BT2WL_RESET /* Force trap wl */ dhd_wbrc_wl_trap(); #endif /* WBRC_BT2WL_RESET */ wbrc_wl_reset_ack(wbrc_data); return 0; } /* WBRC_LOCK should be held from caller */ static int wbrc_wl_reset_ack(struct wbrc_pvt_data *wbrc_data) { pr_info("%s\n", __func__); wbrc_data->wlan_reset_ack = TRUE; /* Below message will be read by userspace using .read */ wbrc_data->wl2bt_message[0] = HEADER_DIR_WL2BT; // Minimal Header wbrc_data->wl2bt_message[1] = 2; // Length wbrc_data->wl2bt_message[2] = TYPE_WIFI_ACK; // Type wbrc_data->wl2bt_message[3] = ACK_RESET_WIFI_COMPLETE; // Value wbrc_data->read_data_available = TRUE; smp_wmb(); wake_up_interruptible(&wbrc_data->outmsg_waitq); smp_wmb(); wake_up(&wbrc_data->wlan_reset_waitq); return 0; } /* WBRC_LOCK should be held from caller */ static int wbrc_bt2wl_reset(void) { int ret = 0; struct wbrc_pvt_data *wbrc_data = g_wbrc_data; pr_info("%s\n", __func__); wbrc_data->wlan_reset_ack = FALSE; wbrc_signal_wlan_reset(wbrc_data); /* Wait till WLAN reset is done */ wbrc_reset_wait_on_condition(&wbrc_data->wlan_reset_waitq, &wbrc_data->wlan_reset_ack, TRUE); if (wbrc_data->wlan_reset_ack == FALSE) { pr_err("%s: WLAN reset timeout\n", __func__); ret = -1; } return ret; }