diff options
| author | plunky <plunky@NetBSD.org> | 2009-08-03 15:59:41 +0000 |
|---|---|---|
| committer | plunky <plunky@NetBSD.org> | 2009-08-03 15:59:41 +0000 |
| commit | bd42c2e8d0fecd0bc87d1a20447b52bbadff1857 (patch) | |
| tree | 98f578da88f8b4c407261abd7979e947c587d4ea /lib/libbluetooth/bt_dev.c | |
| parent | d90603b57337200e55203a4a5d9f823639a226aa (diff) | |
Add bt_dev(3), an platform neutral API for accessing (locally configured)
Bluetooth devices, and bump libbluetooth minor version.
This is a reimplementation of an API largely developed by Maksim Yevmenkin
on FreeBSD to make it easier to port BlueZ/Linux programs which depend on
something similar. Alas, the BlueZ/Linux API is incompatible and unportable
as it depends on a 'device' being referenced by an int, but this will
hopefully make it easier to port software using that.
(bump libbluetooth minor version)
Diffstat (limited to 'lib/libbluetooth/bt_dev.c')
| -rw-r--r-- | lib/libbluetooth/bt_dev.c | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/lib/libbluetooth/bt_dev.c b/lib/libbluetooth/bt_dev.c new file mode 100644 index 00000000000..7638c88c85b --- /dev/null +++ b/lib/libbluetooth/bt_dev.c @@ -0,0 +1,915 @@ +/* $NetBSD: bt_dev.c,v 1.1 2009/08/03 15:59:42 plunky Exp $ */ + +/*- + * Copyright (c) 2009 Iain Hibbert + * Copyright (c) 2009 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2006 Itronix Inc. + * All rights reserved. + * + * Written by Iain Hibbert for Itronix Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of Itronix Inc. may not be used to endorse + * or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: bt_dev.c,v 1.1 2009/08/03 15:59:42 plunky Exp $"); + +#include <sys/event.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/uio.h> + +#include <bluetooth.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +int +bt_devaddr(const char *name, bdaddr_t *addr) +{ + struct btreq btr; + bdaddr_t bdaddr; + int s, rv; + + if (name == NULL) { + errno = EINVAL; + return 0; + } + + if (addr == NULL) + addr = &bdaddr; + + if (bt_aton(name, addr)) + return bt_devname(NULL, addr); + + memset(&btr, 0, sizeof(btr)); + strncpy(btr.btr_name, name, HCI_DEVNAME_SIZE); + + s = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (s == -1) + return 0; + + rv = ioctl(s, SIOCGBTINFO, &btr); + close(s); + + if (rv == -1) + return 0; + + if ((btr.btr_flags & BTF_UP) == 0) { + errno = ENXIO; + return 0; + } + + bdaddr_copy(addr, &btr.btr_bdaddr); + return 1; +} + +int +bt_devname(char *name, const bdaddr_t *bdaddr) +{ + struct btreq btr; + int s, rv; + + if (bdaddr == NULL) { + errno = EINVAL; + return 0; + } + + memset(&btr, 0, sizeof(btr)); + bdaddr_copy(&btr.btr_bdaddr, bdaddr); + + s = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (s == -1) + return 0; + + rv = ioctl(s, SIOCGBTINFOA, &btr); + close(s); + + if (rv == -1) + return 0; + + if ((btr.btr_flags & BTF_UP) == 0) { + errno = ENXIO; + return 0; + } + + if (name != NULL) + strlcpy(name, btr.btr_name, HCI_DEVNAME_SIZE); + + return 1; +} + +int +bt_devopen(const char *name, int options) +{ + struct sockaddr_bt sa; + int opt, s; + + memset(&sa, 0, sizeof(sa)); + sa.bt_len = sizeof(sa); + sa.bt_family = AF_BLUETOOTH; + + if (name != NULL && !bt_devaddr(name, &sa.bt_bdaddr)) + return -1; + + s = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (s == -1) + return -1; + + opt = 1; + + if ((options & BTOPT_DIRECTION) && setsockopt(s, BTPROTO_HCI, + SO_HCI_DIRECTION, &opt, sizeof(opt)) == -1) { + close(s); + return -1; + } + + if ((options & BTOPT_TIMESTAMP) && setsockopt(s, SOL_SOCKET, + SO_TIMESTAMP, &opt, sizeof(opt)) == -1) { + close(s); + return -1; + } + + if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + close(s); + return -1; + } + + if (name != NULL + && connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + close(s); + return -1; + } + + return s; +} + +ssize_t +bt_devsend(int s, uint16_t opcode, void *param, size_t plen) +{ + hci_cmd_hdr_t hdr; + struct iovec iov[2]; + ssize_t n; + + if (plen > UINT8_MAX + || (plen == 0 && param != NULL) + || (plen != 0 && param == NULL)) { + errno = EINVAL; + return -1; + } + + hdr.type = HCI_CMD_PKT; + hdr.opcode = htole16(opcode); + hdr.length = (uint8_t)plen; + + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); + + iov[1].iov_base = param; + iov[1].iov_len = plen; + + while ((n = writev(s, iov, __arraycount(iov))) == -1) { + if (errno == EINTR) + continue; + + return -1; + } + + return n; +} + +ssize_t +bt_devrecv(int s, void *buf, size_t size, time_t to) +{ + struct kevent ev; + struct timespec ts; + uint8_t *p; + ssize_t n; + int kq; + + if (buf == NULL || size == 0) { + errno = EINVAL; + return -1; + } + + if (to >= 0) { /* timeout is optional */ + kq = kqueue(); + if (kq == -1) + return -1; + + EV_SET(&ev, s, EVFILT_READ, EV_ADD, 0, 0, 0); + + ts.tv_sec = to; + ts.tv_nsec = 0; + + while (kevent(kq, &ev, 1, &ev, 1, &ts) == -1) { + if (errno == EINTR) + continue; + + close(kq); + return -1; + } + + close(kq); + + if (ev.data == 0) { + errno = ETIMEDOUT; + return -1; + } + } + + while ((n = recv(s, buf, size, 0)) == -1) { + if (errno == EINTR) + continue; + + return -1; + } + + if (n == 0) + return 0; + + p = buf; + switch (p[0]) { /* validate that they get complete packets */ + case HCI_CMD_PKT: + if (sizeof(hci_cmd_hdr_t) > (size_t)n + || sizeof(hci_cmd_hdr_t) + p[3] != (size_t)n) + break; + + return n; + + case HCI_ACL_DATA_PKT: + if (sizeof(hci_acldata_hdr_t) > (size_t)n + || sizeof(hci_acldata_hdr_t) + le16dec(p + 3) != (size_t)n) + break; + + return n; + + case HCI_SCO_DATA_PKT: + if (sizeof(hci_scodata_hdr_t) > (size_t)n + || sizeof(hci_scodata_hdr_t) + p[3] != (size_t)n) + break; + + return n; + + case HCI_EVENT_PKT: + if (sizeof(hci_event_hdr_t) > (size_t)n + || sizeof(hci_event_hdr_t) + p[2] != (size_t)n) + break; + + return n; + + default: + break; + } + + errno = EIO; + return -1; +} + +/* + * Internal handler for bt_devreq(), do the actual request. + */ +static int +bt__devreq(int s, struct bt_devreq *req, time_t t_end) +{ + uint8_t buf[HCI_EVENT_PKT_SIZE], *p; + hci_event_hdr_t ev; + hci_command_status_ep cs; + hci_command_compl_ep cc; + time_t to; + ssize_t n; + + n = bt_devsend(s, req->opcode, req->cparam, req->clen); + if (n == -1) + return errno; + + for (;;) { + to = t_end - time(NULL); + if (to < 0) + return ETIMEDOUT; + + p = buf; + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n == -1) + return errno; + + if (sizeof(ev) > (size_t)n || p[0] != HCI_EVENT_PKT) + return EIO; + + memcpy(&ev, p, sizeof(ev)); + p += sizeof(ev); + n -= sizeof(ev); + + if (ev.event == req->event) + break; + + if (ev.event == HCI_EVENT_COMMAND_STATUS) { + if (sizeof(cs) > (size_t)n) + return EIO; + + memcpy(&cs, p, sizeof(cs)); + p += sizeof(cs); + n -= sizeof(cs); + + if (le16toh(cs.opcode) == req->opcode) { + if (cs.status != 0) + return EIO; + + if (req->event == 0) + break; + } + + continue; + } + + if (ev.event == HCI_EVENT_COMMAND_COMPL) { + if (sizeof(cc) > (size_t)n) + return EIO; + + memcpy(&cc, p, sizeof(cc)); + p += sizeof(cc); + n -= sizeof(cc); + + if (le16toh(cc.opcode) == req->opcode) + break; + + continue; + } + } + + /* copy out response data */ + if (req->rlen >= (size_t)n) { + req->rlen = n; + memcpy(req->rparam, p, req->rlen); + } else if (req->rlen > 0) + return EIO; + + return 0; +} + +int +bt_devreq(int s, struct bt_devreq *req, time_t to) +{ + struct bt_devfilter new, old; + int error; + + if (req == NULL || to < 0 + || (req->rlen == 0 && req->rparam != NULL) + || (req->rlen != 0 && req->rparam == NULL)) { + errno = EINVAL; + return -1; + } + + memset(&new, 0, sizeof(new)); + bt_devfilter_pkt_set(&new, HCI_EVENT_PKT); + bt_devfilter_evt_set(&new, HCI_EVENT_COMMAND_COMPL); + bt_devfilter_evt_set(&new, HCI_EVENT_COMMAND_STATUS); + + if (req->event != 0) + bt_devfilter_evt_set(&new, req->event); + + if (bt_devfilter(s, &new, &old) == -1) + return -1; + + error = bt__devreq(s, req, to + time(NULL)); + + (void)bt_devfilter(s, &old, NULL); + + if (error != 0) { + errno = error; + return -1; + } + + return 0; +} + +int +bt_devfilter(int s, const struct bt_devfilter *new, struct bt_devfilter *old) +{ + socklen_t len; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return -1; + } + + len = sizeof(struct hci_filter); + + if (old != NULL) { + if (getsockopt(s, BTPROTO_HCI, + SO_HCI_PKT_FILTER, &old->packet_mask, &len) == -1 + || len != sizeof(struct hci_filter)) + return -1; + + if (getsockopt(s, BTPROTO_HCI, + SO_HCI_EVT_FILTER, &old->event_mask, &len) == -1 + || len != sizeof(struct hci_filter)) + return -1; + } + + if (new != NULL) { + if (setsockopt(s, BTPROTO_HCI, + SO_HCI_PKT_FILTER, &new->packet_mask, len) == -1) + return -1; + + if (setsockopt(s, BTPROTO_HCI, + SO_HCI_EVT_FILTER, &new->event_mask, len) == -1) + return -1; + } + + return 0; +} + +void +bt_devfilter_pkt_set(struct bt_devfilter *filter, uint8_t type) +{ + + hci_filter_set(type, &filter->packet_mask); +} + +void +bt_devfilter_pkt_clr(struct bt_devfilter *filter, uint8_t type) +{ + + hci_filter_clr(type, &filter->packet_mask); +} + +int +bt_devfilter_pkt_tst(const struct bt_devfilter *filter, uint8_t type) +{ + + return hci_filter_test(type, &filter->packet_mask); +} + +void +bt_devfilter_evt_set(struct bt_devfilter *filter, uint8_t event) +{ + + hci_filter_set(event, &filter->event_mask); +} + +void +bt_devfilter_evt_clr(struct bt_devfilter *filter, uint8_t event) +{ + + hci_filter_clr(event, &filter->event_mask); +} + +int +bt_devfilter_evt_tst(const struct bt_devfilter *filter, uint8_t event) +{ + + return hci_filter_test(event, &filter->event_mask); +} + +/* + * Internal function used by bt_devinquiry to find the first + * active device. + */ +static int +bt__devany_cb(int s, const struct bt_devinfo *info, void *arg) +{ + + if ((info->enabled)) { + strlcpy(arg, info->devname, HCI_DEVNAME_SIZE + 1); + return 1; + } + + return 0; +} + +/* + * Internal function used by bt_devinquiry to insert inquiry + * results to an array. Make sure that a bdaddr only appears + * once in the list and always use the latest result. + */ +static void +bt__devresult(struct bt_devinquiry *ii, int *count, int max_count, + bdaddr_t *ba, uint8_t psrm, uint8_t pspm, uint8_t *cl, uint16_t co, + int8_t rssi, uint8_t *data) +{ + int n; + + for (n = 0; ; n++, ii++) { + if (n == *count) { + if (*count == max_count) + return; + + (*count)++; + break; + } + + if (bdaddr_same(&ii->bdaddr, ba)) + break; + } + + bdaddr_copy(&ii->bdaddr, ba); + ii->pscan_rep_mode = psrm; + ii->pscan_period_mode = pspm; + ii->clock_offset = le16toh(co); + ii->rssi = rssi; + + if (cl != NULL) + memcpy(ii->dev_class, cl, HCI_CLASS_SIZE); + + if (data != NULL) + memcpy(ii->data, data, 240); +} + +int +bt_devinquiry(const char *name, time_t to, int max_rsp, + struct bt_devinquiry **iip) +{ + uint8_t buf[HCI_EVENT_PKT_SIZE], *p; + struct bt_devfilter f; + hci_event_hdr_t ev; + hci_command_status_ep sp; + hci_inquiry_cp cp; + hci_inquiry_result_ep ip; + hci_inquiry_response ir; + hci_rssi_result_ep rp; + hci_rssi_response rr; + hci_extended_result_ep ep; + struct bt_devinquiry *ii; + int count, i, s; + time_t t_end; + ssize_t n; + + if (iip == NULL) { + errno = EINVAL; + return -1; + } + + if (name == NULL) { + if (bt_devenum(bt__devany_cb, buf) == -1) + return -1; + + name = (const char *)buf; + } + + s = bt_devopen(name, 0); + if (s == -1) + return -1; + + memset(&f, 0, sizeof(f)); + bt_devfilter_pkt_set(&f, HCI_EVENT_PKT); + bt_devfilter_evt_set(&f, HCI_EVENT_COMMAND_STATUS); + bt_devfilter_evt_set(&f, HCI_EVENT_INQUIRY_COMPL); + bt_devfilter_evt_set(&f, HCI_EVENT_INQUIRY_RESULT); + bt_devfilter_evt_set(&f, HCI_EVENT_RSSI_RESULT); + bt_devfilter_evt_set(&f, HCI_EVENT_EXTENDED_RESULT); + if (bt_devfilter(s, &f, NULL) == -1) { + close(s); + return -1; + } + + /* + * silently adjust number of reponses to fit in uint8_t + */ + if (max_rsp < 1) + max_rsp = 8; + else if (max_rsp > UINT8_MAX) + max_rsp = UINT8_MAX; + + ii = calloc((size_t)max_rsp, sizeof(struct bt_devinquiry)); + if (ii == NULL) { + close(s); + return -1; + } + + /* + * silently adjust timeout value so that inquiry_length + * falls into the range 0x01->0x30 (unit is 1.28 seconds) + */ + if (to < 1) + to = 5; + else if (to == 1) + to = 2; + else if (to > 62) + to = 62; + + /* General Inquiry LAP is 0x9e8b33 */ + cp.lap[0] = 0x33; + cp.lap[1] = 0x8b; + cp.lap[2] = 0x9e; + cp.inquiry_length = (uint8_t)(to * 100 / 128); + cp.num_responses = (uint8_t)max_rsp; + + if (bt_devsend(s, HCI_CMD_INQUIRY, &cp, sizeof(cp)) == -1) + goto fail; + + count = 0; + + for (t_end = time(NULL) + to + 1; to > 0; to = t_end - time(NULL)) { + p = buf; + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n == -1) + goto fail; + + if (sizeof(ev) > (size_t)n) { + errno = EIO; + goto fail; + } + + memcpy(&ev, p, sizeof(ev)); + p += sizeof(ev); + n -= sizeof(ev); + + switch (ev.event) { + case HCI_EVENT_COMMAND_STATUS: + if (sizeof(sp) > (size_t)n) + break; + + memcpy(&sp, p, sizeof(sp)); + + if (le16toh(sp.opcode) != HCI_CMD_INQUIRY + || sp.status == 0) + break; + + errno = EIO; + goto fail; + + case HCI_EVENT_INQUIRY_COMPL: + close(s); + *iip = ii; + return count; + + case HCI_EVENT_INQUIRY_RESULT: + if (sizeof(ip) > (size_t)n) + break; + + memcpy(&ip, p, sizeof(ip)); + p += sizeof(ip); + n -= sizeof(ip); + + if (sizeof(ir) * ip.num_responses != (size_t)n) + break; + + for (i = 0; i < ip.num_responses; i++) { + memcpy(&ir, p, sizeof(ir)); + p += sizeof(ir); + + bt__devresult(ii, &count, max_rsp, + &ir.bdaddr, + ir.page_scan_rep_mode, + ir.page_scan_period_mode, + ir.uclass, + ir.clock_offset, + 0, /* rssi */ + NULL); /* extended data */ + } + + break; + + case HCI_EVENT_RSSI_RESULT: + if (sizeof(rp) > (size_t)n) + break; + + memcpy(&rp, p, sizeof(rp)); + p += sizeof(rp); + n -= sizeof(rp); + + if (sizeof(rr) * rp.num_responses != (size_t)n) + break; + + for (i = 0; i < rp.num_responses; i++) { + memcpy(&rr, p, sizeof(rr)); + p += sizeof(rr); + + bt__devresult(ii, &count, max_rsp, + &rr.bdaddr, + rr.page_scan_rep_mode, + 0, /* page scan period mode */ + rr.uclass, + rr.clock_offset, + rr.rssi, + NULL); /* extended data */ + } + + break; + + case HCI_EVENT_EXTENDED_RESULT: + if (sizeof(ep) != (size_t)n) + break; + + memcpy(&ep, p, sizeof(ep)); + + if (ep.num_responses != 1) + break; + + bt__devresult(ii, &count, max_rsp, + &ep.bdaddr, + ep.page_scan_rep_mode, + 0, /* page scan period mode */ + ep.uclass, + ep.clock_offset, + ep.rssi, + ep.response); + + break; + + default: + break; + } + } + + errno = ETIMEDOUT; + +fail: + free(ii); + close(s); + return -1; +} + +/* + * Internal version of bt_devinfo. Fill in the devinfo structure + * with the socket handle provided. If the device is present and + * active, the socket will be left connected to the device. + */ +static int +bt__devinfo(int s, const char *name, struct bt_devinfo *info) +{ + struct sockaddr_bt sa; + struct bt_devreq req; + struct btreq btr; + hci_read_buffer_size_rp bp; + hci_read_local_features_rp fp; + + memset(&btr, 0, sizeof(btr)); + strncpy(btr.btr_name, name, HCI_DEVNAME_SIZE); + + if (ioctl(s, SIOCGBTINFO, &btr) == -1) + return -1; + + memset(info, 0, sizeof(struct bt_devinfo)); + memcpy(info->devname, btr.btr_name, HCI_DEVNAME_SIZE); + bdaddr_copy(&info->bdaddr, &btr.btr_bdaddr); + info->enabled = ((btr.btr_flags & BTF_UP) ? 1 : 0); + + info->sco_size = btr.btr_sco_mtu; + info->acl_size = btr.btr_acl_mtu; + info->cmd_free = btr.btr_num_cmd; + info->sco_free = btr.btr_num_sco; + info->acl_free = btr.btr_num_acl; + + info->link_policy_info = btr.btr_link_policy; + info->packet_type_info = btr.btr_packet_type; + + if (ioctl(s, SIOCGBTSTATS, &btr) == -1) + return -1; + + info->cmd_sent = btr.btr_stats.cmd_tx; + info->evnt_recv = btr.btr_stats.evt_rx; + info->acl_recv = btr.btr_stats.acl_rx; + info->acl_sent = btr.btr_stats.acl_tx; + info->sco_recv = btr.btr_stats.sco_rx; + info->sco_sent = btr.btr_stats.sco_tx; + info->bytes_recv = btr.btr_stats.byte_rx; + info->bytes_sent = btr.btr_stats.byte_tx; + + /* can only get the rest from enabled devices */ + if ((info->enabled) == 0) + return 0; + + memset(&sa, 0, sizeof(sa)); + sa.bt_len = sizeof(sa); + sa.bt_family = AF_BLUETOOTH; + bdaddr_copy(&sa.bt_bdaddr, &info->bdaddr); + + if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 + || connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) + return -1; + + memset(&req, 0, sizeof(req)); + req.opcode = HCI_CMD_READ_BUFFER_SIZE; + req.rparam = &bp; + req.rlen = sizeof(bp); + + if (bt_devreq(s, &req, 5) == -1) + return -1; + + info->acl_pkts = bp.max_acl_size; + info->sco_pkts = bp.max_sco_size; + + memset(&req, 0, sizeof(req)); + req.opcode = HCI_CMD_READ_LOCAL_FEATURES; + req.rparam = &fp; + req.rlen = sizeof(fp); + + if (bt_devreq(s, &req, 5) == -1) + return -1; + + memcpy(info->features, fp.features, HCI_FEATURES_SIZE); + + return 0; +} + +int +bt_devinfo(const char *name, struct bt_devinfo *info) +{ + int rv, s; + + if (name == NULL || info == NULL) { + errno = EINVAL; + return -1; + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (s == -1) + return -1; + + rv = bt__devinfo(s, name, info); + close(s); + return rv; +} + +int +bt_devenum(bt_devenum_cb_t cb, void *arg) +{ + struct btreq btr; + struct bt_devinfo info; + int count, fd, rv, s; + + s = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (s == -1) + return -1; + + memset(&btr, 0, sizeof(btr)); + count = 0; + + while (ioctl(s, SIOCNBTINFO, &btr) != -1) { + count++; + + if (cb == NULL) + continue; + + fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (fd == -1) { + close(s); + return -1; + } + + if (bt__devinfo(fd, btr.btr_name, &info) == -1) { + close(fd); + close(s); + return -1; + } + + rv = (*cb)(fd, &info, arg); + close(fd); + if (rv != 0) + break; + } + + close(s); + return count; +} |
