Commit | Line | Data |
---|---|---|
349e7104 AS |
1 | /* |
2 | * Copyright (c) 2010 Broadcom Corporation | |
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 ANY | |
11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | |
13 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |
14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | #include <linux/types.h> | |
17 | #include <linux/if_ether.h> | |
18 | #include <linux/spinlock.h> | |
19 | #include <linux/skbuff.h> | |
20 | #include <linux/netdevice.h> | |
21 | #include <linux/err.h> | |
22 | #include <uapi/linux/nl80211.h> | |
23 | ||
24 | #include <brcmu_utils.h> | |
25 | #include <brcmu_wifi.h> | |
26 | #include "dhd.h" | |
27 | #include "dhd_dbg.h" | |
28 | #include "fwil.h" | |
29 | #include "fweh.h" | |
30 | #include "fwsignal.h" | |
31 | ||
32 | /** | |
33 | * DOC: Firmware Signalling | |
34 | * | |
35 | * Firmware can send signals to host and vice versa, which are passed in the | |
36 | * data packets using TLV based header. This signalling layer is on top of the | |
37 | * BDC bus protocol layer. | |
38 | */ | |
39 | ||
40 | /* | |
41 | * single definition for firmware-driver flow control tlv's. | |
42 | * | |
43 | * each tlv is specified by BRCMF_FWS_TLV_DEF(name, ID, length). | |
44 | * A length value 0 indicates variable length tlv. | |
45 | */ | |
46 | #define BRCMF_FWS_TLV_DEFLIST \ | |
47 | BRCMF_FWS_TLV_DEF(MAC_OPEN, 1, 1) \ | |
48 | BRCMF_FWS_TLV_DEF(MAC_CLOSE, 2, 1) \ | |
49 | BRCMF_FWS_TLV_DEF(MAC_REQUEST_CREDIT, 3, 2) \ | |
50 | BRCMF_FWS_TLV_DEF(TXSTATUS, 4, 4) \ | |
51 | BRCMF_FWS_TLV_DEF(PKTTAG, 5, 4) \ | |
52 | BRCMF_FWS_TLV_DEF(MACDESC_ADD, 6, 8) \ | |
53 | BRCMF_FWS_TLV_DEF(MACDESC_DEL, 7, 8) \ | |
54 | BRCMF_FWS_TLV_DEF(RSSI, 8, 1) \ | |
55 | BRCMF_FWS_TLV_DEF(INTERFACE_OPEN, 9, 1) \ | |
56 | BRCMF_FWS_TLV_DEF(INTERFACE_CLOSE, 10, 1) \ | |
57 | BRCMF_FWS_TLV_DEF(FIFO_CREDITBACK, 11, 8) \ | |
58 | BRCMF_FWS_TLV_DEF(PENDING_TRAFFIC_BMP, 12, 2) \ | |
59 | BRCMF_FWS_TLV_DEF(MAC_REQUEST_PACKET, 13, 3) \ | |
60 | BRCMF_FWS_TLV_DEF(HOST_REORDER_RXPKTS, 14, 10) \ | |
61 | BRCMF_FWS_TLV_DEF(TRANS_ID, 18, 6) \ | |
62 | BRCMF_FWS_TLV_DEF(COMP_TXSTATUS, 19, 1) \ | |
63 | BRCMF_FWS_TLV_DEF(FILLER, 255, 0) | |
64 | ||
65 | /** | |
66 | * enum brcmf_fws_tlv_type - definition of tlv identifiers. | |
67 | */ | |
68 | #define BRCMF_FWS_TLV_DEF(name, id, len) \ | |
69 | BRCMF_FWS_TYPE_ ## name = id, | |
70 | enum brcmf_fws_tlv_type { | |
71 | BRCMF_FWS_TLV_DEFLIST | |
72 | BRCMF_FWS_TYPE_INVALID | |
73 | }; | |
74 | #undef BRCMF_FWS_TLV_DEF | |
75 | ||
349e7104 AS |
76 | #ifdef DEBUG |
77 | /** | |
78 | * brcmf_fws_tlv_names - array of tlv names. | |
79 | */ | |
80 | #define BRCMF_FWS_TLV_DEF(name, id, len) \ | |
81 | { id, #name }, | |
82 | static struct { | |
83 | enum brcmf_fws_tlv_type id; | |
84 | const char *name; | |
85 | } brcmf_fws_tlv_names[] = { | |
86 | BRCMF_FWS_TLV_DEFLIST | |
87 | }; | |
88 | #undef BRCMF_FWS_TLV_DEF | |
89 | ||
90 | static const char *brcmf_fws_get_tlv_name(enum brcmf_fws_tlv_type id) | |
91 | { | |
92 | int i; | |
93 | ||
94 | for (i = 0; i < ARRAY_SIZE(brcmf_fws_tlv_names); i++) | |
95 | if (brcmf_fws_tlv_names[i].id == id) | |
96 | return brcmf_fws_tlv_names[i].name; | |
97 | ||
98 | return "INVALID"; | |
99 | } | |
100 | #else | |
101 | static const char *brcmf_fws_get_tlv_name(enum brcmf_fws_tlv_type id) | |
102 | { | |
103 | return "NODEBUG"; | |
104 | } | |
105 | #endif /* DEBUG */ | |
106 | ||
107 | /** | |
108 | * flags used to enable tlv signalling from firmware. | |
109 | */ | |
bb8c8063 AS |
110 | #define BRCMF_FWS_FLAGS_RSSI_SIGNALS 0x0001 |
111 | #define BRCMF_FWS_FLAGS_XONXOFF_SIGNALS 0x0002 | |
112 | #define BRCMF_FWS_FLAGS_CREDIT_STATUS_SIGNALS 0x0004 | |
113 | #define BRCMF_FWS_FLAGS_HOST_PROPTXSTATUS_ACTIVE 0x0008 | |
114 | #define BRCMF_FWS_FLAGS_PSQ_GENERATIONFSM_ENABLE 0x0010 | |
115 | #define BRCMF_FWS_FLAGS_PSQ_ZERO_BUFFER_ENABLE 0x0020 | |
116 | #define BRCMF_FWS_FLAGS_HOST_RXREORDER_ACTIVE 0x0040 | |
117 | ||
118 | #define BRCMF_FWS_HANGER_MAXITEMS 1024 | |
119 | #define BRCMF_FWS_HANGER_ITEM_STATE_FREE 1 | |
120 | #define BRCMF_FWS_HANGER_ITEM_STATE_INUSE 2 | |
121 | #define BRCMF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED 3 | |
122 | ||
123 | #define BRCMF_FWS_STATE_OPEN 1 | |
349e7104 AS |
124 | #define BRCMF_FWS_STATE_CLOSE 2 |
125 | ||
126 | #define BRCMF_FWS_FCMODE_NONE 0 | |
127 | #define BRCMF_FWS_FCMODE_IMPLIED_CREDIT 1 | |
bb8c8063 | 128 | #define BRCMF_FWS_FCMODE_EXPLICIT_CREDIT 2 |
349e7104 AS |
129 | |
130 | #define BRCMF_FWS_MAC_DESC_TABLE_SIZE 32 | |
bb8c8063 | 131 | #define BRCMF_FWS_MAX_IFNUM 16 |
349e7104 AS |
132 | #define BRCMF_FWS_MAC_DESC_ID_INVALID 0xff |
133 | ||
134 | #define BRCMF_FWS_HOSTIF_FLOWSTATE_OFF 0 | |
135 | #define BRCMF_FWS_HOSTIF_FLOWSTATE_ON 1 | |
136 | ||
bb8c8063 AS |
137 | #define BRCMF_FWS_PSQ_PREC_COUNT ((NL80211_NUM_ACS + 1) * 2) |
138 | #define BRCMF_FWS_PSQ_LEN 256 | |
139 | ||
140 | /** | |
141 | * struct brcmf_fws_mac_descriptor - firmware signalling data per node/interface | |
142 | * | |
143 | * @occupied: slot is in use. | |
144 | * @interface_id: interface index. | |
145 | * @state: current state. | |
146 | * @ac_bitmap: ac queue bitmap. | |
147 | * @requested_credit: credits requested by firmware. | |
148 | * @ea: ethernet address. | |
149 | * @psq: power-save queue. | |
150 | */ | |
151 | struct brcmf_fws_mac_descriptor { | |
152 | u8 occupied; | |
153 | u8 interface_id; | |
154 | u8 state; | |
155 | u8 ac_bitmap; | |
156 | u8 requested_credit; | |
157 | u8 ea[ETH_ALEN]; | |
158 | struct pktq psq; | |
159 | }; | |
160 | ||
349e7104 AS |
161 | /** |
162 | * FWFC packet identifier | |
163 | * | |
164 | * 32-bit packet identifier used in PKTTAG tlv from host to dongle. | |
165 | * | |
166 | * - Generated at the host (e.g. dhd) | |
167 | * - Seen as a generic sequence number by wlc except the flags field | |
168 | * | |
169 | * Generation : b[31] => generation number for this packet [host->fw] | |
170 | * OR, current generation number [fw->host] | |
171 | * Flags : b[30:27] => command, status flags | |
172 | * FIFO-AC : b[26:24] => AC-FIFO id | |
173 | * h-slot : b[23:8] => hanger-slot | |
174 | * freerun : b[7:0] => A free running counter | |
175 | */ | |
176 | #define BRCMF_FWS_PKTTAG_GENERATION_MASK 0x80000000 | |
177 | #define BRCMF_FWS_PKTTAG_GENERATION_SHIFT 31 | |
178 | #define BRCMF_FWS_PKTTAG_FLAGS_MASK 0x78000000 | |
179 | #define BRCMF_FWS_PKTTAG_FLAGS_SHIFT 27 | |
180 | #define BRCMF_FWS_PKTTAG_FIFO_MASK 0x07000000 | |
181 | #define BRCMF_FWS_PKTTAG_FIFO_SHIFT 24 | |
182 | #define BRCMF_FWS_PKTTAG_HSLOT_MASK 0x00ffff00 | |
183 | #define BRCMF_FWS_PKTTAG_HSLOT_SHIFT 8 | |
184 | #define BRCMF_FWS_PKTTAG_FREERUN_MASK 0x000000ff | |
185 | #define BRCMF_FWS_PKTTAG_FREERUN_SHIFT 0 | |
186 | ||
187 | #define brcmf_fws_pkttag_set_field(var, field, value) \ | |
188 | brcmu_maskset32((var), BRCMF_FWS_PKTTAG_ ## field ## _MASK, \ | |
189 | BRCMF_FWS_PKTTAG_ ## field ## _SHIFT, (value)) | |
190 | #define brcmf_fws_pkttag_get_field(var, field) \ | |
191 | brcmu_maskget32((var), BRCMF_FWS_PKTTAG_ ## field ## _MASK, \ | |
192 | BRCMF_FWS_PKTTAG_ ## field ## _SHIFT) | |
193 | ||
194 | struct brcmf_fws_info { | |
195 | struct brcmf_pub *drvr; | |
196 | struct brcmf_fws_stats stats; | |
197 | }; | |
198 | ||
bb8c8063 AS |
199 | /** |
200 | * brcmf_fws_get_tlv_len() - returns defined length for given tlv id. | |
201 | */ | |
202 | #define BRCMF_FWS_TLV_DEF(name, id, len) \ | |
203 | case BRCMF_FWS_TYPE_ ## name: \ | |
204 | return len; | |
205 | ||
206 | static int brcmf_fws_get_tlv_len(struct brcmf_fws_info *fws, | |
207 | enum brcmf_fws_tlv_type id) | |
208 | { | |
209 | switch (id) { | |
210 | BRCMF_FWS_TLV_DEFLIST | |
211 | default: | |
212 | brcmf_err("invalid tlv id: %d\n", id); | |
213 | fws->stats.tlv_invalid_type++; | |
214 | break; | |
215 | } | |
216 | return -EINVAL; | |
217 | } | |
218 | #undef BRCMF_FWS_TLV_DEF | |
219 | ||
349e7104 AS |
220 | static int brcmf_fws_rssi_indicate(struct brcmf_fws_info *fws, s8 rssi) |
221 | { | |
222 | brcmf_dbg(CTL, "rssi %d\n", rssi); | |
223 | return 0; | |
224 | } | |
225 | ||
226 | static int brcmf_fws_dbg_seqnum_check(struct brcmf_fws_info *fws, u8 *data) | |
227 | { | |
228 | __le32 timestamp; | |
229 | ||
230 | memcpy(×tamp, &data[2], sizeof(timestamp)); | |
231 | brcmf_dbg(INFO, "received: seq %d, timestamp %d\n", data[1], | |
232 | le32_to_cpu(timestamp)); | |
233 | return 0; | |
234 | } | |
235 | ||
236 | /* using macro so sparse checking does not complain | |
237 | * about locking imbalance. | |
238 | */ | |
239 | #define brcmf_fws_lock(drvr, flags) \ | |
240 | do { \ | |
241 | flags = 0; \ | |
242 | spin_lock_irqsave(&((drvr)->fws_spinlock), (flags)); \ | |
243 | } while (0) | |
244 | ||
245 | /* using macro so sparse checking does not complain | |
246 | * about locking imbalance. | |
247 | */ | |
248 | #define brcmf_fws_unlock(drvr, flags) \ | |
249 | spin_unlock_irqrestore(&((drvr)->fws_spinlock), (flags)) | |
250 | ||
251 | int brcmf_fws_init(struct brcmf_pub *drvr) | |
252 | { | |
253 | u32 tlv; | |
254 | int rc; | |
255 | ||
256 | /* enable rssi signals */ | |
257 | tlv = drvr->fw_signals ? BRCMF_FWS_FLAGS_RSSI_SIGNALS : 0; | |
258 | ||
259 | spin_lock_init(&drvr->fws_spinlock); | |
260 | ||
261 | drvr->fws = kzalloc(sizeof(*(drvr->fws)), GFP_KERNEL); | |
262 | if (!drvr->fws) { | |
263 | rc = -ENOMEM; | |
264 | goto fail; | |
265 | } | |
266 | ||
267 | /* enable proptxtstatus signaling by default */ | |
268 | rc = brcmf_fil_iovar_int_set(drvr->iflist[0], "tlv", tlv); | |
269 | if (rc < 0) { | |
270 | brcmf_err("failed to set bdcv2 tlv signaling\n"); | |
271 | goto fail; | |
272 | } | |
273 | /* set linkage back */ | |
274 | drvr->fws->drvr = drvr; | |
275 | ||
276 | /* create debugfs file for statistics */ | |
277 | brcmf_debugfs_create_fws_stats(drvr, &drvr->fws->stats); | |
278 | ||
279 | /* TODO: remove upon feature delivery */ | |
280 | brcmf_err("%s bdcv2 tlv signaling [%x]\n", | |
281 | drvr->fw_signals ? "enabled" : "disabled", tlv); | |
282 | return 0; | |
283 | ||
284 | fail: | |
285 | /* disable flow control entirely */ | |
286 | drvr->fw_signals = false; | |
287 | brcmf_fws_deinit(drvr); | |
288 | return rc; | |
289 | } | |
290 | ||
291 | void brcmf_fws_deinit(struct brcmf_pub *drvr) | |
292 | { | |
293 | /* free top structure */ | |
294 | kfree(drvr->fws); | |
295 | drvr->fws = NULL; | |
296 | } | |
297 | ||
298 | int brcmf_fws_hdrpull(struct brcmf_pub *drvr, int ifidx, s16 signal_len, | |
299 | struct sk_buff *skb) | |
300 | { | |
301 | struct brcmf_fws_info *fws = drvr->fws; | |
302 | ulong flags; | |
303 | u8 *signal_data; | |
304 | s16 data_len; | |
305 | u8 type; | |
306 | u8 len; | |
307 | u8 *data; | |
308 | ||
309 | brcmf_dbg(TRACE, "enter: ifidx %d, skblen %u, sig %d\n", | |
310 | ifidx, skb->len, signal_len); | |
311 | ||
312 | WARN_ON(signal_len > skb->len); | |
313 | ||
314 | /* if flow control disabled, skip to packet data and leave */ | |
315 | if (!signal_len || !drvr->fw_signals) { | |
316 | skb_pull(skb, signal_len); | |
317 | return 0; | |
318 | } | |
319 | ||
320 | /* lock during tlv parsing */ | |
321 | brcmf_fws_lock(drvr, flags); | |
322 | ||
323 | fws->stats.header_pulls++; | |
324 | data_len = signal_len; | |
325 | signal_data = skb->data; | |
326 | ||
327 | while (data_len > 0) { | |
328 | /* extract tlv info */ | |
329 | type = signal_data[0]; | |
330 | ||
331 | /* FILLER type is actually not a TLV, but | |
332 | * a single byte that can be skipped. | |
333 | */ | |
334 | if (type == BRCMF_FWS_TYPE_FILLER) { | |
335 | signal_data += 1; | |
336 | data_len -= 1; | |
337 | continue; | |
338 | } | |
339 | len = signal_data[1]; | |
340 | data = signal_data + 2; | |
341 | ||
342 | /* abort parsing when length invalid */ | |
343 | if (data_len < len + 2) | |
344 | break; | |
345 | ||
bb8c8063 AS |
346 | if (len != brcmf_fws_get_tlv_len(fws, type)) |
347 | break; | |
348 | ||
349e7104 AS |
349 | brcmf_dbg(INFO, "tlv type=%d (%s), len=%d\n", type, |
350 | brcmf_fws_get_tlv_name(type), len); | |
351 | switch (type) { | |
352 | case BRCMF_FWS_TYPE_MAC_OPEN: | |
353 | case BRCMF_FWS_TYPE_MAC_CLOSE: | |
349e7104 | 354 | case BRCMF_FWS_TYPE_MAC_REQUEST_CREDIT: |
349e7104 | 355 | case BRCMF_FWS_TYPE_TXSTATUS: |
349e7104 | 356 | case BRCMF_FWS_TYPE_PKTTAG: |
349e7104 AS |
357 | case BRCMF_FWS_TYPE_INTERFACE_OPEN: |
358 | case BRCMF_FWS_TYPE_INTERFACE_CLOSE: | |
349e7104 | 359 | case BRCMF_FWS_TYPE_FIFO_CREDITBACK: |
349e7104 | 360 | case BRCMF_FWS_TYPE_PENDING_TRAFFIC_BMP: |
349e7104 | 361 | case BRCMF_FWS_TYPE_MAC_REQUEST_PACKET: |
349e7104 | 362 | case BRCMF_FWS_TYPE_HOST_REORDER_RXPKTS: |
bb8c8063 AS |
363 | case BRCMF_FWS_TYPE_COMP_TXSTATUS: |
364 | case BRCMF_FWS_TYPE_MACDESC_ADD: | |
365 | case BRCMF_FWS_TYPE_MACDESC_DEL: | |
366 | break; | |
367 | case BRCMF_FWS_TYPE_RSSI: | |
368 | brcmf_fws_rssi_indicate(fws, *data); | |
349e7104 AS |
369 | break; |
370 | case BRCMF_FWS_TYPE_TRANS_ID: | |
349e7104 AS |
371 | brcmf_fws_dbg_seqnum_check(fws, data); |
372 | break; | |
349e7104 AS |
373 | default: |
374 | fws->stats.tlv_invalid_type++; | |
375 | break; | |
376 | } | |
377 | ||
378 | signal_data += len + 2; | |
379 | data_len -= len + 2; | |
380 | } | |
381 | ||
382 | if (data_len != 0) | |
383 | fws->stats.tlv_parse_failed++; | |
384 | ||
385 | /* signalling processing result does | |
386 | * not affect the actual ethernet packet. | |
387 | */ | |
388 | skb_pull(skb, signal_len); | |
389 | ||
390 | /* this may be a signal-only packet | |
391 | */ | |
392 | if (skb->len == 0) | |
393 | fws->stats.header_only_pkt++; | |
394 | ||
395 | brcmf_fws_unlock(drvr, flags); | |
396 | return 0; | |
397 | } | |
bb8c8063 AS |
398 | |
399 | void brcmf_fws_reset_interface(struct brcmf_if *ifp) | |
400 | { | |
401 | struct brcmf_fws_mac_descriptor *entry = ifp->fws_desc; | |
402 | ||
403 | brcmf_dbg(TRACE, "enter: idx=%d\n", ifp->bssidx); | |
404 | if (!entry) | |
405 | return; | |
406 | ||
407 | entry->occupied = 1; | |
408 | entry->state = BRCMF_FWS_STATE_OPEN; | |
409 | entry->requested_credit = 0; | |
410 | /* depending on use may need ifp->bssidx instead */ | |
411 | entry->interface_id = ifp->ifidx; | |
412 | entry->ac_bitmap = 0xff; /* update this when handling APSD */ | |
413 | memcpy(&entry->ea[0], ifp->mac_addr, ETH_ALEN); | |
414 | } | |
415 | ||
416 | void brcmf_fws_add_interface(struct brcmf_if *ifp) | |
417 | { | |
418 | struct brcmf_fws_mac_descriptor *entry; | |
419 | ||
420 | brcmf_dbg(TRACE, "enter: idx=%d, mac=%pM\n", | |
421 | ifp->bssidx, ifp->mac_addr); | |
422 | if (!ifp->drvr->fw_signals) | |
423 | return; | |
424 | ||
425 | entry = kzalloc(sizeof(*entry), GFP_ATOMIC); | |
426 | if (entry) { | |
427 | ifp->fws_desc = entry; | |
428 | brcmf_fws_reset_interface(ifp); | |
429 | brcmu_pktq_init(&entry->psq, BRCMF_FWS_PSQ_PREC_COUNT, | |
430 | BRCMF_FWS_PSQ_LEN); | |
431 | } else { | |
432 | brcmf_err("no firmware signalling\n"); | |
433 | } | |
434 | } | |
435 | ||
436 | void brcmf_fws_del_interface(struct brcmf_if *ifp) | |
437 | { | |
438 | struct brcmf_fws_mac_descriptor *entry = ifp->fws_desc; | |
439 | ||
440 | brcmf_dbg(TRACE, "enter: idx=%d\n", ifp->bssidx); | |
441 | if (!entry) | |
442 | return; | |
443 | ||
444 | ifp->fws_desc = NULL; | |
445 | entry->occupied = 0; | |
446 | entry->state = BRCMF_FWS_STATE_CLOSE; | |
447 | entry->requested_credit = 0; | |
448 | kfree(entry); | |
449 | } |