root/drivers/bluetooth/hci_vhci.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 *  Bluetooth virtual HCI driver
 *
 *  Copyright (C) 2000-2001  Qualcomm Incorporated
 *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
 *  Copyright (C) 2004-2006  Marcel Holtmann <marcel@holtmann.org>
 */

#include <linux/module.h>
#include <linux/unaligned.h>

#include <linux/atomic.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include <linux/skbuff.h>
#include <linux/miscdevice.h>
#include <linux/debugfs.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>

#define VERSION "1.5"

static bool amp;

struct vhci_data {
        struct hci_dev *hdev;

        wait_queue_head_t read_wait;
        struct sk_buff_head readq;

        struct mutex open_mutex;
        struct delayed_work open_timeout;
        struct work_struct suspend_work;

        bool suspended;
        bool wakeup;
        __u16 msft_opcode;
        bool aosp_capable;
        atomic_t initialized;
};

static int vhci_open_dev(struct hci_dev *hdev)
{
        return 0;
}

static int vhci_close_dev(struct hci_dev *hdev)
{
        struct vhci_data *data = hci_get_drvdata(hdev);

        skb_queue_purge(&data->readq);

        return 0;
}

static int vhci_flush(struct hci_dev *hdev)
{
        struct vhci_data *data = hci_get_drvdata(hdev);

        skb_queue_purge(&data->readq);

        return 0;
}

static int vhci_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
        struct vhci_data *data = hci_get_drvdata(hdev);

        memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1);

        skb_queue_tail(&data->readq, skb);

        if (atomic_read(&data->initialized))
                wake_up_interruptible(&data->read_wait);
        return 0;
}

static int vhci_get_data_path_id(struct hci_dev *hdev, u8 *data_path_id)
{
        *data_path_id = 0;
        return 0;
}

static int vhci_get_codec_config_data(struct hci_dev *hdev, __u8 type,
                                      struct bt_codec *codec, __u8 *vnd_len,
                                      __u8 **vnd_data)
{
        if (type != ESCO_LINK)
                return -EINVAL;

        *vnd_len = 0;
        *vnd_data = NULL;
        return 0;
}

static bool vhci_wakeup(struct hci_dev *hdev)
{
        struct vhci_data *data = hci_get_drvdata(hdev);

        return data->wakeup;
}

static ssize_t force_suspend_read(struct file *file, char __user *user_buf,
                                  size_t count, loff_t *ppos)
{
        struct vhci_data *data = file->private_data;
        char buf[3];

        buf[0] = data->suspended ? 'Y' : 'N';
        buf[1] = '\n';
        buf[2] = '\0';
        return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
}

static void vhci_suspend_work(struct work_struct *work)
{
        struct vhci_data *data = container_of(work, struct vhci_data,
                                              suspend_work);

        if (data->suspended)
                hci_suspend_dev(data->hdev);
        else
                hci_resume_dev(data->hdev);
}

static ssize_t force_suspend_write(struct file *file,
                                   const char __user *user_buf,
                                   size_t count, loff_t *ppos)
{
        struct vhci_data *data = file->private_data;
        bool enable;
        int err;

        err = kstrtobool_from_user(user_buf, count, &enable);
        if (err)
                return err;

        if (data->suspended == enable)
                return -EALREADY;

        data->suspended = enable;

        schedule_work(&data->suspend_work);

        return count;
}

static const struct file_operations force_suspend_fops = {
        .open           = simple_open,
        .read           = force_suspend_read,
        .write          = force_suspend_write,
        .llseek         = default_llseek,
};

static ssize_t force_wakeup_read(struct file *file, char __user *user_buf,
                                 size_t count, loff_t *ppos)
{
        struct vhci_data *data = file->private_data;
        char buf[3];

        buf[0] = data->wakeup ? 'Y' : 'N';
        buf[1] = '\n';
        buf[2] = '\0';
        return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
}

static ssize_t force_wakeup_write(struct file *file,
                                  const char __user *user_buf, size_t count,
                                  loff_t *ppos)
{
        struct vhci_data *data = file->private_data;
        bool enable;
        int err;

        err = kstrtobool_from_user(user_buf, count, &enable);
        if (err)
                return err;

        if (data->wakeup == enable)
                return -EALREADY;

        data->wakeup = enable;

        return count;
}

static const struct file_operations force_wakeup_fops = {
        .open           = simple_open,
        .read           = force_wakeup_read,
        .write          = force_wakeup_write,
        .llseek         = default_llseek,
};

static int msft_opcode_set(void *data, u64 val)
{
        struct vhci_data *vhci = data;

        if (val > 0xffff || hci_opcode_ogf(val) != 0x3f)
                return -EINVAL;

        if (vhci->msft_opcode)
                return -EALREADY;

        vhci->msft_opcode = val;

        return 0;
}

static int msft_opcode_get(void *data, u64 *val)
{
        struct vhci_data *vhci = data;

        *val = vhci->msft_opcode;

        return 0;
}

DEFINE_DEBUGFS_ATTRIBUTE(msft_opcode_fops, msft_opcode_get, msft_opcode_set,
                         "%llu\n");

static ssize_t aosp_capable_read(struct file *file, char __user *user_buf,
                                 size_t count, loff_t *ppos)
{
        struct vhci_data *vhci = file->private_data;
        char buf[3];

        buf[0] = vhci->aosp_capable ? 'Y' : 'N';
        buf[1] = '\n';
        buf[2] = '\0';
        return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
}

static ssize_t aosp_capable_write(struct file *file,
                                  const char __user *user_buf, size_t count,
                                  loff_t *ppos)
{
        struct vhci_data *vhci = file->private_data;
        bool enable;
        int err;

        err = kstrtobool_from_user(user_buf, count, &enable);
        if (err)
                return err;

        if (!enable)
                return -EINVAL;

        if (vhci->aosp_capable)
                return -EALREADY;

        vhci->aosp_capable = enable;

        return count;
}

static const struct file_operations aosp_capable_fops = {
        .open           = simple_open,
        .read           = aosp_capable_read,
        .write          = aosp_capable_write,
        .llseek         = default_llseek,
};

static int vhci_setup(struct hci_dev *hdev)
{
        struct vhci_data *vhci = hci_get_drvdata(hdev);

        if (vhci->msft_opcode)
                hci_set_msft_opcode(hdev, vhci->msft_opcode);

        if (vhci->aosp_capable)
                hci_set_aosp_capable(hdev);

        return 0;
}

static void vhci_coredump(struct hci_dev *hdev)
{
        /* No need to do anything */
}

static void vhci_coredump_hdr(struct hci_dev *hdev, struct sk_buff *skb)
{
        const char *buf;

        buf = "Controller Name: vhci_ctrl\n";
        skb_put_data(skb, buf, strlen(buf));

        buf = "Firmware Version: vhci_fw\n";
        skb_put_data(skb, buf, strlen(buf));

        buf = "Driver: vhci_drv\n";
        skb_put_data(skb, buf, strlen(buf));

        buf = "Vendor: vhci\n";
        skb_put_data(skb, buf, strlen(buf));
}

#define MAX_COREDUMP_LINE_LEN   40

struct devcoredump_test_data {
        enum devcoredump_state state;
        unsigned int timeout;
        char data[MAX_COREDUMP_LINE_LEN];
};

static inline void force_devcd_timeout(struct hci_dev *hdev,
                                       unsigned int timeout)
{
#ifdef CONFIG_DEV_COREDUMP
        hdev->dump.timeout = secs_to_jiffies(timeout);
#endif
}

static ssize_t force_devcd_write(struct file *file, const char __user *user_buf,
                                 size_t count, loff_t *ppos)
{
        struct vhci_data *data = file->private_data;
        struct hci_dev *hdev = data->hdev;
        struct sk_buff *skb = NULL;
        struct devcoredump_test_data dump_data;
        size_t data_size;
        int ret;

        if (count < offsetof(struct devcoredump_test_data, data) ||
            count > sizeof(dump_data))
                return -EINVAL;

        if (copy_from_user(&dump_data, user_buf, count))
                return -EFAULT;

        data_size = count - offsetof(struct devcoredump_test_data, data);
        skb = alloc_skb(data_size, GFP_ATOMIC);
        if (!skb)
                return -ENOMEM;
        skb_put_data(skb, &dump_data.data, data_size);

        hci_devcd_register(hdev, vhci_coredump, vhci_coredump_hdr, NULL);

        /* Force the devcoredump timeout */
        if (dump_data.timeout)
                force_devcd_timeout(hdev, dump_data.timeout);

        ret = hci_devcd_init(hdev, skb->len);
        if (ret) {
                BT_ERR("Failed to generate devcoredump");
                kfree_skb(skb);
                return ret;
        }

        hci_devcd_append(hdev, skb);

        switch (dump_data.state) {
        case HCI_DEVCOREDUMP_DONE:
                hci_devcd_complete(hdev);
                break;
        case HCI_DEVCOREDUMP_ABORT:
                hci_devcd_abort(hdev);
                break;
        case HCI_DEVCOREDUMP_TIMEOUT:
                /* Do nothing */
                break;
        default:
                return -EINVAL;
        }

        return count;
}

static const struct file_operations force_devcoredump_fops = {
        .open           = simple_open,
        .write          = force_devcd_write,
};

static void vhci_debugfs_init(struct vhci_data *data)
{
        struct hci_dev *hdev = data->hdev;

        debugfs_create_file("force_suspend", 0644, hdev->debugfs, data,
                            &force_suspend_fops);

        debugfs_create_file("force_wakeup", 0644, hdev->debugfs, data,
                            &force_wakeup_fops);

        if (IS_ENABLED(CONFIG_BT_MSFTEXT))
                debugfs_create_file("msft_opcode", 0644, hdev->debugfs, data,
                                    &msft_opcode_fops);

        if (IS_ENABLED(CONFIG_BT_AOSPEXT))
                debugfs_create_file("aosp_capable", 0644, hdev->debugfs, data,
                                    &aosp_capable_fops);

        debugfs_create_file("force_devcoredump", 0644, hdev->debugfs, data,
                            &force_devcoredump_fops);
}

static int __vhci_create_device(struct vhci_data *data, __u8 opcode)
{
        struct hci_dev *hdev;
        struct sk_buff *skb;

        if (data->hdev)
                return -EBADFD;

        /* bits 2-5 are reserved (must be zero) */
        if (opcode & 0x3c)
                return -EINVAL;

        skb = bt_skb_alloc(4, GFP_KERNEL);
        if (!skb)
                return -ENOMEM;

        hdev = hci_alloc_dev();
        if (!hdev) {
                kfree_skb(skb);
                return -ENOMEM;
        }

        data->hdev = hdev;

        hdev->bus = HCI_VIRTUAL;
        hci_set_drvdata(hdev, data);

        hdev->open  = vhci_open_dev;
        hdev->close = vhci_close_dev;
        hdev->flush = vhci_flush;
        hdev->send  = vhci_send_frame;
        hdev->get_data_path_id = vhci_get_data_path_id;
        hdev->get_codec_config_data = vhci_get_codec_config_data;
        hdev->wakeup = vhci_wakeup;
        hdev->setup = vhci_setup;
        hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP);
        hci_set_quirk(hdev, HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED);

        /* bit 6 is for external configuration */
        if (opcode & 0x40)
                hci_set_quirk(hdev, HCI_QUIRK_EXTERNAL_CONFIG);

        /* bit 7 is for raw device */
        if (opcode & 0x80)
                hci_set_quirk(hdev, HCI_QUIRK_RAW_DEVICE);

        if (hci_register_dev(hdev) < 0) {
                BT_ERR("Can't register HCI device");
                hci_free_dev(hdev);
                data->hdev = NULL;
                kfree_skb(skb);
                return -EBUSY;
        }

        if (!IS_ERR_OR_NULL(hdev->debugfs))
                vhci_debugfs_init(data);

        hci_skb_pkt_type(skb) = HCI_VENDOR_PKT;

        skb_put_u8(skb, 0xff);
        skb_put_u8(skb, opcode);
        put_unaligned_le16(hdev->id, skb_put(skb, 2));
        skb_queue_head(&data->readq, skb);
        atomic_inc(&data->initialized);

        wake_up_interruptible(&data->read_wait);
        return 0;
}

static int vhci_create_device(struct vhci_data *data, __u8 opcode)
{
        int err;

        mutex_lock(&data->open_mutex);
        err = __vhci_create_device(data, opcode);
        mutex_unlock(&data->open_mutex);

        return err;
}

static inline ssize_t vhci_get_user(struct vhci_data *data,
                                    struct iov_iter *from)
{
        size_t len = iov_iter_count(from);
        struct sk_buff *skb;
        __u8 pkt_type, opcode;
        int ret;

        if (len < 2 || len > HCI_MAX_FRAME_SIZE)
                return -EINVAL;

        skb = bt_skb_alloc(len, GFP_KERNEL);
        if (!skb)
                return -ENOMEM;

        if (!copy_from_iter_full(skb_put(skb, len), len, from)) {
                kfree_skb(skb);
                return -EFAULT;
        }

        pkt_type = *((__u8 *) skb->data);
        skb_pull(skb, 1);

        switch (pkt_type) {
        case HCI_EVENT_PKT:
        case HCI_ACLDATA_PKT:
        case HCI_SCODATA_PKT:
        case HCI_ISODATA_PKT:
                if (!data->hdev) {
                        kfree_skb(skb);
                        return -ENODEV;
                }

                hci_skb_pkt_type(skb) = pkt_type;

                ret = hci_recv_frame(data->hdev, skb);
                break;

        case HCI_VENDOR_PKT:
                cancel_delayed_work_sync(&data->open_timeout);

                opcode = *((__u8 *) skb->data);
                skb_pull(skb, 1);

                if (skb->len > 0) {
                        kfree_skb(skb);
                        return -EINVAL;
                }

                kfree_skb(skb);

                ret = vhci_create_device(data, opcode);
                break;

        default:
                kfree_skb(skb);
                return -EINVAL;
        }

        return (ret < 0) ? ret : len;
}

static inline ssize_t vhci_put_user(struct vhci_data *data,
                                    struct sk_buff *skb,
                                    char __user *buf, int count)
{
        char __user *ptr = buf;
        int len;

        len = min_t(unsigned int, skb->len, count);

        if (copy_to_user(ptr, skb->data, len))
                return -EFAULT;

        if (!data->hdev)
                return len;

        data->hdev->stat.byte_tx += len;

        switch (hci_skb_pkt_type(skb)) {
        case HCI_COMMAND_PKT:
                data->hdev->stat.cmd_tx++;
                break;
        case HCI_ACLDATA_PKT:
                data->hdev->stat.acl_tx++;
                break;
        case HCI_SCODATA_PKT:
                data->hdev->stat.sco_tx++;
                break;
        }

        return len;
}

static ssize_t vhci_read(struct file *file,
                         char __user *buf, size_t count, loff_t *pos)
{
        struct vhci_data *data = file->private_data;
        struct sk_buff *skb;
        ssize_t ret = 0;

        while (count) {
                skb = skb_dequeue(&data->readq);
                if (skb) {
                        ret = vhci_put_user(data, skb, buf, count);
                        if (ret < 0)
                                skb_queue_head(&data->readq, skb);
                        else
                                kfree_skb(skb);
                        break;
                }

                if (file->f_flags & O_NONBLOCK) {
                        ret = -EAGAIN;
                        break;
                }

                ret = wait_event_interruptible(data->read_wait,
                                               !skb_queue_empty(&data->readq));
                if (ret < 0)
                        break;
        }

        return ret;
}

static ssize_t vhci_write(struct kiocb *iocb, struct iov_iter *from)
{
        struct file *file = iocb->ki_filp;
        struct vhci_data *data = file->private_data;

        return vhci_get_user(data, from);
}

static __poll_t vhci_poll(struct file *file, poll_table *wait)
{
        struct vhci_data *data = file->private_data;

        poll_wait(file, &data->read_wait, wait);

        if (!skb_queue_empty(&data->readq))
                return EPOLLIN | EPOLLRDNORM;

        return EPOLLOUT | EPOLLWRNORM;
}

static void vhci_open_timeout(struct work_struct *work)
{
        struct vhci_data *data = container_of(work, struct vhci_data,
                                              open_timeout.work);

        vhci_create_device(data, 0x00);
}

static int vhci_open(struct inode *inode, struct file *file)
{
        struct vhci_data *data;

        data = kzalloc_obj(*data);
        if (!data)
                return -ENOMEM;

        skb_queue_head_init(&data->readq);
        init_waitqueue_head(&data->read_wait);

        mutex_init(&data->open_mutex);
        INIT_DELAYED_WORK(&data->open_timeout, vhci_open_timeout);
        INIT_WORK(&data->suspend_work, vhci_suspend_work);

        file->private_data = data;
        nonseekable_open(inode, file);

        schedule_delayed_work(&data->open_timeout, secs_to_jiffies(1));

        return 0;
}

static void vhci_debugfs_remove(struct hci_dev *hdev)
{
        debugfs_lookup_and_remove("force_suspend", hdev->debugfs);

        debugfs_lookup_and_remove("force_wakeup", hdev->debugfs);

        if (IS_ENABLED(CONFIG_BT_MSFTEXT))
                debugfs_lookup_and_remove("msft_opcode", hdev->debugfs);

        if (IS_ENABLED(CONFIG_BT_AOSPEXT))
                debugfs_lookup_and_remove("aosp_capable", hdev->debugfs);

        debugfs_lookup_and_remove("force_devcoredump", hdev->debugfs);
}

static int vhci_release(struct inode *inode, struct file *file)
{
        struct vhci_data *data = file->private_data;
        struct hci_dev *hdev;

        cancel_delayed_work_sync(&data->open_timeout);
        flush_work(&data->suspend_work);

        hdev = data->hdev;

        if (hdev) {
                if (!IS_ERR_OR_NULL(hdev->debugfs))
                        vhci_debugfs_remove(hdev);
                hci_unregister_dev(hdev);
                hci_free_dev(hdev);
        }

        skb_queue_purge(&data->readq);
        file->private_data = NULL;
        kfree(data);

        return 0;
}

static const struct file_operations vhci_fops = {
        .owner          = THIS_MODULE,
        .read           = vhci_read,
        .write_iter     = vhci_write,
        .poll           = vhci_poll,
        .open           = vhci_open,
        .release        = vhci_release,
};

static struct miscdevice vhci_miscdev = {
        .name   = "vhci",
        .fops   = &vhci_fops,
        .minor  = VHCI_MINOR,
};
module_misc_device(vhci_miscdev);

module_param(amp, bool, 0644);
MODULE_PARM_DESC(amp, "Create AMP controller device");

MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth virtual HCI driver ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
MODULE_ALIAS("devname:vhci");
MODULE_ALIAS_MISCDEV(VHCI_MINOR);