/* $OpenBSD: hci.c,v 1.6 2008/11/27 00:51:17 uwe Exp $ */ /* $NetBSD: btconfig.c,v 1.13 2008/07/21 13:36:57 lukem Exp $ */ /*- * Copyright (c) 2008 Uwe Stuehler * 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 #include #include #include #include #include #include #include "btd.h" struct hci_physif { TAILQ_ENTRY(hci_physif) entry; struct btd *env; int present; bdaddr_t addr; char xname[16]; struct event ev; int fd; }; struct hci_state { TAILQ_HEAD(, hci_physif) physifs; int raw_sock; }; void hci_update_physifs(struct btd *); struct hci_physif *hci_find_physif(struct hci_state *, bdaddr_t *); int hci_unit_up(const char *); int hci_unit_down(const char *); void hci_if_config(struct bt_interface *, const struct bt_interface *); void hci_if_apply(struct bt_interface *); int hci_interface_open(struct bt_interface *); void hci_interface_close(struct bt_interface *); int hci_interface_reinit(struct bt_interface *); void hci_eventcb(int, short, void *); int hci_process_pin_code_req(struct hci_physif *, struct sockaddr_bt *, const bdaddr_t *); int hci_process_link_key_req(struct hci_physif *, struct sockaddr_bt *, const bdaddr_t *); int hci_process_link_key_notification(struct hci_physif *, struct sockaddr_bt *, const hci_link_key_notification_ep *); int hci_req(int, uint16_t, uint8_t , void *, size_t, void *, size_t); int hci_send_cmd(int, struct sockaddr_bt *, uint16_t, size_t, void *); #define hci_write(fd, opcode, wbuf, wlen) \ hci_req(fd, opcode, 0, wbuf, wlen, NULL, 0) #define hci_read(fd, opcode, rbuf, rlen) \ hci_req(fd, opcode, 0, NULL, 0, rbuf, rlen) #define hci_cmd(fd, opcode, cbuf, clen) \ hci_req(fd, opcode, 0, cbuf, clen, NULL, 0) #define MIN(a, b) ((a) < (b) ? (a) : (b)) void hci_init(struct btd *env) { struct hci_state *hci; hci = env->hci = calloc(1, sizeof(*env->hci)); hci->raw_sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (hci->raw_sock == -1) fatal("could not open raw HCI socket"); TAILQ_INIT(&hci->physifs); } void hci_update_physifs(struct btd *env) { struct btreq btr; struct hci_state *hci = env->hci; struct hci_physif *physif; int sock; bzero(&btr, sizeof(btr)); sock = hci->raw_sock; TAILQ_FOREACH(physif, &hci->physifs, entry) physif->present = 0; for (;;) { int flags; if (ioctl(sock, SIOCNBTINFO, &btr) < 0) break; /* * XXX Interfaces must be up, just to determine their * XXX bdaddr. Grrrr... */ if (!((flags = btr.btr_flags) & BTF_UP) || bdaddr_any(&btr.btr_bdaddr)) { if (hci_unit_up(btr.btr_name) < 0) continue; if (ioctl(sock, SIOCGBTINFO, &btr) < 0) { log_warn("%s: SIOCGBTINFO", btr.btr_name); continue; } if (!(btr.btr_flags & BTF_UP) || bdaddr_any(&btr.btr_bdaddr)) { log_warnx("could not enable %s", btr.btr_name); continue; } #if 0 if (hci_unit_down(btr.btr_name) < 0) continue; #endif } if ((physif = hci_find_physif(hci, &btr.btr_bdaddr))) { physif->present = 1; goto found; } if ((physif = calloc(1, sizeof(*physif))) == NULL) fatalx("hci physif calloc"); physif->env = env; physif->fd = -1; physif->present = 1; bdaddr_copy(&physif->addr, &btr.btr_bdaddr); strlcpy(physif->xname, btr.btr_name, sizeof(physif->xname)); TAILQ_INSERT_TAIL(&hci->physifs, physif, entry); found: (void)conf_add_interface(env, &physif->addr); } for (physif = TAILQ_FIRST(&hci->physifs); physif != NULL;) { if (!physif->present) { struct hci_physif *next; struct bt_interface *iface; iface = conf_find_interface(env, &physif->addr); if (iface != NULL) { hci_interface_close(iface); conf_delete_interface(iface); } next = TAILQ_NEXT(physif, entry); TAILQ_REMOVE(&hci->physifs, physif, entry); free(physif); physif = next; continue; } physif = TAILQ_NEXT(physif, entry); } } struct hci_physif * hci_find_physif(struct hci_state *hci, bdaddr_t *addr) { struct hci_physif *physif; TAILQ_FOREACH(physif, &hci->physifs, entry) if (bdaddr_same(&physif->addr, addr)) return physif; return NULL; } int hci_reinit(struct btd *env, const struct btd *conf) { struct bt_interface *conf_iface; struct bt_interface *iface; struct bt_device *conf_btdev; struct bt_device *btdev; hci_update_physifs(env); /* * "Downgrade" all explicit interfaces, which are not in the * configuration anymore. */ TAILQ_FOREACH(iface, &env->interfaces, entry) { conf_iface = conf_find_interface(conf, &iface->addr); if (conf_iface == NULL) iface->flags &= ~BTIF_EXPLICIT; } /* * Add new interfaces from the configuration and mark the * changed properties of existing interfaces. The wildcard * interface is not treated specially in this loop. */ TAILQ_FOREACH(conf_iface, &conf->interfaces, entry) { iface = conf_find_interface(env, &conf_iface->addr); if (iface == NULL) { iface = conf_add_interface(env, &conf_iface->addr); if (iface == NULL) { int err = errno; log_warn("could not add interface %s", bt_ntoa(&conf_iface->addr, NULL)); errno = err; return -1; } iface->changes |= BTIF_NAME_CHANGED; } iface->flags |= BTIF_EXPLICIT; hci_if_config(iface, conf_iface); } /* * Apply the changes in the wildcard interface to all * non-explicit interfaces. If there is no wildcard interface, * delete all non-explicit interfaces. */ conf_iface = conf_find_interface(conf, BDADDR_ANY); TAILQ_FOREACH(iface, &env->interfaces, entry) { if (!(iface->flags & BTIF_EXPLICIT)) { if (conf_iface != NULL) hci_if_config(iface, conf_iface); else iface->changes |= BTIF_DELETED; } } /* * Apply all interface changes now. */ for (iface = TAILQ_FIRST(&env->interfaces); iface != NULL;) { if (iface->changes & BTIF_DELETED) { struct bt_interface *next; hci_interface_close(iface); next = TAILQ_NEXT(iface, entry); conf_delete_interface(iface); iface = next; continue; } if (!bdaddr_any(&iface->addr)) hci_if_apply(iface); iface = TAILQ_NEXT(iface, entry); } TAILQ_FOREACH(btdev, &env->devices, entry) { conf_btdev = conf_find_device(conf, &btdev->addr); if (conf_btdev == NULL) { btdev->flags &= ~BTDF_ATTACH; btdev->flags |= BTDF_DELETED; } } TAILQ_FOREACH(conf_btdev, &conf->devices, entry) { btdev = conf_find_device(env, &conf_btdev->addr); if (btdev == NULL) btdev = conf_add_device(env, &conf_btdev->addr); if (btdev == NULL) fatal("hci_reinit conf_add_device"); btdev->type = conf_btdev->type; memcpy(btdev->pin, conf_btdev->pin, sizeof(btdev->pin)); btdev->pin_size = conf_btdev->pin_size; btdev->flags |= conf_btdev->flags & BTDF_ATTACH; } bt_devices_changed(); return 0; } int hci_unit_up(const char *xname) { struct btreq btr; memset(&btr, 0, sizeof(btr)); strlcpy(btr.btr_name, xname, sizeof(btr.btr_name)); btr.btr_flags = BTF_UP; if (bt_set_interface_flags(&btr) < 0) { log_warn("hci_unit_up: %s", xname); return -1; } return 0; } int hci_unit_down(const char *xname) { struct btreq btr; memset(&btr, 0, sizeof(btr)); strlcpy(btr.btr_name, xname, sizeof(btr.btr_name)); btr.btr_flags = 0; if (bt_set_interface_flags(&btr) < 0) { log_warn("hci_unit_down: %s", xname); return -1; } return 0; } void hci_if_config(struct bt_interface *iface, const struct bt_interface *other) { if (memcmp(iface->name, other->name, sizeof(iface->name))) { iface->changes |= BTIF_NAME_CHANGED; memcpy(iface->name, other->name, sizeof(iface->name)); } iface->disabled = other->disabled; } void hci_if_apply(struct bt_interface *iface) { if (iface->disabled) { hci_interface_close(iface); return; } if (hci_interface_open(iface) < 0) return; if (iface->changes != 0) { (void)hci_interface_reinit(iface); iface->changes = 0; } } int hci_interface_open(struct bt_interface *iface) { struct hci_filter filter; struct hci_state *hci = iface->env->hci; struct hci_physif *physif; int err; if (iface->physif == NULL) { iface->physif = hci_find_physif(hci, &iface->addr); if (iface->physif == NULL) /* no physical interface to open, but that's ok */ return 0; } physif = iface->physif; if (physif->fd != -1) return 0; if (hci_unit_up(physif->xname) < 0) return -1; bt_priv_msg(IMSG_OPEN_HCI); bt_priv_send(&physif->addr, sizeof(bdaddr_t)); physif->fd = receive_fd(priv_fd); bt_priv_recv(&err, sizeof(int)); if (err != 0) { log_warnx("OPEN_HCI failed (%s)", strerror(err)); return -1; } memset(&filter, 0, sizeof(filter)); hci_filter_set(HCI_EVENT_COMMAND_STATUS, &filter); hci_filter_set(HCI_EVENT_COMMAND_COMPL, &filter); hci_filter_set(HCI_EVENT_PIN_CODE_REQ, &filter); hci_filter_set(HCI_EVENT_LINK_KEY_REQ, &filter); hci_filter_set(HCI_EVENT_LINK_KEY_NOTIFICATION, &filter); if (setsockopt(physif->fd, BTPROTO_HCI, SO_HCI_EVT_FILTER, &filter, sizeof(filter)) < 0) { log_warn("SO_HCI_EVT_FILTER"); return -1; } event_set(&physif->ev, physif->fd, EV_READ|EV_PERSIST, &hci_eventcb, physif); if (event_add(&physif->ev, NULL)) { log_warnx("event_add"); return -1; } log_info("listening on %s (%s)", bt_ntoa(&physif->addr, NULL), physif->xname); return 0; } void hci_interface_close(struct bt_interface *iface) { struct hci_physif *physif = iface->physif; if (physif == NULL) return; if (physif->fd != -1) { close(physif->fd); physif->fd = -1; } iface->physif = NULL; #if 0 if (hci_unit_down(physif->xname) < 0) return; #endif log_info("stopped listening on %s", bt_ntoa(&physif->addr, NULL)); } int hci_interface_reinit(struct bt_interface *iface) { struct btreq btr; struct hci_physif *physif; uint8_t val; int err; if ((physif = iface->physif) == NULL) /* no physical interface, so we keep changes pending */ return 0; if (iface->changes & BTIF_NAME_CHANGED) { if (hci_write(physif->fd, HCI_CMD_WRITE_LOCAL_NAME, iface->name, HCI_UNIT_NAME_SIZE)) return -1; iface->changes &= ~BTIF_NAME_CHANGED; } /* The rest is unconditional configuration. */ if (hci_read(physif->fd, HCI_CMD_READ_SCAN_ENABLE, &val, sizeof(val))) return -1; val |= HCI_PAGE_SCAN_ENABLE; val |= HCI_INQUIRY_SCAN_ENABLE; if (hci_write(physif->fd, HCI_CMD_WRITE_SCAN_ENABLE, &val, sizeof(val))) return -1; bdaddr_copy(&btr.btr_bdaddr, &physif->addr); if (ioctl(physif->fd, SIOCGBTINFOA, &btr) < 0) { log_warn("SIOCGBTINFOA"); return -1; } btr.btr_link_policy |= HCI_LINK_POLICY_ENABLE_ROLE_SWITCH; bt_priv_msg(IMSG_SET_LINK_POLICY); bt_priv_send(&physif->addr, sizeof(bdaddr_t)); bt_priv_send(&btr.btr_link_policy, sizeof(uint16_t)); bt_priv_recv(&err, sizeof(int)); if (err != 0) { log_warnx("SET_LINK_POLICY failed (%s)", strerror(err)); return -1; } return 0; } void hci_eventcb(int fd, short evflags, void *arg) { char buf[HCI_EVENT_PKT_SIZE]; hci_event_hdr_t *event = (hci_event_hdr_t *)buf; struct hci_physif *physif = arg; struct sockaddr_bt sa; socklen_t size; bdaddr_t *addr; void *ep; int n; if (physif == NULL) fatal("HCI event on closed socket?"); size = sizeof(sa); n = recvfrom(physif->fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &size); if (n < 0) { log_warn("could not receive from HCI socket"); return; } if (n < sizeof(hci_event_hdr_t)) { log_warnx("short HCI packet"); return; } if (event->type != HCI_EVENT_PKT) { log_packet(&sa.bt_bdaddr, &physif->addr, "unexpected HCI packet, type=%#x", event->type); return; } addr = (bdaddr_t *)(event + 1); ep = (bdaddr_t *)(event + 1); /* XXX check packet size */ switch (event->event) { case HCI_EVENT_COMMAND_STATUS: case HCI_EVENT_COMMAND_COMPL: break; case HCI_EVENT_PIN_CODE_REQ: hci_process_pin_code_req(physif, &sa, addr); break; case HCI_EVENT_LINK_KEY_REQ: hci_process_link_key_req(physif, &sa, addr); break; case HCI_EVENT_LINK_KEY_NOTIFICATION: hci_process_link_key_notification(physif, &sa, ep); break; default: log_packet(&sa.bt_bdaddr, &physif->addr, "unexpected HCI event, event=%#x", event->event); break; } } int hci_process_pin_code_req(struct hci_physif *physif, struct sockaddr_bt *sa, const bdaddr_t *addr) { hci_pin_code_rep_cp cp; struct btd *env = physif->env; int fd = physif->fd; conf_lookup_pin(env, addr, cp.pin, &cp.pin_size); if (cp.pin_size == 0) { log_info("%s: PIN code not found", bt_ntoa(addr, NULL)); return hci_send_cmd(fd, sa, HCI_CMD_PIN_CODE_NEG_REP, sizeof(bdaddr_t), (void *)addr); } else { log_info("%s: PIN code found", bt_ntoa(addr, NULL)); bdaddr_copy(&cp.bdaddr, addr); return hci_send_cmd(fd, sa, HCI_CMD_PIN_CODE_REP, sizeof(cp), &cp); } } int hci_process_link_key_req(struct hci_physif *physif, struct sockaddr_bt *sa, const bdaddr_t *addr) { hci_link_key_rep_cp cp; struct btd *env = physif->env; int fd = physif->fd; if (db_get_link_key(&env->db, addr, cp.key)) { log_info("%s: link key not found", bt_ntoa(addr, NULL)); return hci_send_cmd(fd, sa, HCI_CMD_LINK_KEY_NEG_REP, sizeof(bdaddr_t), (void *)addr); } log_info("%s: link key found", bt_ntoa(addr, NULL)); bdaddr_copy(&cp.bdaddr, addr); return hci_send_cmd(fd, sa, HCI_CMD_LINK_KEY_REP, sizeof(cp), &cp); } int hci_process_link_key_notification(struct hci_physif *physif, struct sockaddr_bt *sa, const hci_link_key_notification_ep *ep) { const bdaddr_t *addr = &ep->bdaddr; struct btd *env = physif->env; int res; if ((res = db_put_link_key(&env->db, addr, ep->key)) == 0) log_info("%s: link key stored", bt_ntoa(addr, NULL)); else log_info("%s: link key not stored", bt_ntoa(addr, NULL)); return res; } /* * Basic HCI cmd request function with argument return. * * Normally, this will return on COMMAND_STATUS or COMMAND_COMPLETE * for the given opcode, but if event is given then it will ignore * COMMAND_STATUS (unless error) and wait for the specified event. * * If rbuf/rlen is given, results will be copied into the result * buffer for COMMAND_COMPLETE/event responses. */ int hci_req(int hci, uint16_t opcode, uint8_t event, void *cbuf, size_t clen, void *rbuf, size_t rlen) { uint8_t msg[sizeof(hci_cmd_hdr_t) + HCI_CMD_PKT_SIZE]; hci_event_hdr_t *ep; hci_cmd_hdr_t *cp; cp = (hci_cmd_hdr_t *)msg; cp->type = HCI_CMD_PKT; cp->opcode = opcode = htole16(opcode); cp->length = clen = MIN(clen, sizeof(msg) - sizeof(hci_cmd_hdr_t)); if (clen) memcpy((cp + 1), cbuf, clen); if (send(hci, msg, sizeof(hci_cmd_hdr_t) + clen, 0) < 0) { log_warn("HCI send"); return -1; } ep = (hci_event_hdr_t *)msg; for(;;) { if (recv(hci, msg, sizeof(msg), 0) < 0) { if (errno == EAGAIN || errno == EINTR) continue; log_warn("HCI recv"); return -1; } if (ep->event == HCI_EVENT_COMMAND_STATUS) { hci_command_status_ep *cs; cs = (hci_command_status_ep *)(ep + 1); if (cs->opcode != opcode) continue; if (cs->status) { log_warnx("HCI cmd (%4.4x) failed (status %d)", opcode, cs->status); return -1; } if (event == 0) break; continue; } if (ep->event == HCI_EVENT_COMMAND_COMPL) { hci_command_compl_ep *cc; uint8_t *ptr; cc = (hci_command_compl_ep *)(ep + 1); if (cc->opcode != opcode) continue; if (rbuf == NULL) break; ptr = (uint8_t *)(cc + 1); if (*ptr) { log_warn("HCI cmd (%4.4x) failed (status %d)", opcode, *ptr); return -1; } memcpy(rbuf, ++ptr, rlen); break; } if (ep->event == event) { if (rbuf == NULL) break; memcpy(rbuf, (ep + 1), rlen); break; } } return 0; } /* Send HCI Command Packet to socket */ int hci_send_cmd(int sock, struct sockaddr_bt *sa, uint16_t opcode, size_t len, void *buf) { char msg[HCI_CMD_PKT_SIZE]; hci_cmd_hdr_t *h = (hci_cmd_hdr_t *)msg; h->type = HCI_CMD_PKT; h->opcode = htole16(opcode); h->length = len; if (len > 0) memcpy(msg + sizeof(hci_cmd_hdr_t), buf, len); if (sendto(sock, msg, sizeof(hci_cmd_hdr_t) + len, 0, (struct sockaddr *)sa, sizeof(*sa)) == -1) { log_warn("HCI send command"); return -1; } return 0; }