[Bluetooth]: Update and cleanup of the virtual HCI driver

This patch cleans up the virtual HCI driver. It also adds support for
the dynamic minor device number allocation.

Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Marcel Holtmann 2005-08-09 20:27:37 -07:00 committed by David S. Miller
parent f6ccf55419
commit 4aa769b997
2 changed files with 197 additions and 227 deletions

View File

@ -1,229 +1,220 @@
/*
BlueZ - Bluetooth protocol stack for Linux
Copyright (C) 2000-2001 Qualcomm Incorporated
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
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;
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
SOFTWARE IS DISCLAIMED.
*/
/*
* Bluetooth HCI virtual device driver.
*
* $Id: hci_vhci.c,v 1.3 2002/04/17 17:37:20 maxk Exp $
* Bluetooth virtual HCI driver
*
* Copyright (C) 2000-2001 Qualcomm Incorporated
* Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
* Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#define VERSION "1.1"
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/random.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 <asm/system.h>
#include <asm/uaccess.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include "hci_vhci.h"
/* HCI device part */
#ifndef CONFIG_BT_HCIVHCI_DEBUG
#undef BT_DBG
#define BT_DBG(D...)
#endif
static int hci_vhci_open(struct hci_dev *hdev)
#define VERSION "1.2"
static int minor = MISC_DYNAMIC_MINOR;
struct vhci_data {
struct hci_dev *hdev;
unsigned long flags;
wait_queue_head_t read_wait;
struct sk_buff_head readq;
struct fasync_struct *fasync;
};
#define VHCI_FASYNC 0x0010
static struct miscdevice vhci_miscdev;
static int vhci_open_dev(struct hci_dev *hdev)
{
set_bit(HCI_RUNNING, &hdev->flags);
return 0;
}
static int hci_vhci_flush(struct hci_dev *hdev)
static int vhci_close_dev(struct hci_dev *hdev)
{
struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) hdev->driver_data;
skb_queue_purge(&hci_vhci->readq);
return 0;
}
struct vhci_data *vhci = hdev->driver_data;
static int hci_vhci_close(struct hci_dev *hdev)
{
if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
return 0;
hci_vhci_flush(hdev);
skb_queue_purge(&vhci->readq);
return 0;
}
static void hci_vhci_destruct(struct hci_dev *hdev)
static int vhci_flush(struct hci_dev *hdev)
{
struct hci_vhci_struct *vhci;
struct vhci_data *vhci = hdev->driver_data;
if (!hdev) return;
skb_queue_purge(&vhci->readq);
vhci = (struct hci_vhci_struct *) hdev->driver_data;
kfree(vhci);
return 0;
}
static int hci_vhci_send_frame(struct sk_buff *skb)
static int vhci_send_frame(struct sk_buff *skb)
{
struct hci_dev* hdev = (struct hci_dev *) skb->dev;
struct hci_vhci_struct *hci_vhci;
struct vhci_data *vhci;
if (!hdev) {
BT_ERR("Frame for uknown device (hdev=NULL)");
BT_ERR("Frame for unknown HCI device (hdev=NULL)");
return -ENODEV;
}
if (!test_bit(HCI_RUNNING, &hdev->flags))
return -EBUSY;
hci_vhci = (struct hci_vhci_struct *) hdev->driver_data;
vhci = hdev->driver_data;
memcpy(skb_push(skb, 1), &skb->pkt_type, 1);
skb_queue_tail(&hci_vhci->readq, skb);
skb_queue_tail(&vhci->readq, skb);
if (hci_vhci->flags & VHCI_FASYNC)
kill_fasync(&hci_vhci->fasync, SIGIO, POLL_IN);
wake_up_interruptible(&hci_vhci->read_wait);
if (vhci->flags & VHCI_FASYNC)
kill_fasync(&vhci->fasync, SIGIO, POLL_IN);
wake_up_interruptible(&vhci->read_wait);
return 0;
}
/* Character device part */
/* Poll */
static unsigned int hci_vhci_chr_poll(struct file *file, poll_table * wait)
{
struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data;
poll_wait(file, &hci_vhci->read_wait, wait);
if (!skb_queue_empty(&hci_vhci->readq))
return POLLIN | POLLRDNORM;
return POLLOUT | POLLWRNORM;
static void vhci_destruct(struct hci_dev *hdev)
{
kfree(hdev->driver_data);
}
/* Get packet from user space buffer(already verified) */
static inline ssize_t hci_vhci_get_user(struct hci_vhci_struct *hci_vhci, const char __user *buf, size_t count)
static inline ssize_t vhci_get_user(struct vhci_data *vhci,
const char __user *buf, size_t count)
{
struct sk_buff *skb;
if (count > HCI_MAX_FRAME_SIZE)
return -EINVAL;
if (!(skb = bt_skb_alloc(count, GFP_KERNEL)))
skb = bt_skb_alloc(count, GFP_KERNEL);
if (!skb)
return -ENOMEM;
if (copy_from_user(skb_put(skb, count), buf, count)) {
kfree_skb(skb);
return -EFAULT;
}
skb->dev = (void *) hci_vhci->hdev;
skb->dev = (void *) vhci->hdev;
skb->pkt_type = *((__u8 *) skb->data);
skb_pull(skb, 1);
hci_recv_frame(skb);
return count;
}
/* Write */
static ssize_t hci_vhci_chr_write(struct file * file, const char __user * buf,
size_t count, loff_t *pos)
{
struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data;
if (!access_ok(VERIFY_READ, buf, count))
return -EFAULT;
return hci_vhci_get_user(hci_vhci, buf, count);
}
/* Put packet to user space buffer(already verified) */
static inline ssize_t hci_vhci_put_user(struct hci_vhci_struct *hci_vhci,
struct sk_buff *skb, char __user *buf,
int count)
static inline ssize_t vhci_put_user(struct vhci_data *vhci,
struct sk_buff *skb, char __user *buf, int count)
{
int len = count, total = 0;
char __user *ptr = buf;
int len, total = 0;
len = min_t(unsigned int, skb->len, count);
len = min_t(unsigned int, skb->len, len);
if (copy_to_user(ptr, skb->data, len))
return -EFAULT;
total += len;
hci_vhci->hdev->stat.byte_tx += len;
vhci->hdev->stat.byte_tx += len;
switch (skb->pkt_type) {
case HCI_COMMAND_PKT:
hci_vhci->hdev->stat.cmd_tx++;
vhci->hdev->stat.cmd_tx++;
break;
case HCI_ACLDATA_PKT:
hci_vhci->hdev->stat.acl_tx++;
vhci->hdev->stat.acl_tx++;
break;
case HCI_SCODATA_PKT:
hci_vhci->hdev->stat.cmd_tx++;
vhci->hdev->stat.cmd_tx++;
break;
};
return total;
}
/* Read */
static ssize_t hci_vhci_chr_read(struct file * file, char __user * buf, size_t count, loff_t *pos)
static loff_t vhci_llseek(struct file * file, loff_t offset, int origin)
{
return -ESPIPE;
}
static ssize_t vhci_read(struct file * file, char __user * buf, size_t count, loff_t *pos)
{
struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data;
DECLARE_WAITQUEUE(wait, current);
struct vhci_data *vhci = file->private_data;
struct sk_buff *skb;
ssize_t ret = 0;
add_wait_queue(&hci_vhci->read_wait, &wait);
add_wait_queue(&vhci->read_wait, &wait);
while (count) {
set_current_state(TASK_INTERRUPTIBLE);
/* Read frames from device queue */
if (!(skb = skb_dequeue(&hci_vhci->readq))) {
skb = skb_dequeue(&vhci->readq);
if (!skb) {
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
break;
}
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
/* Nothing to read, let's sleep */
schedule();
continue;
}
if (access_ok(VERIFY_WRITE, buf, count))
ret = hci_vhci_put_user(hci_vhci, skb, buf, count);
ret = vhci_put_user(vhci, skb, buf, count);
else
ret = -EFAULT;
@ -231,84 +222,90 @@ static ssize_t hci_vhci_chr_read(struct file * file, char __user * buf, size_t c
break;
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&hci_vhci->read_wait, &wait);
remove_wait_queue(&vhci->read_wait, &wait);
return ret;
}
static loff_t hci_vhci_chr_lseek(struct file * file, loff_t offset, int origin)
static ssize_t vhci_write(struct file *file,
const char __user *buf, size_t count, loff_t *pos)
{
return -ESPIPE;
struct vhci_data *vhci = file->private_data;
if (!access_ok(VERIFY_READ, buf, count))
return -EFAULT;
return vhci_get_user(vhci, buf, count);
}
static int hci_vhci_chr_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
static unsigned int vhci_poll(struct file *file, poll_table *wait)
{
struct vhci_data *vhci = file->private_data;
poll_wait(file, &vhci->read_wait, wait);
if (!skb_queue_empty(&vhci->readq))
return POLLIN | POLLRDNORM;
return POLLOUT | POLLWRNORM;
}
static int vhci_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
return -EINVAL;
}
static int hci_vhci_chr_fasync(int fd, struct file *file, int on)
static int vhci_open(struct inode *inode, struct file *file)
{
struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data;
int ret;
if ((ret = fasync_helper(fd, file, on, &hci_vhci->fasync)) < 0)
return ret;
if (on)
hci_vhci->flags |= VHCI_FASYNC;
else
hci_vhci->flags &= ~VHCI_FASYNC;
return 0;
}
static int hci_vhci_chr_open(struct inode *inode, struct file * file)
{
struct hci_vhci_struct *hci_vhci = NULL;
struct vhci_data *vhci;
struct hci_dev *hdev;
if (!(hci_vhci = kmalloc(sizeof(struct hci_vhci_struct), GFP_KERNEL)))
vhci = kmalloc(sizeof(struct vhci_data), GFP_KERNEL);
if (!vhci)
return -ENOMEM;
memset(hci_vhci, 0, sizeof(struct hci_vhci_struct));
memset(vhci, 0, sizeof(struct vhci_data));
skb_queue_head_init(&hci_vhci->readq);
init_waitqueue_head(&hci_vhci->read_wait);
skb_queue_head_init(&vhci->readq);
init_waitqueue_head(&vhci->read_wait);
/* Initialize and register HCI device */
hdev = hci_alloc_dev();
if (!hdev) {
kfree(hci_vhci);
kfree(vhci);
return -ENOMEM;
}
hci_vhci->hdev = hdev;
vhci->hdev = hdev;
hdev->type = HCI_VHCI;
hdev->driver_data = hci_vhci;
hdev->driver_data = vhci;
SET_HCIDEV_DEV(hdev, vhci_miscdev.dev);
hdev->open = hci_vhci_open;
hdev->close = hci_vhci_close;
hdev->flush = hci_vhci_flush;
hdev->send = hci_vhci_send_frame;
hdev->destruct = hci_vhci_destruct;
hdev->open = vhci_open_dev;
hdev->close = vhci_close_dev;
hdev->flush = vhci_flush;
hdev->send = vhci_send_frame;
hdev->destruct = vhci_destruct;
hdev->owner = THIS_MODULE;
if (hci_register_dev(hdev) < 0) {
kfree(hci_vhci);
BT_ERR("Can't register HCI device");
kfree(vhci);
hci_free_dev(hdev);
return -EBUSY;
}
file->private_data = hci_vhci;
return nonseekable_open(inode, file);
file->private_data = vhci;
return nonseekable_open(inode, file);
}
static int hci_vhci_chr_close(struct inode *inode, struct file *file)
static int vhci_release(struct inode *inode, struct file *file)
{
struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data;
struct hci_dev *hdev = hci_vhci->hdev;
struct vhci_data *vhci = file->private_data;
struct hci_dev *hdev = vhci->hdev;
if (hci_unregister_dev(hdev) < 0) {
BT_ERR("Can't unregister HCI device %s", hdev->name);
@ -317,48 +314,71 @@ static int hci_vhci_chr_close(struct inode *inode, struct file *file)
hci_free_dev(hdev);
file->private_data = NULL;
return 0;
}
static struct file_operations hci_vhci_fops = {
.owner = THIS_MODULE,
.llseek = hci_vhci_chr_lseek,
.read = hci_vhci_chr_read,
.write = hci_vhci_chr_write,
.poll = hci_vhci_chr_poll,
.ioctl = hci_vhci_chr_ioctl,
.open = hci_vhci_chr_open,
.release = hci_vhci_chr_close,
.fasync = hci_vhci_chr_fasync
static int vhci_fasync(int fd, struct file *file, int on)
{
struct vhci_data *vhci = file->private_data;
int err;
err = fasync_helper(fd, file, on, &vhci->fasync);
if (err < 0)
return err;
if (on)
vhci->flags |= VHCI_FASYNC;
else
vhci->flags &= ~VHCI_FASYNC;
return 0;
}
static struct file_operations vhci_fops = {
.owner = THIS_MODULE,
.llseek = vhci_llseek,
.read = vhci_read,
.write = vhci_write,
.poll = vhci_poll,
.ioctl = vhci_ioctl,
.open = vhci_open,
.release = vhci_release,
.fasync = vhci_fasync,
};
static struct miscdevice hci_vhci_miscdev=
{
VHCI_MINOR,
"hci_vhci",
&hci_vhci_fops
static struct miscdevice vhci_miscdev= {
.name = "vhci",
.fops = &vhci_fops,
};
static int __init hci_vhci_init(void)
static int __init vhci_init(void)
{
BT_INFO("VHCI driver ver %s", VERSION);
BT_INFO("Virtual HCI driver ver %s", VERSION);
if (misc_register(&hci_vhci_miscdev)) {
BT_ERR("Can't register misc device %d\n", VHCI_MINOR);
vhci_miscdev.minor = minor;
if (misc_register(&vhci_miscdev) < 0) {
BT_ERR("Can't register misc device with minor %d", minor);
return -EIO;
}
return 0;
}
static void hci_vhci_cleanup(void)
static void __exit vhci_exit(void)
{
misc_deregister(&hci_vhci_miscdev);
if (misc_deregister(&vhci_miscdev) < 0)
BT_ERR("Can't unregister misc device with minor %d", minor);
}
module_init(hci_vhci_init);
module_exit(hci_vhci_cleanup);
module_init(vhci_init);
module_exit(vhci_exit);
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>");
MODULE_DESCRIPTION("Bluetooth VHCI driver ver " VERSION);
MODULE_LICENSE("GPL");
module_param(minor, int, 0444);
MODULE_PARM_DESC(minor, "Miscellaneous minor device number");
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth virtual HCI driver ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");

View File

@ -1,50 +0,0 @@
/*
BlueZ - Bluetooth protocol stack for Linux
Copyright (C) 2000-2001 Qualcomm Incorporated
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
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;
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
SOFTWARE IS DISCLAIMED.
*/
/*
* $Id: hci_vhci.h,v 1.1.1.1 2002/03/08 21:03:15 maxk Exp $
*/
#ifndef __HCI_VHCI_H
#define __HCI_VHCI_H
#ifdef __KERNEL__
struct hci_vhci_struct {
struct hci_dev *hdev;
__u32 flags;
wait_queue_head_t read_wait;
struct sk_buff_head readq;
struct fasync_struct *fasync;
};
/* VHCI device flags */
#define VHCI_FASYNC 0x0010
#endif /* __KERNEL__ */
#define VHCI_DEV "/dev/vhci"
#define VHCI_MINOR 250
#endif /* __HCI_VHCI_H */