Commit | Line | Data |
---|---|---|
f5144854 RC |
1 | /* |
2 | * WUSB Wire Adapter: WLP interface | |
3 | * Ethernet to device address cache | |
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 | * We need to be able to map ethernet addresses to device addresses | |
24 | * and back because there is not explicit relationship between the eth | |
25 | * addresses used in the ETH frames and the device addresses (no, it | |
26 | * would not have been simpler to force as ETH address the MBOA MAC | |
27 | * address...no, not at all :). | |
28 | * | |
29 | * A device has one MBOA MAC address and one device address. It is possible | |
30 | * for a device to have more than one virtual MAC address (although a | |
31 | * virtual address can be the same as the MBOA MAC address). The device | |
32 | * address is guaranteed to be unique among the devices in the extended | |
33 | * beacon group (see ECMA 17.1.1). We thus use the device address as index | |
34 | * to this cache. We do allow searching based on virtual address as this | |
35 | * is how Ethernet frames will be addressed. | |
36 | * | |
37 | * We need to support virtual EUI-48. Although, right now the virtual | |
38 | * EUI-48 will always be the same as the MAC SAP address. The EDA cache | |
39 | * entry thus contains a MAC SAP address as well as the virtual address | |
40 | * (used to map the network stack address to a neighbor). When we move | |
41 | * to support more than one virtual MAC on a host then this organization | |
42 | * will have to change. Perhaps a neighbor has a list of WSSs, each with a | |
43 | * tag and virtual EUI-48. | |
44 | * | |
45 | * On data transmission | |
46 | * it is used to determine if the neighbor is connected and what WSS it | |
47 | * belongs to. With this we know what tag to add to the WLP frame. Storing | |
48 | * the WSS in the EDA cache may be overkill because we only support one | |
49 | * WSS. Hopefully we will support more than one WSS at some point. | |
50 | * On data reception it is used to determine the WSS based on | |
51 | * the tag and address of the transmitting neighbor. | |
52 | */ | |
53 | ||
f5144854 | 54 | #include <linux/netdevice.h> |
f5144854 | 55 | #include <linux/etherdevice.h> |
5a0e3ad6 | 56 | #include <linux/slab.h> |
f5144854 RC |
57 | #include <linux/wlp.h> |
58 | #include "wlp-internal.h" | |
59 | ||
60 | ||
61 | /* FIXME: cache is not purged, only on device close */ | |
62 | ||
63 | /* FIXME: does not scale, change to dynamic array */ | |
64 | ||
65 | /* | |
66 | * Initialize the EDA cache | |
67 | * | |
68 | * @returns 0 if ok, < 0 errno code on error | |
69 | * | |
70 | * Call when the interface is being brought up | |
71 | * | |
72 | * NOTE: Keep it as a separate function as the implementation will | |
73 | * change and be more complex. | |
74 | */ | |
75 | void wlp_eda_init(struct wlp_eda *eda) | |
76 | { | |
77 | INIT_LIST_HEAD(&eda->cache); | |
78 | spin_lock_init(&eda->lock); | |
79 | } | |
80 | ||
81 | /* | |
82 | * Release the EDA cache | |
83 | * | |
84 | * @returns 0 if ok, < 0 errno code on error | |
85 | * | |
86 | * Called when the interface is brought down | |
87 | */ | |
88 | void wlp_eda_release(struct wlp_eda *eda) | |
89 | { | |
90 | unsigned long flags; | |
91 | struct wlp_eda_node *itr, *next; | |
92 | ||
93 | spin_lock_irqsave(&eda->lock, flags); | |
94 | list_for_each_entry_safe(itr, next, &eda->cache, list_node) { | |
95 | list_del(&itr->list_node); | |
96 | kfree(itr); | |
97 | } | |
98 | spin_unlock_irqrestore(&eda->lock, flags); | |
99 | } | |
100 | ||
101 | /* | |
102 | * Add an address mapping | |
103 | * | |
104 | * @returns 0 if ok, < 0 errno code on error | |
105 | * | |
106 | * An address mapping is initially created when the neighbor device is seen | |
107 | * for the first time (it is "onair"). At this time the neighbor is not | |
108 | * connected or associated with a WSS so we only populate the Ethernet and | |
109 | * Device address fields. | |
110 | * | |
111 | */ | |
112 | int wlp_eda_create_node(struct wlp_eda *eda, | |
113 | const unsigned char eth_addr[ETH_ALEN], | |
114 | const struct uwb_dev_addr *dev_addr) | |
115 | { | |
116 | int result = 0; | |
117 | struct wlp_eda_node *itr; | |
118 | unsigned long flags; | |
119 | ||
120 | BUG_ON(dev_addr == NULL || eth_addr == NULL); | |
121 | spin_lock_irqsave(&eda->lock, flags); | |
122 | list_for_each_entry(itr, &eda->cache, list_node) { | |
123 | if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { | |
124 | printk(KERN_ERR "EDA cache already contains entry " | |
125 | "for neighbor %02x:%02x\n", | |
126 | dev_addr->data[1], dev_addr->data[0]); | |
127 | result = -EEXIST; | |
128 | goto out_unlock; | |
129 | } | |
130 | } | |
131 | itr = kzalloc(sizeof(*itr), GFP_ATOMIC); | |
132 | if (itr != NULL) { | |
133 | memcpy(itr->eth_addr, eth_addr, sizeof(itr->eth_addr)); | |
134 | itr->dev_addr = *dev_addr; | |
135 | list_add(&itr->list_node, &eda->cache); | |
136 | } else | |
137 | result = -ENOMEM; | |
138 | out_unlock: | |
139 | spin_unlock_irqrestore(&eda->lock, flags); | |
140 | return result; | |
141 | } | |
142 | ||
143 | /* | |
144 | * Remove entry from EDA cache | |
145 | * | |
146 | * This is done when the device goes off air. | |
147 | */ | |
148 | void wlp_eda_rm_node(struct wlp_eda *eda, const struct uwb_dev_addr *dev_addr) | |
149 | { | |
150 | struct wlp_eda_node *itr, *next; | |
151 | unsigned long flags; | |
152 | ||
153 | spin_lock_irqsave(&eda->lock, flags); | |
154 | list_for_each_entry_safe(itr, next, &eda->cache, list_node) { | |
155 | if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { | |
156 | list_del(&itr->list_node); | |
157 | kfree(itr); | |
158 | break; | |
159 | } | |
160 | } | |
161 | spin_unlock_irqrestore(&eda->lock, flags); | |
162 | } | |
163 | ||
164 | /* | |
165 | * Update an address mapping | |
166 | * | |
167 | * @returns 0 if ok, < 0 errno code on error | |
168 | */ | |
169 | int wlp_eda_update_node(struct wlp_eda *eda, | |
170 | const struct uwb_dev_addr *dev_addr, | |
171 | struct wlp_wss *wss, | |
172 | const unsigned char virt_addr[ETH_ALEN], | |
173 | const u8 tag, const enum wlp_wss_connect state) | |
174 | { | |
175 | int result = -ENOENT; | |
176 | struct wlp_eda_node *itr; | |
177 | unsigned long flags; | |
178 | ||
179 | spin_lock_irqsave(&eda->lock, flags); | |
180 | list_for_each_entry(itr, &eda->cache, list_node) { | |
181 | if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { | |
182 | /* Found it, update it */ | |
183 | itr->wss = wss; | |
184 | memcpy(itr->virt_addr, virt_addr, | |
185 | sizeof(itr->virt_addr)); | |
186 | itr->tag = tag; | |
187 | itr->state = state; | |
188 | result = 0; | |
189 | goto out_unlock; | |
190 | } | |
191 | } | |
192 | /* Not found */ | |
193 | out_unlock: | |
194 | spin_unlock_irqrestore(&eda->lock, flags); | |
195 | return result; | |
196 | } | |
197 | ||
198 | /* | |
199 | * Update only state field of an address mapping | |
200 | * | |
201 | * @returns 0 if ok, < 0 errno code on error | |
202 | */ | |
203 | int wlp_eda_update_node_state(struct wlp_eda *eda, | |
204 | const struct uwb_dev_addr *dev_addr, | |
205 | const enum wlp_wss_connect state) | |
206 | { | |
207 | int result = -ENOENT; | |
208 | struct wlp_eda_node *itr; | |
209 | unsigned long flags; | |
210 | ||
211 | spin_lock_irqsave(&eda->lock, flags); | |
212 | list_for_each_entry(itr, &eda->cache, list_node) { | |
213 | if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { | |
214 | /* Found it, update it */ | |
215 | itr->state = state; | |
216 | result = 0; | |
217 | goto out_unlock; | |
218 | } | |
219 | } | |
220 | /* Not found */ | |
221 | out_unlock: | |
222 | spin_unlock_irqrestore(&eda->lock, flags); | |
223 | return result; | |
224 | } | |
225 | ||
226 | /* | |
227 | * Return contents of EDA cache entry | |
228 | * | |
229 | * @dev_addr: index to EDA cache | |
230 | * @eda_entry: pointer to where contents of EDA cache will be copied | |
231 | */ | |
232 | int wlp_copy_eda_node(struct wlp_eda *eda, struct uwb_dev_addr *dev_addr, | |
233 | struct wlp_eda_node *eda_entry) | |
234 | { | |
235 | int result = -ENOENT; | |
236 | struct wlp_eda_node *itr; | |
237 | unsigned long flags; | |
238 | ||
239 | spin_lock_irqsave(&eda->lock, flags); | |
240 | list_for_each_entry(itr, &eda->cache, list_node) { | |
241 | if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { | |
242 | *eda_entry = *itr; | |
243 | result = 0; | |
244 | goto out_unlock; | |
245 | } | |
246 | } | |
247 | /* Not found */ | |
248 | out_unlock: | |
249 | spin_unlock_irqrestore(&eda->lock, flags); | |
250 | return result; | |
251 | } | |
252 | ||
253 | /* | |
254 | * Execute function for every element in the cache | |
255 | * | |
256 | * @function: function to execute on element of cache (must be atomic) | |
257 | * @priv: private data of function | |
258 | * @returns: result of first function that failed, or last function | |
259 | * executed if no function failed. | |
260 | * | |
261 | * Stop executing when function returns error for any element in cache. | |
262 | * | |
263 | * IMPORTANT: We are using a spinlock here: the function executed on each | |
264 | * element has to be atomic. | |
265 | */ | |
266 | int wlp_eda_for_each(struct wlp_eda *eda, wlp_eda_for_each_f function, | |
267 | void *priv) | |
268 | { | |
269 | int result = 0; | |
270 | struct wlp *wlp = container_of(eda, struct wlp, eda); | |
271 | struct wlp_eda_node *entry; | |
272 | unsigned long flags; | |
273 | ||
274 | spin_lock_irqsave(&eda->lock, flags); | |
275 | list_for_each_entry(entry, &eda->cache, list_node) { | |
276 | result = (*function)(wlp, entry, priv); | |
277 | if (result < 0) | |
278 | break; | |
279 | } | |
280 | spin_unlock_irqrestore(&eda->lock, flags); | |
281 | return result; | |
282 | } | |
283 | ||
284 | /* | |
285 | * Execute function for single element in the cache (return dev addr) | |
286 | * | |
287 | * @virt_addr: index into EDA cache used to determine which element to | |
288 | * execute the function on | |
289 | * @dev_addr: device address of element in cache will be returned using | |
290 | * @dev_addr | |
291 | * @function: function to execute on element of cache (must be atomic) | |
292 | * @priv: private data of function | |
293 | * @returns: result of function | |
294 | * | |
295 | * IMPORTANT: We are using a spinlock here: the function executed on the | |
296 | * element has to be atomic. | |
297 | */ | |
298 | int wlp_eda_for_virtual(struct wlp_eda *eda, | |
299 | const unsigned char virt_addr[ETH_ALEN], | |
300 | struct uwb_dev_addr *dev_addr, | |
301 | wlp_eda_for_each_f function, | |
302 | void *priv) | |
303 | { | |
304 | int result = 0; | |
305 | struct wlp *wlp = container_of(eda, struct wlp, eda); | |
f5144854 RC |
306 | struct wlp_eda_node *itr; |
307 | unsigned long flags; | |
308 | int found = 0; | |
309 | ||
310 | spin_lock_irqsave(&eda->lock, flags); | |
311 | list_for_each_entry(itr, &eda->cache, list_node) { | |
312 | if (!memcmp(itr->virt_addr, virt_addr, | |
313 | sizeof(itr->virt_addr))) { | |
f5144854 RC |
314 | result = (*function)(wlp, itr, priv); |
315 | *dev_addr = itr->dev_addr; | |
316 | found = 1; | |
317 | break; | |
bce83697 | 318 | } |
f5144854 | 319 | } |
bce83697 | 320 | if (!found) |
f5144854 | 321 | result = -ENODEV; |
f5144854 RC |
322 | spin_unlock_irqrestore(&eda->lock, flags); |
323 | return result; | |
324 | } | |
325 | ||
326 | static const char *__wlp_wss_connect_state[] = { "WLP_WSS_UNCONNECTED", | |
327 | "WLP_WSS_CONNECTED", | |
328 | "WLP_WSS_CONNECT_FAILED", | |
329 | }; | |
330 | ||
331 | static const char *wlp_wss_connect_state_str(unsigned id) | |
332 | { | |
333 | if (id >= ARRAY_SIZE(__wlp_wss_connect_state)) | |
334 | return "unknown WSS connection state"; | |
335 | return __wlp_wss_connect_state[id]; | |
336 | } | |
337 | ||
338 | /* | |
339 | * View EDA cache from user space | |
340 | * | |
341 | * A debugging feature to give user visibility into the EDA cache. Also | |
342 | * used to display members of WSS to user (called from wlp_wss_members_show()) | |
343 | */ | |
344 | ssize_t wlp_eda_show(struct wlp *wlp, char *buf) | |
345 | { | |
346 | ssize_t result = 0; | |
347 | struct wlp_eda_node *entry; | |
348 | unsigned long flags; | |
349 | struct wlp_eda *eda = &wlp->eda; | |
350 | spin_lock_irqsave(&eda->lock, flags); | |
351 | result = scnprintf(buf, PAGE_SIZE, "#eth_addr dev_addr wss_ptr " | |
352 | "tag state virt_addr\n"); | |
353 | list_for_each_entry(entry, &eda->cache, list_node) { | |
354 | result += scnprintf(buf + result, PAGE_SIZE - result, | |
a20fd0a7 HH |
355 | "%pM %02x:%02x %p 0x%02x %s %pM\n", |
356 | entry->eth_addr, | |
f5144854 RC |
357 | entry->dev_addr.data[1], |
358 | entry->dev_addr.data[0], entry->wss, | |
359 | entry->tag, | |
360 | wlp_wss_connect_state_str(entry->state), | |
a20fd0a7 | 361 | entry->virt_addr); |
f5144854 RC |
362 | if (result >= PAGE_SIZE) |
363 | break; | |
364 | } | |
365 | spin_unlock_irqrestore(&eda->lock, flags); | |
366 | return result; | |
367 | } | |
368 | EXPORT_SYMBOL_GPL(wlp_eda_show); | |
369 | ||
370 | /* | |
371 | * Add new EDA cache entry based on user input in sysfs | |
372 | * | |
373 | * Should only be used for debugging. | |
374 | * | |
375 | * The WSS is assumed to be the only WSS supported. This needs to be | |
376 | * redesigned when we support more than one WSS. | |
377 | */ | |
378 | ssize_t wlp_eda_store(struct wlp *wlp, const char *buf, size_t size) | |
379 | { | |
380 | ssize_t result; | |
381 | struct wlp_eda *eda = &wlp->eda; | |
382 | u8 eth_addr[6]; | |
383 | struct uwb_dev_addr dev_addr; | |
384 | u8 tag; | |
385 | unsigned state; | |
386 | ||
387 | result = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx " | |
388 | "%02hhx:%02hhx %02hhx %u\n", | |
389 | ð_addr[0], ð_addr[1], | |
390 | ð_addr[2], ð_addr[3], | |
391 | ð_addr[4], ð_addr[5], | |
392 | &dev_addr.data[1], &dev_addr.data[0], &tag, &state); | |
393 | switch (result) { | |
394 | case 6: /* no dev addr specified -- remove entry NOT IMPLEMENTED */ | |
395 | /*result = wlp_eda_rm(eda, eth_addr, &dev_addr);*/ | |
396 | result = -ENOSYS; | |
397 | break; | |
398 | case 10: | |
399 | state = state >= 1 ? 1 : 0; | |
400 | result = wlp_eda_create_node(eda, eth_addr, &dev_addr); | |
401 | if (result < 0 && result != -EEXIST) | |
402 | goto error; | |
403 | /* Set virtual addr to be same as MAC */ | |
404 | result = wlp_eda_update_node(eda, &dev_addr, &wlp->wss, | |
405 | eth_addr, tag, state); | |
406 | if (result < 0) | |
407 | goto error; | |
408 | break; | |
409 | default: /* bad format */ | |
410 | result = -EINVAL; | |
411 | } | |
412 | error: | |
413 | return result < 0 ? result : size; | |
414 | } | |
415 | EXPORT_SYMBOL_GPL(wlp_eda_store); |