Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04837f64 authored by Marcel Holtmann's avatar Marcel Holtmann Committed by David S. Miller
Browse files

[Bluetooth] Add automatic sniff mode support



This patch introduces the automatic sniff mode feature. This allows
the host to switch idle connections into sniff mode to safe power.

Signed-off-by: default avatarUlisses Furquim <ulissesf@gmail.com>
Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
parent da1f5198
Loading
Loading
Loading
Loading
+56 −14
Original line number Diff line number Diff line
@@ -101,9 +101,10 @@ enum {
#define HCIINQUIRY	_IOR('H', 240, int)

/* HCI timeouts */
#define HCI_CONN_TIMEOUT	(HZ * 40)
#define HCI_DISCONN_TIMEOUT	(HZ * 2)
#define HCI_CONN_IDLE_TIMEOUT	(HZ * 60)
#define HCI_CONNECT_TIMEOUT	(40000)	/* 40 seconds */
#define HCI_DISCONN_TIMEOUT	(2000)	/* 2 seconds */
#define HCI_IDLE_TIMEOUT	(6000)	/* 6 seconds */
#define HCI_INIT_TIMEOUT	(10000)	/* 10 seconds */

/* HCI Packet types */
#define HCI_COMMAND_PKT		0x01
@@ -145,7 +146,7 @@ enum {
#define LMP_TACCURACY	0x10
#define LMP_RSWITCH	0x20
#define LMP_HOLD	0x40
#define LMP_SNIF	0x80
#define LMP_SNIFF	0x80

#define LMP_PARK	0x01
#define LMP_RSSI	0x02
@@ -160,13 +161,21 @@ enum {
#define LMP_PSCHEME	0x02
#define LMP_PCONTROL	0x04

#define LMP_SNIFF_SUBR	0x02

/* Connection modes */
#define HCI_CM_ACTIVE	0x0000
#define HCI_CM_HOLD	0x0001
#define HCI_CM_SNIFF	0x0002
#define HCI_CM_PARK	0x0003

/* Link policies */
#define HCI_LP_RSWITCH	0x0001
#define HCI_LP_HOLD	0x0002
#define HCI_LP_SNIFF	0x0004
#define HCI_LP_PARK	0x0008

/* Link mode */
/* Link modes */
#define HCI_LM_ACCEPT	0x8000
#define HCI_LM_MASTER	0x0001
#define HCI_LM_AUTH	0x0002
@@ -192,7 +201,7 @@ struct hci_rp_read_loc_version {
} __attribute__ ((packed));

#define OCF_READ_LOCAL_FEATURES	0x0003
struct hci_rp_read_loc_features {
struct hci_rp_read_local_features {
	__u8 status;
	__u8 features[8];
} __attribute__ ((packed));
@@ -376,17 +385,32 @@ struct hci_cp_change_conn_link_key {
} __attribute__ ((packed));

#define OCF_READ_REMOTE_FEATURES 0x001B
struct hci_cp_read_rmt_features {
struct hci_cp_read_remote_features {
	__le16   handle;
} __attribute__ ((packed));

#define OCF_READ_REMOTE_VERSION 0x001D
struct hci_cp_read_rmt_version {
struct hci_cp_read_remote_version {
	__le16   handle;
} __attribute__ ((packed));

/* Link Policy */
#define OGF_LINK_POLICY	0x02   

#define OCF_SNIFF_MODE		0x0003
struct hci_cp_sniff_mode {
	__le16   handle;
	__le16   max_interval;
	__le16   min_interval;
	__le16   attempt;
	__le16   timeout;
} __attribute__ ((packed));

#define OCF_EXIT_SNIFF_MODE	0x0004
struct hci_cp_exit_sniff_mode {
	__le16   handle;
} __attribute__ ((packed));

#define OCF_ROLE_DISCOVERY	0x0009
struct hci_cp_role_discovery {
	__le16   handle;
@@ -423,6 +447,14 @@ struct hci_rp_write_link_policy {
	__le16   handle;
} __attribute__ ((packed));

#define OCF_SNIFF_SUBRATE	0x0011
struct hci_cp_sniff_subrate {
	__le16   handle;
	__le16   max_latency;
	__le16   min_remote_timeout;
	__le16   min_local_timeout;
} __attribute__ ((packed));

/* Status params */
#define OGF_STATUS_PARAM	0x05

@@ -582,15 +614,15 @@ struct hci_ev_link_key_notify {
	__u8	 key_type;
} __attribute__ ((packed));

#define HCI_EV_RMT_FEATURES	0x0B
struct hci_ev_rmt_features {
#define HCI_EV_REMOTE_FEATURES	0x0B
struct hci_ev_remote_features {
	__u8     status;
	__le16   handle;
	__u8     features[8];
} __attribute__ ((packed));

#define HCI_EV_RMT_VERSION	0x0C
struct hci_ev_rmt_version {
#define HCI_EV_REMOTE_VERSION	0x0C
struct hci_ev_remote_version {
	__u8     status;
	__le16   handle;
	__u8     lmp_ver;
@@ -611,6 +643,16 @@ struct hci_ev_pscan_rep_mode {
	__u8     pscan_rep_mode;
} __attribute__ ((packed));

#define HCI_EV_SNIFF_SUBRATE	0x2E
struct hci_ev_sniff_subrate {
	__u8     status;
	__le16   handle;
	__le16   max_tx_latency;
	__le16   max_rx_latency;
	__le16   max_remote_timeout;
	__le16   max_local_timeout;
} __attribute__ ((packed));

/* Internal events generated by Bluetooth stack */
#define HCI_EV_STACK_INTERNAL	0xFD
struct hci_ev_stack_internal {
+30 −24
Original line number Diff line number Diff line
@@ -31,10 +31,7 @@
#define HCI_PROTO_L2CAP	0
#define HCI_PROTO_SCO	1

#define HCI_INIT_TIMEOUT (HZ * 10)

/* HCI Core structures */

struct inquiry_data {
	bdaddr_t	bdaddr;
	__u8		pscan_rep_mode;
@@ -81,6 +78,10 @@ struct hci_dev {
	__u16		link_policy;
	__u16		link_mode;

	__u32		idle_timeout;
	__u16		sniff_min_interval;
	__u16		sniff_max_interval;

	unsigned long	quirks;

	atomic_t	cmd_cnt;
@@ -145,17 +146,23 @@ struct hci_conn {
	bdaddr_t	 dst;
	__u16		 handle;
	__u16		 state;
	__u8             mode;
	__u8		 type;
	__u8		 out;
	__u8		 dev_class[3];
	__u8             features[8];
	__u16            interval;
	__u16            link_policy;
	__u32		 link_mode;
	__u8             power_save;
	unsigned long	 pend;

	unsigned int	 sent;

	struct sk_buff_head data_q;

	struct timer_list timer;
	struct timer_list disc_timer;
	struct timer_list idle_timer;

	struct hci_dev	*hdev;
	void		*l2cap_data;
@@ -211,7 +218,8 @@ void hci_inquiry_cache_update(struct hci_dev *hdev, struct inquiry_data *data);
enum {
	HCI_CONN_AUTH_PEND,
	HCI_CONN_ENCRYPT_PEND,
	HCI_CONN_RSWITCH_PEND
	HCI_CONN_RSWITCH_PEND,
	HCI_CONN_MODE_CHANGE_PEND,
};

static inline void hci_conn_hash_init(struct hci_dev *hdev)
@@ -286,31 +294,27 @@ int hci_conn_encrypt(struct hci_conn *conn);
int hci_conn_change_link_key(struct hci_conn *conn);
int hci_conn_switch_role(struct hci_conn *conn, uint8_t role);

static inline void hci_conn_set_timer(struct hci_conn *conn, unsigned long timeout)
{
	mod_timer(&conn->timer, jiffies + timeout);
}

static inline void hci_conn_del_timer(struct hci_conn *conn)
{
	del_timer(&conn->timer);
}
void hci_conn_enter_active_mode(struct hci_conn *conn);
void hci_conn_enter_sniff_mode(struct hci_conn *conn);

static inline void hci_conn_hold(struct hci_conn *conn)
{
	atomic_inc(&conn->refcnt);
	hci_conn_del_timer(conn);
	del_timer(&conn->disc_timer);
}

static inline void hci_conn_put(struct hci_conn *conn)
{
	if (atomic_dec_and_test(&conn->refcnt)) {
		unsigned long timeo;
		if (conn->type == ACL_LINK) {
			unsigned long timeo = (conn->out) ?
				HCI_DISCONN_TIMEOUT : HCI_DISCONN_TIMEOUT * 2;
			hci_conn_set_timer(conn, timeo);
			timeo = msecs_to_jiffies(HCI_DISCONN_TIMEOUT);
			if (!conn->out)
				timeo *= 2;
			del_timer(&conn->idle_timer);
		} else
			hci_conn_set_timer(conn, HZ / 100);
			timeo = msecs_to_jiffies(10);
		mod_timer(&conn->disc_timer, jiffies + timeo);
	}
}

@@ -411,8 +415,10 @@ void hci_unregister_sysfs(struct hci_dev *hdev);
#define SET_HCIDEV_DEV(hdev, pdev) ((hdev)->class_dev.dev = (pdev))

/* ----- LMP capabilities ----- */
#define lmp_rswitch_capable(dev) (dev->features[0] & LMP_RSWITCH)
#define lmp_encrypt_capable(dev) (dev->features[0] & LMP_ENCRYPT)
#define lmp_rswitch_capable(dev)   ((dev)->features[0] & LMP_RSWITCH)
#define lmp_encrypt_capable(dev)   ((dev)->features[0] & LMP_ENCRYPT)
#define lmp_sniff_capable(dev)     ((dev)->features[0] & LMP_SNIFF)
#define lmp_sniffsubr_capable(dev) ((dev)->features[5] & LMP_SNIFF_SUBR)

/* ----- HCI protocols ----- */
struct hci_proto {
+1 −1
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@
#define BT_DBG(D...)
#endif

#define VERSION "2.8"
#define VERSION "2.9"

/* Bluetooth sockets */
#define BT_MAX_PROTO	8
+89 −11
Original line number Diff line number Diff line
@@ -132,11 +132,13 @@ static void hci_conn_timeout(unsigned long arg)
	return;
}

static void hci_conn_init_timer(struct hci_conn *conn)
static void hci_conn_idle(unsigned long arg)
{
	init_timer(&conn->timer);
	conn->timer.function = hci_conn_timeout;
	conn->timer.data = (unsigned long)conn;
	struct hci_conn *conn = (void *) arg;

	BT_DBG("conn %p mode %d", conn, conn->mode);

	hci_conn_enter_sniff_mode(conn);
}

struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
@@ -145,17 +147,27 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)

	BT_DBG("%s dst %s", hdev->name, batostr(dst));

	if (!(conn = kmalloc(sizeof(struct hci_conn), GFP_ATOMIC)))
	conn = kzalloc(sizeof(struct hci_conn), GFP_ATOMIC);
	if (!conn)
		return NULL;
	memset(conn, 0, sizeof(struct hci_conn));

	bacpy(&conn->dst, dst);
	conn->type   = type;
	conn->hdev   = hdev;
	conn->type   = type;
	conn->mode   = HCI_CM_ACTIVE;
	conn->state  = BT_OPEN;

	conn->power_save = 1;

	skb_queue_head_init(&conn->data_q);
	hci_conn_init_timer(conn);

	init_timer(&conn->disc_timer);
	conn->disc_timer.function = hci_conn_timeout;
	conn->disc_timer.data = (unsigned long) conn;

	init_timer(&conn->idle_timer);
	conn->idle_timer.function = hci_conn_idle;
	conn->idle_timer.data = (unsigned long) conn;

	atomic_set(&conn->refcnt, 0);

@@ -178,7 +190,9 @@ int hci_conn_del(struct hci_conn *conn)

	BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle);

	hci_conn_del_timer(conn);
	del_timer(&conn->idle_timer);

	del_timer(&conn->disc_timer);

	if (conn->type == SCO_LINK) {
		struct hci_conn *acl = conn->link;
@@ -364,6 +378,70 @@ int hci_conn_switch_role(struct hci_conn *conn, uint8_t role)
}
EXPORT_SYMBOL(hci_conn_switch_role);

/* Enter active mode */
void hci_conn_enter_active_mode(struct hci_conn *conn)
{
	struct hci_dev *hdev = conn->hdev;

	BT_DBG("conn %p mode %d", conn, conn->mode);

	if (test_bit(HCI_RAW, &hdev->flags))
		return;

	if (conn->mode != HCI_CM_SNIFF || !conn->power_save)
		goto timer;

	if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) {
		struct hci_cp_exit_sniff_mode cp;
		cp.handle = __cpu_to_le16(conn->handle);
		hci_send_cmd(hdev, OGF_LINK_POLICY,
				OCF_EXIT_SNIFF_MODE, sizeof(cp), &cp);
	}

timer:
	if (hdev->idle_timeout > 0)
		mod_timer(&conn->idle_timer,
			jiffies + msecs_to_jiffies(hdev->idle_timeout));
}

/* Enter sniff mode */
void hci_conn_enter_sniff_mode(struct hci_conn *conn)
{
	struct hci_dev *hdev = conn->hdev;

	BT_DBG("conn %p mode %d", conn, conn->mode);

	if (test_bit(HCI_RAW, &hdev->flags))
		return;

	if (!lmp_sniff_capable(hdev) || !lmp_sniff_capable(conn))
		return;

	if (conn->mode != HCI_CM_ACTIVE || !(conn->link_policy & HCI_LP_SNIFF))
		return;

	if (lmp_sniffsubr_capable(hdev) && lmp_sniffsubr_capable(conn)) {
		struct hci_cp_sniff_subrate cp;
		cp.handle             = __cpu_to_le16(conn->handle);
		cp.max_latency        = __constant_cpu_to_le16(0);
		cp.min_remote_timeout = __constant_cpu_to_le16(0);
		cp.min_local_timeout  = __constant_cpu_to_le16(0);
		hci_send_cmd(hdev, OGF_LINK_POLICY,
				OCF_SNIFF_SUBRATE, sizeof(cp), &cp);
	}

	if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) {
		struct hci_cp_sniff_mode cp;
		cp.handle       = __cpu_to_le16(conn->handle);
		cp.max_interval = __cpu_to_le16(hdev->sniff_max_interval);
		cp.min_interval = __cpu_to_le16(hdev->sniff_min_interval);
		cp.attempt      = __constant_cpu_to_le16(4);
		cp.timeout      = __constant_cpu_to_le16(1);
		hci_send_cmd(hdev, OGF_LINK_POLICY,
				OCF_SNIFF_MODE, sizeof(cp), &cp);
	}
}

/* Drop all connection on the device */
void hci_conn_hash_flush(struct hci_dev *hdev)
{
+24 −10
Original line number Diff line number Diff line
@@ -411,7 +411,7 @@ int hci_inquiry(void __user *arg)
	}
	hci_dev_unlock_bh(hdev);

	timeo = ir.length * 2 * HZ;
	timeo = ir.length * msecs_to_jiffies(2000);
	if (do_inquiry && (err = hci_request(hdev, hci_inq_req, (unsigned long)&ir, timeo)) < 0)
		goto done;

@@ -479,7 +479,8 @@ int hci_dev_open(__u16 dev)
		set_bit(HCI_INIT, &hdev->flags);

		//__hci_request(hdev, hci_reset_req, 0, HZ);
		ret = __hci_request(hdev, hci_init_req, 0, HCI_INIT_TIMEOUT);
		ret = __hci_request(hdev, hci_init_req, 0,
					msecs_to_jiffies(HCI_INIT_TIMEOUT));

		clear_bit(HCI_INIT, &hdev->flags);
	}
@@ -546,7 +547,8 @@ static int hci_dev_do_close(struct hci_dev *hdev)
	atomic_set(&hdev->cmd_cnt, 1);
	if (!test_bit(HCI_RAW, &hdev->flags)) {
		set_bit(HCI_INIT, &hdev->flags);
		__hci_request(hdev, hci_reset_req, 0, HZ/4);
		__hci_request(hdev, hci_reset_req, 0,
					msecs_to_jiffies(250));
		clear_bit(HCI_INIT, &hdev->flags);
	}

@@ -619,7 +621,8 @@ int hci_dev_reset(__u16 dev)
	hdev->acl_cnt = 0; hdev->sco_cnt = 0;

	if (!test_bit(HCI_RAW, &hdev->flags))
		ret = __hci_request(hdev, hci_reset_req, 0, HCI_INIT_TIMEOUT);
		ret = __hci_request(hdev, hci_reset_req, 0,
					msecs_to_jiffies(HCI_INIT_TIMEOUT));

done:
	tasklet_enable(&hdev->tx_task);
@@ -657,7 +660,8 @@ int hci_dev_cmd(unsigned int cmd, void __user *arg)

	switch (cmd) {
	case HCISETAUTH:
		err = hci_request(hdev, hci_auth_req, dr.dev_opt, HCI_INIT_TIMEOUT);
		err = hci_request(hdev, hci_auth_req, dr.dev_opt,
					msecs_to_jiffies(HCI_INIT_TIMEOUT));
		break;

	case HCISETENCRYPT:
@@ -668,18 +672,19 @@ int hci_dev_cmd(unsigned int cmd, void __user *arg)

		if (!test_bit(HCI_AUTH, &hdev->flags)) {
			/* Auth must be enabled first */
			err = hci_request(hdev, hci_auth_req,
					dr.dev_opt, HCI_INIT_TIMEOUT);
			err = hci_request(hdev, hci_auth_req, dr.dev_opt,
					msecs_to_jiffies(HCI_INIT_TIMEOUT));
			if (err)
				break;
		}

		err = hci_request(hdev, hci_encrypt_req,
					dr.dev_opt, HCI_INIT_TIMEOUT);
		err = hci_request(hdev, hci_encrypt_req, dr.dev_opt,
					msecs_to_jiffies(HCI_INIT_TIMEOUT));
		break;

	case HCISETSCAN:
		err = hci_request(hdev, hci_scan_req, dr.dev_opt, HCI_INIT_TIMEOUT);
		err = hci_request(hdev, hci_scan_req, dr.dev_opt,
					msecs_to_jiffies(HCI_INIT_TIMEOUT));
		break;

	case HCISETPTYPE:
@@ -848,6 +853,10 @@ int hci_register_dev(struct hci_dev *hdev)
	hdev->pkt_type  = (HCI_DM1 | HCI_DH1 | HCI_HV1);
	hdev->link_mode = (HCI_LM_ACCEPT);

	hdev->idle_timeout = 0;
	hdev->sniff_max_interval = 800;
	hdev->sniff_min_interval = 80;

	tasklet_init(&hdev->cmd_task, hci_cmd_task,(unsigned long) hdev);
	tasklet_init(&hdev->rx_task, hci_rx_task, (unsigned long) hdev);
	tasklet_init(&hdev->tx_task, hci_tx_task, (unsigned long) hdev);
@@ -1220,6 +1229,9 @@ static inline void hci_sched_acl(struct hci_dev *hdev)
	while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, &quote))) {
		while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
			BT_DBG("skb %p len %d", skb, skb->len);

			hci_conn_enter_active_mode(conn);

			hci_send_frame(skb);
			hdev->acl_last_tx = jiffies;

@@ -1298,6 +1310,8 @@ static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb)
	if (conn) {
		register struct hci_proto *hp;

		hci_conn_enter_active_mode(conn);

		/* Send to upper protocol */
		if ((hp = hci_proto[HCI_PROTO_L2CAP]) && hp->recv_acldata) {
			hp->recv_acldata(conn, skb, flags);
Loading