diff options
Diffstat (limited to 'drivers/uwb/est.c')
-rw-r--r-- | drivers/uwb/est.c | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/drivers/uwb/est.c b/drivers/uwb/est.c new file mode 100644 index 000000000000..3791cd95b63d --- /dev/null +++ b/drivers/uwb/est.c | |||
@@ -0,0 +1,485 @@ | |||
1 | /* | ||
2 | * Ultra Wide Band Radio Control | ||
3 | * Event Size Tables management | ||
4 | * | ||
5 | * Copyright (C) 2005-2006 Intel Corporation | ||
6 | * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License version | ||
10 | * 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
20 | * 02110-1301, USA. | ||
21 | * | ||
22 | * | ||
23 | * FIXME: docs | ||
24 | * | ||
25 | * Infrastructure, code and data tables for guessing the size of | ||
26 | * events received on the notification endpoints of UWB radio | ||
27 | * controllers. | ||
28 | * | ||
29 | * You define a table of events and for each, its size and how to get | ||
30 | * the extra size. | ||
31 | * | ||
32 | * ENTRY POINTS: | ||
33 | * | ||
34 | * uwb_est_{init/destroy}(): To initialize/release the EST subsystem. | ||
35 | * | ||
36 | * uwb_est_[u]register(): To un/register event size tables | ||
37 | * uwb_est_grow() | ||
38 | * | ||
39 | * uwb_est_find_size(): Get the size of an event | ||
40 | * uwb_est_get_size() | ||
41 | */ | ||
42 | #include <linux/spinlock.h> | ||
43 | #define D_LOCAL 0 | ||
44 | #include <linux/uwb/debug.h> | ||
45 | #include "uwb-internal.h" | ||
46 | |||
47 | |||
48 | struct uwb_est { | ||
49 | u16 type_event_high; | ||
50 | u16 vendor, product; | ||
51 | u8 entries; | ||
52 | const struct uwb_est_entry *entry; | ||
53 | }; | ||
54 | |||
55 | |||
56 | static struct uwb_est *uwb_est; | ||
57 | static u8 uwb_est_size; | ||
58 | static u8 uwb_est_used; | ||
59 | static DEFINE_RWLOCK(uwb_est_lock); | ||
60 | |||
61 | /** | ||
62 | * WUSB Standard Event Size Table, HWA-RC interface | ||
63 | * | ||
64 | * Sizes for events and notifications type 0 (general), high nibble 0. | ||
65 | */ | ||
66 | static | ||
67 | struct uwb_est_entry uwb_est_00_00xx[] = { | ||
68 | [UWB_RC_EVT_IE_RCV] = { | ||
69 | .size = sizeof(struct uwb_rc_evt_ie_rcv), | ||
70 | .offset = 1 + offsetof(struct uwb_rc_evt_ie_rcv, wIELength), | ||
71 | }, | ||
72 | [UWB_RC_EVT_BEACON] = { | ||
73 | .size = sizeof(struct uwb_rc_evt_beacon), | ||
74 | .offset = 1 + offsetof(struct uwb_rc_evt_beacon, wBeaconInfoLength), | ||
75 | }, | ||
76 | [UWB_RC_EVT_BEACON_SIZE] = { | ||
77 | .size = sizeof(struct uwb_rc_evt_beacon_size), | ||
78 | }, | ||
79 | [UWB_RC_EVT_BPOIE_CHANGE] = { | ||
80 | .size = sizeof(struct uwb_rc_evt_bpoie_change), | ||
81 | .offset = 1 + offsetof(struct uwb_rc_evt_bpoie_change, | ||
82 | wBPOIELength), | ||
83 | }, | ||
84 | [UWB_RC_EVT_BP_SLOT_CHANGE] = { | ||
85 | .size = sizeof(struct uwb_rc_evt_bp_slot_change), | ||
86 | }, | ||
87 | [UWB_RC_EVT_BP_SWITCH_IE_RCV] = { | ||
88 | .size = sizeof(struct uwb_rc_evt_bp_switch_ie_rcv), | ||
89 | .offset = 1 + offsetof(struct uwb_rc_evt_bp_switch_ie_rcv, wIELength), | ||
90 | }, | ||
91 | [UWB_RC_EVT_DEV_ADDR_CONFLICT] = { | ||
92 | .size = sizeof(struct uwb_rc_evt_dev_addr_conflict), | ||
93 | }, | ||
94 | [UWB_RC_EVT_DRP_AVAIL] = { | ||
95 | .size = sizeof(struct uwb_rc_evt_drp_avail) | ||
96 | }, | ||
97 | [UWB_RC_EVT_DRP] = { | ||
98 | .size = sizeof(struct uwb_rc_evt_drp), | ||
99 | .offset = 1 + offsetof(struct uwb_rc_evt_drp, ie_length), | ||
100 | }, | ||
101 | [UWB_RC_EVT_BP_SWITCH_STATUS] = { | ||
102 | .size = sizeof(struct uwb_rc_evt_bp_switch_status), | ||
103 | }, | ||
104 | [UWB_RC_EVT_CMD_FRAME_RCV] = { | ||
105 | .size = sizeof(struct uwb_rc_evt_cmd_frame_rcv), | ||
106 | .offset = 1 + offsetof(struct uwb_rc_evt_cmd_frame_rcv, dataLength), | ||
107 | }, | ||
108 | [UWB_RC_EVT_CHANNEL_CHANGE_IE_RCV] = { | ||
109 | .size = sizeof(struct uwb_rc_evt_channel_change_ie_rcv), | ||
110 | .offset = 1 + offsetof(struct uwb_rc_evt_channel_change_ie_rcv, wIELength), | ||
111 | }, | ||
112 | [UWB_RC_CMD_CHANNEL_CHANGE] = { | ||
113 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
114 | }, | ||
115 | [UWB_RC_CMD_DEV_ADDR_MGMT] = { | ||
116 | .size = sizeof(struct uwb_rc_evt_dev_addr_mgmt) }, | ||
117 | [UWB_RC_CMD_GET_IE] = { | ||
118 | .size = sizeof(struct uwb_rc_evt_get_ie), | ||
119 | .offset = 1 + offsetof(struct uwb_rc_evt_get_ie, wIELength), | ||
120 | }, | ||
121 | [UWB_RC_CMD_RESET] = { | ||
122 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
123 | }, | ||
124 | [UWB_RC_CMD_SCAN] = { | ||
125 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
126 | }, | ||
127 | [UWB_RC_CMD_SET_BEACON_FILTER] = { | ||
128 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
129 | }, | ||
130 | [UWB_RC_CMD_SET_DRP_IE] = { | ||
131 | .size = sizeof(struct uwb_rc_evt_set_drp_ie), | ||
132 | }, | ||
133 | [UWB_RC_CMD_SET_IE] = { | ||
134 | .size = sizeof(struct uwb_rc_evt_set_ie), | ||
135 | }, | ||
136 | [UWB_RC_CMD_SET_NOTIFICATION_FILTER] = { | ||
137 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
138 | }, | ||
139 | [UWB_RC_CMD_SET_TX_POWER] = { | ||
140 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
141 | }, | ||
142 | [UWB_RC_CMD_SLEEP] = { | ||
143 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
144 | }, | ||
145 | [UWB_RC_CMD_START_BEACON] = { | ||
146 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
147 | }, | ||
148 | [UWB_RC_CMD_STOP_BEACON] = { | ||
149 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
150 | }, | ||
151 | [UWB_RC_CMD_BP_MERGE] = { | ||
152 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
153 | }, | ||
154 | [UWB_RC_CMD_SEND_COMMAND_FRAME] = { | ||
155 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
156 | }, | ||
157 | [UWB_RC_CMD_SET_ASIE_NOTIF] = { | ||
158 | .size = sizeof(struct uwb_rc_evt_confirm), | ||
159 | }, | ||
160 | }; | ||
161 | |||
162 | static | ||
163 | struct uwb_est_entry uwb_est_01_00xx[] = { | ||
164 | [UWB_RC_DAA_ENERGY_DETECTED] = { | ||
165 | .size = sizeof(struct uwb_rc_evt_daa_energy_detected), | ||
166 | }, | ||
167 | [UWB_RC_SET_DAA_ENERGY_MASK] = { | ||
168 | .size = sizeof(struct uwb_rc_evt_set_daa_energy_mask), | ||
169 | }, | ||
170 | [UWB_RC_SET_NOTIFICATION_FILTER_EX] = { | ||
171 | .size = sizeof(struct uwb_rc_evt_set_notification_filter_ex), | ||
172 | }, | ||
173 | }; | ||
174 | |||
175 | /** | ||
176 | * Initialize the EST subsystem | ||
177 | * | ||
178 | * Register the standard tables also. | ||
179 | * | ||
180 | * FIXME: tag init | ||
181 | */ | ||
182 | int uwb_est_create(void) | ||
183 | { | ||
184 | int result; | ||
185 | |||
186 | uwb_est_size = 2; | ||
187 | uwb_est_used = 0; | ||
188 | uwb_est = kzalloc(uwb_est_size * sizeof(uwb_est[0]), GFP_KERNEL); | ||
189 | if (uwb_est == NULL) | ||
190 | return -ENOMEM; | ||
191 | |||
192 | result = uwb_est_register(UWB_RC_CET_GENERAL, 0, 0xffff, 0xffff, | ||
193 | uwb_est_00_00xx, ARRAY_SIZE(uwb_est_00_00xx)); | ||
194 | if (result < 0) | ||
195 | goto out; | ||
196 | result = uwb_est_register(UWB_RC_CET_EX_TYPE_1, 0, 0xffff, 0xffff, | ||
197 | uwb_est_01_00xx, ARRAY_SIZE(uwb_est_01_00xx)); | ||
198 | out: | ||
199 | return result; | ||
200 | } | ||
201 | |||
202 | |||
203 | /** Clean it up */ | ||
204 | void uwb_est_destroy(void) | ||
205 | { | ||
206 | kfree(uwb_est); | ||
207 | uwb_est = NULL; | ||
208 | uwb_est_size = uwb_est_used = 0; | ||
209 | } | ||
210 | |||
211 | |||
212 | /** | ||
213 | * Double the capacity of the EST table | ||
214 | * | ||
215 | * @returns 0 if ok, < 0 errno no error. | ||
216 | */ | ||
217 | static | ||
218 | int uwb_est_grow(void) | ||
219 | { | ||
220 | size_t actual_size = uwb_est_size * sizeof(uwb_est[0]); | ||
221 | void *new = kmalloc(2 * actual_size, GFP_ATOMIC); | ||
222 | if (new == NULL) | ||
223 | return -ENOMEM; | ||
224 | memcpy(new, uwb_est, actual_size); | ||
225 | memset(new + actual_size, 0, actual_size); | ||
226 | kfree(uwb_est); | ||
227 | uwb_est = new; | ||
228 | uwb_est_size *= 2; | ||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | |||
233 | /** | ||
234 | * Register an event size table | ||
235 | * | ||
236 | * Makes room for it if the table is full, and then inserts it in the | ||
237 | * right position (entries are sorted by type, event_high, vendor and | ||
238 | * then product). | ||
239 | * | ||
240 | * @vendor: vendor code for matching against the device (0x0000 and | ||
241 | * 0xffff mean any); use 0x0000 to force all to match without | ||
242 | * checking possible vendor specific ones, 0xfffff to match | ||
243 | * after checking vendor specific ones. | ||
244 | * | ||
245 | * @product: product code from that vendor; same matching rules, use | ||
246 | * 0x0000 for not allowing vendor specific matches, 0xffff | ||
247 | * for allowing. | ||
248 | * | ||
249 | * This arragement just makes the tables sort differenty. Because the | ||
250 | * table is sorted by growing type-event_high-vendor-product, a zero | ||
251 | * vendor will match before than a 0x456a vendor, that will match | ||
252 | * before a 0xfffff vendor. | ||
253 | * | ||
254 | * @returns 0 if ok, < 0 errno on error (-ENOENT if not found). | ||
255 | */ | ||
256 | /* FIXME: add bus type to vendor/product code */ | ||
257 | int uwb_est_register(u8 type, u8 event_high, u16 vendor, u16 product, | ||
258 | const struct uwb_est_entry *entry, size_t entries) | ||
259 | { | ||
260 | unsigned long flags; | ||
261 | unsigned itr; | ||
262 | u16 type_event_high; | ||
263 | int result = 0; | ||
264 | |||
265 | write_lock_irqsave(&uwb_est_lock, flags); | ||
266 | if (uwb_est_used == uwb_est_size) { | ||
267 | result = uwb_est_grow(); | ||
268 | if (result < 0) | ||
269 | goto out; | ||
270 | } | ||
271 | /* Find the right spot to insert it in */ | ||
272 | type_event_high = type << 8 | event_high; | ||
273 | for (itr = 0; itr < uwb_est_used; itr++) | ||
274 | if (uwb_est[itr].type_event_high < type | ||
275 | && uwb_est[itr].vendor < vendor | ||
276 | && uwb_est[itr].product < product) | ||
277 | break; | ||
278 | |||
279 | /* Shift others to make room for the new one? */ | ||
280 | if (itr < uwb_est_used) | ||
281 | memmove(&uwb_est[itr+1], &uwb_est[itr], uwb_est_used - itr); | ||
282 | uwb_est[itr].type_event_high = type << 8 | event_high; | ||
283 | uwb_est[itr].vendor = vendor; | ||
284 | uwb_est[itr].product = product; | ||
285 | uwb_est[itr].entry = entry; | ||
286 | uwb_est[itr].entries = entries; | ||
287 | uwb_est_used++; | ||
288 | out: | ||
289 | write_unlock_irqrestore(&uwb_est_lock, flags); | ||
290 | return result; | ||
291 | } | ||
292 | EXPORT_SYMBOL_GPL(uwb_est_register); | ||
293 | |||
294 | |||
295 | /** | ||
296 | * Unregister an event size table | ||
297 | * | ||
298 | * This just removes the specified entry and moves the ones after it | ||
299 | * to fill in the gap. This is needed to keep the list sorted; no | ||
300 | * reallocation is done to reduce the size of the table. | ||
301 | * | ||
302 | * We unregister by all the data we used to register instead of by | ||
303 | * pointer to the @entry array because we might have used the same | ||
304 | * table for a bunch of IDs (for example). | ||
305 | * | ||
306 | * @returns 0 if ok, < 0 errno on error (-ENOENT if not found). | ||
307 | */ | ||
308 | int uwb_est_unregister(u8 type, u8 event_high, u16 vendor, u16 product, | ||
309 | const struct uwb_est_entry *entry, size_t entries) | ||
310 | { | ||
311 | unsigned long flags; | ||
312 | unsigned itr; | ||
313 | struct uwb_est est_cmp = { | ||
314 | .type_event_high = type << 8 | event_high, | ||
315 | .vendor = vendor, | ||
316 | .product = product, | ||
317 | .entry = entry, | ||
318 | .entries = entries | ||
319 | }; | ||
320 | write_lock_irqsave(&uwb_est_lock, flags); | ||
321 | for (itr = 0; itr < uwb_est_used; itr++) | ||
322 | if (!memcmp(&uwb_est[itr], &est_cmp, sizeof(est_cmp))) | ||
323 | goto found; | ||
324 | write_unlock_irqrestore(&uwb_est_lock, flags); | ||
325 | return -ENOENT; | ||
326 | |||
327 | found: | ||
328 | if (itr < uwb_est_used - 1) /* Not last one? move ones above */ | ||
329 | memmove(&uwb_est[itr], &uwb_est[itr+1], uwb_est_used - itr - 1); | ||
330 | uwb_est_used--; | ||
331 | write_unlock_irqrestore(&uwb_est_lock, flags); | ||
332 | return 0; | ||
333 | } | ||
334 | EXPORT_SYMBOL_GPL(uwb_est_unregister); | ||
335 | |||
336 | |||
337 | /** | ||
338 | * Get the size of an event from a table | ||
339 | * | ||
340 | * @rceb: pointer to the buffer with the event | ||
341 | * @rceb_size: size of the area pointed to by @rceb in bytes. | ||
342 | * @returns: > 0 Size of the event | ||
343 | * -ENOSPC An area big enough was not provided to look | ||
344 | * ahead into the event's guts and guess the size. | ||
345 | * -EINVAL Unknown event code (wEvent). | ||
346 | * | ||
347 | * This will look at the received RCEB and guess what is the total | ||
348 | * size. For variable sized events, it will look further ahead into | ||
349 | * their length field to see how much data should be read. | ||
350 | * | ||
351 | * Note this size is *not* final--the neh (Notification/Event Handle) | ||
352 | * might specificy an extra size to add. | ||
353 | */ | ||
354 | static | ||
355 | ssize_t uwb_est_get_size(struct uwb_rc *uwb_rc, struct uwb_est *est, | ||
356 | u8 event_low, const struct uwb_rceb *rceb, | ||
357 | size_t rceb_size) | ||
358 | { | ||
359 | unsigned offset; | ||
360 | ssize_t size; | ||
361 | struct device *dev = &uwb_rc->uwb_dev.dev; | ||
362 | const struct uwb_est_entry *entry; | ||
363 | |||
364 | size = -ENOENT; | ||
365 | if (event_low >= est->entries) { /* in range? */ | ||
366 | if (printk_ratelimit()) | ||
367 | dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " | ||
368 | "event %u out of range\n", | ||
369 | est, est->type_event_high, est->vendor, | ||
370 | est->product, est->entries, | ||
371 | event_low); | ||
372 | goto out; | ||
373 | } | ||
374 | size = -ENOENT; | ||
375 | entry = &est->entry[event_low]; | ||
376 | if (entry->size == 0 && entry->offset == 0) { /* unknown? */ | ||
377 | if (printk_ratelimit()) | ||
378 | dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " | ||
379 | "event %u unknown\n", | ||
380 | est, est->type_event_high, est->vendor, | ||
381 | est->product, est->entries, event_low); | ||
382 | goto out; | ||
383 | } | ||
384 | offset = entry->offset; /* extra fries with that? */ | ||
385 | if (offset == 0) | ||
386 | size = entry->size; | ||
387 | else { | ||
388 | /* Ops, got an extra size field at 'offset'--read it */ | ||
389 | const void *ptr = rceb; | ||
390 | size_t type_size = 0; | ||
391 | offset--; | ||
392 | size = -ENOSPC; /* enough data for more? */ | ||
393 | switch (entry->type) { | ||
394 | case UWB_EST_16: type_size = sizeof(__le16); break; | ||
395 | case UWB_EST_8: type_size = sizeof(u8); break; | ||
396 | default: BUG(); | ||
397 | } | ||
398 | if (offset + type_size > rceb_size) { | ||
399 | if (printk_ratelimit()) | ||
400 | dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " | ||
401 | "not enough data to read extra size\n", | ||
402 | est, est->type_event_high, est->vendor, | ||
403 | est->product, est->entries); | ||
404 | goto out; | ||
405 | } | ||
406 | size = entry->size; | ||
407 | ptr += offset; | ||
408 | switch (entry->type) { | ||
409 | case UWB_EST_16: size += le16_to_cpu(*(__le16 *)ptr); break; | ||
410 | case UWB_EST_8: size += *(u8 *)ptr; break; | ||
411 | default: BUG(); | ||
412 | } | ||
413 | } | ||
414 | out: | ||
415 | return size; | ||
416 | } | ||
417 | |||
418 | |||
419 | /** | ||
420 | * Guesses the size of a WA event | ||
421 | * | ||
422 | * @rceb: pointer to the buffer with the event | ||
423 | * @rceb_size: size of the area pointed to by @rceb in bytes. | ||
424 | * @returns: > 0 Size of the event | ||
425 | * -ENOSPC An area big enough was not provided to look | ||
426 | * ahead into the event's guts and guess the size. | ||
427 | * -EINVAL Unknown event code (wEvent). | ||
428 | * | ||
429 | * This will look at the received RCEB and guess what is the total | ||
430 | * size by checking all the tables registered with | ||
431 | * uwb_est_register(). For variable sized events, it will look further | ||
432 | * ahead into their length field to see how much data should be read. | ||
433 | * | ||
434 | * Note this size is *not* final--the neh (Notification/Event Handle) | ||
435 | * might specificy an extra size to add or replace. | ||
436 | */ | ||
437 | ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, | ||
438 | size_t rceb_size) | ||
439 | { | ||
440 | /* FIXME: add vendor/product data */ | ||
441 | ssize_t size; | ||
442 | struct device *dev = &rc->uwb_dev.dev; | ||
443 | unsigned long flags; | ||
444 | unsigned itr; | ||
445 | u16 type_event_high, event; | ||
446 | u8 *ptr = (u8 *) rceb; | ||
447 | |||
448 | read_lock_irqsave(&uwb_est_lock, flags); | ||
449 | d_printf(2, dev, "Size query for event 0x%02x/%04x/%02x," | ||
450 | " buffer size %ld\n", | ||
451 | (unsigned) rceb->bEventType, | ||
452 | (unsigned) le16_to_cpu(rceb->wEvent), | ||
453 | (unsigned) rceb->bEventContext, | ||
454 | (long) rceb_size); | ||
455 | size = -ENOSPC; | ||
456 | if (rceb_size < sizeof(*rceb)) | ||
457 | goto out; | ||
458 | event = le16_to_cpu(rceb->wEvent); | ||
459 | type_event_high = rceb->bEventType << 8 | (event & 0xff00) >> 8; | ||
460 | for (itr = 0; itr < uwb_est_used; itr++) { | ||
461 | d_printf(3, dev, "Checking EST 0x%04x/%04x/%04x\n", | ||
462 | uwb_est[itr].type_event_high, uwb_est[itr].vendor, | ||
463 | uwb_est[itr].product); | ||
464 | if (uwb_est[itr].type_event_high != type_event_high) | ||
465 | continue; | ||
466 | size = uwb_est_get_size(rc, &uwb_est[itr], | ||
467 | event & 0x00ff, rceb, rceb_size); | ||
468 | /* try more tables that might handle the same type */ | ||
469 | if (size != -ENOENT) | ||
470 | goto out; | ||
471 | } | ||
472 | /* FIXME: downgrade to _dbg() */ | ||
473 | if (printk_ratelimit()) | ||
474 | dev_err(dev, "event 0x%02x/%04x/%02x: no handlers available; " | ||
475 | "RCEB %02x %02x %02x %02x\n", | ||
476 | (unsigned) rceb->bEventType, | ||
477 | (unsigned) le16_to_cpu(rceb->wEvent), | ||
478 | (unsigned) rceb->bEventContext, | ||
479 | ptr[0], ptr[1], ptr[2], ptr[3]); | ||
480 | size = -ENOENT; | ||
481 | out: | ||
482 | read_unlock_irqrestore(&uwb_est_lock, flags); | ||
483 | return size; | ||
484 | } | ||
485 | EXPORT_SYMBOL_GPL(uwb_est_find_size); | ||