]>
Commit | Line | Data |
---|---|---|
fb9987d0 S |
1 | /* |
2 | * Copyright (c) 2010 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 "htc.h" | |
18 | ||
19 | static int htc_issue_send(struct htc_target *target, struct sk_buff* skb, | |
20 | u16 len, u8 flags, u8 epid, | |
21 | struct ath9k_htc_tx_ctl *tx_ctl) | |
22 | { | |
23 | struct htc_frame_hdr *hdr; | |
24 | struct htc_endpoint *endpoint = &target->endpoint[epid]; | |
25 | int status; | |
26 | ||
27 | hdr = (struct htc_frame_hdr *) | |
28 | skb_push(skb, sizeof(struct htc_frame_hdr)); | |
29 | hdr->endpoint_id = epid; | |
30 | hdr->flags = flags; | |
31 | hdr->payload_len = cpu_to_be16(len); | |
32 | ||
33 | status = target->hif->send(target->hif_dev, endpoint->ul_pipeid, skb, | |
34 | tx_ctl); | |
35 | return status; | |
36 | } | |
37 | ||
38 | static struct htc_endpoint *get_next_avail_ep(struct htc_endpoint *endpoint) | |
39 | { | |
40 | enum htc_endpoint_id avail_epid; | |
41 | ||
42 | for (avail_epid = ENDPOINT_MAX; avail_epid > ENDPOINT0; avail_epid--) | |
43 | if (endpoint[avail_epid].service_id == 0) | |
44 | return &endpoint[avail_epid]; | |
45 | return NULL; | |
46 | } | |
47 | ||
48 | static u8 service_to_ulpipe(u16 service_id) | |
49 | { | |
50 | switch (service_id) { | |
51 | case WMI_CONTROL_SVC: | |
52 | return 4; | |
53 | case WMI_BEACON_SVC: | |
54 | case WMI_CAB_SVC: | |
55 | case WMI_UAPSD_SVC: | |
56 | case WMI_MGMT_SVC: | |
57 | case WMI_DATA_VO_SVC: | |
58 | case WMI_DATA_VI_SVC: | |
59 | case WMI_DATA_BE_SVC: | |
60 | case WMI_DATA_BK_SVC: | |
61 | return 1; | |
62 | default: | |
63 | return 0; | |
64 | } | |
65 | } | |
66 | ||
67 | static u8 service_to_dlpipe(u16 service_id) | |
68 | { | |
69 | switch (service_id) { | |
70 | case WMI_CONTROL_SVC: | |
71 | return 3; | |
72 | case WMI_BEACON_SVC: | |
73 | case WMI_CAB_SVC: | |
74 | case WMI_UAPSD_SVC: | |
75 | case WMI_MGMT_SVC: | |
76 | case WMI_DATA_VO_SVC: | |
77 | case WMI_DATA_VI_SVC: | |
78 | case WMI_DATA_BE_SVC: | |
79 | case WMI_DATA_BK_SVC: | |
80 | return 2; | |
81 | default: | |
82 | return 0; | |
83 | } | |
84 | } | |
85 | ||
86 | static void htc_process_target_rdy(struct htc_target *target, | |
87 | void *buf) | |
88 | { | |
89 | struct htc_endpoint *endpoint; | |
90 | struct htc_ready_msg *htc_ready_msg = (struct htc_ready_msg *) buf; | |
91 | ||
92 | target->credits = be16_to_cpu(htc_ready_msg->credits); | |
93 | target->credit_size = be16_to_cpu(htc_ready_msg->credit_size); | |
94 | ||
95 | endpoint = &target->endpoint[ENDPOINT0]; | |
96 | endpoint->service_id = HTC_CTRL_RSVD_SVC; | |
97 | endpoint->max_msglen = HTC_MAX_CONTROL_MESSAGE_LENGTH; | |
98 | complete(&target->target_wait); | |
99 | } | |
100 | ||
101 | static void htc_process_conn_rsp(struct htc_target *target, | |
102 | struct htc_frame_hdr *htc_hdr) | |
103 | { | |
104 | struct htc_conn_svc_rspmsg *svc_rspmsg; | |
105 | struct htc_endpoint *endpoint, *tmp_endpoint = NULL; | |
106 | u16 service_id; | |
107 | u16 max_msglen; | |
108 | enum htc_endpoint_id epid, tepid; | |
109 | ||
110 | svc_rspmsg = (struct htc_conn_svc_rspmsg *) | |
111 | ((void *) htc_hdr + sizeof(struct htc_frame_hdr)); | |
112 | ||
113 | if (svc_rspmsg->status == HTC_SERVICE_SUCCESS) { | |
114 | epid = svc_rspmsg->endpoint_id; | |
115 | service_id = be16_to_cpu(svc_rspmsg->service_id); | |
116 | max_msglen = be16_to_cpu(svc_rspmsg->max_msg_len); | |
117 | endpoint = &target->endpoint[epid]; | |
118 | ||
119 | for (tepid = ENDPOINT_MAX; tepid > ENDPOINT0; tepid--) { | |
120 | tmp_endpoint = &target->endpoint[tepid]; | |
121 | if (tmp_endpoint->service_id == service_id) { | |
122 | tmp_endpoint->service_id = 0; | |
123 | break; | |
124 | } | |
125 | } | |
126 | ||
127 | if (!tmp_endpoint) | |
128 | return; | |
129 | ||
130 | endpoint->service_id = service_id; | |
131 | endpoint->max_txqdepth = tmp_endpoint->max_txqdepth; | |
132 | endpoint->ep_callbacks = tmp_endpoint->ep_callbacks; | |
133 | endpoint->ul_pipeid = tmp_endpoint->ul_pipeid; | |
134 | endpoint->dl_pipeid = tmp_endpoint->dl_pipeid; | |
135 | endpoint->max_msglen = max_msglen; | |
136 | target->conn_rsp_epid = epid; | |
137 | complete(&target->cmd_wait); | |
138 | } else { | |
139 | target->conn_rsp_epid = ENDPOINT_UNUSED; | |
140 | } | |
141 | } | |
142 | ||
143 | static int htc_config_pipe_credits(struct htc_target *target) | |
144 | { | |
145 | struct sk_buff *skb; | |
146 | struct htc_config_pipe_msg *cp_msg; | |
147 | int ret, time_left; | |
148 | ||
0fa35a58 | 149 | skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); |
fb9987d0 S |
150 | if (!skb) { |
151 | dev_err(target->dev, "failed to allocate send buffer\n"); | |
152 | return -ENOMEM; | |
153 | } | |
154 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
155 | ||
156 | cp_msg = (struct htc_config_pipe_msg *) | |
157 | skb_put(skb, sizeof(struct htc_config_pipe_msg)); | |
158 | ||
159 | cp_msg->message_id = cpu_to_be16(HTC_MSG_CONFIG_PIPE_ID); | |
160 | cp_msg->pipe_id = USB_WLAN_TX_PIPE; | |
161 | cp_msg->credits = 28; | |
162 | ||
163 | target->htc_flags |= HTC_OP_CONFIG_PIPE_CREDITS; | |
164 | ||
165 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL); | |
166 | if (ret) | |
167 | goto err; | |
168 | ||
169 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
170 | if (!time_left) { | |
171 | dev_err(target->dev, "HTC credit config timeout\n"); | |
172 | return -ETIMEDOUT; | |
173 | } | |
174 | ||
175 | return 0; | |
176 | err: | |
0fa35a58 | 177 | kfree_skb(skb); |
fb9987d0 S |
178 | return -EINVAL; |
179 | } | |
180 | ||
181 | static int htc_setup_complete(struct htc_target *target) | |
182 | { | |
183 | struct sk_buff *skb; | |
184 | struct htc_comp_msg *comp_msg; | |
185 | int ret = 0, time_left; | |
186 | ||
0fa35a58 | 187 | skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); |
fb9987d0 S |
188 | if (!skb) { |
189 | dev_err(target->dev, "failed to allocate send buffer\n"); | |
190 | return -ENOMEM; | |
191 | } | |
192 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
193 | ||
194 | comp_msg = (struct htc_comp_msg *) | |
195 | skb_put(skb, sizeof(struct htc_comp_msg)); | |
196 | comp_msg->msg_id = cpu_to_be16(HTC_MSG_SETUP_COMPLETE_ID); | |
197 | ||
198 | target->htc_flags |= HTC_OP_START_WAIT; | |
199 | ||
200 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL); | |
201 | if (ret) | |
202 | goto err; | |
203 | ||
204 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
205 | if (!time_left) { | |
206 | dev_err(target->dev, "HTC start timeout\n"); | |
207 | return -ETIMEDOUT; | |
208 | } | |
209 | ||
210 | return 0; | |
211 | ||
212 | err: | |
0fa35a58 | 213 | kfree_skb(skb); |
fb9987d0 S |
214 | return -EINVAL; |
215 | } | |
216 | ||
217 | /* HTC APIs */ | |
218 | ||
219 | int htc_init(struct htc_target *target) | |
220 | { | |
221 | int ret; | |
222 | ||
223 | ret = htc_config_pipe_credits(target); | |
224 | if (ret) | |
225 | return ret; | |
226 | ||
227 | return htc_setup_complete(target); | |
228 | } | |
229 | ||
230 | int htc_connect_service(struct htc_target *target, | |
231 | struct htc_service_connreq *service_connreq, | |
232 | enum htc_endpoint_id *conn_rsp_epid) | |
233 | { | |
234 | struct sk_buff *skb; | |
235 | struct htc_endpoint *endpoint; | |
236 | struct htc_conn_svc_msg *conn_msg; | |
237 | int ret, time_left; | |
238 | ||
239 | /* Find an available endpoint */ | |
240 | endpoint = get_next_avail_ep(target->endpoint); | |
241 | if (!endpoint) { | |
242 | dev_err(target->dev, "Endpoint is not available for" | |
243 | "service %d\n", service_connreq->service_id); | |
244 | return -EINVAL; | |
245 | } | |
246 | ||
247 | endpoint->service_id = service_connreq->service_id; | |
248 | endpoint->max_txqdepth = service_connreq->max_send_qdepth; | |
249 | endpoint->ul_pipeid = service_to_ulpipe(service_connreq->service_id); | |
250 | endpoint->dl_pipeid = service_to_dlpipe(service_connreq->service_id); | |
251 | endpoint->ep_callbacks = service_connreq->ep_callbacks; | |
252 | ||
0fa35a58 ML |
253 | skb = alloc_skb(sizeof(struct htc_conn_svc_msg) + |
254 | sizeof(struct htc_frame_hdr), GFP_ATOMIC); | |
fb9987d0 S |
255 | if (!skb) { |
256 | dev_err(target->dev, "Failed to allocate buf to send" | |
257 | "service connect req\n"); | |
258 | return -ENOMEM; | |
259 | } | |
260 | ||
261 | skb_reserve(skb, sizeof(struct htc_frame_hdr)); | |
262 | ||
263 | conn_msg = (struct htc_conn_svc_msg *) | |
264 | skb_put(skb, sizeof(struct htc_conn_svc_msg)); | |
265 | conn_msg->service_id = cpu_to_be16(service_connreq->service_id); | |
266 | conn_msg->msg_id = cpu_to_be16(HTC_MSG_CONNECT_SERVICE_ID); | |
267 | conn_msg->con_flags = cpu_to_be16(service_connreq->con_flags); | |
268 | conn_msg->dl_pipeid = endpoint->dl_pipeid; | |
269 | conn_msg->ul_pipeid = endpoint->ul_pipeid; | |
270 | ||
271 | ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL); | |
272 | if (ret) | |
273 | goto err; | |
274 | ||
275 | time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); | |
276 | if (!time_left) { | |
277 | dev_err(target->dev, "Service connection timeout for: %d\n", | |
278 | service_connreq->service_id); | |
279 | return -ETIMEDOUT; | |
280 | } | |
281 | ||
282 | *conn_rsp_epid = target->conn_rsp_epid; | |
283 | return 0; | |
284 | err: | |
0fa35a58 | 285 | kfree_skb(skb); |
fb9987d0 S |
286 | return ret; |
287 | } | |
288 | ||
289 | int htc_send(struct htc_target *target, struct sk_buff *skb, | |
290 | enum htc_endpoint_id epid, struct ath9k_htc_tx_ctl *tx_ctl) | |
291 | { | |
292 | return htc_issue_send(target, skb, skb->len, 0, epid, tx_ctl); | |
293 | } | |
294 | ||
295 | void htc_stop(struct htc_target *target) | |
296 | { | |
297 | enum htc_endpoint_id epid; | |
298 | struct htc_endpoint *endpoint; | |
299 | ||
300 | for (epid = ENDPOINT0; epid <= ENDPOINT_MAX; epid++) { | |
301 | endpoint = &target->endpoint[epid]; | |
302 | if (endpoint->service_id != 0) | |
303 | target->hif->stop(target->hif_dev, endpoint->ul_pipeid); | |
304 | } | |
305 | } | |
306 | ||
307 | void htc_start(struct htc_target *target) | |
308 | { | |
309 | enum htc_endpoint_id epid; | |
310 | struct htc_endpoint *endpoint; | |
311 | ||
312 | for (epid = ENDPOINT0; epid <= ENDPOINT_MAX; epid++) { | |
313 | endpoint = &target->endpoint[epid]; | |
314 | if (endpoint->service_id != 0) | |
315 | target->hif->start(target->hif_dev, | |
316 | endpoint->ul_pipeid); | |
317 | } | |
318 | } | |
319 | ||
320 | void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle, | |
321 | struct sk_buff *skb, bool txok) | |
322 | { | |
323 | struct htc_endpoint *endpoint; | |
0fa35a58 | 324 | struct htc_frame_hdr *htc_hdr = NULL; |
fb9987d0 S |
325 | |
326 | if (htc_handle->htc_flags & HTC_OP_CONFIG_PIPE_CREDITS) { | |
327 | complete(&htc_handle->cmd_wait); | |
328 | htc_handle->htc_flags &= ~HTC_OP_CONFIG_PIPE_CREDITS; | |
f984d94c | 329 | goto ret; |
fb9987d0 S |
330 | } |
331 | ||
332 | if (htc_handle->htc_flags & HTC_OP_START_WAIT) { | |
333 | complete(&htc_handle->cmd_wait); | |
334 | htc_handle->htc_flags &= ~HTC_OP_START_WAIT; | |
f984d94c | 335 | goto ret; |
fb9987d0 S |
336 | } |
337 | ||
338 | if (skb) { | |
339 | htc_hdr = (struct htc_frame_hdr *) skb->data; | |
340 | endpoint = &htc_handle->endpoint[htc_hdr->endpoint_id]; | |
341 | skb_pull(skb, sizeof(struct htc_frame_hdr)); | |
342 | ||
343 | if (endpoint->ep_callbacks.tx) { | |
f6689072 S |
344 | endpoint->ep_callbacks.tx(endpoint->ep_callbacks.priv, |
345 | skb, htc_hdr->endpoint_id, | |
346 | txok); | |
fb9987d0 S |
347 | } |
348 | } | |
f984d94c S |
349 | |
350 | return; | |
351 | ret: | |
352 | /* HTC-generated packets are freed here. */ | |
0fa35a58 ML |
353 | if (htc_hdr && htc_hdr->endpoint_id != ENDPOINT0) |
354 | dev_kfree_skb_any(skb); | |
355 | else | |
356 | kfree_skb(skb); | |
fb9987d0 S |
357 | } |
358 | ||
359 | /* | |
360 | * HTC Messages are handled directly here and the obtained SKB | |
361 | * is freed. | |
362 | * | |
363 | * Sevice messages (Data, WMI) passed to the corresponding | |
364 | * endpoint RX handlers, which have to free the SKB. | |
365 | */ | |
366 | void ath9k_htc_rx_msg(struct htc_target *htc_handle, | |
367 | struct sk_buff *skb, u32 len, u8 pipe_id) | |
368 | { | |
369 | struct htc_frame_hdr *htc_hdr; | |
370 | enum htc_endpoint_id epid; | |
371 | struct htc_endpoint *endpoint; | |
7f1f5a00 | 372 | __be16 *msg_id; |
fb9987d0 S |
373 | |
374 | if (!htc_handle || !skb) | |
375 | return; | |
376 | ||
377 | htc_hdr = (struct htc_frame_hdr *) skb->data; | |
378 | epid = htc_hdr->endpoint_id; | |
379 | ||
380 | if (epid >= ENDPOINT_MAX) { | |
e6c6d33c ML |
381 | if (pipe_id != USB_REG_IN_PIPE) |
382 | dev_kfree_skb_any(skb); | |
383 | else | |
384 | kfree_skb(skb); | |
fb9987d0 S |
385 | return; |
386 | } | |
387 | ||
388 | if (epid == ENDPOINT0) { | |
389 | ||
390 | /* Handle trailer */ | |
391 | if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) { | |
7f1f5a00 | 392 | if (be32_to_cpu(*(__be32 *) skb->data) == 0x00C60000) |
fb9987d0 | 393 | /* Move past the Watchdog pattern */ |
d5a4c5e3 | 394 | htc_hdr = (struct htc_frame_hdr *)(skb->data + 4); |
fb9987d0 S |
395 | } |
396 | ||
397 | /* Get the message ID */ | |
7f1f5a00 S |
398 | msg_id = (__be16 *) ((void *) htc_hdr + |
399 | sizeof(struct htc_frame_hdr)); | |
fb9987d0 S |
400 | |
401 | /* Now process HTC messages */ | |
402 | switch (be16_to_cpu(*msg_id)) { | |
403 | case HTC_MSG_READY_ID: | |
404 | htc_process_target_rdy(htc_handle, htc_hdr); | |
405 | break; | |
406 | case HTC_MSG_CONNECT_SERVICE_RESPONSE_ID: | |
407 | htc_process_conn_rsp(htc_handle, htc_hdr); | |
408 | break; | |
409 | default: | |
410 | break; | |
411 | } | |
412 | ||
e6c6d33c | 413 | kfree_skb(skb); |
fb9987d0 S |
414 | |
415 | } else { | |
416 | if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) | |
417 | skb_trim(skb, len - htc_hdr->control[0]); | |
418 | ||
419 | skb_pull(skb, sizeof(struct htc_frame_hdr)); | |
420 | ||
421 | endpoint = &htc_handle->endpoint[epid]; | |
422 | if (endpoint->ep_callbacks.rx) | |
423 | endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv, | |
424 | skb, epid); | |
425 | } | |
426 | } | |
427 | ||
428 | struct htc_target *ath9k_htc_hw_alloc(void *hif_handle) | |
429 | { | |
430 | struct htc_target *target; | |
431 | ||
432 | target = kzalloc(sizeof(struct htc_target), GFP_KERNEL); | |
433 | if (!target) | |
434 | printk(KERN_ERR "Unable to allocate memory for" | |
435 | "target device\n"); | |
436 | ||
437 | return target; | |
438 | } | |
439 | ||
440 | void ath9k_htc_hw_free(struct htc_target *htc) | |
441 | { | |
442 | kfree(htc); | |
443 | } | |
444 | ||
445 | int ath9k_htc_hw_init(struct ath9k_htc_hif *hif, struct htc_target *target, | |
446 | void *hif_handle, struct device *dev, u16 devid, | |
447 | enum ath9k_hif_transports transport) | |
448 | { | |
449 | struct htc_endpoint *endpoint; | |
450 | int err = 0; | |
451 | ||
452 | init_completion(&target->target_wait); | |
453 | init_completion(&target->cmd_wait); | |
454 | ||
455 | target->hif = hif; | |
456 | target->hif_dev = hif_handle; | |
457 | target->dev = dev; | |
458 | ||
459 | /* Assign control endpoint pipe IDs */ | |
460 | endpoint = &target->endpoint[ENDPOINT0]; | |
461 | endpoint->ul_pipeid = hif->control_ul_pipe; | |
462 | endpoint->dl_pipeid = hif->control_dl_pipe; | |
463 | ||
464 | err = ath9k_htc_probe_device(target, dev, devid); | |
465 | if (err) { | |
466 | printk(KERN_ERR "Failed to initialize the device\n"); | |
467 | return -ENODEV; | |
468 | } | |
469 | ||
470 | return 0; | |
471 | } | |
472 | ||
473 | void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug) | |
474 | { | |
475 | if (target) | |
476 | ath9k_htc_disconnect_device(target, hot_unplug); | |
477 | } |