diff options
author | Kalle Valo <kvalo@qca.qualcomm.com> | 2012-03-25 10:15:28 -0400 |
---|---|---|
committer | Kalle Valo <kvalo@qca.qualcomm.com> | 2012-03-26 09:36:46 -0400 |
commit | 636f828844fad9421ea6e7df053bba995febdecf (patch) | |
tree | 48686f3155d6f7640ab3e975bf57612e7b02f52e /drivers/net | |
parent | e76ac2bf637defbe3b7fc644813be584b941ff0a (diff) |
ath6kl: Add HTC pipe implementation
This is needed for USB.
Based on code by Kevin Fang.
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/core.c | 15 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/core.h | 4 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/hif-ops.h | 34 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/hif.h | 6 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/htc.h | 35 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/htc_pipe.c | 1713 |
7 files changed, 1808 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/ath6kl/Makefile b/drivers/net/wireless/ath/ath6kl/Makefile index f4ac8174b24c..8cae8886f17d 100644 --- a/drivers/net/wireless/ath/ath6kl/Makefile +++ b/drivers/net/wireless/ath/ath6kl/Makefile | |||
@@ -26,6 +26,7 @@ obj-$(CONFIG_ATH6KL) += ath6kl_core.o | |||
26 | ath6kl_core-y += debug.o | 26 | ath6kl_core-y += debug.o |
27 | ath6kl_core-y += hif.o | 27 | ath6kl_core-y += hif.o |
28 | ath6kl_core-y += htc_mbox.o | 28 | ath6kl_core-y += htc_mbox.o |
29 | ath6kl_core-y += htc_pipe.o | ||
29 | ath6kl_core-y += bmi.o | 30 | ath6kl_core-y += bmi.o |
30 | ath6kl_core-y += cfg80211.o | 31 | ath6kl_core-y += cfg80211.o |
31 | ath6kl_core-y += init.o | 32 | ath6kl_core-y += init.o |
diff --git a/drivers/net/wireless/ath/ath6kl/core.c b/drivers/net/wireless/ath/ath6kl/core.c index bb9fe381c3c6..5c20a043a470 100644 --- a/drivers/net/wireless/ath/ath6kl/core.c +++ b/drivers/net/wireless/ath/ath6kl/core.c | |||
@@ -40,6 +40,18 @@ module_param(uart_debug, uint, 0644); | |||
40 | module_param(ath6kl_p2p, uint, 0644); | 40 | module_param(ath6kl_p2p, uint, 0644); |
41 | module_param(testmode, uint, 0644); | 41 | module_param(testmode, uint, 0644); |
42 | 42 | ||
43 | void ath6kl_core_tx_complete(struct ath6kl *ar, struct sk_buff *skb) | ||
44 | { | ||
45 | ath6kl_htc_tx_complete(ar, skb); | ||
46 | } | ||
47 | EXPORT_SYMBOL(ath6kl_core_tx_complete); | ||
48 | |||
49 | void ath6kl_core_rx_complete(struct ath6kl *ar, struct sk_buff *skb, u8 pipe) | ||
50 | { | ||
51 | ath6kl_htc_rx_complete(ar, skb, pipe); | ||
52 | } | ||
53 | EXPORT_SYMBOL(ath6kl_core_rx_complete); | ||
54 | |||
43 | int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type) | 55 | int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type) |
44 | { | 56 | { |
45 | struct ath6kl_bmi_target_info targ_info; | 57 | struct ath6kl_bmi_target_info targ_info; |
@@ -50,6 +62,9 @@ int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type) | |||
50 | case ATH6KL_HTC_TYPE_MBOX: | 62 | case ATH6KL_HTC_TYPE_MBOX: |
51 | ath6kl_htc_mbox_attach(ar); | 63 | ath6kl_htc_mbox_attach(ar); |
52 | break; | 64 | break; |
65 | case ATH6KL_HTC_TYPE_PIPE: | ||
66 | ath6kl_htc_pipe_attach(ar); | ||
67 | break; | ||
53 | default: | 68 | default: |
54 | WARN_ON(1); | 69 | WARN_ON(1); |
55 | return -ENOMEM; | 70 | return -ENOMEM; |
diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h index f71c3a7a5e72..75b1d864090a 100644 --- a/drivers/net/wireless/ath/ath6kl/core.h +++ b/drivers/net/wireless/ath/ath6kl/core.h | |||
@@ -464,6 +464,7 @@ enum ath6kl_hif_type { | |||
464 | 464 | ||
465 | enum ath6kl_htc_type { | 465 | enum ath6kl_htc_type { |
466 | ATH6KL_HTC_TYPE_MBOX, | 466 | ATH6KL_HTC_TYPE_MBOX, |
467 | ATH6KL_HTC_TYPE_PIPE, | ||
467 | }; | 468 | }; |
468 | 469 | ||
469 | /* Max number of filters that hw supports */ | 470 | /* Max number of filters that hw supports */ |
@@ -835,6 +836,9 @@ int ath6kl_init_hw_params(struct ath6kl *ar); | |||
835 | 836 | ||
836 | void ath6kl_check_wow_status(struct ath6kl *ar); | 837 | void ath6kl_check_wow_status(struct ath6kl *ar); |
837 | 838 | ||
839 | void ath6kl_core_tx_complete(struct ath6kl *ar, struct sk_buff *skb); | ||
840 | void ath6kl_core_rx_complete(struct ath6kl *ar, struct sk_buff *skb, u8 pipe); | ||
841 | |||
838 | struct ath6kl *ath6kl_core_create(struct device *dev); | 842 | struct ath6kl *ath6kl_core_create(struct device *dev); |
839 | int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type); | 843 | int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type); |
840 | void ath6kl_core_cleanup(struct ath6kl *ar); | 844 | void ath6kl_core_cleanup(struct ath6kl *ar); |
diff --git a/drivers/net/wireless/ath/ath6kl/hif-ops.h b/drivers/net/wireless/ath/ath6kl/hif-ops.h index fd84086638e3..8c9e72d5250d 100644 --- a/drivers/net/wireless/ath/ath6kl/hif-ops.h +++ b/drivers/net/wireless/ath/ath6kl/hif-ops.h | |||
@@ -150,4 +150,38 @@ static inline void ath6kl_hif_stop(struct ath6kl *ar) | |||
150 | ar->hif_ops->stop(ar); | 150 | ar->hif_ops->stop(ar); |
151 | } | 151 | } |
152 | 152 | ||
153 | static inline int ath6kl_hif_pipe_send(struct ath6kl *ar, | ||
154 | u8 pipe, struct sk_buff *hdr_buf, | ||
155 | struct sk_buff *buf) | ||
156 | { | ||
157 | ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe send\n"); | ||
158 | |||
159 | return ar->hif_ops->pipe_send(ar, pipe, hdr_buf, buf); | ||
160 | } | ||
161 | |||
162 | static inline void ath6kl_hif_pipe_get_default(struct ath6kl *ar, | ||
163 | u8 *ul_pipe, u8 *dl_pipe) | ||
164 | { | ||
165 | ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get default\n"); | ||
166 | |||
167 | ar->hif_ops->pipe_get_default(ar, ul_pipe, dl_pipe); | ||
168 | } | ||
169 | |||
170 | static inline int ath6kl_hif_pipe_map_service(struct ath6kl *ar, | ||
171 | u16 service_id, u8 *ul_pipe, | ||
172 | u8 *dl_pipe) | ||
173 | { | ||
174 | ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get default\n"); | ||
175 | |||
176 | return ar->hif_ops->pipe_map_service(ar, service_id, ul_pipe, dl_pipe); | ||
177 | } | ||
178 | |||
179 | static inline u16 ath6kl_hif_pipe_get_free_queue_number(struct ath6kl *ar, | ||
180 | u8 pipe) | ||
181 | { | ||
182 | ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get free queue number\n"); | ||
183 | |||
184 | return ar->hif_ops->pipe_get_free_queue_number(ar, pipe); | ||
185 | } | ||
186 | |||
153 | #endif | 187 | #endif |
diff --git a/drivers/net/wireless/ath/ath6kl/hif.h b/drivers/net/wireless/ath/ath6kl/hif.h index 20ed6b73517b..61f6b21fb0ae 100644 --- a/drivers/net/wireless/ath/ath6kl/hif.h +++ b/drivers/net/wireless/ath/ath6kl/hif.h | |||
@@ -256,6 +256,12 @@ struct ath6kl_hif_ops { | |||
256 | int (*power_on)(struct ath6kl *ar); | 256 | int (*power_on)(struct ath6kl *ar); |
257 | int (*power_off)(struct ath6kl *ar); | 257 | int (*power_off)(struct ath6kl *ar); |
258 | void (*stop)(struct ath6kl *ar); | 258 | void (*stop)(struct ath6kl *ar); |
259 | int (*pipe_send)(struct ath6kl *ar, u8 pipe, struct sk_buff *hdr_buf, | ||
260 | struct sk_buff *buf); | ||
261 | void (*pipe_get_default)(struct ath6kl *ar, u8 *pipe_ul, u8 *pipe_dl); | ||
262 | int (*pipe_map_service)(struct ath6kl *ar, u16 service_id, u8 *pipe_ul, | ||
263 | u8 *pipe_dl); | ||
264 | u16 (*pipe_get_free_queue_number)(struct ath6kl *ar, u8 pipe); | ||
259 | }; | 265 | }; |
260 | 266 | ||
261 | int ath6kl_hif_setup(struct ath6kl_device *dev); | 267 | int ath6kl_hif_setup(struct ath6kl_device *dev); |
diff --git a/drivers/net/wireless/ath/ath6kl/htc.h b/drivers/net/wireless/ath/ath6kl/htc.h index 43cb2cf270d6..a2c8ff809793 100644 --- a/drivers/net/wireless/ath/ath6kl/htc.h +++ b/drivers/net/wireless/ath/ath6kl/htc.h | |||
@@ -25,6 +25,7 @@ | |||
25 | /* send direction */ | 25 | /* send direction */ |
26 | #define HTC_FLAGS_NEED_CREDIT_UPDATE (1 << 0) | 26 | #define HTC_FLAGS_NEED_CREDIT_UPDATE (1 << 0) |
27 | #define HTC_FLAGS_SEND_BUNDLE (1 << 1) | 27 | #define HTC_FLAGS_SEND_BUNDLE (1 << 1) |
28 | #define HTC_FLAGS_TX_FIXUP_NETBUF (1 << 2) | ||
28 | 29 | ||
29 | /* receive direction */ | 30 | /* receive direction */ |
30 | #define HTC_FLG_RX_UNUSED (1 << 0) | 31 | #define HTC_FLG_RX_UNUSED (1 << 0) |
@@ -56,6 +57,10 @@ | |||
56 | #define HTC_CONN_FLGS_THRESH_LVL_THREE_QUAT 0x2 | 57 | #define HTC_CONN_FLGS_THRESH_LVL_THREE_QUAT 0x2 |
57 | #define HTC_CONN_FLGS_REDUCE_CRED_DRIB 0x4 | 58 | #define HTC_CONN_FLGS_REDUCE_CRED_DRIB 0x4 |
58 | #define HTC_CONN_FLGS_THRESH_MASK 0x3 | 59 | #define HTC_CONN_FLGS_THRESH_MASK 0x3 |
60 | /* disable credit flow control on a specific service */ | ||
61 | #define HTC_CONN_FLGS_DISABLE_CRED_FLOW_CTRL (1 << 3) | ||
62 | #define HTC_CONN_FLGS_SET_RECV_ALLOC_SHIFT 8 | ||
63 | #define HTC_CONN_FLGS_SET_RECV_ALLOC_MASK 0xFF00 | ||
59 | 64 | ||
60 | /* connect response status codes */ | 65 | /* connect response status codes */ |
61 | #define HTC_SERVICE_SUCCESS 0 | 66 | #define HTC_SERVICE_SUCCESS 0 |
@@ -75,6 +80,7 @@ | |||
75 | #define HTC_RECORD_LOOKAHEAD_BUNDLE 3 | 80 | #define HTC_RECORD_LOOKAHEAD_BUNDLE 3 |
76 | 81 | ||
77 | #define HTC_SETUP_COMP_FLG_RX_BNDL_EN (1 << 0) | 82 | #define HTC_SETUP_COMP_FLG_RX_BNDL_EN (1 << 0) |
83 | #define HTC_SETUP_COMP_FLG_DISABLE_TX_CREDIT_FLOW (1 << 1) | ||
78 | 84 | ||
79 | #define MAKE_SERVICE_ID(group, index) \ | 85 | #define MAKE_SERVICE_ID(group, index) \ |
80 | (int)(((int)group << 8) | (int)(index)) | 86 | (int)(((int)group << 8) | (int)(index)) |
@@ -109,6 +115,8 @@ | |||
109 | 115 | ||
110 | /* HTC operational parameters */ | 116 | /* HTC operational parameters */ |
111 | #define HTC_TARGET_RESPONSE_TIMEOUT 2000 /* in ms */ | 117 | #define HTC_TARGET_RESPONSE_TIMEOUT 2000 /* in ms */ |
118 | #define HTC_TARGET_RESPONSE_POLL_WAIT 10 | ||
119 | #define HTC_TARGET_RESPONSE_POLL_COUNT 200 | ||
112 | #define HTC_TARGET_DEBUG_INTR_MASK 0x01 | 120 | #define HTC_TARGET_DEBUG_INTR_MASK 0x01 |
113 | #define HTC_TARGET_CREDIT_INTR_MASK 0xF0 | 121 | #define HTC_TARGET_CREDIT_INTR_MASK 0xF0 |
114 | 122 | ||
@@ -128,6 +136,7 @@ | |||
128 | 136 | ||
129 | #define HTC_RECV_WAIT_BUFFERS (1 << 0) | 137 | #define HTC_RECV_WAIT_BUFFERS (1 << 0) |
130 | #define HTC_OP_STATE_STOPPING (1 << 0) | 138 | #define HTC_OP_STATE_STOPPING (1 << 0) |
139 | #define HTC_OP_STATE_SETUP_COMPLETE (1 << 1) | ||
131 | 140 | ||
132 | /* | 141 | /* |
133 | * The frame header length and message formats defined herein were selected | 142 | * The frame header length and message formats defined herein were selected |
@@ -512,6 +521,13 @@ struct htc_endpoint { | |||
512 | u32 conn_flags; | 521 | u32 conn_flags; |
513 | struct htc_endpoint_stats ep_st; | 522 | struct htc_endpoint_stats ep_st; |
514 | u16 tx_drop_packet_threshold; | 523 | u16 tx_drop_packet_threshold; |
524 | |||
525 | struct { | ||
526 | u8 pipeid_ul; | ||
527 | u8 pipeid_dl; | ||
528 | struct list_head tx_lookup_queue; | ||
529 | bool tx_credit_flow_enabled; | ||
530 | } pipe; | ||
515 | }; | 531 | }; |
516 | 532 | ||
517 | struct htc_control_buffer { | 533 | struct htc_control_buffer { |
@@ -519,6 +535,16 @@ struct htc_control_buffer { | |||
519 | u8 *buf; | 535 | u8 *buf; |
520 | }; | 536 | }; |
521 | 537 | ||
538 | struct htc_pipe_txcredit_alloc { | ||
539 | u16 service_id; | ||
540 | u8 credit_alloc; | ||
541 | }; | ||
542 | |||
543 | enum htc_send_queue_result { | ||
544 | HTC_SEND_QUEUE_OK = 0, /* packet was queued */ | ||
545 | HTC_SEND_QUEUE_DROP = 1, /* this packet should be dropped */ | ||
546 | }; | ||
547 | |||
522 | struct ath6kl_htc_ops { | 548 | struct ath6kl_htc_ops { |
523 | void* (*create)(struct ath6kl *ar); | 549 | void* (*create)(struct ath6kl *ar); |
524 | int (*wait_target)(struct htc_target *target); | 550 | int (*wait_target)(struct htc_target *target); |
@@ -593,6 +619,14 @@ struct htc_target { | |||
593 | 619 | ||
594 | /* counts the number of Tx without bundling continously per AC */ | 620 | /* counts the number of Tx without bundling continously per AC */ |
595 | u32 ac_tx_count[WMM_NUM_AC]; | 621 | u32 ac_tx_count[WMM_NUM_AC]; |
622 | |||
623 | struct { | ||
624 | struct htc_packet *htc_packet_pool; | ||
625 | u8 ctrl_response_buf[HTC_MAX_CTRL_MSG_LEN]; | ||
626 | int ctrl_response_len; | ||
627 | bool ctrl_response_valid; | ||
628 | struct htc_pipe_txcredit_alloc txcredit_alloc[ENDPOINT_MAX]; | ||
629 | } pipe; | ||
596 | }; | 630 | }; |
597 | 631 | ||
598 | int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, | 632 | int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, |
@@ -637,6 +671,7 @@ static inline int get_queue_depth(struct list_head *queue) | |||
637 | return depth; | 671 | return depth; |
638 | } | 672 | } |
639 | 673 | ||
674 | void ath6kl_htc_pipe_attach(struct ath6kl *ar); | ||
640 | void ath6kl_htc_mbox_attach(struct ath6kl *ar); | 675 | void ath6kl_htc_mbox_attach(struct ath6kl *ar); |
641 | 676 | ||
642 | #endif | 677 | #endif |
diff --git a/drivers/net/wireless/ath/ath6kl/htc_pipe.c b/drivers/net/wireless/ath/ath6kl/htc_pipe.c new file mode 100644 index 000000000000..b277b3446882 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc_pipe.c | |||
@@ -0,0 +1,1713 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2007-2011 Atheros Communications Inc. | ||
3 | * | ||
4 | * Permission to use, copy, modify, and/or distribute this software for any | ||
5 | * purpose with or without fee is hereby granted, provided that the above | ||
6 | * copyright notice and this permission notice appear in all copies. | ||
7 | * | ||
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
15 | */ | ||
16 | |||
17 | #include "core.h" | ||
18 | #include "debug.h" | ||
19 | #include "hif-ops.h" | ||
20 | |||
21 | #define HTC_PACKET_CONTAINER_ALLOCATION 32 | ||
22 | #define HTC_CONTROL_BUFFER_SIZE (HTC_MAX_CTRL_MSG_LEN + HTC_HDR_LENGTH) | ||
23 | |||
24 | static int ath6kl_htc_pipe_tx(struct htc_target *handle, | ||
25 | struct htc_packet *packet); | ||
26 | static void ath6kl_htc_pipe_cleanup(struct htc_target *handle); | ||
27 | |||
28 | /* htc pipe tx path */ | ||
29 | static inline void restore_tx_packet(struct htc_packet *packet) | ||
30 | { | ||
31 | if (packet->info.tx.flags & HTC_FLAGS_TX_FIXUP_NETBUF) { | ||
32 | skb_pull(packet->skb, sizeof(struct htc_frame_hdr)); | ||
33 | packet->info.tx.flags &= ~HTC_FLAGS_TX_FIXUP_NETBUF; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | static void do_send_completion(struct htc_endpoint *ep, | ||
38 | struct list_head *queue_to_indicate) | ||
39 | { | ||
40 | struct htc_packet *packet; | ||
41 | |||
42 | if (list_empty(queue_to_indicate)) { | ||
43 | /* nothing to indicate */ | ||
44 | return; | ||
45 | } | ||
46 | |||
47 | if (ep->ep_cb.tx_comp_multi != NULL) { | ||
48 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
49 | "%s: calling ep %d, send complete multiple callback (%d pkts)\n", | ||
50 | __func__, ep->eid, | ||
51 | get_queue_depth(queue_to_indicate)); | ||
52 | /* | ||
53 | * a multiple send complete handler is being used, | ||
54 | * pass the queue to the handler | ||
55 | */ | ||
56 | ep->ep_cb.tx_comp_multi(ep->target, queue_to_indicate); | ||
57 | /* | ||
58 | * all packets are now owned by the callback, | ||
59 | * reset queue to be safe | ||
60 | */ | ||
61 | INIT_LIST_HEAD(queue_to_indicate); | ||
62 | } else { | ||
63 | /* using legacy EpTxComplete */ | ||
64 | do { | ||
65 | packet = list_first_entry(queue_to_indicate, | ||
66 | struct htc_packet, list); | ||
67 | |||
68 | list_del(&packet->list); | ||
69 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
70 | "%s: calling ep %d send complete callback on packet 0x%p\n", | ||
71 | __func__, ep->eid, packet); | ||
72 | ep->ep_cb.tx_complete(ep->target, packet); | ||
73 | } while (!list_empty(queue_to_indicate)); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | static void send_packet_completion(struct htc_target *target, | ||
78 | struct htc_packet *packet) | ||
79 | { | ||
80 | struct htc_endpoint *ep = &target->endpoint[packet->endpoint]; | ||
81 | struct list_head container; | ||
82 | |||
83 | restore_tx_packet(packet); | ||
84 | INIT_LIST_HEAD(&container); | ||
85 | list_add_tail(&packet->list, &container); | ||
86 | |||
87 | /* do completion */ | ||
88 | do_send_completion(ep, &container); | ||
89 | } | ||
90 | |||
91 | static void get_htc_packet_credit_based(struct htc_target *target, | ||
92 | struct htc_endpoint *ep, | ||
93 | struct list_head *queue) | ||
94 | { | ||
95 | int credits_required; | ||
96 | int remainder; | ||
97 | u8 send_flags; | ||
98 | struct htc_packet *packet; | ||
99 | unsigned int transfer_len; | ||
100 | |||
101 | /* NOTE : the TX lock is held when this function is called */ | ||
102 | |||
103 | /* loop until we can grab as many packets out of the queue as we can */ | ||
104 | while (true) { | ||
105 | send_flags = 0; | ||
106 | if (list_empty(&ep->txq)) | ||
107 | break; | ||
108 | |||
109 | /* get packet at head, but don't remove it */ | ||
110 | packet = list_first_entry(&ep->txq, struct htc_packet, list); | ||
111 | if (packet == NULL) | ||
112 | break; | ||
113 | |||
114 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
115 | "%s: got head packet:0x%p , queue depth: %d\n", | ||
116 | __func__, packet, get_queue_depth(&ep->txq)); | ||
117 | |||
118 | transfer_len = packet->act_len + HTC_HDR_LENGTH; | ||
119 | |||
120 | if (transfer_len <= target->tgt_cred_sz) { | ||
121 | credits_required = 1; | ||
122 | } else { | ||
123 | /* figure out how many credits this message requires */ | ||
124 | credits_required = transfer_len / target->tgt_cred_sz; | ||
125 | remainder = transfer_len % target->tgt_cred_sz; | ||
126 | |||
127 | if (remainder) | ||
128 | credits_required++; | ||
129 | } | ||
130 | |||
131 | ath6kl_dbg(ATH6KL_DBG_HTC, "%s: creds required:%d got:%d\n", | ||
132 | __func__, credits_required, ep->cred_dist.credits); | ||
133 | |||
134 | if (ep->eid == ENDPOINT_0) { | ||
135 | /* | ||
136 | * endpoint 0 is special, it always has a credit and | ||
137 | * does not require credit based flow control | ||
138 | */ | ||
139 | credits_required = 0; | ||
140 | |||
141 | } else { | ||
142 | |||
143 | if (ep->cred_dist.credits < credits_required) | ||
144 | break; | ||
145 | |||
146 | ep->cred_dist.credits -= credits_required; | ||
147 | ep->ep_st.cred_cosumd += credits_required; | ||
148 | |||
149 | /* check if we need credits back from the target */ | ||
150 | if (ep->cred_dist.credits < | ||
151 | ep->cred_dist.cred_per_msg) { | ||
152 | /* tell the target we need credits ASAP! */ | ||
153 | send_flags |= HTC_FLAGS_NEED_CREDIT_UPDATE; | ||
154 | ep->ep_st.cred_low_indicate += 1; | ||
155 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
156 | "%s: host needs credits\n", | ||
157 | __func__); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | /* now we can fully dequeue */ | ||
162 | packet = list_first_entry(&ep->txq, struct htc_packet, list); | ||
163 | |||
164 | list_del(&packet->list); | ||
165 | /* save the number of credits this packet consumed */ | ||
166 | packet->info.tx.cred_used = credits_required; | ||
167 | /* save send flags */ | ||
168 | packet->info.tx.flags = send_flags; | ||
169 | packet->info.tx.seqno = ep->seqno; | ||
170 | ep->seqno++; | ||
171 | /* queue this packet into the caller's queue */ | ||
172 | list_add_tail(&packet->list, queue); | ||
173 | } | ||
174 | |||
175 | } | ||
176 | |||
177 | static void get_htc_packet(struct htc_target *target, | ||
178 | struct htc_endpoint *ep, | ||
179 | struct list_head *queue, int resources) | ||
180 | { | ||
181 | struct htc_packet *packet; | ||
182 | |||
183 | /* NOTE : the TX lock is held when this function is called */ | ||
184 | |||
185 | /* loop until we can grab as many packets out of the queue as we can */ | ||
186 | while (resources) { | ||
187 | if (list_empty(&ep->txq)) | ||
188 | break; | ||
189 | |||
190 | packet = list_first_entry(&ep->txq, struct htc_packet, list); | ||
191 | list_del(&packet->list); | ||
192 | |||
193 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
194 | "%s: got packet:0x%p , new queue depth: %d\n", | ||
195 | __func__, packet, get_queue_depth(&ep->txq)); | ||
196 | packet->info.tx.seqno = ep->seqno; | ||
197 | packet->info.tx.flags = 0; | ||
198 | packet->info.tx.cred_used = 0; | ||
199 | ep->seqno++; | ||
200 | |||
201 | /* queue this packet into the caller's queue */ | ||
202 | list_add_tail(&packet->list, queue); | ||
203 | resources--; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | static int htc_issue_packets(struct htc_target *target, | ||
208 | struct htc_endpoint *ep, | ||
209 | struct list_head *pkt_queue) | ||
210 | { | ||
211 | int status = 0; | ||
212 | u16 payload_len; | ||
213 | struct sk_buff *skb; | ||
214 | struct htc_frame_hdr *htc_hdr; | ||
215 | struct htc_packet *packet; | ||
216 | |||
217 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
218 | "%s: queue: 0x%p, pkts %d\n", __func__, | ||
219 | pkt_queue, get_queue_depth(pkt_queue)); | ||
220 | |||
221 | while (!list_empty(pkt_queue)) { | ||
222 | packet = list_first_entry(pkt_queue, struct htc_packet, list); | ||
223 | list_del(&packet->list); | ||
224 | |||
225 | skb = packet->skb; | ||
226 | if (!skb) { | ||
227 | WARN_ON_ONCE(1); | ||
228 | status = -EINVAL; | ||
229 | break; | ||
230 | } | ||
231 | |||
232 | payload_len = packet->act_len; | ||
233 | |||
234 | /* setup HTC frame header */ | ||
235 | htc_hdr = (struct htc_frame_hdr *) skb_push(skb, | ||
236 | sizeof(*htc_hdr)); | ||
237 | if (!htc_hdr) { | ||
238 | WARN_ON_ONCE(1); | ||
239 | status = -EINVAL; | ||
240 | break; | ||
241 | } | ||
242 | |||
243 | packet->info.tx.flags |= HTC_FLAGS_TX_FIXUP_NETBUF; | ||
244 | |||
245 | /* Endianess? */ | ||
246 | put_unaligned((u16) payload_len, &htc_hdr->payld_len); | ||
247 | htc_hdr->flags = packet->info.tx.flags; | ||
248 | htc_hdr->eid = (u8) packet->endpoint; | ||
249 | htc_hdr->ctrl[0] = 0; | ||
250 | htc_hdr->ctrl[1] = (u8) packet->info.tx.seqno; | ||
251 | |||
252 | spin_lock_bh(&target->tx_lock); | ||
253 | |||
254 | /* store in look up queue to match completions */ | ||
255 | list_add_tail(&packet->list, &ep->pipe.tx_lookup_queue); | ||
256 | ep->ep_st.tx_issued += 1; | ||
257 | spin_unlock_bh(&target->tx_lock); | ||
258 | |||
259 | status = ath6kl_hif_pipe_send(target->dev->ar, | ||
260 | ep->pipe.pipeid_ul, NULL, skb); | ||
261 | |||
262 | if (status != 0) { | ||
263 | if (status != -ENOMEM) { | ||
264 | /* TODO: if more than 1 endpoint maps to the | ||
265 | * same PipeID, it is possible to run out of | ||
266 | * resources in the HIF layer. | ||
267 | * Don't emit the error | ||
268 | */ | ||
269 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
270 | "%s: failed status:%d\n", | ||
271 | __func__, status); | ||
272 | } | ||
273 | spin_lock_bh(&target->tx_lock); | ||
274 | list_del(&packet->list); | ||
275 | |||
276 | /* reclaim credits */ | ||
277 | ep->cred_dist.credits += packet->info.tx.cred_used; | ||
278 | spin_unlock_bh(&target->tx_lock); | ||
279 | |||
280 | /* put it back into the callers queue */ | ||
281 | list_add(&packet->list, pkt_queue); | ||
282 | break; | ||
283 | } | ||
284 | |||
285 | } | ||
286 | |||
287 | if (status != 0) { | ||
288 | while (!list_empty(pkt_queue)) { | ||
289 | if (status != -ENOMEM) { | ||
290 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
291 | "%s: failed pkt:0x%p status:%d\n", | ||
292 | __func__, packet, status); | ||
293 | } | ||
294 | |||
295 | packet = list_first_entry(pkt_queue, | ||
296 | struct htc_packet, list); | ||
297 | list_del(&packet->list); | ||
298 | packet->status = status; | ||
299 | send_packet_completion(target, packet); | ||
300 | } | ||
301 | } | ||
302 | |||
303 | return status; | ||
304 | } | ||
305 | |||
306 | static enum htc_send_queue_result htc_try_send(struct htc_target *target, | ||
307 | struct htc_endpoint *ep, | ||
308 | struct list_head *txq) | ||
309 | { | ||
310 | struct list_head send_queue; /* temp queue to hold packets */ | ||
311 | struct htc_packet *packet, *tmp_pkt; | ||
312 | struct ath6kl *ar = target->dev->ar; | ||
313 | enum htc_send_full_action action; | ||
314 | int tx_resources, overflow, txqueue_depth, i, good_pkts; | ||
315 | u8 pipeid; | ||
316 | |||
317 | ath6kl_dbg(ATH6KL_DBG_HTC, "%s: (queue:0x%p depth:%d)\n", | ||
318 | __func__, txq, | ||
319 | (txq == NULL) ? 0 : get_queue_depth(txq)); | ||
320 | |||
321 | /* init the local send queue */ | ||
322 | INIT_LIST_HEAD(&send_queue); | ||
323 | |||
324 | /* | ||
325 | * txq equals to NULL means | ||
326 | * caller didn't provide a queue, just wants us to | ||
327 | * check queues and send | ||
328 | */ | ||
329 | if (txq != NULL) { | ||
330 | if (list_empty(txq)) { | ||
331 | /* empty queue */ | ||
332 | return HTC_SEND_QUEUE_DROP; | ||
333 | } | ||
334 | |||
335 | spin_lock_bh(&target->tx_lock); | ||
336 | txqueue_depth = get_queue_depth(&ep->txq); | ||
337 | spin_unlock_bh(&target->tx_lock); | ||
338 | |||
339 | if (txqueue_depth >= ep->max_txq_depth) { | ||
340 | /* we've already overflowed */ | ||
341 | overflow = get_queue_depth(txq); | ||
342 | } else { | ||
343 | /* get how much we will overflow by */ | ||
344 | overflow = txqueue_depth; | ||
345 | overflow += get_queue_depth(txq); | ||
346 | /* get how much we will overflow the TX queue by */ | ||
347 | overflow -= ep->max_txq_depth; | ||
348 | } | ||
349 | |||
350 | /* if overflow is negative or zero, we are okay */ | ||
351 | if (overflow > 0) { | ||
352 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
353 | "%s: Endpoint %d, TX queue will overflow :%d, Tx Depth:%d, Max:%d\n", | ||
354 | __func__, ep->eid, overflow, txqueue_depth, | ||
355 | ep->max_txq_depth); | ||
356 | } | ||
357 | if ((overflow <= 0) || | ||
358 | (ep->ep_cb.tx_full == NULL)) { | ||
359 | /* | ||
360 | * all packets will fit or caller did not provide send | ||
361 | * full indication handler -- just move all of them | ||
362 | * to the local send_queue object | ||
363 | */ | ||
364 | list_splice_tail_init(txq, &send_queue); | ||
365 | } else { | ||
366 | good_pkts = get_queue_depth(txq) - overflow; | ||
367 | if (good_pkts < 0) { | ||
368 | WARN_ON_ONCE(1); | ||
369 | return HTC_SEND_QUEUE_DROP; | ||
370 | } | ||
371 | |||
372 | /* we have overflowed, and a callback is provided */ | ||
373 | /* dequeue all non-overflow packets to the sendqueue */ | ||
374 | for (i = 0; i < good_pkts; i++) { | ||
375 | /* pop off caller's queue */ | ||
376 | packet = list_first_entry(txq, | ||
377 | struct htc_packet, | ||
378 | list); | ||
379 | list_del(&packet->list); | ||
380 | /* insert into local queue */ | ||
381 | list_add_tail(&packet->list, &send_queue); | ||
382 | } | ||
383 | |||
384 | /* | ||
385 | * the caller's queue has all the packets that won't fit | ||
386 | * walk through the caller's queue and indicate each to | ||
387 | * the send full handler | ||
388 | */ | ||
389 | list_for_each_entry_safe(packet, tmp_pkt, | ||
390 | txq, list) { | ||
391 | |||
392 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
393 | "%s: Indicat overflowed TX pkts: %p\n", | ||
394 | __func__, packet); | ||
395 | action = ep->ep_cb.tx_full(ep->target, packet); | ||
396 | if (action == HTC_SEND_FULL_DROP) { | ||
397 | /* callback wants the packet dropped */ | ||
398 | ep->ep_st.tx_dropped += 1; | ||
399 | |||
400 | /* leave this one in the caller's queue | ||
401 | * for cleanup */ | ||
402 | } else { | ||
403 | /* callback wants to keep this packet, | ||
404 | * remove from caller's queue */ | ||
405 | list_del(&packet->list); | ||
406 | /* put it in the send queue */ | ||
407 | list_add_tail(&packet->list, | ||
408 | &send_queue); | ||
409 | } | ||
410 | |||
411 | } | ||
412 | |||
413 | if (list_empty(&send_queue)) { | ||
414 | /* no packets made it in, caller will cleanup */ | ||
415 | return HTC_SEND_QUEUE_DROP; | ||
416 | } | ||
417 | } | ||
418 | } | ||
419 | |||
420 | if (!ep->pipe.tx_credit_flow_enabled) { | ||
421 | tx_resources = | ||
422 | ath6kl_hif_pipe_get_free_queue_number(ar, | ||
423 | ep->pipe.pipeid_ul); | ||
424 | } else { | ||
425 | tx_resources = 0; | ||
426 | } | ||
427 | |||
428 | spin_lock_bh(&target->tx_lock); | ||
429 | if (!list_empty(&send_queue)) { | ||
430 | /* transfer packets to tail */ | ||
431 | list_splice_tail_init(&send_queue, &ep->txq); | ||
432 | if (!list_empty(&send_queue)) { | ||
433 | WARN_ON_ONCE(1); | ||
434 | spin_unlock_bh(&target->tx_lock); | ||
435 | return HTC_SEND_QUEUE_DROP; | ||
436 | } | ||
437 | INIT_LIST_HEAD(&send_queue); | ||
438 | } | ||
439 | |||
440 | /* increment tx processing count on entry */ | ||
441 | ep->tx_proc_cnt++; | ||
442 | |||
443 | if (ep->tx_proc_cnt > 1) { | ||
444 | /* | ||
445 | * Another thread or task is draining the TX queues on this | ||
446 | * endpoint that thread will reset the tx processing count | ||
447 | * when the queue is drained. | ||
448 | */ | ||
449 | ep->tx_proc_cnt--; | ||
450 | spin_unlock_bh(&target->tx_lock); | ||
451 | return HTC_SEND_QUEUE_OK; | ||
452 | } | ||
453 | |||
454 | /***** beyond this point only 1 thread may enter ******/ | ||
455 | |||
456 | /* | ||
457 | * Now drain the endpoint TX queue for transmission as long as we have | ||
458 | * enough transmit resources. | ||
459 | */ | ||
460 | while (true) { | ||
461 | |||
462 | if (get_queue_depth(&ep->txq) == 0) | ||
463 | break; | ||
464 | |||
465 | if (ep->pipe.tx_credit_flow_enabled) { | ||
466 | /* | ||
467 | * Credit based mechanism provides flow control | ||
468 | * based on target transmit resource availability, | ||
469 | * we assume that the HIF layer will always have | ||
470 | * bus resources greater than target transmit | ||
471 | * resources. | ||
472 | */ | ||
473 | get_htc_packet_credit_based(target, ep, &send_queue); | ||
474 | } else { | ||
475 | /* | ||
476 | * Get all packets for this endpoint that we can | ||
477 | * for this pass. | ||
478 | */ | ||
479 | get_htc_packet(target, ep, &send_queue, tx_resources); | ||
480 | } | ||
481 | |||
482 | if (get_queue_depth(&send_queue) == 0) { | ||
483 | /* | ||
484 | * Didn't get packets due to out of resources or TX | ||
485 | * queue was drained. | ||
486 | */ | ||
487 | break; | ||
488 | } | ||
489 | |||
490 | spin_unlock_bh(&target->tx_lock); | ||
491 | |||
492 | /* send what we can */ | ||
493 | htc_issue_packets(target, ep, &send_queue); | ||
494 | |||
495 | if (!ep->pipe.tx_credit_flow_enabled) { | ||
496 | pipeid = ep->pipe.pipeid_ul; | ||
497 | tx_resources = | ||
498 | ath6kl_hif_pipe_get_free_queue_number(ar, pipeid); | ||
499 | } | ||
500 | |||
501 | spin_lock_bh(&target->tx_lock); | ||
502 | |||
503 | } | ||
504 | /* done with this endpoint, we can clear the count */ | ||
505 | ep->tx_proc_cnt = 0; | ||
506 | spin_unlock_bh(&target->tx_lock); | ||
507 | |||
508 | return HTC_SEND_QUEUE_OK; | ||
509 | } | ||
510 | |||
511 | /* htc control packet manipulation */ | ||
512 | static void destroy_htc_txctrl_packet(struct htc_packet *packet) | ||
513 | { | ||
514 | struct sk_buff *skb; | ||
515 | skb = packet->skb; | ||
516 | if (skb != NULL) | ||
517 | dev_kfree_skb(skb); | ||
518 | |||
519 | kfree(packet); | ||
520 | } | ||
521 | |||
522 | static struct htc_packet *build_htc_txctrl_packet(void) | ||
523 | { | ||
524 | struct htc_packet *packet = NULL; | ||
525 | struct sk_buff *skb; | ||
526 | |||
527 | packet = kzalloc(sizeof(struct htc_packet), GFP_KERNEL); | ||
528 | if (packet == NULL) | ||
529 | return NULL; | ||
530 | |||
531 | skb = __dev_alloc_skb(HTC_CONTROL_BUFFER_SIZE, GFP_KERNEL); | ||
532 | |||
533 | if (skb == NULL) { | ||
534 | kfree(packet); | ||
535 | return NULL; | ||
536 | } | ||
537 | packet->skb = skb; | ||
538 | |||
539 | return packet; | ||
540 | } | ||
541 | |||
542 | static void htc_free_txctrl_packet(struct htc_target *target, | ||
543 | struct htc_packet *packet) | ||
544 | { | ||
545 | destroy_htc_txctrl_packet(packet); | ||
546 | } | ||
547 | |||
548 | static struct htc_packet *htc_alloc_txctrl_packet(struct htc_target *target) | ||
549 | { | ||
550 | return build_htc_txctrl_packet(); | ||
551 | } | ||
552 | |||
553 | static void htc_txctrl_complete(struct htc_target *target, | ||
554 | struct htc_packet *packet) | ||
555 | { | ||
556 | htc_free_txctrl_packet(target, packet); | ||
557 | } | ||
558 | |||
559 | #define MAX_MESSAGE_SIZE 1536 | ||
560 | |||
561 | static int htc_setup_target_buffer_assignments(struct htc_target *target) | ||
562 | { | ||
563 | int status, credits, credit_per_maxmsg, i; | ||
564 | struct htc_pipe_txcredit_alloc *entry; | ||
565 | unsigned int hif_usbaudioclass = 0; | ||
566 | |||
567 | credit_per_maxmsg = MAX_MESSAGE_SIZE / target->tgt_cred_sz; | ||
568 | if (MAX_MESSAGE_SIZE % target->tgt_cred_sz) | ||
569 | credit_per_maxmsg++; | ||
570 | |||
571 | /* TODO, this should be configured by the caller! */ | ||
572 | |||
573 | credits = target->tgt_creds; | ||
574 | entry = &target->pipe.txcredit_alloc[0]; | ||
575 | |||
576 | status = -ENOMEM; | ||
577 | |||
578 | /* FIXME: hif_usbaudioclass is always zero */ | ||
579 | if (hif_usbaudioclass) { | ||
580 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
581 | "%s: For USB Audio Class- Total:%d\n", | ||
582 | __func__, credits); | ||
583 | entry++; | ||
584 | entry++; | ||
585 | /* Setup VO Service To have Max Credits */ | ||
586 | entry->service_id = WMI_DATA_VO_SVC; | ||
587 | entry->credit_alloc = (credits - 6); | ||
588 | if (entry->credit_alloc == 0) | ||
589 | entry->credit_alloc++; | ||
590 | |||
591 | credits -= (int) entry->credit_alloc; | ||
592 | if (credits <= 0) | ||
593 | return status; | ||
594 | |||
595 | entry++; | ||
596 | entry->service_id = WMI_CONTROL_SVC; | ||
597 | entry->credit_alloc = credit_per_maxmsg; | ||
598 | credits -= (int) entry->credit_alloc; | ||
599 | if (credits <= 0) | ||
600 | return status; | ||
601 | |||
602 | /* leftovers go to best effort */ | ||
603 | entry++; | ||
604 | entry++; | ||
605 | entry->service_id = WMI_DATA_BE_SVC; | ||
606 | entry->credit_alloc = (u8) credits; | ||
607 | status = 0; | ||
608 | } else { | ||
609 | entry++; | ||
610 | entry->service_id = WMI_DATA_VI_SVC; | ||
611 | entry->credit_alloc = credits / 4; | ||
612 | if (entry->credit_alloc == 0) | ||
613 | entry->credit_alloc++; | ||
614 | |||
615 | credits -= (int) entry->credit_alloc; | ||
616 | if (credits <= 0) | ||
617 | return status; | ||
618 | |||
619 | entry++; | ||
620 | entry->service_id = WMI_DATA_VO_SVC; | ||
621 | entry->credit_alloc = credits / 4; | ||
622 | if (entry->credit_alloc == 0) | ||
623 | entry->credit_alloc++; | ||
624 | |||
625 | credits -= (int) entry->credit_alloc; | ||
626 | if (credits <= 0) | ||
627 | return status; | ||
628 | |||
629 | entry++; | ||
630 | entry->service_id = WMI_CONTROL_SVC; | ||
631 | entry->credit_alloc = credit_per_maxmsg; | ||
632 | credits -= (int) entry->credit_alloc; | ||
633 | if (credits <= 0) | ||
634 | return status; | ||
635 | |||
636 | entry++; | ||
637 | entry->service_id = WMI_DATA_BK_SVC; | ||
638 | entry->credit_alloc = credit_per_maxmsg; | ||
639 | credits -= (int) entry->credit_alloc; | ||
640 | if (credits <= 0) | ||
641 | return status; | ||
642 | |||
643 | /* leftovers go to best effort */ | ||
644 | entry++; | ||
645 | entry->service_id = WMI_DATA_BE_SVC; | ||
646 | entry->credit_alloc = (u8) credits; | ||
647 | status = 0; | ||
648 | } | ||
649 | |||
650 | if (status == 0) { | ||
651 | for (i = 0; i < ENDPOINT_MAX; i++) { | ||
652 | if (target->pipe.txcredit_alloc[i].service_id != 0) { | ||
653 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
654 | "HTC Service Index : %d TX : 0x%2.2X : alloc:%d\n", | ||
655 | i, | ||
656 | target->pipe.txcredit_alloc[i]. | ||
657 | service_id, | ||
658 | target->pipe.txcredit_alloc[i]. | ||
659 | credit_alloc); | ||
660 | } | ||
661 | } | ||
662 | } | ||
663 | return status; | ||
664 | } | ||
665 | |||
666 | /* process credit reports and call distribution function */ | ||
667 | static void htc_process_credit_report(struct htc_target *target, | ||
668 | struct htc_credit_report *rpt, | ||
669 | int num_entries, | ||
670 | enum htc_endpoint_id from_ep) | ||
671 | { | ||
672 | int total_credits = 0, i; | ||
673 | struct htc_endpoint *ep; | ||
674 | |||
675 | /* lock out TX while we update credits */ | ||
676 | spin_lock_bh(&target->tx_lock); | ||
677 | |||
678 | for (i = 0; i < num_entries; i++, rpt++) { | ||
679 | if (rpt->eid >= ENDPOINT_MAX) { | ||
680 | WARN_ON_ONCE(1); | ||
681 | spin_unlock_bh(&target->tx_lock); | ||
682 | return; | ||
683 | } | ||
684 | |||
685 | ep = &target->endpoint[rpt->eid]; | ||
686 | ep->cred_dist.credits += rpt->credits; | ||
687 | |||
688 | if (ep->cred_dist.credits && get_queue_depth(&ep->txq)) { | ||
689 | spin_unlock_bh(&target->tx_lock); | ||
690 | htc_try_send(target, ep, NULL); | ||
691 | spin_lock_bh(&target->tx_lock); | ||
692 | } | ||
693 | |||
694 | total_credits += rpt->credits; | ||
695 | } | ||
696 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
697 | "Report indicated %d credits to distribute\n", | ||
698 | total_credits); | ||
699 | |||
700 | spin_unlock_bh(&target->tx_lock); | ||
701 | } | ||
702 | |||
703 | /* flush endpoint TX queue */ | ||
704 | static void htc_flush_tx_endpoint(struct htc_target *target, | ||
705 | struct htc_endpoint *ep, u16 tag) | ||
706 | { | ||
707 | struct htc_packet *packet; | ||
708 | |||
709 | spin_lock_bh(&target->tx_lock); | ||
710 | while (get_queue_depth(&ep->txq)) { | ||
711 | packet = list_first_entry(&ep->txq, struct htc_packet, list); | ||
712 | list_del(&packet->list); | ||
713 | packet->status = 0; | ||
714 | send_packet_completion(target, packet); | ||
715 | } | ||
716 | spin_unlock_bh(&target->tx_lock); | ||
717 | } | ||
718 | |||
719 | /* | ||
720 | * In the adapted HIF layer, struct sk_buff * are passed between HIF and HTC, | ||
721 | * since upper layers expects struct htc_packet containers we use the completed | ||
722 | * skb and lookup it's corresponding HTC packet buffer from a lookup list. | ||
723 | * This is extra overhead that can be fixed by re-aligning HIF interfaces with | ||
724 | * HTC. | ||
725 | */ | ||
726 | static struct htc_packet *htc_lookup_tx_packet(struct htc_target *target, | ||
727 | struct htc_endpoint *ep, | ||
728 | struct sk_buff *skb) | ||
729 | { | ||
730 | struct htc_packet *packet, *tmp_pkt, *found_packet = NULL; | ||
731 | |||
732 | spin_lock_bh(&target->tx_lock); | ||
733 | |||
734 | /* | ||
735 | * interate from the front of tx lookup queue | ||
736 | * this lookup should be fast since lower layers completes in-order and | ||
737 | * so the completed packet should be at the head of the list generally | ||
738 | */ | ||
739 | list_for_each_entry_safe(packet, tmp_pkt, &ep->pipe.tx_lookup_queue, | ||
740 | list) { | ||
741 | /* check for removal */ | ||
742 | if (skb == packet->skb) { | ||
743 | /* found it */ | ||
744 | list_del(&packet->list); | ||
745 | found_packet = packet; | ||
746 | break; | ||
747 | } | ||
748 | } | ||
749 | |||
750 | spin_unlock_bh(&target->tx_lock); | ||
751 | |||
752 | return found_packet; | ||
753 | } | ||
754 | |||
755 | static int ath6kl_htc_pipe_tx_complete(struct ath6kl *ar, struct sk_buff *skb) | ||
756 | { | ||
757 | struct htc_target *target = ar->htc_target; | ||
758 | struct htc_frame_hdr *htc_hdr; | ||
759 | struct htc_endpoint *ep; | ||
760 | struct htc_packet *packet; | ||
761 | u8 ep_id, *netdata; | ||
762 | u32 netlen; | ||
763 | |||
764 | netdata = skb->data; | ||
765 | netlen = skb->len; | ||
766 | |||
767 | htc_hdr = (struct htc_frame_hdr *) netdata; | ||
768 | |||
769 | ep_id = htc_hdr->eid; | ||
770 | ep = &target->endpoint[ep_id]; | ||
771 | |||
772 | packet = htc_lookup_tx_packet(target, ep, skb); | ||
773 | if (packet == NULL) { | ||
774 | /* may have already been flushed and freed */ | ||
775 | ath6kl_err("HTC TX lookup failed!\n"); | ||
776 | } else { | ||
777 | /* will be giving this buffer back to upper layers */ | ||
778 | packet->status = 0; | ||
779 | send_packet_completion(target, packet); | ||
780 | } | ||
781 | skb = NULL; | ||
782 | |||
783 | if (!ep->pipe.tx_credit_flow_enabled) { | ||
784 | /* | ||
785 | * note: when using TX credit flow, the re-checking of queues | ||
786 | * happens when credits flow back from the target. in the | ||
787 | * non-TX credit case, we recheck after the packet completes | ||
788 | */ | ||
789 | htc_try_send(target, ep, NULL); | ||
790 | } | ||
791 | |||
792 | return 0; | ||
793 | } | ||
794 | |||
795 | static int htc_send_packets_multiple(struct htc_target *target, | ||
796 | struct list_head *pkt_queue) | ||
797 | { | ||
798 | struct htc_endpoint *ep; | ||
799 | struct htc_packet *packet, *tmp_pkt; | ||
800 | |||
801 | if (list_empty(pkt_queue)) | ||
802 | return -EINVAL; | ||
803 | |||
804 | /* get first packet to find out which ep the packets will go into */ | ||
805 | packet = list_first_entry(pkt_queue, struct htc_packet, list); | ||
806 | if (packet == NULL) | ||
807 | return -EINVAL; | ||
808 | |||
809 | if (packet->endpoint >= ENDPOINT_MAX) { | ||
810 | WARN_ON_ONCE(1); | ||
811 | return -EINVAL; | ||
812 | } | ||
813 | ep = &target->endpoint[packet->endpoint]; | ||
814 | |||
815 | htc_try_send(target, ep, pkt_queue); | ||
816 | |||
817 | /* do completion on any packets that couldn't get in */ | ||
818 | if (!list_empty(pkt_queue)) { | ||
819 | list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) { | ||
820 | packet->status = -ENOMEM; | ||
821 | } | ||
822 | |||
823 | do_send_completion(ep, pkt_queue); | ||
824 | } | ||
825 | |||
826 | return 0; | ||
827 | } | ||
828 | |||
829 | /* htc pipe rx path */ | ||
830 | static struct htc_packet *alloc_htc_packet_container(struct htc_target *target) | ||
831 | { | ||
832 | struct htc_packet *packet; | ||
833 | spin_lock_bh(&target->rx_lock); | ||
834 | |||
835 | if (target->pipe.htc_packet_pool == NULL) { | ||
836 | spin_unlock_bh(&target->rx_lock); | ||
837 | return NULL; | ||
838 | } | ||
839 | |||
840 | packet = target->pipe.htc_packet_pool; | ||
841 | target->pipe.htc_packet_pool = (struct htc_packet *) packet->list.next; | ||
842 | |||
843 | spin_unlock_bh(&target->rx_lock); | ||
844 | |||
845 | packet->list.next = NULL; | ||
846 | return packet; | ||
847 | } | ||
848 | |||
849 | static void free_htc_packet_container(struct htc_target *target, | ||
850 | struct htc_packet *packet) | ||
851 | { | ||
852 | struct list_head *lh; | ||
853 | |||
854 | spin_lock_bh(&target->rx_lock); | ||
855 | |||
856 | if (target->pipe.htc_packet_pool == NULL) { | ||
857 | target->pipe.htc_packet_pool = packet; | ||
858 | packet->list.next = NULL; | ||
859 | } else { | ||
860 | lh = (struct list_head *) target->pipe.htc_packet_pool; | ||
861 | packet->list.next = lh; | ||
862 | target->pipe.htc_packet_pool = packet; | ||
863 | } | ||
864 | |||
865 | spin_unlock_bh(&target->rx_lock); | ||
866 | } | ||
867 | |||
868 | static int htc_process_trailer(struct htc_target *target, u8 *buffer, | ||
869 | int len, enum htc_endpoint_id from_ep) | ||
870 | { | ||
871 | struct htc_credit_report *report; | ||
872 | struct htc_record_hdr *record; | ||
873 | u8 *record_buf, *orig_buf; | ||
874 | int orig_len, status; | ||
875 | |||
876 | orig_buf = buffer; | ||
877 | orig_len = len; | ||
878 | status = 0; | ||
879 | |||
880 | while (len > 0) { | ||
881 | if (len < sizeof(struct htc_record_hdr)) { | ||
882 | status = -EINVAL; | ||
883 | break; | ||
884 | } | ||
885 | |||
886 | /* these are byte aligned structs */ | ||
887 | record = (struct htc_record_hdr *) buffer; | ||
888 | len -= sizeof(struct htc_record_hdr); | ||
889 | buffer += sizeof(struct htc_record_hdr); | ||
890 | |||
891 | if (record->len > len) { | ||
892 | /* no room left in buffer for record */ | ||
893 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
894 | "invalid length: %d (id:%d) buffer has: %d bytes left\n", | ||
895 | record->len, record->rec_id, len); | ||
896 | status = -EINVAL; | ||
897 | break; | ||
898 | } | ||
899 | |||
900 | /* start of record follows the header */ | ||
901 | record_buf = buffer; | ||
902 | |||
903 | switch (record->rec_id) { | ||
904 | case HTC_RECORD_CREDITS: | ||
905 | if (record->len < sizeof(struct htc_credit_report)) { | ||
906 | WARN_ON_ONCE(1); | ||
907 | return -EINVAL; | ||
908 | } | ||
909 | |||
910 | report = (struct htc_credit_report *) record_buf; | ||
911 | htc_process_credit_report(target, report, | ||
912 | record->len / sizeof(*report), | ||
913 | from_ep); | ||
914 | break; | ||
915 | default: | ||
916 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
917 | "unhandled record: id:%d length:%d\n", | ||
918 | record->rec_id, record->len); | ||
919 | break; | ||
920 | } | ||
921 | |||
922 | if (status != 0) | ||
923 | break; | ||
924 | |||
925 | /* advance buffer past this record for next time around */ | ||
926 | buffer += record->len; | ||
927 | len -= record->len; | ||
928 | } | ||
929 | |||
930 | return status; | ||
931 | } | ||
932 | |||
933 | static void do_recv_completion(struct htc_endpoint *ep, | ||
934 | struct list_head *queue_to_indicate) | ||
935 | { | ||
936 | struct htc_packet *packet; | ||
937 | |||
938 | if (list_empty(queue_to_indicate)) { | ||
939 | /* nothing to indicate */ | ||
940 | return; | ||
941 | } | ||
942 | |||
943 | /* using legacy EpRecv */ | ||
944 | while (!list_empty(queue_to_indicate)) { | ||
945 | packet = list_first_entry(queue_to_indicate, | ||
946 | struct htc_packet, list); | ||
947 | list_del(&packet->list); | ||
948 | ep->ep_cb.rx(ep->target, packet); | ||
949 | } | ||
950 | |||
951 | return; | ||
952 | } | ||
953 | |||
954 | static void recv_packet_completion(struct htc_target *target, | ||
955 | struct htc_endpoint *ep, | ||
956 | struct htc_packet *packet) | ||
957 | { | ||
958 | struct list_head container; | ||
959 | INIT_LIST_HEAD(&container); | ||
960 | list_add_tail(&packet->list, &container); | ||
961 | |||
962 | /* do completion */ | ||
963 | do_recv_completion(ep, &container); | ||
964 | } | ||
965 | |||
966 | static int ath6kl_htc_pipe_rx_complete(struct ath6kl *ar, struct sk_buff *skb, | ||
967 | u8 pipeid) | ||
968 | { | ||
969 | struct htc_target *target = ar->htc_target; | ||
970 | u8 *netdata, *trailer, hdr_info; | ||
971 | struct htc_frame_hdr *htc_hdr; | ||
972 | u32 netlen, trailerlen = 0; | ||
973 | struct htc_packet *packet; | ||
974 | struct htc_endpoint *ep; | ||
975 | u16 payload_len; | ||
976 | int status = 0; | ||
977 | |||
978 | netdata = skb->data; | ||
979 | netlen = skb->len; | ||
980 | |||
981 | htc_hdr = (struct htc_frame_hdr *) netdata; | ||
982 | |||
983 | ep = &target->endpoint[htc_hdr->eid]; | ||
984 | |||
985 | if (htc_hdr->eid >= ENDPOINT_MAX) { | ||
986 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
987 | "HTC Rx: invalid EndpointID=%d\n", | ||
988 | htc_hdr->eid); | ||
989 | status = -EINVAL; | ||
990 | goto free_skb; | ||
991 | } | ||
992 | |||
993 | payload_len = le16_to_cpu(get_unaligned(&htc_hdr->payld_len)); | ||
994 | |||
995 | if (netlen < (payload_len + HTC_HDR_LENGTH)) { | ||
996 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
997 | "HTC Rx: insufficient length, got:%d expected =%u\n", | ||
998 | netlen, payload_len + HTC_HDR_LENGTH); | ||
999 | status = -EINVAL; | ||
1000 | goto free_skb; | ||
1001 | } | ||
1002 | |||
1003 | /* get flags to check for trailer */ | ||
1004 | hdr_info = htc_hdr->flags; | ||
1005 | if (hdr_info & HTC_FLG_RX_TRAILER) { | ||
1006 | /* extract the trailer length */ | ||
1007 | hdr_info = htc_hdr->ctrl[0]; | ||
1008 | if ((hdr_info < sizeof(struct htc_record_hdr)) || | ||
1009 | (hdr_info > payload_len)) { | ||
1010 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1011 | "invalid header: payloadlen should be %d, CB[0]: %d\n", | ||
1012 | payload_len, hdr_info); | ||
1013 | status = -EINVAL; | ||
1014 | goto free_skb; | ||
1015 | } | ||
1016 | |||
1017 | trailerlen = hdr_info; | ||
1018 | /* process trailer after hdr/apps payload */ | ||
1019 | trailer = (u8 *) htc_hdr + HTC_HDR_LENGTH + | ||
1020 | payload_len - hdr_info; | ||
1021 | status = htc_process_trailer(target, trailer, hdr_info, | ||
1022 | htc_hdr->eid); | ||
1023 | if (status != 0) | ||
1024 | goto free_skb; | ||
1025 | } | ||
1026 | |||
1027 | if (((int) payload_len - (int) trailerlen) <= 0) { | ||
1028 | /* zero length packet with trailer, just drop these */ | ||
1029 | goto free_skb; | ||
1030 | } | ||
1031 | |||
1032 | if (htc_hdr->eid == ENDPOINT_0) { | ||
1033 | /* handle HTC control message */ | ||
1034 | if (target->htc_flags & HTC_OP_STATE_SETUP_COMPLETE) { | ||
1035 | /* | ||
1036 | * fatal: target should not send unsolicited | ||
1037 | * messageson the endpoint 0 | ||
1038 | */ | ||
1039 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1040 | "HTC ignores Rx Ctrl after setup complete\n"); | ||
1041 | status = -EINVAL; | ||
1042 | goto free_skb; | ||
1043 | } | ||
1044 | |||
1045 | /* remove HTC header */ | ||
1046 | skb_pull(skb, HTC_HDR_LENGTH); | ||
1047 | |||
1048 | netdata = skb->data; | ||
1049 | netlen = skb->len; | ||
1050 | |||
1051 | spin_lock_bh(&target->rx_lock); | ||
1052 | |||
1053 | target->pipe.ctrl_response_valid = true; | ||
1054 | target->pipe.ctrl_response_len = min_t(int, netlen, | ||
1055 | HTC_MAX_CTRL_MSG_LEN); | ||
1056 | memcpy(target->pipe.ctrl_response_buf, netdata, | ||
1057 | target->pipe.ctrl_response_len); | ||
1058 | |||
1059 | spin_unlock_bh(&target->rx_lock); | ||
1060 | |||
1061 | dev_kfree_skb(skb); | ||
1062 | skb = NULL; | ||
1063 | goto free_skb; | ||
1064 | } | ||
1065 | |||
1066 | /* | ||
1067 | * TODO: the message based HIF architecture allocates net bufs | ||
1068 | * for recv packets since it bridges that HIF to upper layers, | ||
1069 | * which expects HTC packets, we form the packets here | ||
1070 | */ | ||
1071 | packet = alloc_htc_packet_container(target); | ||
1072 | if (packet == NULL) { | ||
1073 | status = -ENOMEM; | ||
1074 | goto free_skb; | ||
1075 | } | ||
1076 | |||
1077 | packet->status = 0; | ||
1078 | packet->endpoint = htc_hdr->eid; | ||
1079 | packet->pkt_cntxt = skb; | ||
1080 | |||
1081 | /* TODO: for backwards compatibility */ | ||
1082 | packet->buf = skb_push(skb, 0) + HTC_HDR_LENGTH; | ||
1083 | packet->act_len = netlen - HTC_HDR_LENGTH - trailerlen; | ||
1084 | |||
1085 | /* | ||
1086 | * TODO: this is a hack because the driver layer will set the | ||
1087 | * actual len of the skb again which will just double the len | ||
1088 | */ | ||
1089 | skb_trim(skb, 0); | ||
1090 | |||
1091 | recv_packet_completion(target, ep, packet); | ||
1092 | |||
1093 | /* recover the packet container */ | ||
1094 | free_htc_packet_container(target, packet); | ||
1095 | skb = NULL; | ||
1096 | |||
1097 | free_skb: | ||
1098 | if (skb != NULL) | ||
1099 | dev_kfree_skb(skb); | ||
1100 | |||
1101 | return status; | ||
1102 | |||
1103 | } | ||
1104 | |||
1105 | static void htc_flush_rx_queue(struct htc_target *target, | ||
1106 | struct htc_endpoint *ep) | ||
1107 | { | ||
1108 | struct list_head container; | ||
1109 | struct htc_packet *packet; | ||
1110 | |||
1111 | spin_lock_bh(&target->rx_lock); | ||
1112 | |||
1113 | while (1) { | ||
1114 | if (list_empty(&ep->rx_bufq)) | ||
1115 | break; | ||
1116 | |||
1117 | packet = list_first_entry(&ep->rx_bufq, | ||
1118 | struct htc_packet, list); | ||
1119 | list_del(&packet->list); | ||
1120 | |||
1121 | spin_unlock_bh(&target->rx_lock); | ||
1122 | packet->status = -ECANCELED; | ||
1123 | packet->act_len = 0; | ||
1124 | |||
1125 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1126 | "Flushing RX packet:0x%p, length:%d, ep:%d\n", | ||
1127 | packet, packet->buf_len, | ||
1128 | packet->endpoint); | ||
1129 | |||
1130 | INIT_LIST_HEAD(&container); | ||
1131 | list_add_tail(&packet->list, &container); | ||
1132 | |||
1133 | /* give the packet back */ | ||
1134 | do_recv_completion(ep, &container); | ||
1135 | spin_lock_bh(&target->rx_lock); | ||
1136 | } | ||
1137 | |||
1138 | spin_unlock_bh(&target->rx_lock); | ||
1139 | } | ||
1140 | |||
1141 | /* polling routine to wait for a control packet to be received */ | ||
1142 | static int htc_wait_recv_ctrl_message(struct htc_target *target) | ||
1143 | { | ||
1144 | int count = HTC_TARGET_RESPONSE_POLL_COUNT; | ||
1145 | |||
1146 | while (count > 0) { | ||
1147 | spin_lock_bh(&target->rx_lock); | ||
1148 | |||
1149 | if (target->pipe.ctrl_response_valid) { | ||
1150 | target->pipe.ctrl_response_valid = false; | ||
1151 | spin_unlock_bh(&target->rx_lock); | ||
1152 | break; | ||
1153 | } | ||
1154 | |||
1155 | spin_unlock_bh(&target->rx_lock); | ||
1156 | |||
1157 | count--; | ||
1158 | |||
1159 | msleep_interruptible(HTC_TARGET_RESPONSE_POLL_WAIT); | ||
1160 | } | ||
1161 | |||
1162 | if (count <= 0) { | ||
1163 | ath6kl_dbg(ATH6KL_DBG_HTC, "%s: Timeout!\n", __func__); | ||
1164 | return -ECOMM; | ||
1165 | } | ||
1166 | |||
1167 | return 0; | ||
1168 | } | ||
1169 | |||
1170 | static void htc_rxctrl_complete(struct htc_target *context, | ||
1171 | struct htc_packet *packet) | ||
1172 | { | ||
1173 | /* TODO, can't really receive HTC control messages yet.... */ | ||
1174 | ath6kl_dbg(ATH6KL_DBG_HTC, "%s: invalid call function\n", __func__); | ||
1175 | } | ||
1176 | |||
1177 | /* htc pipe initialization */ | ||
1178 | static void reset_endpoint_states(struct htc_target *target) | ||
1179 | { | ||
1180 | struct htc_endpoint *ep; | ||
1181 | int i; | ||
1182 | |||
1183 | for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) { | ||
1184 | ep = &target->endpoint[i]; | ||
1185 | ep->svc_id = 0; | ||
1186 | ep->len_max = 0; | ||
1187 | ep->max_txq_depth = 0; | ||
1188 | ep->eid = i; | ||
1189 | INIT_LIST_HEAD(&ep->txq); | ||
1190 | INIT_LIST_HEAD(&ep->pipe.tx_lookup_queue); | ||
1191 | INIT_LIST_HEAD(&ep->rx_bufq); | ||
1192 | ep->target = target; | ||
1193 | ep->pipe.tx_credit_flow_enabled = (bool) 1; /* FIXME */ | ||
1194 | } | ||
1195 | } | ||
1196 | |||
1197 | /* start HTC, this is called after all services are connected */ | ||
1198 | static int htc_config_target_hif_pipe(struct htc_target *target) | ||
1199 | { | ||
1200 | return 0; | ||
1201 | } | ||
1202 | |||
1203 | /* htc service functions */ | ||
1204 | static u8 htc_get_credit_alloc(struct htc_target *target, u16 service_id) | ||
1205 | { | ||
1206 | u8 allocation = 0; | ||
1207 | int i; | ||
1208 | |||
1209 | for (i = 0; i < ENDPOINT_MAX; i++) { | ||
1210 | if (target->pipe.txcredit_alloc[i].service_id == service_id) | ||
1211 | allocation = | ||
1212 | target->pipe.txcredit_alloc[i].credit_alloc; | ||
1213 | } | ||
1214 | |||
1215 | if (allocation == 0) { | ||
1216 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1217 | "HTC Service TX : 0x%2.2X : allocation is zero!\n", | ||
1218 | service_id); | ||
1219 | } | ||
1220 | |||
1221 | return allocation; | ||
1222 | } | ||
1223 | |||
1224 | static int ath6kl_htc_pipe_conn_service(struct htc_target *target, | ||
1225 | struct htc_service_connect_req *conn_req, | ||
1226 | struct htc_service_connect_resp *conn_resp) | ||
1227 | { | ||
1228 | struct ath6kl *ar = target->dev->ar; | ||
1229 | struct htc_packet *packet = NULL; | ||
1230 | struct htc_conn_service_resp *resp_msg; | ||
1231 | struct htc_conn_service_msg *conn_msg; | ||
1232 | enum htc_endpoint_id assigned_epid = ENDPOINT_MAX; | ||
1233 | bool disable_credit_flowctrl = false; | ||
1234 | unsigned int max_msg_size = 0; | ||
1235 | struct htc_endpoint *ep; | ||
1236 | int length, status = 0; | ||
1237 | struct sk_buff *skb; | ||
1238 | u8 tx_alloc; | ||
1239 | u16 flags; | ||
1240 | |||
1241 | if (conn_req->svc_id == 0) { | ||
1242 | WARN_ON_ONCE(1); | ||
1243 | status = -EINVAL; | ||
1244 | goto free_packet; | ||
1245 | } | ||
1246 | |||
1247 | if (conn_req->svc_id == HTC_CTRL_RSVD_SVC) { | ||
1248 | /* special case for pseudo control service */ | ||
1249 | assigned_epid = ENDPOINT_0; | ||
1250 | max_msg_size = HTC_MAX_CTRL_MSG_LEN; | ||
1251 | tx_alloc = 0; | ||
1252 | |||
1253 | } else { | ||
1254 | |||
1255 | tx_alloc = htc_get_credit_alloc(target, conn_req->svc_id); | ||
1256 | if (tx_alloc == 0) { | ||
1257 | status = -ENOMEM; | ||
1258 | goto free_packet; | ||
1259 | } | ||
1260 | |||
1261 | /* allocate a packet to send to the target */ | ||
1262 | packet = htc_alloc_txctrl_packet(target); | ||
1263 | |||
1264 | if (packet == NULL) { | ||
1265 | WARN_ON_ONCE(1); | ||
1266 | status = -ENOMEM; | ||
1267 | goto free_packet; | ||
1268 | } | ||
1269 | |||
1270 | skb = packet->skb; | ||
1271 | length = sizeof(struct htc_conn_service_msg); | ||
1272 | |||
1273 | /* assemble connect service message */ | ||
1274 | conn_msg = (struct htc_conn_service_msg *) skb_put(skb, | ||
1275 | length); | ||
1276 | if (conn_msg == NULL) { | ||
1277 | WARN_ON_ONCE(1); | ||
1278 | status = -EINVAL; | ||
1279 | goto free_packet; | ||
1280 | } | ||
1281 | |||
1282 | memset(conn_msg, 0, | ||
1283 | sizeof(struct htc_conn_service_msg)); | ||
1284 | conn_msg->msg_id = cpu_to_le16(HTC_MSG_CONN_SVC_ID); | ||
1285 | conn_msg->svc_id = cpu_to_le16(conn_req->svc_id); | ||
1286 | conn_msg->conn_flags = cpu_to_le16(conn_req->conn_flags & | ||
1287 | ~HTC_CONN_FLGS_SET_RECV_ALLOC_MASK); | ||
1288 | |||
1289 | /* tell target desired recv alloc for this ep */ | ||
1290 | flags = tx_alloc << HTC_CONN_FLGS_SET_RECV_ALLOC_SHIFT; | ||
1291 | conn_msg->conn_flags |= cpu_to_le16(flags); | ||
1292 | |||
1293 | if (conn_req->conn_flags & | ||
1294 | HTC_CONN_FLGS_DISABLE_CRED_FLOW_CTRL) { | ||
1295 | disable_credit_flowctrl = true; | ||
1296 | } | ||
1297 | |||
1298 | set_htc_pkt_info(packet, NULL, (u8 *) conn_msg, | ||
1299 | length, | ||
1300 | ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); | ||
1301 | |||
1302 | status = ath6kl_htc_pipe_tx(target, packet); | ||
1303 | |||
1304 | /* we don't own it anymore */ | ||
1305 | packet = NULL; | ||
1306 | if (status != 0) | ||
1307 | goto free_packet; | ||
1308 | |||
1309 | /* wait for response */ | ||
1310 | status = htc_wait_recv_ctrl_message(target); | ||
1311 | if (status != 0) | ||
1312 | goto free_packet; | ||
1313 | |||
1314 | /* we controlled the buffer creation so it has to be | ||
1315 | * properly aligned | ||
1316 | */ | ||
1317 | resp_msg = (struct htc_conn_service_resp *) | ||
1318 | target->pipe.ctrl_response_buf; | ||
1319 | |||
1320 | if (resp_msg->msg_id != cpu_to_le16(HTC_MSG_CONN_SVC_RESP_ID) || | ||
1321 | (target->pipe.ctrl_response_len < sizeof(*resp_msg))) { | ||
1322 | /* this message is not valid */ | ||
1323 | WARN_ON_ONCE(1); | ||
1324 | status = -EINVAL; | ||
1325 | goto free_packet; | ||
1326 | } | ||
1327 | |||
1328 | ath6kl_dbg(ATH6KL_DBG_TRC, | ||
1329 | "%s: service 0x%X conn resp: status: %d ep: %d\n", | ||
1330 | __func__, resp_msg->svc_id, resp_msg->status, | ||
1331 | resp_msg->eid); | ||
1332 | |||
1333 | conn_resp->resp_code = resp_msg->status; | ||
1334 | /* check response status */ | ||
1335 | if (resp_msg->status != HTC_SERVICE_SUCCESS) { | ||
1336 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1337 | "Target failed service 0x%X connect request (status:%d)\n", | ||
1338 | resp_msg->svc_id, resp_msg->status); | ||
1339 | status = -EINVAL; | ||
1340 | goto free_packet; | ||
1341 | } | ||
1342 | |||
1343 | assigned_epid = (enum htc_endpoint_id) resp_msg->eid; | ||
1344 | max_msg_size = le16_to_cpu(resp_msg->max_msg_sz); | ||
1345 | } | ||
1346 | |||
1347 | /* the rest are parameter checks so set the error status */ | ||
1348 | status = -EINVAL; | ||
1349 | |||
1350 | if (assigned_epid >= ENDPOINT_MAX) { | ||
1351 | WARN_ON_ONCE(1); | ||
1352 | goto free_packet; | ||
1353 | } | ||
1354 | |||
1355 | if (max_msg_size == 0) { | ||
1356 | WARN_ON_ONCE(1); | ||
1357 | goto free_packet; | ||
1358 | } | ||
1359 | |||
1360 | ep = &target->endpoint[assigned_epid]; | ||
1361 | ep->eid = assigned_epid; | ||
1362 | if (ep->svc_id != 0) { | ||
1363 | /* endpoint already in use! */ | ||
1364 | WARN_ON_ONCE(1); | ||
1365 | goto free_packet; | ||
1366 | } | ||
1367 | |||
1368 | /* return assigned endpoint to caller */ | ||
1369 | conn_resp->endpoint = assigned_epid; | ||
1370 | conn_resp->len_max = max_msg_size; | ||
1371 | |||
1372 | /* setup the endpoint */ | ||
1373 | ep->svc_id = conn_req->svc_id; /* this marks ep in use */ | ||
1374 | ep->max_txq_depth = conn_req->max_txq_depth; | ||
1375 | ep->len_max = max_msg_size; | ||
1376 | ep->cred_dist.credits = tx_alloc; | ||
1377 | ep->cred_dist.cred_sz = target->tgt_cred_sz; | ||
1378 | ep->cred_dist.cred_per_msg = max_msg_size / target->tgt_cred_sz; | ||
1379 | if (max_msg_size % target->tgt_cred_sz) | ||
1380 | ep->cred_dist.cred_per_msg++; | ||
1381 | |||
1382 | /* copy all the callbacks */ | ||
1383 | ep->ep_cb = conn_req->ep_cb; | ||
1384 | |||
1385 | status = ath6kl_hif_pipe_map_service(ar, ep->svc_id, | ||
1386 | &ep->pipe.pipeid_ul, | ||
1387 | &ep->pipe.pipeid_dl); | ||
1388 | if (status != 0) | ||
1389 | goto free_packet; | ||
1390 | |||
1391 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1392 | "SVC Ready: 0x%4.4X: ULpipe:%d DLpipe:%d id:%d\n", | ||
1393 | ep->svc_id, ep->pipe.pipeid_ul, | ||
1394 | ep->pipe.pipeid_dl, ep->eid); | ||
1395 | |||
1396 | if (disable_credit_flowctrl && ep->pipe.tx_credit_flow_enabled) { | ||
1397 | ep->pipe.tx_credit_flow_enabled = false; | ||
1398 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1399 | "SVC: 0x%4.4X ep:%d TX flow control off\n", | ||
1400 | ep->svc_id, assigned_epid); | ||
1401 | } | ||
1402 | |||
1403 | free_packet: | ||
1404 | if (packet != NULL) | ||
1405 | htc_free_txctrl_packet(target, packet); | ||
1406 | return status; | ||
1407 | } | ||
1408 | |||
1409 | /* htc export functions */ | ||
1410 | static void *ath6kl_htc_pipe_create(struct ath6kl *ar) | ||
1411 | { | ||
1412 | int status = 0; | ||
1413 | struct htc_endpoint *ep = NULL; | ||
1414 | struct htc_target *target = NULL; | ||
1415 | struct htc_packet *packet; | ||
1416 | int i; | ||
1417 | |||
1418 | target = kzalloc(sizeof(struct htc_target), GFP_KERNEL); | ||
1419 | if (target == NULL) { | ||
1420 | ath6kl_err("htc create unable to allocate memory\n"); | ||
1421 | status = -ENOMEM; | ||
1422 | goto fail_htc_create; | ||
1423 | } | ||
1424 | |||
1425 | spin_lock_init(&target->htc_lock); | ||
1426 | spin_lock_init(&target->rx_lock); | ||
1427 | spin_lock_init(&target->tx_lock); | ||
1428 | |||
1429 | reset_endpoint_states(target); | ||
1430 | |||
1431 | for (i = 0; i < HTC_PACKET_CONTAINER_ALLOCATION; i++) { | ||
1432 | packet = kzalloc(sizeof(struct htc_packet), GFP_KERNEL); | ||
1433 | |||
1434 | if (packet != NULL) | ||
1435 | free_htc_packet_container(target, packet); | ||
1436 | } | ||
1437 | |||
1438 | target->dev = kzalloc(sizeof(*target->dev), GFP_KERNEL); | ||
1439 | if (!target->dev) { | ||
1440 | ath6kl_err("unable to allocate memory\n"); | ||
1441 | status = -ENOMEM; | ||
1442 | goto fail_htc_create; | ||
1443 | } | ||
1444 | target->dev->ar = ar; | ||
1445 | target->dev->htc_cnxt = target; | ||
1446 | |||
1447 | /* Get HIF default pipe for HTC message exchange */ | ||
1448 | ep = &target->endpoint[ENDPOINT_0]; | ||
1449 | |||
1450 | ath6kl_hif_pipe_get_default(ar, &ep->pipe.pipeid_ul, | ||
1451 | &ep->pipe.pipeid_dl); | ||
1452 | |||
1453 | return target; | ||
1454 | |||
1455 | fail_htc_create: | ||
1456 | if (status != 0) { | ||
1457 | if (target != NULL) | ||
1458 | ath6kl_htc_pipe_cleanup(target); | ||
1459 | |||
1460 | target = NULL; | ||
1461 | } | ||
1462 | return target; | ||
1463 | } | ||
1464 | |||
1465 | /* cleanup the HTC instance */ | ||
1466 | static void ath6kl_htc_pipe_cleanup(struct htc_target *target) | ||
1467 | { | ||
1468 | struct htc_packet *packet; | ||
1469 | |||
1470 | while (true) { | ||
1471 | packet = alloc_htc_packet_container(target); | ||
1472 | if (packet == NULL) | ||
1473 | break; | ||
1474 | kfree(packet); | ||
1475 | } | ||
1476 | |||
1477 | kfree(target->dev); | ||
1478 | |||
1479 | /* kfree our instance */ | ||
1480 | kfree(target); | ||
1481 | } | ||
1482 | |||
1483 | static int ath6kl_htc_pipe_start(struct htc_target *target) | ||
1484 | { | ||
1485 | struct sk_buff *skb; | ||
1486 | struct htc_setup_comp_ext_msg *setup; | ||
1487 | struct htc_packet *packet; | ||
1488 | |||
1489 | htc_config_target_hif_pipe(target); | ||
1490 | |||
1491 | /* allocate a buffer to send */ | ||
1492 | packet = htc_alloc_txctrl_packet(target); | ||
1493 | if (packet == NULL) { | ||
1494 | WARN_ON_ONCE(1); | ||
1495 | return -ENOMEM; | ||
1496 | } | ||
1497 | |||
1498 | skb = packet->skb; | ||
1499 | |||
1500 | /* assemble setup complete message */ | ||
1501 | setup = (struct htc_setup_comp_ext_msg *) skb_put(skb, | ||
1502 | sizeof(*setup)); | ||
1503 | memset(setup, 0, sizeof(struct htc_setup_comp_ext_msg)); | ||
1504 | setup->msg_id = cpu_to_le16(HTC_MSG_SETUP_COMPLETE_EX_ID); | ||
1505 | |||
1506 | ath6kl_dbg(ATH6KL_DBG_HTC, "HTC using TX credit flow control\n"); | ||
1507 | |||
1508 | set_htc_pkt_info(packet, NULL, (u8 *) setup, | ||
1509 | sizeof(struct htc_setup_comp_ext_msg), | ||
1510 | ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); | ||
1511 | |||
1512 | target->htc_flags |= HTC_OP_STATE_SETUP_COMPLETE; | ||
1513 | |||
1514 | return ath6kl_htc_pipe_tx(target, packet); | ||
1515 | } | ||
1516 | |||
1517 | static void ath6kl_htc_pipe_stop(struct htc_target *target) | ||
1518 | { | ||
1519 | int i; | ||
1520 | struct htc_endpoint *ep; | ||
1521 | |||
1522 | /* cleanup endpoints */ | ||
1523 | for (i = 0; i < ENDPOINT_MAX; i++) { | ||
1524 | ep = &target->endpoint[i]; | ||
1525 | htc_flush_rx_queue(target, ep); | ||
1526 | htc_flush_tx_endpoint(target, ep, HTC_TX_PACKET_TAG_ALL); | ||
1527 | } | ||
1528 | |||
1529 | reset_endpoint_states(target); | ||
1530 | target->htc_flags &= ~HTC_OP_STATE_SETUP_COMPLETE; | ||
1531 | } | ||
1532 | |||
1533 | static int ath6kl_htc_pipe_get_rxbuf_num(struct htc_target *target, | ||
1534 | enum htc_endpoint_id endpoint) | ||
1535 | { | ||
1536 | int num; | ||
1537 | |||
1538 | spin_lock_bh(&target->rx_lock); | ||
1539 | num = get_queue_depth(&(target->endpoint[endpoint].rx_bufq)); | ||
1540 | spin_unlock_bh(&target->rx_lock); | ||
1541 | |||
1542 | return num; | ||
1543 | } | ||
1544 | |||
1545 | static int ath6kl_htc_pipe_tx(struct htc_target *target, | ||
1546 | struct htc_packet *packet) | ||
1547 | { | ||
1548 | struct list_head queue; | ||
1549 | |||
1550 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1551 | "%s: endPointId: %d, buffer: 0x%p, length: %d\n", | ||
1552 | __func__, packet->endpoint, packet->buf, | ||
1553 | packet->act_len); | ||
1554 | |||
1555 | INIT_LIST_HEAD(&queue); | ||
1556 | list_add_tail(&packet->list, &queue); | ||
1557 | |||
1558 | return htc_send_packets_multiple(target, &queue); | ||
1559 | } | ||
1560 | |||
1561 | static int ath6kl_htc_pipe_wait_target(struct htc_target *target) | ||
1562 | { | ||
1563 | struct htc_ready_ext_msg *ready_msg; | ||
1564 | struct htc_service_connect_req connect; | ||
1565 | struct htc_service_connect_resp resp; | ||
1566 | int status = 0; | ||
1567 | |||
1568 | status = htc_wait_recv_ctrl_message(target); | ||
1569 | |||
1570 | if (status != 0) | ||
1571 | return status; | ||
1572 | |||
1573 | if (target->pipe.ctrl_response_len < sizeof(*ready_msg)) { | ||
1574 | ath6kl_dbg(ATH6KL_DBG_HTC, "invalid htc ready msg len:%d!\n", | ||
1575 | target->pipe.ctrl_response_len); | ||
1576 | return -ECOMM; | ||
1577 | } | ||
1578 | |||
1579 | ready_msg = (struct htc_ready_ext_msg *) target->pipe.ctrl_response_buf; | ||
1580 | |||
1581 | if (ready_msg->ver2_0_info.msg_id != cpu_to_le16(HTC_MSG_READY_ID)) { | ||
1582 | ath6kl_dbg(ATH6KL_DBG_HTC, "invalid htc ready msg : 0x%X !\n", | ||
1583 | ready_msg->ver2_0_info.msg_id); | ||
1584 | return -ECOMM; | ||
1585 | } | ||
1586 | |||
1587 | ath6kl_dbg(ATH6KL_DBG_HTC, | ||
1588 | "Target Ready! : transmit resources : %d size:%d\n", | ||
1589 | ready_msg->ver2_0_info.cred_cnt, | ||
1590 | ready_msg->ver2_0_info.cred_sz); | ||
1591 | |||
1592 | target->tgt_creds = le16_to_cpu(ready_msg->ver2_0_info.cred_cnt); | ||
1593 | target->tgt_cred_sz = le16_to_cpu(ready_msg->ver2_0_info.cred_sz); | ||
1594 | |||
1595 | if ((target->tgt_creds == 0) || (target->tgt_cred_sz == 0)) | ||
1596 | return -ECOMM; | ||
1597 | |||
1598 | htc_setup_target_buffer_assignments(target); | ||
1599 | |||
1600 | /* setup our pseudo HTC control endpoint connection */ | ||
1601 | memset(&connect, 0, sizeof(connect)); | ||
1602 | memset(&resp, 0, sizeof(resp)); | ||
1603 | connect.ep_cb.tx_complete = htc_txctrl_complete; | ||
1604 | connect.ep_cb.rx = htc_rxctrl_complete; | ||
1605 | connect.max_txq_depth = NUM_CONTROL_TX_BUFFERS; | ||
1606 | connect.svc_id = HTC_CTRL_RSVD_SVC; | ||
1607 | |||
1608 | /* connect fake service */ | ||
1609 | status = ath6kl_htc_pipe_conn_service(target, &connect, &resp); | ||
1610 | |||
1611 | return status; | ||
1612 | } | ||
1613 | |||
1614 | static void ath6kl_htc_pipe_flush_txep(struct htc_target *target, | ||
1615 | enum htc_endpoint_id endpoint, u16 tag) | ||
1616 | { | ||
1617 | struct htc_endpoint *ep = &target->endpoint[endpoint]; | ||
1618 | |||
1619 | if (ep->svc_id == 0) { | ||
1620 | WARN_ON_ONCE(1); | ||
1621 | /* not in use.. */ | ||
1622 | return; | ||
1623 | } | ||
1624 | |||
1625 | htc_flush_tx_endpoint(target, ep, tag); | ||
1626 | } | ||
1627 | |||
1628 | static int ath6kl_htc_pipe_add_rxbuf_multiple(struct htc_target *target, | ||
1629 | struct list_head *pkt_queue) | ||
1630 | { | ||
1631 | struct htc_packet *packet, *tmp_pkt, *first; | ||
1632 | struct htc_endpoint *ep; | ||
1633 | int status = 0; | ||
1634 | |||
1635 | if (list_empty(pkt_queue)) | ||
1636 | return -EINVAL; | ||
1637 | |||
1638 | first = list_first_entry(pkt_queue, struct htc_packet, list); | ||
1639 | if (first == NULL) { | ||
1640 | WARN_ON_ONCE(1); | ||
1641 | return -EINVAL; | ||
1642 | } | ||
1643 | |||
1644 | if (first->endpoint >= ENDPOINT_MAX) { | ||
1645 | WARN_ON_ONCE(1); | ||
1646 | return -EINVAL; | ||
1647 | } | ||
1648 | |||
1649 | ath6kl_dbg(ATH6KL_DBG_HTC, "%s: epid: %d, cnt:%d, len: %d\n", | ||
1650 | __func__, first->endpoint, get_queue_depth(pkt_queue), | ||
1651 | first->buf_len); | ||
1652 | |||
1653 | ep = &target->endpoint[first->endpoint]; | ||
1654 | |||
1655 | spin_lock_bh(&target->rx_lock); | ||
1656 | |||
1657 | /* store receive packets */ | ||
1658 | list_splice_tail_init(pkt_queue, &ep->rx_bufq); | ||
1659 | |||
1660 | spin_unlock_bh(&target->rx_lock); | ||
1661 | |||
1662 | if (status != 0) { | ||
1663 | /* walk through queue and mark each one canceled */ | ||
1664 | list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) { | ||
1665 | packet->status = -ECANCELED; | ||
1666 | } | ||
1667 | |||
1668 | do_recv_completion(ep, pkt_queue); | ||
1669 | } | ||
1670 | |||
1671 | return status; | ||
1672 | } | ||
1673 | |||
1674 | static void ath6kl_htc_pipe_activity_changed(struct htc_target *target, | ||
1675 | enum htc_endpoint_id ep, | ||
1676 | bool active) | ||
1677 | { | ||
1678 | /* TODO */ | ||
1679 | } | ||
1680 | |||
1681 | static void ath6kl_htc_pipe_flush_rx_buf(struct htc_target *target) | ||
1682 | { | ||
1683 | /* TODO */ | ||
1684 | } | ||
1685 | |||
1686 | static int ath6kl_htc_pipe_credit_setup(struct htc_target *target, | ||
1687 | struct ath6kl_htc_credit_info *info) | ||
1688 | { | ||
1689 | return 0; | ||
1690 | } | ||
1691 | |||
1692 | static const struct ath6kl_htc_ops ath6kl_htc_pipe_ops = { | ||
1693 | .create = ath6kl_htc_pipe_create, | ||
1694 | .wait_target = ath6kl_htc_pipe_wait_target, | ||
1695 | .start = ath6kl_htc_pipe_start, | ||
1696 | .conn_service = ath6kl_htc_pipe_conn_service, | ||
1697 | .tx = ath6kl_htc_pipe_tx, | ||
1698 | .stop = ath6kl_htc_pipe_stop, | ||
1699 | .cleanup = ath6kl_htc_pipe_cleanup, | ||
1700 | .flush_txep = ath6kl_htc_pipe_flush_txep, | ||
1701 | .flush_rx_buf = ath6kl_htc_pipe_flush_rx_buf, | ||
1702 | .activity_changed = ath6kl_htc_pipe_activity_changed, | ||
1703 | .get_rxbuf_num = ath6kl_htc_pipe_get_rxbuf_num, | ||
1704 | .add_rxbuf_multiple = ath6kl_htc_pipe_add_rxbuf_multiple, | ||
1705 | .credit_setup = ath6kl_htc_pipe_credit_setup, | ||
1706 | .tx_complete = ath6kl_htc_pipe_tx_complete, | ||
1707 | .rx_complete = ath6kl_htc_pipe_rx_complete, | ||
1708 | }; | ||
1709 | |||
1710 | void ath6kl_htc_pipe_attach(struct ath6kl *ar) | ||
1711 | { | ||
1712 | ar->htc_ops = &ath6kl_htc_pipe_ops; | ||
1713 | } | ||