📄 hci_core.c
字号:
/* 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.*//* * BlueZ HCI Core. * * $Id: hci_core.c,v 1.22 2001/08/03 04:19:50 maxk Exp $ */#include <linux/config.h>#include <linux/module.h>#include <linux/types.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/skbuff.h>#include <linux/interrupt.h>#include <linux/notifier.h>#include <net/sock.h>#include <asm/system.h>#include <asm/uaccess.h>#include <asm/unaligned.h>#include <net/bluetooth/bluetooth.h>#include <net/bluetooth/bluez.h>#include <net/bluetooth/hci_core.h>#ifndef HCI_CORE_DEBUG#undef DBG#define DBG( A... )#endifstatic void hci_cmd_task(unsigned long arg);static void hci_rx_task(unsigned long arg);static void hci_tx_task(unsigned long arg);static void hci_notify(struct hci_dev *hdev, int event);static rwlock_t hci_task_lock = RW_LOCK_UNLOCKED;/* HCI device list */struct hci_dev *hdev_list[HCI_MAX_DEV];spinlock_t hdev_list_lock;#define GET_HDEV(a) (hdev_list[a])/* HCI protocol list */struct hci_proto *hproto_list[HCI_MAX_PROTO];#define GET_HPROTO(a) (hproto_list[a])/* HCI notifiers list */struct notifier_block *hci_dev_notifier;/* HCI device notifications */int hci_register_notifier(struct notifier_block *nb){ int err, i; struct hci_dev *hdev; if ((err = notifier_chain_register(&hci_dev_notifier, nb))) return err; /* Notify about already registered devices */ spin_lock(&hdev_list_lock); for (i = 0; i < HCI_MAX_DEV; i++) { if (!(hdev = GET_HDEV(i))) continue; if (hdev->flags & HCI_UP) (*nb->notifier_call)(nb, HCI_DEV_UP, hdev); } spin_unlock(&hdev_list_lock); return 0;}int hci_unregister_notifier(struct notifier_block *nb){ return notifier_chain_unregister(&hci_dev_notifier, nb);}static inline void hci_notify(struct hci_dev *hdev, int event){ notifier_call_chain(&hci_dev_notifier, event, hdev);}/* Get HCI device by index (device is locked on return)*/struct hci_dev *hci_dev_get(int index){ struct hci_dev *hdev; DBG("%d", index); if (index < 0 || index >= HCI_MAX_DEV) return NULL; spin_lock(&hdev_list_lock); if ((hdev = GET_HDEV(index))) hci_dev_hold(hdev); spin_unlock(&hdev_list_lock); return hdev;}/* Flush inquiry cache */void inquiry_cache_flush(struct inquiry_cache *cache){ struct inquiry_entry *next = cache->list, *e; DBG("cache %p", cache); cache->list = NULL; while ((e = next)) { next = e->next; kfree(e); }}/* Lookup by bdaddr. * Cache must be locked. */static struct inquiry_entry * __inquiry_cache_lookup(struct inquiry_cache *cache, bdaddr_t *bdaddr){ struct inquiry_entry *e; DBG("cache %p, %s", cache, batostr(bdaddr)); for (e = cache->list; e; e = e->next) if (!bacmp(&e->info.bdaddr, bdaddr)) break; return e;}static void inquiry_cache_update(struct inquiry_cache *cache, inquiry_info *info){ struct inquiry_entry *e; DBG("cache %p, %s", cache, batostr(&info->bdaddr)); inquiry_cache_lock(cache); if (!(e = __inquiry_cache_lookup(cache, &info->bdaddr))) { /* Entry not in the cache. Add new one. */ if (!(e = kmalloc(sizeof(struct inquiry_entry), GFP_ATOMIC))) goto unlock; memset(e, 0, sizeof(struct inquiry_entry)); e->next = cache->list; cache->list = e; } memcpy(&e->info, info, sizeof(inquiry_info)); e->timestamp = jiffies; cache->timestamp = jiffies;unlock: inquiry_cache_unlock(cache);}static int inquiry_cache_dump(struct inquiry_cache *cache, int num, __u8 *buf){ inquiry_info *info = (inquiry_info *) buf; struct inquiry_entry *e; int copied = 0; inquiry_cache_lock(cache); for (e = cache->list; e && copied < num; e = e->next, copied++) memcpy(info++, &e->info, sizeof(inquiry_info)); inquiry_cache_unlock(cache); DBG("cache %p, copied %d", cache, copied); return copied;}/* --------- BaseBand connections --------- */static struct hci_conn *hci_conn_add(struct hci_dev *hdev, __u16 handle, __u8 type, bdaddr_t *dst){ struct hci_conn *conn; DBG("%s handle %d dst %s", hdev->name, handle, batostr(dst)); if ( conn_hash_lookup(&hdev->conn_hash, handle)) { ERR("%s handle 0x%x already exists", hdev->name, handle); return NULL; } if (!(conn = kmalloc(sizeof(struct hci_conn), GFP_ATOMIC))) return NULL; memset(conn, 0, sizeof(struct hci_conn)); bacpy(&conn->dst, dst); conn->handle = handle; conn->type = type; conn->hdev = hdev; skb_queue_head_init(&conn->data_q); hci_dev_hold(hdev); conn_hash_add(&hdev->conn_hash, handle, conn); return conn;}static int hci_conn_del(struct hci_dev *hdev, struct hci_conn *conn){ DBG("%s conn %p handle %d", hdev->name, conn, conn->handle); conn_hash_del(&hdev->conn_hash, conn); hci_dev_put(hdev); /* Unacked frames */ hdev->acl_cnt += conn->sent; skb_queue_purge(&conn->data_q); kfree(conn); return 0;}/* Drop all connection on the device */static void hci_conn_hash_flush(struct hci_dev *hdev){ struct conn_hash *h = &hdev->conn_hash; struct hci_proto *hp; struct list_head *p; DBG("hdev %s", hdev->name); p = h->list.next; while (p != &h->list) { struct hci_conn *c; c = list_entry(p, struct hci_conn, list); p = p->next; if (c->type == ACL_LINK) { /* ACL link notify L2CAP layer */ if ((hp = GET_HPROTO(HCI_PROTO_L2CAP)) && hp->disconn_ind) hp->disconn_ind(c, 0x16); } else { /* SCO link (no notification) */ } hci_conn_del(hdev, c); }}int hci_connect(struct hci_dev *hdev, bdaddr_t *bdaddr){ struct inquiry_cache *cache = &hdev->inq_cache; struct inquiry_entry *e; create_conn_cp cc; __u16 clock_offset; DBG("%s bdaddr %s", hdev->name, batostr(bdaddr)); if (!(hdev->flags & HCI_UP)) return -ENODEV; inquiry_cache_lock_bh(cache); if (!(e = __inquiry_cache_lookup(cache, bdaddr)) || inquiry_entry_age(e) > INQUIRY_ENTRY_AGE_MAX) { cc.pscan_rep_mode = 0; cc.pscan_mode = 0; clock_offset = 0; } else { cc.pscan_rep_mode = e->info.pscan_rep_mode; cc.pscan_mode = e->info.pscan_mode; clock_offset = __le16_to_cpu(e->info.clock_offset) & 0x8000; } inquiry_cache_unlock_bh(cache); bacpy(&cc.bdaddr, bdaddr); cc.pkt_type = __cpu_to_le16(hdev->pkt_type); cc.clock_offset = __cpu_to_le16(clock_offset); if (lmp_rswitch_capable(hdev)) cc.role_switch = 0x01; else cc.role_switch = 0x00; hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CREATE_CONN, CREATE_CONN_CP_SIZE, &cc); return 0;}int hci_disconnect(struct hci_conn *conn, __u8 reason){ disconnect_cp dc; DBG("conn %p handle %d", conn, conn->handle); dc.handle = __cpu_to_le16(conn->handle); dc.reason = reason; hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_DISCONNECT, DISCONNECT_CP_SIZE, &dc); return 0;}/* --------- HCI request handling ------------ */static inline void hci_req_lock(struct hci_dev *hdev){ down(&hdev->req_lock);}static inline void hci_req_unlock(struct hci_dev *hdev){ up(&hdev->req_lock);}static inline void hci_req_complete(struct hci_dev *hdev, int result){ DBG("%s result 0x%2.2x", hdev->name, result); if (hdev->req_status == HCI_REQ_PEND) { hdev->req_result = result; hdev->req_status = HCI_REQ_DONE; wake_up_interruptible(&hdev->req_wait_q); }}static inline void hci_req_cancel(struct hci_dev *hdev, int err){ DBG("%s err 0x%2.2x", hdev->name, err); if (hdev->req_status == HCI_REQ_PEND) { hdev->req_result = err; hdev->req_status = HCI_REQ_CANCELED; wake_up_interruptible(&hdev->req_wait_q); }}/* Execute request and wait for completion. */static int __hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt), unsigned long opt, __u32 timeout){ DECLARE_WAITQUEUE(wait, current); int err = 0; DBG("%s start", hdev->name); hdev->req_status = HCI_REQ_PEND; add_wait_queue(&hdev->req_wait_q, &wait); current->state = TASK_INTERRUPTIBLE; req(hdev, opt); schedule_timeout(timeout); current->state = TASK_RUNNING; remove_wait_queue(&hdev->req_wait_q, &wait); if (signal_pending(current)) return -EINTR; switch (hdev->req_status) { case HCI_REQ_DONE: err = -bterr(hdev->req_result); break; case HCI_REQ_CANCELED: err = -hdev->req_result; break; default: err = -ETIMEDOUT; break; }; hdev->req_status = hdev->req_result = 0; DBG("%s end: err %d", hdev->name, err); return err;}static inline int hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt), unsigned long opt, __u32 timeout){ int ret; /* Serialize all requests */ hci_req_lock(hdev); ret = __hci_request(hdev, req, opt, timeout); hci_req_unlock(hdev); return ret;}/* --------- HCI requests ---------- */static void hci_reset_req(struct hci_dev *hdev, unsigned long opt){ DBG("%s %ld", hdev->name, opt); /* Reset device */ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL);}static void hci_init_req(struct hci_dev *hdev, unsigned long opt){ set_event_flt_cp ec; __u16 param; DBG("%s %ld", hdev->name, opt); /* Mandatory initialization */ /* Read Local Supported Features */ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES, 0, NULL); /* Read Buffer Size (ACL mtu, max pkt, etc.) */ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE, 0, NULL); /* Read BD Address */ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BD_ADDR, 0, NULL); /* Optional initialization */ /* Clear Event Filters */ ec.flt_type = FLT_CLEAR_ALL; hci_send_cmd(hdev, OGF_HOST_CTL, OCF_SET_EVENT_FLT, 1, &ec); /* Page timeout ~20 secs */ param = __cpu_to_le16(0x8000); hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_PG_TIMEOUT, 2, ¶m); /* Connection accept timeout ~20 secs */ param = __cpu_to_le16(0x7d00); hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_CA_TIMEOUT, 2, ¶m);}static void hci_scan_req(struct hci_dev *hdev, unsigned long opt){ __u8 scan = opt; DBG("%s %x", hdev->name, scan); /* Inquiry and Page scans */ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan);}static void hci_auth_req(struct hci_dev *hdev, unsigned long opt){ __u8 auth = opt; DBG("%s %x", hdev->name, auth); /* Authentication */ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE, 1, &auth);}static void hci_inq_req(struct hci_dev *hdev, unsigned long opt){ struct hci_inquiry_req *ir = (struct hci_inquiry_req *) opt; inquiry_cp ic; DBG("%s", hdev->name); /* Start Inquiry */ memcpy(&ic.lap, &ir->lap, 3); ic.lenght = ir->length; ic.num_rsp = ir->num_rsp; hci_send_cmd(hdev, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &ic);}/* HCI ioctl helpers */int hci_dev_open(__u16 dev){ struct hci_dev *hdev; int ret = 0; if (!(hdev = hci_dev_get(dev))) return -ENODEV; DBG("%s %p", hdev->name, hdev); hci_req_lock(hdev); if (hdev->flags & HCI_UP) { ret = -EALREADY; goto done; } if (hdev->open(hdev)) { ret = -EIO; goto done; } if (hdev->flags & HCI_NORMAL) { atomic_set(&hdev->cmd_cnt, 1); hdev->flags |= HCI_INIT; //__hci_request(hdev, hci_reset_req, 0, HZ); ret = __hci_request(hdev, hci_init_req, 0, HCI_INIT_TIMEOUT); hdev->flags &= ~HCI_INIT; } if (!ret) { hdev->flags |= HCI_UP; hci_notify(hdev, HCI_DEV_UP); } else { /* Init failed, cleanup */ tasklet_kill(&hdev->rx_task); tasklet_kill(&hdev->tx_task); tasklet_kill(&hdev->cmd_task); skb_queue_purge(&hdev->cmd_q); skb_queue_purge(&hdev->rx_q); if (hdev->flush) hdev->flush(hdev); if (hdev->sent_cmd) { kfree_skb(hdev->sent_cmd); hdev->sent_cmd = NULL; } hdev->close(hdev); }done: hci_req_unlock(hdev); hci_dev_put(hdev); return ret;}int hci_dev_close(__u16 dev){ struct hci_dev *hdev; if (!(hdev = hci_dev_get(dev))) return -ENODEV; DBG("%s %p", hdev->name, hdev); hci_req_cancel(hdev, ENODEV); hci_req_lock(hdev); if (!(hdev->flags & HCI_UP)) goto done; /* Kill RX and TX tasks */ tasklet_kill(&hdev->rx_task); tasklet_kill(&hdev->tx_task); inquiry_cache_flush(&hdev->inq_cache); hci_conn_hash_flush(hdev); /* Clear flags */ hdev->flags &= HCI_SOCK; hdev->flags |= HCI_NORMAL; hci_notify(hdev, HCI_DEV_DOWN); if (hdev->flush) hdev->flush(hdev); /* Reset device */ skb_queue_purge(&hdev->cmd_q); atomic_set(&hdev->cmd_cnt, 1); hdev->flags |= HCI_INIT; __hci_request(hdev, hci_reset_req, 0, HZ); hdev->flags &= ~HCI_INIT; /* Kill cmd task */ tasklet_kill(&hdev->cmd_task); /* Drop queues */ skb_queue_purge(&hdev->rx_q); skb_queue_purge(&hdev->cmd_q); skb_queue_purge(&hdev->raw_q); /* Drop last sent command */ if (hdev->sent_cmd) { kfree_skb(hdev->sent_cmd); hdev->sent_cmd = NULL; } /* After this point our queues are empty * and no tasks are scheduled. */ hdev->close(hdev);done: hci_req_unlock(hdev); hci_dev_put(hdev); return 0;}int hci_dev_reset(__u16 dev){ struct hci_dev *hdev; int ret = 0; if (!(hdev = hci_dev_get(dev))) return -ENODEV; hci_req_lock(hdev); tasklet_disable(&hdev->tx_task); if (!(hdev->flags & HCI_UP)) goto done; /* Drop queues */ skb_queue_purge(&hdev->rx_q); skb_queue_purge(&hdev->cmd_q); inquiry_cache_flush(&hdev->inq_cache); hci_conn_hash_flush(hdev); if (hdev->flush) hdev->flush(hdev); atomic_set(&hdev->cmd_cnt, 1); hdev->acl_cnt = 0; hdev->sco_cnt = 0; ret = __hci_request(hdev, hci_reset_req, 0, HCI_INIT_TIMEOUT);done: tasklet_enable(&hdev->tx_task); hci_req_unlock(hdev); hci_dev_put(hdev); return ret;}int hci_dev_reset_stat(__u16 dev){ struct hci_dev *hdev; int ret = 0; if (!(hdev = hci_dev_get(dev))) return -ENODEV; memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); hci_dev_put(hdev); return ret;}int hci_dev_setauth(unsigned long arg){ struct hci_dev *hdev; struct hci_dev_req dr; int ret = 0; if (copy_from_user(&dr, (void *) arg, sizeof(dr)))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -