aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/net/bluetooth/hci_core.h2
-rw-r--r--include/net/bluetooth/mgmt.h12
-rw-r--r--net/bluetooth/mgmt.c196
3 files changed, 210 insertions, 0 deletions
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 4623f45c8892..cbbab6327621 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -384,6 +384,8 @@ struct hci_conn {
384 __s8 tx_power; 384 __s8 tx_power;
385 unsigned long flags; 385 unsigned long flags;
386 386
387 unsigned long conn_info_timestamp;
388
387 __u8 remote_cap; 389 __u8 remote_cap;
388 __u8 remote_auth; 390 __u8 remote_auth;
389 __u8 remote_id; 391 __u8 remote_id;
diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h
index d4b571c2f9fd..226ae03cafe7 100644
--- a/include/net/bluetooth/mgmt.h
+++ b/include/net/bluetooth/mgmt.h
@@ -409,6 +409,18 @@ struct mgmt_cp_load_irks {
409} __packed; 409} __packed;
410#define MGMT_LOAD_IRKS_SIZE 2 410#define MGMT_LOAD_IRKS_SIZE 2
411 411
412#define MGMT_OP_GET_CONN_INFO 0x0031
413struct mgmt_cp_get_conn_info {
414 struct mgmt_addr_info addr;
415} __packed;
416#define MGMT_GET_CONN_INFO_SIZE MGMT_ADDR_INFO_SIZE
417struct mgmt_rp_get_conn_info {
418 struct mgmt_addr_info addr;
419 __s8 rssi;
420 __s8 tx_power;
421 __s8 max_tx_power;
422} __packed;
423
412#define MGMT_EV_CMD_COMPLETE 0x0001 424#define MGMT_EV_CMD_COMPLETE 0x0001
413struct mgmt_ev_cmd_complete { 425struct mgmt_ev_cmd_complete {
414 __le16 opcode; 426 __le16 opcode;
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index f2a9422d6139..0e5a316fafbf 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -83,6 +83,7 @@ static const u16 mgmt_commands[] = {
83 MGMT_OP_SET_DEBUG_KEYS, 83 MGMT_OP_SET_DEBUG_KEYS,
84 MGMT_OP_SET_PRIVACY, 84 MGMT_OP_SET_PRIVACY,
85 MGMT_OP_LOAD_IRKS, 85 MGMT_OP_LOAD_IRKS,
86 MGMT_OP_GET_CONN_INFO,
86}; 87};
87 88
88static const u16 mgmt_events[] = { 89static const u16 mgmt_events[] = {
@@ -4557,6 +4558,200 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
4557 return err; 4558 return err;
4558} 4559}
4559 4560
4561struct cmd_conn_lookup {
4562 struct hci_conn *conn;
4563 bool valid_tx_power;
4564 u8 mgmt_status;
4565};
4566
4567static void get_conn_info_complete(struct pending_cmd *cmd, void *data)
4568{
4569 struct cmd_conn_lookup *match = data;
4570 struct mgmt_cp_get_conn_info *cp;
4571 struct mgmt_rp_get_conn_info rp;
4572 struct hci_conn *conn = cmd->user_data;
4573
4574 if (conn != match->conn)
4575 return;
4576
4577 cp = (struct mgmt_cp_get_conn_info *) cmd->param;
4578
4579 memset(&rp, 0, sizeof(rp));
4580 bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr);
4581 rp.addr.type = cp->addr.type;
4582
4583 if (!match->mgmt_status) {
4584 rp.rssi = conn->rssi;
4585
4586 if (match->valid_tx_power)
4587 rp.tx_power = conn->tx_power;
4588 else
4589 rp.tx_power = HCI_TX_POWER_INVALID;
4590
4591 rp.max_tx_power = HCI_TX_POWER_INVALID;
4592 }
4593
4594 cmd_complete(cmd->sk, cmd->index, MGMT_OP_GET_CONN_INFO,
4595 match->mgmt_status, &rp, sizeof(rp));
4596
4597 hci_conn_drop(conn);
4598
4599 mgmt_pending_remove(cmd);
4600}
4601
4602static void conn_info_refresh_complete(struct hci_dev *hdev, u8 status)
4603{
4604 struct hci_cp_read_rssi *cp;
4605 struct hci_conn *conn;
4606 struct cmd_conn_lookup match;
4607 u16 handle;
4608
4609 BT_DBG("status 0x%02x", status);
4610
4611 hci_dev_lock(hdev);
4612
4613 /* TX power data is valid in case request completed successfully,
4614 * otherwise we assume it's not valid.
4615 */
4616 match.valid_tx_power = !status;
4617
4618 /* Commands sent in request are either Read RSSI or Read Transmit Power
4619 * Level so we check which one was last sent to retrieve connection
4620 * handle. Both commands have handle as first parameter so it's safe to
4621 * cast data on the same command struct.
4622 *
4623 * First command sent is always Read RSSI and we fail only if it fails.
4624 * In other case we simply override error to indicate success as we
4625 * already remembered if TX power value is actually valid.
4626 */
4627 cp = hci_sent_cmd_data(hdev, HCI_OP_READ_RSSI);
4628 if (!cp) {
4629 cp = hci_sent_cmd_data(hdev, HCI_OP_READ_TX_POWER);
4630 status = 0;
4631 }
4632
4633 if (!cp) {
4634 BT_ERR("invalid sent_cmd in response");
4635 goto unlock;
4636 }
4637
4638 handle = __le16_to_cpu(cp->handle);
4639 conn = hci_conn_hash_lookup_handle(hdev, handle);
4640 if (!conn) {
4641 BT_ERR("unknown handle (%d) in response", handle);
4642 goto unlock;
4643 }
4644
4645 match.conn = conn;
4646 match.mgmt_status = mgmt_status(status);
4647
4648 /* Cache refresh is complete, now reply for mgmt request for given
4649 * connection only.
4650 */
4651 mgmt_pending_foreach(MGMT_OP_GET_CONN_INFO, hdev,
4652 get_conn_info_complete, &match);
4653
4654unlock:
4655 hci_dev_unlock(hdev);
4656}
4657
4658static int get_conn_info(struct sock *sk, struct hci_dev *hdev, void *data,
4659 u16 len)
4660{
4661 struct mgmt_cp_get_conn_info *cp = data;
4662 struct mgmt_rp_get_conn_info rp;
4663 struct hci_conn *conn;
4664 unsigned long conn_info_age;
4665 int err = 0;
4666
4667 BT_DBG("%s", hdev->name);
4668
4669 memset(&rp, 0, sizeof(rp));
4670 bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr);
4671 rp.addr.type = cp->addr.type;
4672
4673 if (!bdaddr_type_is_valid(cp->addr.type))
4674 return cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
4675 MGMT_STATUS_INVALID_PARAMS,
4676 &rp, sizeof(rp));
4677
4678 hci_dev_lock(hdev);
4679
4680 if (!hdev_is_powered(hdev)) {
4681 err = cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
4682 MGMT_STATUS_NOT_POWERED, &rp, sizeof(rp));
4683 goto unlock;
4684 }
4685
4686 if (cp->addr.type == BDADDR_BREDR)
4687 conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK,
4688 &cp->addr.bdaddr);
4689 else
4690 conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr);
4691
4692 if (!conn || conn->state != BT_CONNECTED) {
4693 err = cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
4694 MGMT_STATUS_NOT_CONNECTED, &rp, sizeof(rp));
4695 goto unlock;
4696 }
4697
4698 /* To avoid client trying to guess when to poll again for information we
4699 * calculate conn info age as random value between min/max set in hdev.
4700 */
4701 conn_info_age = hdev->conn_info_min_age +
4702 prandom_u32_max(hdev->conn_info_max_age -
4703 hdev->conn_info_min_age);
4704
4705 /* Query controller to refresh cached values if they are too old or were
4706 * never read.
4707 */
4708 if (time_after(jiffies, conn->conn_info_timestamp + conn_info_age) ||
4709 !conn->conn_info_timestamp) {
4710 struct hci_request req;
4711 struct hci_cp_read_tx_power req_txp_cp;
4712 struct hci_cp_read_rssi req_rssi_cp;
4713 struct pending_cmd *cmd;
4714
4715 hci_req_init(&req, hdev);
4716 req_rssi_cp.handle = cpu_to_le16(conn->handle);
4717 hci_req_add(&req, HCI_OP_READ_RSSI, sizeof(req_rssi_cp),
4718 &req_rssi_cp);
4719
4720 req_txp_cp.handle = cpu_to_le16(conn->handle);
4721 req_txp_cp.type = 0x00;
4722 hci_req_add(&req, HCI_OP_READ_TX_POWER,
4723 sizeof(req_txp_cp), &req_txp_cp);
4724
4725 err = hci_req_run(&req, conn_info_refresh_complete);
4726 if (err < 0)
4727 goto unlock;
4728
4729 cmd = mgmt_pending_add(sk, MGMT_OP_GET_CONN_INFO, hdev,
4730 data, len);
4731 if (!cmd) {
4732 err = -ENOMEM;
4733 goto unlock;
4734 }
4735
4736 hci_conn_hold(conn);
4737 cmd->user_data = conn;
4738
4739 conn->conn_info_timestamp = jiffies;
4740 } else {
4741 /* Cache is valid, just reply with values cached in hci_conn */
4742 rp.rssi = conn->rssi;
4743 rp.tx_power = conn->tx_power;
4744 rp.max_tx_power = HCI_TX_POWER_INVALID;
4745
4746 err = cmd_complete(sk, hdev->id, MGMT_OP_GET_CONN_INFO,
4747 MGMT_STATUS_SUCCESS, &rp, sizeof(rp));
4748 }
4749
4750unlock:
4751 hci_dev_unlock(hdev);
4752 return err;
4753}
4754
4560static const struct mgmt_handler { 4755static const struct mgmt_handler {
4561 int (*func) (struct sock *sk, struct hci_dev *hdev, void *data, 4756 int (*func) (struct sock *sk, struct hci_dev *hdev, void *data,
4562 u16 data_len); 4757 u16 data_len);
@@ -4612,6 +4807,7 @@ static const struct mgmt_handler {
4612 { set_debug_keys, false, MGMT_SETTING_SIZE }, 4807 { set_debug_keys, false, MGMT_SETTING_SIZE },
4613 { set_privacy, false, MGMT_SET_PRIVACY_SIZE }, 4808 { set_privacy, false, MGMT_SET_PRIVACY_SIZE },
4614 { load_irks, true, MGMT_LOAD_IRKS_SIZE }, 4809 { load_irks, true, MGMT_LOAD_IRKS_SIZE },
4810 { get_conn_info, false, MGMT_GET_CONN_INFO_SIZE },
4615}; 4811};
4616 4812
4617 4813