提交 4aa769b9 编写于 作者: M Marcel Holtmann 提交者: David S. Miller

[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: NMarcel Holtmann <marcel@holtmann.org>
Signed-off-by: NDavid S. Miller <davem@davemloft.net>
上级 f6ccf554
/* /*
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/config.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/major.h> #include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/poll.h> #include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/random.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/miscdevice.h> #include <linux/miscdevice.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <net/bluetooth/bluetooth.h> #include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.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
#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 hci_vhci_open(struct hci_dev *hdev) static int vhci_open_dev(struct hci_dev *hdev)
{ {
set_bit(HCI_RUNNING, &hdev->flags); set_bit(HCI_RUNNING, &hdev->flags);
return 0;
}
static int hci_vhci_flush(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; return 0;
} }
static int hci_vhci_close(struct hci_dev *hdev) static int vhci_close_dev(struct hci_dev *hdev)
{ {
struct vhci_data *vhci = hdev->driver_data;
if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
return 0; return 0;
hci_vhci_flush(hdev); skb_queue_purge(&vhci->readq);
return 0; 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; return 0;
kfree(vhci);
} }
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_dev* hdev = (struct hci_dev *) skb->dev;
struct hci_vhci_struct *hci_vhci; struct vhci_data *vhci;
if (!hdev) { if (!hdev) {
BT_ERR("Frame for uknown device (hdev=NULL)"); BT_ERR("Frame for unknown HCI device (hdev=NULL)");
return -ENODEV; return -ENODEV;
} }
if (!test_bit(HCI_RUNNING, &hdev->flags)) if (!test_bit(HCI_RUNNING, &hdev->flags))
return -EBUSY; return -EBUSY;
hci_vhci = (struct hci_vhci_struct *) hdev->driver_data; vhci = hdev->driver_data;
memcpy(skb_push(skb, 1), &skb->pkt_type, 1); memcpy(skb_push(skb, 1), &skb->pkt_type, 1);
skb_queue_tail(&hci_vhci->readq, skb); skb_queue_tail(&vhci->readq, skb);
if (vhci->flags & VHCI_FASYNC)
kill_fasync(&vhci->fasync, SIGIO, POLL_IN);
if (hci_vhci->flags & VHCI_FASYNC) wake_up_interruptible(&vhci->read_wait);
kill_fasync(&hci_vhci->fasync, SIGIO, POLL_IN);
wake_up_interruptible(&hci_vhci->read_wait);
return 0; return 0;
} }
/* Character device part */ static void vhci_destruct(struct hci_dev *hdev)
/* 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; kfree(hdev->driver_data);
poll_wait(file, &hci_vhci->read_wait, wait);
if (!skb_queue_empty(&hci_vhci->readq))
return POLLIN | POLLRDNORM;
return POLLOUT | POLLWRNORM;
} }
/* Get packet from user space buffer(already verified) */ static inline ssize_t vhci_get_user(struct vhci_data *vhci,
static inline ssize_t hci_vhci_get_user(struct hci_vhci_struct *hci_vhci, const char __user *buf, size_t count) const char __user *buf, size_t count)
{ {
struct sk_buff *skb; struct sk_buff *skb;
if (count > HCI_MAX_FRAME_SIZE) if (count > HCI_MAX_FRAME_SIZE)
return -EINVAL; return -EINVAL;
if (!(skb = bt_skb_alloc(count, GFP_KERNEL))) skb = bt_skb_alloc(count, GFP_KERNEL);
if (!skb)
return -ENOMEM; return -ENOMEM;
if (copy_from_user(skb_put(skb, count), buf, count)) { if (copy_from_user(skb_put(skb, count), buf, count)) {
...@@ -142,7 +140,7 @@ static inline ssize_t hci_vhci_get_user(struct hci_vhci_struct *hci_vhci, const ...@@ -142,7 +140,7 @@ static inline ssize_t hci_vhci_get_user(struct hci_vhci_struct *hci_vhci, const
return -EFAULT; return -EFAULT;
} }
skb->dev = (void *) hci_vhci->hdev; skb->dev = (void *) vhci->hdev;
skb->pkt_type = *((__u8 *) skb->data); skb->pkt_type = *((__u8 *) skb->data);
skb_pull(skb, 1); skb_pull(skb, 1);
...@@ -151,79 +149,72 @@ static inline ssize_t hci_vhci_get_user(struct hci_vhci_struct *hci_vhci, const ...@@ -151,79 +149,72 @@ static inline ssize_t hci_vhci_get_user(struct hci_vhci_struct *hci_vhci, const
return count; return count;
} }
/* Write */ static inline ssize_t vhci_put_user(struct vhci_data *vhci,
static ssize_t hci_vhci_chr_write(struct file * file, const char __user * buf, struct sk_buff *skb, char __user *buf, int count)
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)
{
int len = count, total = 0;
char __user *ptr = buf; 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)) if (copy_to_user(ptr, skb->data, len))
return -EFAULT; return -EFAULT;
total += len; total += len;
hci_vhci->hdev->stat.byte_tx += len; vhci->hdev->stat.byte_tx += len;
switch (skb->pkt_type) { switch (skb->pkt_type) {
case HCI_COMMAND_PKT: case HCI_COMMAND_PKT:
hci_vhci->hdev->stat.cmd_tx++; vhci->hdev->stat.cmd_tx++;
break; break;
case HCI_ACLDATA_PKT: case HCI_ACLDATA_PKT:
hci_vhci->hdev->stat.acl_tx++; vhci->hdev->stat.acl_tx++;
break; break;
case HCI_SCODATA_PKT: case HCI_SCODATA_PKT:
hci_vhci->hdev->stat.cmd_tx++; vhci->hdev->stat.cmd_tx++;
break; break;
}; };
return total; return total;
} }
/* Read */ static loff_t vhci_llseek(struct file * file, loff_t offset, int origin)
static ssize_t hci_vhci_chr_read(struct file * file, char __user * buf, size_t count, loff_t *pos) {
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); DECLARE_WAITQUEUE(wait, current);
struct vhci_data *vhci = file->private_data;
struct sk_buff *skb; struct sk_buff *skb;
ssize_t ret = 0; ssize_t ret = 0;
add_wait_queue(&hci_vhci->read_wait, &wait); add_wait_queue(&vhci->read_wait, &wait);
while (count) { while (count) {
set_current_state(TASK_INTERRUPTIBLE); set_current_state(TASK_INTERRUPTIBLE);
/* Read frames from device queue */ skb = skb_dequeue(&vhci->readq);
if (!(skb = skb_dequeue(&hci_vhci->readq))) { if (!skb) {
if (file->f_flags & O_NONBLOCK) { if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN; ret = -EAGAIN;
break; break;
} }
if (signal_pending(current)) { if (signal_pending(current)) {
ret = -ERESTARTSYS; ret = -ERESTARTSYS;
break; break;
} }
/* Nothing to read, let's sleep */
schedule(); schedule();
continue; continue;
} }
if (access_ok(VERIFY_WRITE, buf, count)) 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 else
ret = -EFAULT; ret = -EFAULT;
...@@ -231,84 +222,90 @@ static ssize_t hci_vhci_chr_read(struct file * file, char __user * buf, size_t c ...@@ -231,84 +222,90 @@ static ssize_t hci_vhci_chr_read(struct file * file, char __user * buf, size_t c
break; break;
} }
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
remove_wait_queue(&hci_vhci->read_wait, &wait); remove_wait_queue(&vhci->read_wait, &wait);
return ret; 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;
}
static int hci_vhci_chr_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) if (!access_ok(VERIFY_READ, buf, count))
{ return -EFAULT;
return -EINVAL;
return vhci_get_user(vhci, buf, count);
} }
static int hci_vhci_chr_fasync(int fd, struct file *file, int on) static unsigned int vhci_poll(struct file *file, poll_table *wait)
{ {
struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data; struct vhci_data *vhci = file->private_data;
int ret;
if ((ret = fasync_helper(fd, file, on, &hci_vhci->fasync)) < 0) poll_wait(file, &vhci->read_wait, wait);
return ret;
if (on) if (!skb_queue_empty(&vhci->readq))
hci_vhci->flags |= VHCI_FASYNC; return POLLIN | POLLRDNORM;
else
hci_vhci->flags &= ~VHCI_FASYNC;
return 0; 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_open(struct inode *inode, struct file * file) static int vhci_open(struct inode *inode, struct file *file)
{ {
struct hci_vhci_struct *hci_vhci = NULL; struct vhci_data *vhci;
struct hci_dev *hdev; 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; 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); skb_queue_head_init(&vhci->readq);
init_waitqueue_head(&hci_vhci->read_wait); init_waitqueue_head(&vhci->read_wait);
/* Initialize and register HCI device */
hdev = hci_alloc_dev(); hdev = hci_alloc_dev();
if (!hdev) { if (!hdev) {
kfree(hci_vhci); kfree(vhci);
return -ENOMEM; return -ENOMEM;
} }
hci_vhci->hdev = hdev; vhci->hdev = hdev;
hdev->type = HCI_VHCI; 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->open = vhci_open_dev;
hdev->close = hci_vhci_close; hdev->close = vhci_close_dev;
hdev->flush = hci_vhci_flush; hdev->flush = vhci_flush;
hdev->send = hci_vhci_send_frame; hdev->send = vhci_send_frame;
hdev->destruct = hci_vhci_destruct; hdev->destruct = vhci_destruct;
hdev->owner = THIS_MODULE; hdev->owner = THIS_MODULE;
if (hci_register_dev(hdev) < 0) { if (hci_register_dev(hdev) < 0) {
kfree(hci_vhci); BT_ERR("Can't register HCI device");
kfree(vhci);
hci_free_dev(hdev); hci_free_dev(hdev);
return -EBUSY; return -EBUSY;
} }
file->private_data = hci_vhci; file->private_data = vhci;
return nonseekable_open(inode, file); 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 vhci_data *vhci = file->private_data;
struct hci_dev *hdev = hci_vhci->hdev; struct hci_dev *hdev = vhci->hdev;
if (hci_unregister_dev(hdev) < 0) { if (hci_unregister_dev(hdev) < 0) {
BT_ERR("Can't unregister HCI device %s", hdev->name); 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) ...@@ -317,48 +314,71 @@ static int hci_vhci_chr_close(struct inode *inode, struct file *file)
hci_free_dev(hdev); hci_free_dev(hdev);
file->private_data = NULL; file->private_data = NULL;
return 0;
}
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; return 0;
} }
static struct file_operations hci_vhci_fops = { static struct file_operations vhci_fops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.llseek = hci_vhci_chr_lseek, .llseek = vhci_llseek,
.read = hci_vhci_chr_read, .read = vhci_read,
.write = hci_vhci_chr_write, .write = vhci_write,
.poll = hci_vhci_chr_poll, .poll = vhci_poll,
.ioctl = hci_vhci_chr_ioctl, .ioctl = vhci_ioctl,
.open = hci_vhci_chr_open, .open = vhci_open,
.release = hci_vhci_chr_close, .release = vhci_release,
.fasync = hci_vhci_chr_fasync .fasync = vhci_fasync,
}; };
static struct miscdevice hci_vhci_miscdev= static struct miscdevice vhci_miscdev= {
{ .name = "vhci",
VHCI_MINOR, .fops = &vhci_fops,
"hci_vhci",
&hci_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)) { vhci_miscdev.minor = minor;
BT_ERR("Can't register misc device %d\n", VHCI_MINOR);
if (misc_register(&vhci_miscdev) < 0) {
BT_ERR("Can't register misc device with minor %d", minor);
return -EIO; return -EIO;
} }
return 0; 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_init(vhci_init);
module_exit(hci_vhci_cleanup); module_exit(vhci_exit);
module_param(minor, int, 0444);
MODULE_PARM_DESC(minor, "Miscellaneous minor device number");
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>"); MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth VHCI driver ver " VERSION); MODULE_DESCRIPTION("Bluetooth virtual HCI driver ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
/*
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 */
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册