forked from mirrors/linux
		
	Bluetooth: Use LE buffers for LE traffic
Bluetooth chips may have separate buffers for LE traffic. This patch add support to use LE buffers provided by the chip. Signed-off-by: Ville Tervo <ville.tervo@nokia.com> Acked-by: Marcel Holtmann <marcel@holtmann.org> Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
This commit is contained in:
		
							parent
							
								
									fcd89c09a5
								
							
						
					
					
						commit
						6ed58ec520
					
				
					 4 changed files with 113 additions and 4 deletions
				
			
		|  | @ -123,15 +123,19 @@ struct hci_dev { | |||
| 	atomic_t	cmd_cnt; | ||||
| 	unsigned int	acl_cnt; | ||||
| 	unsigned int	sco_cnt; | ||||
| 	unsigned int	le_cnt; | ||||
| 
 | ||||
| 	unsigned int	acl_mtu; | ||||
| 	unsigned int	sco_mtu; | ||||
| 	unsigned int	le_mtu; | ||||
| 	unsigned int	acl_pkts; | ||||
| 	unsigned int	sco_pkts; | ||||
| 	unsigned int	le_pkts; | ||||
| 
 | ||||
| 	unsigned long	cmd_last_tx; | ||||
| 	unsigned long	acl_last_tx; | ||||
| 	unsigned long	sco_last_tx; | ||||
| 	unsigned long	le_last_tx; | ||||
| 
 | ||||
| 	struct workqueue_struct	*workqueue; | ||||
| 
 | ||||
|  | @ -521,6 +525,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn); | |||
| #define lmp_esco_capable(dev)      ((dev)->features[3] & LMP_ESCO) | ||||
| #define lmp_ssp_capable(dev)       ((dev)->features[6] & LMP_SIMPLE_PAIR) | ||||
| #define lmp_no_flush_capable(dev)  ((dev)->features[6] & LMP_NO_FLUSH) | ||||
| #define lmp_le_capable(dev)        ((dev)->features[4] & LMP_LE) | ||||
| 
 | ||||
| /* ----- HCI protocols ----- */ | ||||
| struct hci_proto { | ||||
|  |  | |||
|  | @ -326,6 +326,11 @@ int hci_conn_del(struct hci_conn *conn) | |||
| 
 | ||||
| 		/* Unacked frames */ | ||||
| 		hdev->acl_cnt += conn->sent; | ||||
| 	} else if (conn->type == LE_LINK) { | ||||
| 		if (hdev->le_pkts) | ||||
| 			hdev->le_cnt += conn->sent; | ||||
| 		else | ||||
| 			hdev->acl_cnt += conn->sent; | ||||
| 	} else { | ||||
| 		struct hci_conn *acl = conn->link; | ||||
| 		if (acl) { | ||||
|  |  | |||
|  | @ -263,6 +263,14 @@ static void hci_init_req(struct hci_dev *hdev, unsigned long opt) | |||
| 	hci_send_cmd(hdev, HCI_OP_DELETE_STORED_LINK_KEY, sizeof(cp), &cp); | ||||
| } | ||||
| 
 | ||||
| static void hci_le_init_req(struct hci_dev *hdev, unsigned long opt) | ||||
| { | ||||
| 	BT_DBG("%s", hdev->name); | ||||
| 
 | ||||
| 	/* Read LE buffer size */ | ||||
| 	hci_send_cmd(hdev, HCI_OP_LE_READ_BUFFER_SIZE, 0, NULL); | ||||
| } | ||||
| 
 | ||||
| static void hci_scan_req(struct hci_dev *hdev, unsigned long opt) | ||||
| { | ||||
| 	__u8 scan = opt; | ||||
|  | @ -529,6 +537,10 @@ int hci_dev_open(__u16 dev) | |||
| 		ret = __hci_request(hdev, hci_init_req, 0, | ||||
| 					msecs_to_jiffies(HCI_INIT_TIMEOUT)); | ||||
| 
 | ||||
| 		if (lmp_le_capable(hdev)) | ||||
| 			ret = __hci_request(hdev, hci_le_init_req, 0, | ||||
| 					msecs_to_jiffies(HCI_INIT_TIMEOUT)); | ||||
| 
 | ||||
| 		clear_bit(HCI_INIT, &hdev->flags); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -671,7 +683,7 @@ int hci_dev_reset(__u16 dev) | |||
| 		hdev->flush(hdev); | ||||
| 
 | ||||
| 	atomic_set(&hdev->cmd_cnt, 1); | ||||
| 	hdev->acl_cnt = 0; hdev->sco_cnt = 0; | ||||
| 	hdev->acl_cnt = 0; hdev->sco_cnt = 0; hdev->le_cnt = 0; | ||||
| 
 | ||||
| 	if (!test_bit(HCI_RAW, &hdev->flags)) | ||||
| 		ret = __hci_request(hdev, hci_reset_req, 0, | ||||
|  | @ -1672,8 +1684,25 @@ static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int | |||
| 	} | ||||
| 
 | ||||
| 	if (conn) { | ||||
| 		int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt); | ||||
| 		int q = cnt / num; | ||||
| 		int cnt, q; | ||||
| 
 | ||||
| 		switch (conn->type) { | ||||
| 		case ACL_LINK: | ||||
| 			cnt = hdev->acl_cnt; | ||||
| 			break; | ||||
| 		case SCO_LINK: | ||||
| 		case ESCO_LINK: | ||||
| 			cnt = hdev->sco_cnt; | ||||
| 			break; | ||||
| 		case LE_LINK: | ||||
| 			cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt; | ||||
| 			break; | ||||
| 		default: | ||||
| 			cnt = 0; | ||||
| 			BT_ERR("Unknown link type"); | ||||
| 		} | ||||
| 
 | ||||
| 		q = cnt / num; | ||||
| 		*quote = q ? q : 1; | ||||
| 	} else | ||||
| 		*quote = 0; | ||||
|  | @ -1772,6 +1801,40 @@ static inline void hci_sched_esco(struct hci_dev *hdev) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| static inline void hci_sched_le(struct hci_dev *hdev) | ||||
| { | ||||
| 	struct hci_conn *conn; | ||||
| 	struct sk_buff *skb; | ||||
| 	int quote, cnt; | ||||
| 
 | ||||
| 	BT_DBG("%s", hdev->name); | ||||
| 
 | ||||
| 	if (!test_bit(HCI_RAW, &hdev->flags)) { | ||||
| 		/* LE tx timeout must be longer than maximum
 | ||||
| 		 * link supervision timeout (40.9 seconds) */ | ||||
| 		if (!hdev->le_cnt && | ||||
| 				time_after(jiffies, hdev->le_last_tx + HZ * 45)) | ||||
| 			hci_acl_tx_to(hdev); | ||||
| 	} | ||||
| 
 | ||||
| 	cnt = hdev->le_pkts ? hdev->le_cnt : hdev->acl_cnt; | ||||
| 	while (cnt && (conn = hci_low_sent(hdev, LE_LINK, "e))) { | ||||
| 		while (quote-- && (skb = skb_dequeue(&conn->data_q))) { | ||||
| 			BT_DBG("skb %p len %d", skb, skb->len); | ||||
| 
 | ||||
| 			hci_send_frame(skb); | ||||
| 			hdev->le_last_tx = jiffies; | ||||
| 
 | ||||
| 			cnt--; | ||||
| 			conn->sent++; | ||||
| 		} | ||||
| 	} | ||||
| 	if (hdev->le_pkts) | ||||
| 		hdev->le_cnt = cnt; | ||||
| 	else | ||||
| 		hdev->acl_cnt = cnt; | ||||
| } | ||||
| 
 | ||||
| static void hci_tx_task(unsigned long arg) | ||||
| { | ||||
| 	struct hci_dev *hdev = (struct hci_dev *) arg; | ||||
|  | @ -1779,7 +1842,8 @@ static void hci_tx_task(unsigned long arg) | |||
| 
 | ||||
| 	read_lock(&hci_task_lock); | ||||
| 
 | ||||
| 	BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt); | ||||
| 	BT_DBG("%s acl %d sco %d le %d", hdev->name, hdev->acl_cnt, | ||||
| 		hdev->sco_cnt, hdev->le_cnt); | ||||
| 
 | ||||
| 	/* Schedule queues and send stuff to HCI driver */ | ||||
| 
 | ||||
|  | @ -1789,6 +1853,8 @@ static void hci_tx_task(unsigned long arg) | |||
| 
 | ||||
| 	hci_sched_esco(hdev); | ||||
| 
 | ||||
| 	hci_sched_le(hdev); | ||||
| 
 | ||||
| 	/* Send next queued raw (unknown type) packet */ | ||||
| 	while ((skb = skb_dequeue(&hdev->raw_q))) | ||||
| 		hci_send_frame(skb); | ||||
|  |  | |||
|  | @ -776,6 +776,25 @@ static void hci_cc_pin_code_neg_reply(struct hci_dev *hdev, struct sk_buff *skb) | |||
| 		mgmt_pin_code_neg_reply_complete(hdev->id, &rp->bdaddr, | ||||
| 								rp->status); | ||||
| } | ||||
| static void hci_cc_le_read_buffer_size(struct hci_dev *hdev, | ||||
| 				       struct sk_buff *skb) | ||||
| { | ||||
| 	struct hci_rp_le_read_buffer_size *rp = (void *) skb->data; | ||||
| 
 | ||||
| 	BT_DBG("%s status 0x%x", hdev->name, rp->status); | ||||
| 
 | ||||
| 	if (rp->status) | ||||
| 		return; | ||||
| 
 | ||||
| 	hdev->le_mtu = __le16_to_cpu(rp->le_mtu); | ||||
| 	hdev->le_pkts = rp->le_max_pkt; | ||||
| 
 | ||||
| 	hdev->le_cnt = hdev->le_pkts; | ||||
| 
 | ||||
| 	BT_DBG("%s le mtu %d:%d", hdev->name, hdev->le_mtu, hdev->le_pkts); | ||||
| 
 | ||||
| 	hci_req_complete(hdev, HCI_OP_LE_READ_BUFFER_SIZE, rp->status); | ||||
| } | ||||
| 
 | ||||
| static inline void hci_cs_inquiry(struct hci_dev *hdev, __u8 status) | ||||
| { | ||||
|  | @ -1704,6 +1723,10 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk | |||
| 		hci_cc_pin_code_neg_reply(hdev, skb); | ||||
| 		break; | ||||
| 
 | ||||
| 	case HCI_OP_LE_READ_BUFFER_SIZE: | ||||
| 		hci_cc_le_read_buffer_size(hdev, skb); | ||||
| 		break; | ||||
| 
 | ||||
| 	default: | ||||
| 		BT_DBG("%s opcode 0x%x", hdev->name, opcode); | ||||
| 		break; | ||||
|  | @ -1849,6 +1872,16 @@ static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *s | |||
| 				hdev->acl_cnt += count; | ||||
| 				if (hdev->acl_cnt > hdev->acl_pkts) | ||||
| 					hdev->acl_cnt = hdev->acl_pkts; | ||||
| 			} else if (conn->type == LE_LINK) { | ||||
| 				if (hdev->le_pkts) { | ||||
| 					hdev->le_cnt += count; | ||||
| 					if (hdev->le_cnt > hdev->le_pkts) | ||||
| 						hdev->le_cnt = hdev->le_pkts; | ||||
| 				} else { | ||||
| 					hdev->acl_cnt += count; | ||||
| 					if (hdev->acl_cnt > hdev->acl_pkts) | ||||
| 						hdev->acl_cnt = hdev->acl_pkts; | ||||
| 				} | ||||
| 			} else { | ||||
| 				hdev->sco_cnt += count; | ||||
| 				if (hdev->sco_cnt > hdev->sco_pkts) | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Ville Tervo
						Ville Tervo