Commit | Line | Data |
---|---|---|
635d2b00 GKH |
1 | /* |
2 | * --------------------------------------------------------------------------- | |
3 | * FILE: io.c | |
4 | * | |
5 | * PURPOSE: | |
6 | * This file contains routines that the SDIO driver can call when a | |
7 | * UniFi card is first inserted (or detected) and removed. | |
8 | * | |
9 | * When used with sdioemb, the udev scripts (at least on Ubuntu) don't | |
10 | * recognise a UniFi being added to the system. This is because sdioemb | |
11 | * does not register itself as a device_driver, it uses it's own code | |
12 | * to handle insert and remove. | |
13 | * To have Ubuntu recognise UniFi, edit /etc/udev/rules.d/85-ifupdown.rules | |
14 | * to change this line: | |
15 | * SUBSYSTEM=="net", DRIVERS=="?*", GOTO="net_start" | |
16 | * to these: | |
17 | * #SUBSYSTEM=="net", DRIVERS=="?*", GOTO="net_start" | |
18 | * SUBSYSTEM=="net", GOTO="net_start" | |
19 | * | |
20 | * Then you can add a stanza to /etc/network/interfaces like this: | |
21 | * auto eth1 | |
22 | * iface eth1 inet dhcp | |
23 | * wpa-conf /etc/wpa_supplicant.conf | |
24 | * This will then automatically associate when a car dis inserted. | |
25 | * | |
26 | * Copyright (C) 2006-2009 by Cambridge Silicon Radio Ltd. | |
27 | * | |
28 | * Refer to LICENSE.txt included with this source code for details on | |
29 | * the license terms. | |
30 | * | |
31 | * --------------------------------------------------------------------------- | |
32 | */ | |
33 | #include <linux/proc_fs.h> | |
34 | ||
35 | #include "csr_wifi_hip_unifi.h" | |
36 | #include "csr_wifi_hip_unifiversion.h" | |
37 | #include "csr_wifi_hip_unifi_udi.h" /* for unifi_print_status() */ | |
38 | #include "unifiio.h" | |
39 | #include "unifi_priv.h" | |
40 | ||
635d2b00 GKH |
41 | /* |
42 | * Array of pointers to context structs for unifi devices that are present. | |
43 | * The index in the array corresponds to the wlan interface number | |
44 | * (if "wlan*" is used). If "eth*" is used, the eth* numbers are allocated | |
45 | * after any Ethernet cards. | |
46 | * | |
47 | * The Arasan PCI-SDIO controller card supported by this driver has 2 slots, | |
48 | * hence a max of 2 devices. | |
49 | */ | |
50 | static unifi_priv_t *Unifi_instances[MAX_UNIFI_DEVS]; | |
51 | ||
52 | /* Array of pointers to netdev objects used by the UniFi driver, as there | |
53 | * are now many per instance. This is used to determine which netdev events | |
54 | * are for UniFi as opposed to other net interfaces. | |
55 | */ | |
56 | static netInterface_priv_t *Unifi_netdev_instances[MAX_UNIFI_DEVS * CSR_WIFI_NUM_INTERFACES]; | |
57 | ||
58 | /* | |
59 | * Array to hold the status of each unifi device in each slot. | |
60 | * We only process an insert event when In_use[] for the slot is | |
61 | * UNIFI_DEV_NOT_IN_USE. Otherwise, it means that the slot is in use or | |
62 | * we are in the middle of a cleanup (the action on unplug). | |
63 | */ | |
64 | #define UNIFI_DEV_NOT_IN_USE 0 | |
65 | #define UNIFI_DEV_IN_USE 1 | |
66 | #define UNIFI_DEV_CLEANUP 2 | |
67 | static int In_use[MAX_UNIFI_DEVS]; | |
68 | /* | |
69 | * Mutex to prevent UDI clients to open the character device before the priv | |
70 | * is created and initialised. | |
71 | */ | |
635d2b00 | 72 | DEFINE_SEMAPHORE(Unifi_instance_mutex); |
635d2b00 GKH |
73 | /* |
74 | * When the device is removed, unregister waits on Unifi_cleanup_wq | |
75 | * until all the UDI clients release the character device. | |
76 | */ | |
77 | DECLARE_WAIT_QUEUE_HEAD(Unifi_cleanup_wq); | |
78 | ||
79 | ||
80 | static int uf_read_proc(char *page, char **start, off_t offset, int count, | |
81 | int *eof, void *data); | |
82 | ||
83 | #ifdef CSR_WIFI_RX_PATH_SPLIT | |
84 | ||
85 | static CsrResult signal_buffer_init(unifi_priv_t * priv, int size) | |
86 | { | |
87 | int i; | |
635d2b00 GKH |
88 | |
89 | priv->rxSignalBuffer.writePointer = | |
90 | priv->rxSignalBuffer.readPointer = 0; | |
91 | priv->rxSignalBuffer.size = size; | |
92 | /* Allocating Memory for Signal primitive pointer */ | |
93 | for(i=0; i<size; i++) | |
94 | { | |
95 | priv->rxSignalBuffer.rx_buff[i].sig_len=0; | |
70128792 | 96 | priv->rxSignalBuffer.rx_buff[i].bufptr = kmalloc(UNIFI_PACKED_SIGBUF_SIZE, GFP_KERNEL); |
635d2b00 GKH |
97 | if (priv->rxSignalBuffer.rx_buff[i].bufptr == NULL) |
98 | { | |
99 | int j; | |
100 | unifi_error(priv,"signal_buffer_init:Failed to Allocate shared memory for T-H signals \n"); | |
101 | for(j=0;j<i;j++) | |
102 | { | |
103 | priv->rxSignalBuffer.rx_buff[j].sig_len=0; | |
4fe9db37 | 104 | kfree(priv->rxSignalBuffer.rx_buff[j].bufptr); |
635d2b00 GKH |
105 | priv->rxSignalBuffer.rx_buff[j].bufptr = NULL; |
106 | } | |
635d2b00 GKH |
107 | return -1; |
108 | } | |
109 | } | |
635d2b00 GKH |
110 | return 0; |
111 | } | |
112 | ||
113 | ||
114 | static void signal_buffer_free(unifi_priv_t * priv, int size) | |
115 | { | |
116 | int i; | |
117 | ||
118 | for(i=0; i<size; i++) | |
119 | { | |
120 | priv->rxSignalBuffer.rx_buff[i].sig_len=0; | |
4fe9db37 | 121 | kfree(priv->rxSignalBuffer.rx_buff[i].bufptr); |
635d2b00 GKH |
122 | priv->rxSignalBuffer.rx_buff[i].bufptr = NULL; |
123 | } | |
124 | } | |
125 | #endif | |
126 | /* | |
127 | * --------------------------------------------------------------------------- | |
128 | * uf_register_netdev | |
129 | * | |
130 | * Registers the network interface, installes the qdisc, | |
131 | * and registers the inet handler. | |
132 | * In the porting exercise, register the driver to the network | |
133 | * stack if necessary. | |
134 | * | |
135 | * Arguments: | |
136 | * priv Pointer to driver context. | |
137 | * | |
138 | * Returns: | |
139 | * O on success, non-zero otherwise. | |
140 | * | |
141 | * Notes: | |
142 | * We will only unregister when the card is ejected, so we must | |
143 | * only do it once. | |
144 | * --------------------------------------------------------------------------- | |
145 | */ | |
146 | int | |
147 | uf_register_netdev(unifi_priv_t *priv, int interfaceTag) | |
148 | { | |
149 | int r; | |
150 | netInterface_priv_t *interfacePriv = priv->interfacePriv[interfaceTag]; | |
151 | ||
152 | if (interfaceTag >= CSR_WIFI_NUM_INTERFACES) { | |
153 | unifi_error(priv, "uf_register_netdev bad interfaceTag\n"); | |
154 | return -EINVAL; | |
155 | } | |
156 | ||
157 | /* | |
158 | * Allocates a device number and registers device with the network | |
159 | * stack. | |
160 | */ | |
161 | unifi_trace(priv, UDBG5, "uf_register_netdev: netdev %d - 0x%p\n", | |
162 | interfaceTag, priv->netdev[interfaceTag]); | |
163 | r = register_netdev(priv->netdev[interfaceTag]); | |
164 | if (r) { | |
165 | unifi_error(priv, "Failed to register net device\n"); | |
166 | return -EINVAL; | |
167 | } | |
168 | ||
169 | /* The device is registed */ | |
170 | interfacePriv->netdev_registered = 1; | |
171 | ||
635d2b00 GKH |
172 | #ifdef CSR_SUPPORT_SME |
173 | /* | |
174 | * Register the inet handler; it notifies us for changes in the IP address. | |
175 | */ | |
176 | uf_register_inet_notifier(); | |
177 | #endif /* CSR_SUPPORT_SME */ | |
178 | ||
179 | unifi_notice(priv, "unifi%d is %s\n", | |
180 | priv->instance, priv->netdev[interfaceTag]->name); | |
181 | ||
182 | return 0; | |
183 | } /* uf_register_netdev */ | |
184 | ||
185 | ||
186 | /* | |
187 | * --------------------------------------------------------------------------- | |
188 | * uf_unregister_netdev | |
189 | * | |
190 | * Unregisters the network interface and the inet handler. | |
191 | * | |
192 | * Arguments: | |
193 | * priv Pointer to driver context. | |
194 | * | |
195 | * Returns: | |
196 | * None. | |
197 | * | |
198 | * --------------------------------------------------------------------------- | |
199 | */ | |
200 | void | |
201 | uf_unregister_netdev(unifi_priv_t *priv) | |
202 | { | |
203 | int i=0; | |
204 | ||
205 | #ifdef CSR_SUPPORT_SME | |
206 | /* Unregister the inet handler... */ | |
207 | uf_unregister_inet_notifier(); | |
208 | #endif /* CSR_SUPPORT_SME */ | |
209 | ||
210 | for (i=0; i<CSR_WIFI_NUM_INTERFACES; i++) { | |
211 | netInterface_priv_t *interfacePriv = priv->interfacePriv[i]; | |
212 | if (interfacePriv->netdev_registered) { | |
213 | unifi_trace(priv, UDBG5, | |
214 | "uf_unregister_netdev: netdev %d - 0x%p\n", | |
215 | i, priv->netdev[i]); | |
216 | ||
217 | /* ... and the netdev */ | |
218 | unregister_netdev(priv->netdev[i]); | |
219 | interfacePriv->netdev_registered = 0; | |
220 | } | |
221 | ||
222 | interfacePriv->interfaceMode = 0; | |
223 | ||
224 | /* Enable all queues by default */ | |
225 | interfacePriv->queueEnabled[0] = 1; | |
226 | interfacePriv->queueEnabled[1] = 1; | |
227 | interfacePriv->queueEnabled[2] = 1; | |
228 | interfacePriv->queueEnabled[3] = 1; | |
229 | } | |
230 | ||
231 | priv->totalInterfaceCount = 0; | |
232 | } /* uf_unregister_netdev() */ | |
233 | ||
234 | ||
235 | /* | |
236 | * --------------------------------------------------------------------------- | |
237 | * register_unifi_sdio | |
238 | * | |
239 | * This function is called from the Probe (or equivalent) method of | |
240 | * the SDIO driver when a UniFi card is detected. | |
241 | * We allocate the Linux net_device struct, initialise the HIP core | |
242 | * lib, create the char device nodes and start the userspace helper | |
243 | * to initialise the device. | |
244 | * | |
245 | * Arguments: | |
246 | * sdio_dev Pointer to SDIO context handle to use for all | |
247 | * SDIO ops. | |
248 | * bus_id A small number indicating the SDIO card position on the | |
249 | * bus. Typically this is the slot number, e.g. 0, 1 etc. | |
250 | * Valid values are 0 to MAX_UNIFI_DEVS-1. | |
251 | * dev Pointer to kernel device manager struct. | |
252 | * | |
253 | * Returns: | |
254 | * Pointer to the unifi instance, or NULL on error. | |
255 | * --------------------------------------------------------------------------- | |
256 | */ | |
257 | static unifi_priv_t * | |
258 | register_unifi_sdio(CsrSdioFunction *sdio_dev, int bus_id, struct device *dev) | |
259 | { | |
260 | unifi_priv_t *priv = NULL; | |
261 | int r = -1; | |
262 | CsrResult csrResult; | |
263 | ||
635d2b00 GKH |
264 | if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { |
265 | unifi_error(priv, "register_unifi_sdio: invalid device %d\n", | |
266 | bus_id); | |
267 | return NULL; | |
268 | } | |
269 | ||
270 | down(&Unifi_instance_mutex); | |
271 | ||
272 | if (In_use[bus_id] != UNIFI_DEV_NOT_IN_USE) { | |
273 | unifi_error(priv, "register_unifi_sdio: device %d is already in use\n", | |
274 | bus_id); | |
275 | goto failed0; | |
276 | } | |
277 | ||
278 | ||
279 | /* Allocate device private and net_device structs */ | |
280 | priv = uf_alloc_netdevice(sdio_dev, bus_id); | |
281 | if (priv == NULL) { | |
282 | unifi_error(priv, "Failed to allocate driver private\n"); | |
283 | goto failed0; | |
284 | } | |
285 | ||
286 | priv->unifi_device = dev; | |
287 | ||
288 | SET_NETDEV_DEV(priv->netdev[0], dev); | |
289 | ||
290 | /* We are not ready to send data yet. */ | |
291 | netif_carrier_off(priv->netdev[0]); | |
292 | ||
293 | /* Allocate driver context. */ | |
294 | priv->card = unifi_alloc_card(priv->sdio, priv); | |
295 | if (priv->card == NULL) { | |
296 | unifi_error(priv, "Failed to allocate UniFi driver card struct.\n"); | |
297 | goto failed1; | |
298 | } | |
299 | ||
300 | if (Unifi_instances[bus_id]) { | |
301 | unifi_error(priv, "Internal error: instance for slot %d is already taken\n", | |
302 | bus_id); | |
303 | } | |
304 | Unifi_instances[bus_id] = priv; | |
305 | In_use[bus_id] = UNIFI_DEV_IN_USE; | |
306 | ||
307 | /* Save the netdev_priv for use by the netdev event callback mechanism */ | |
308 | Unifi_netdev_instances[bus_id * CSR_WIFI_NUM_INTERFACES] = netdev_priv(priv->netdev[0]); | |
309 | ||
310 | /* Initialise the mini-coredump capture buffers */ | |
8c87f69a | 311 | csrResult = unifi_coredump_init(priv->card, (u16)coredump_max); |
635d2b00 GKH |
312 | if (csrResult != CSR_RESULT_SUCCESS) { |
313 | unifi_error(priv, "Couldn't allocate mini-coredump buffers\n"); | |
314 | } | |
315 | ||
316 | /* Create the character device nodes */ | |
317 | r = uf_create_device_nodes(priv, bus_id); | |
318 | if (r) { | |
319 | goto failed1; | |
320 | } | |
321 | ||
322 | /* | |
323 | * We use the slot number as unifi device index. | |
324 | */ | |
c4f9e644 | 325 | scnprintf(priv->proc_entry_name, 64, "driver/unifi%d", priv->instance); |
635d2b00 GKH |
326 | /* |
327 | * The following complex casting is in place in order to eliminate 64-bit compilation warning | |
328 | * "cast to/from pointer from/to integer of different size" | |
329 | */ | |
330 | if (!create_proc_read_entry(priv->proc_entry_name, 0, 0, | |
331 | uf_read_proc, (void *)(long)priv->instance)) | |
332 | { | |
333 | unifi_error(priv, "unifi: can't create /proc/driver/unifi\n"); | |
334 | } | |
335 | ||
336 | /* Allocate the net_device for interfaces other than 0. */ | |
337 | { | |
338 | int i; | |
339 | priv->totalInterfaceCount =0; | |
340 | ||
341 | for(i=1;i<CSR_WIFI_NUM_INTERFACES;i++) | |
342 | { | |
343 | if( !uf_alloc_netdevice_for_other_interfaces(priv,i) ) | |
344 | { | |
345 | /* error occured while allocating the net_device for interface[i]. The net_device are | |
346 | * allocated for the interfaces with id<i. Dont worry, all the allocated net_device will | |
347 | * be releasing chen the control goes to the label failed0. | |
348 | */ | |
349 | unifi_error(priv, "Failed to allocate driver private for interface[%d]\n",i); | |
350 | goto failed0; | |
351 | } | |
352 | else | |
353 | { | |
354 | SET_NETDEV_DEV(priv->netdev[i], dev); | |
355 | ||
356 | /* We are not ready to send data yet. */ | |
357 | netif_carrier_off(priv->netdev[i]); | |
358 | ||
359 | /* Save the netdev_priv for use by the netdev event callback mechanism */ | |
360 | Unifi_netdev_instances[bus_id * CSR_WIFI_NUM_INTERFACES + i] = netdev_priv(priv->netdev[i]); | |
361 | } | |
362 | } | |
363 | ||
364 | for(i=0;i<CSR_WIFI_NUM_INTERFACES;i++) | |
365 | { | |
366 | netInterface_priv_t *interfacePriv = priv->interfacePriv[i]; | |
367 | interfacePriv->netdev_registered=0; | |
368 | } | |
369 | } | |
370 | ||
371 | #ifdef CSR_WIFI_RX_PATH_SPLIT | |
372 | if (signal_buffer_init(priv, CSR_WIFI_RX_SIGNAL_BUFFER_SIZE)) | |
373 | { | |
374 | unifi_error(priv,"Failed to allocate shared memory for T-H signals\n"); | |
375 | goto failed2; | |
376 | } | |
377 | priv->rx_workqueue = create_singlethread_workqueue("rx_workq"); | |
378 | if (priv->rx_workqueue == NULL) { | |
379 | unifi_error(priv,"create_singlethread_workqueue failed \n"); | |
380 | goto failed3; | |
381 | } | |
382 | INIT_WORK(&priv->rx_work_struct, rx_wq_handler); | |
383 | #endif | |
384 | ||
95edd09e GKH |
385 | #ifdef CSR_WIFI_HIP_DEBUG_OFFLINE |
386 | if (log_hip_signals) | |
387 | { | |
388 | uf_register_hip_offline_debug(priv); | |
389 | } | |
390 | #endif | |
391 | ||
635d2b00 GKH |
392 | /* Initialise the SME related threads and parameters */ |
393 | r = uf_sme_init(priv); | |
394 | if (r) { | |
395 | unifi_error(priv, "SME initialisation failed.\n"); | |
396 | goto failed4; | |
397 | } | |
398 | ||
399 | /* | |
400 | * Run the userspace helper program (unififw) to perform | |
401 | * the device initialisation. | |
402 | */ | |
403 | unifi_trace(priv, UDBG1, "run UniFi helper app...\n"); | |
404 | r = uf_run_unifihelper(priv); | |
405 | if (r) { | |
406 | unifi_notice(priv, "unable to run UniFi helper app\n"); | |
407 | /* Not a fatal error. */ | |
408 | } | |
409 | ||
410 | up(&Unifi_instance_mutex); | |
411 | ||
635d2b00 GKH |
412 | return priv; |
413 | ||
414 | failed4: | |
95edd09e GKH |
415 | #ifdef CSR_WIFI_HIP_DEBUG_OFFLINE |
416 | if (log_hip_signals) | |
417 | { | |
418 | uf_unregister_hip_offline_debug(priv); | |
419 | } | |
420 | #endif | |
635d2b00 GKH |
421 | #ifdef CSR_WIFI_RX_PATH_SPLIT |
422 | flush_workqueue(priv->rx_workqueue); | |
423 | destroy_workqueue(priv->rx_workqueue); | |
424 | failed3: | |
425 | signal_buffer_free(priv,CSR_WIFI_RX_SIGNAL_BUFFER_SIZE); | |
426 | failed2: | |
427 | #endif | |
428 | /* Remove the device nodes */ | |
429 | uf_destroy_device_nodes(priv); | |
430 | failed1: | |
431 | /* Deregister priv->netdev_client */ | |
432 | ul_deregister_client(priv->netdev_client); | |
433 | ||
434 | failed0: | |
435 | if (priv && priv->card) { | |
436 | unifi_coredump_free(priv->card); | |
437 | unifi_free_card(priv->card); | |
438 | } | |
439 | if (priv) { | |
440 | uf_free_netdevice(priv); | |
441 | } | |
442 | ||
443 | up(&Unifi_instance_mutex); | |
444 | ||
635d2b00 GKH |
445 | return NULL; |
446 | } /* register_unifi_sdio() */ | |
447 | ||
448 | ||
449 | /* | |
450 | * --------------------------------------------------------------------------- | |
451 | * ask_unifi_sdio_cleanup | |
452 | * | |
453 | * We can not free our private context, until all the char device | |
454 | * clients have closed the file handles. unregister_unifi_sdio() which | |
455 | * is called when a card is removed, waits on Unifi_cleanup_wq until | |
456 | * the reference count becomes zero. It is time to wake it up now. | |
457 | * | |
458 | * Arguments: | |
459 | * priv Pointer to driver context. | |
460 | * | |
461 | * Returns: | |
462 | * None. | |
463 | * --------------------------------------------------------------------------- | |
464 | */ | |
465 | static void | |
466 | ask_unifi_sdio_cleanup(unifi_priv_t *priv) | |
467 | { | |
635d2b00 GKH |
468 | |
469 | /* | |
470 | * Now clear the flag that says the old instance is in use. | |
471 | * This is used to prevent a new instance being started before old | |
472 | * one has finshed closing down, for example if bounce makes the card | |
473 | * appear to be ejected and re-inserted quickly. | |
474 | */ | |
475 | In_use[priv->instance] = UNIFI_DEV_CLEANUP; | |
476 | ||
477 | unifi_trace(NULL, UDBG5, "ask_unifi_sdio_cleanup: wake up cleanup workqueue.\n"); | |
478 | wake_up(&Unifi_cleanup_wq); | |
479 | ||
635d2b00 GKH |
480 | } /* ask_unifi_sdio_cleanup() */ |
481 | ||
482 | ||
483 | /* | |
484 | * --------------------------------------------------------------------------- | |
485 | * cleanup_unifi_sdio | |
486 | * | |
487 | * Release any resources owned by a unifi instance. | |
488 | * | |
489 | * Arguments: | |
490 | * priv Pointer to the instance to free. | |
491 | * | |
492 | * Returns: | |
493 | * None. | |
494 | * --------------------------------------------------------------------------- | |
495 | */ | |
496 | static void | |
497 | cleanup_unifi_sdio(unifi_priv_t *priv) | |
498 | { | |
499 | int priv_instance; | |
500 | int i; | |
501 | static const CsrWifiMacAddress broadcast_address = {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; | |
502 | ||
635d2b00 GKH |
503 | /* Remove the device nodes */ |
504 | uf_destroy_device_nodes(priv); | |
505 | ||
506 | /* Mark this device as gone away by NULLing the entry in Unifi_instances */ | |
507 | Unifi_instances[priv->instance] = NULL; | |
508 | ||
509 | unifi_trace(priv, UDBG5, "cleanup_unifi_sdio: remove_proc_entry\n"); | |
510 | /* | |
511 | * Free the children of priv before unifi_free_netdevice() frees | |
512 | * the priv struct | |
513 | */ | |
514 | remove_proc_entry(priv->proc_entry_name, 0); | |
515 | ||
516 | ||
517 | /* Unregister netdev as a client. */ | |
518 | if (priv->netdev_client) { | |
519 | unifi_trace(priv, UDBG2, "Netdev client (id:%d s:0x%X) is unregistered\n", | |
520 | priv->netdev_client->client_id, priv->netdev_client->sender_id); | |
521 | ul_deregister_client(priv->netdev_client); | |
522 | } | |
523 | ||
524 | /* Destroy the SME related threads and parameters */ | |
525 | uf_sme_deinit(priv); | |
526 | ||
527 | #ifdef CSR_SME_USERSPACE | |
528 | priv->smepriv = NULL; | |
529 | #endif | |
530 | ||
95edd09e GKH |
531 | #ifdef CSR_WIFI_HIP_DEBUG_OFFLINE |
532 | if (log_hip_signals) | |
533 | { | |
534 | uf_unregister_hip_offline_debug(priv); | |
535 | } | |
536 | #endif | |
537 | ||
635d2b00 GKH |
538 | /* Free any packets left in the Rx queues */ |
539 | for(i=0;i<CSR_WIFI_NUM_INTERFACES;i++) | |
540 | { | |
541 | uf_free_pending_rx_packets(priv, UF_UNCONTROLLED_PORT_Q, broadcast_address,i); | |
542 | uf_free_pending_rx_packets(priv, UF_CONTROLLED_PORT_Q, broadcast_address,i); | |
543 | } | |
544 | /* | |
545 | * We need to free the resources held by the core, which include tx skbs, | |
546 | * otherwise we can not call unregister_netdev(). | |
547 | */ | |
548 | if (priv->card) { | |
549 | unifi_trace(priv, UDBG5, "cleanup_unifi_sdio: free card\n"); | |
550 | unifi_coredump_free(priv->card); | |
551 | unifi_free_card(priv->card); | |
552 | priv->card = NULL; | |
553 | } | |
554 | ||
555 | /* | |
556 | * Unregister the network device. | |
557 | * We can not unregister the netdev before we release | |
558 | * all pending packets in the core. | |
559 | */ | |
560 | uf_unregister_netdev(priv); | |
561 | priv->totalInterfaceCount = 0; | |
562 | ||
563 | /* Clear the table of registered netdev_priv's */ | |
564 | for (i = 0; i < CSR_WIFI_NUM_INTERFACES; i++) { | |
565 | Unifi_netdev_instances[priv->instance * CSR_WIFI_NUM_INTERFACES + i] = NULL; | |
566 | } | |
567 | ||
568 | unifi_trace(priv, UDBG5, "cleanup_unifi_sdio: uf_free_netdevice\n"); | |
569 | /* | |
570 | * When uf_free_netdevice() returns, the priv is invalid | |
571 | * so we need to remember the instance to clear the global flag later. | |
572 | */ | |
573 | priv_instance = priv->instance; | |
574 | ||
575 | #ifdef CSR_WIFI_RX_PATH_SPLIT | |
576 | flush_workqueue(priv->rx_workqueue); | |
577 | destroy_workqueue(priv->rx_workqueue); | |
578 | signal_buffer_free(priv,CSR_WIFI_RX_SIGNAL_BUFFER_SIZE); | |
579 | #endif | |
580 | ||
581 | /* Priv is freed as part of the net_device */ | |
582 | uf_free_netdevice(priv); | |
583 | ||
584 | /* | |
585 | * Now clear the flag that says the old instance is in use. | |
586 | * This is used to prevent a new instance being started before old | |
587 | * one has finshed closing down, for example if bounce makes the card | |
588 | * appear to be ejected and re-inserted quickly. | |
589 | */ | |
590 | In_use[priv_instance] = UNIFI_DEV_NOT_IN_USE; | |
591 | ||
592 | unifi_trace(NULL, UDBG5, "cleanup_unifi_sdio: DONE.\n"); | |
593 | ||
635d2b00 GKH |
594 | } /* cleanup_unifi_sdio() */ |
595 | ||
596 | ||
597 | /* | |
598 | * --------------------------------------------------------------------------- | |
599 | * unregister_unifi_sdio | |
600 | * | |
601 | * Call from SDIO driver when it detects that UniFi has been removed. | |
602 | * | |
603 | * Arguments: | |
604 | * bus_id Number of the card that was ejected. | |
605 | * | |
606 | * Returns: | |
607 | * None. | |
608 | * --------------------------------------------------------------------------- | |
609 | */ | |
610 | static void | |
611 | unregister_unifi_sdio(int bus_id) | |
612 | { | |
613 | unifi_priv_t *priv; | |
614 | int interfaceTag=0; | |
615 | u8 reason = CONFIG_IND_EXIT; | |
616 | ||
617 | if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { | |
618 | unifi_error(NULL, "unregister_unifi_sdio: invalid device %d\n", | |
619 | bus_id); | |
620 | return; | |
621 | } | |
622 | ||
623 | priv = Unifi_instances[bus_id]; | |
624 | if (priv == NULL) { | |
625 | unifi_error(priv, "unregister_unifi_sdio: device %d is not registered\n", | |
626 | bus_id); | |
635d2b00 GKH |
627 | return; |
628 | } | |
629 | ||
630 | /* Stop the network traffic before freeing the core. */ | |
631 | for(interfaceTag=0;interfaceTag<priv->totalInterfaceCount;interfaceTag++) | |
632 | { | |
633 | netInterface_priv_t *interfacePriv = priv->interfacePriv[interfaceTag]; | |
634 | if(interfacePriv->netdev_registered) | |
635 | { | |
636 | netif_carrier_off(priv->netdev[interfaceTag]); | |
4febd649 | 637 | netif_tx_stop_all_queues(priv->netdev[interfaceTag]); |
635d2b00 GKH |
638 | } |
639 | } | |
640 | ||
641 | #ifdef CSR_NATIVE_LINUX | |
642 | /* | |
643 | * If the unifi thread was started, signal it to stop. This | |
644 | * should cause any userspace processes with open unifi device to | |
645 | * close them. | |
646 | */ | |
647 | uf_stop_thread(priv, &priv->bh_thread); | |
648 | ||
649 | /* Unregister the interrupt handler */ | |
650 | if (csr_sdio_linux_remove_irq(priv->sdio)) { | |
651 | unifi_notice(priv, | |
652 | "csr_sdio_linux_remove_irq failed to talk to card.\n"); | |
653 | } | |
654 | ||
655 | /* Ensure no MLME functions are waiting on a the mlme_event semaphore. */ | |
656 | uf_abort_mlme(priv); | |
657 | #endif /* CSR_NATIVE_LINUX */ | |
658 | ||
659 | ul_log_config_ind(priv, &reason, sizeof(u8)); | |
660 | ||
661 | /* Deregister the UDI hook from the core. */ | |
662 | unifi_remove_udi_hook(priv->card, logging_handler); | |
663 | ||
664 | uf_put_instance(bus_id); | |
665 | ||
666 | /* | |
667 | * Wait until the device is cleaned up. i.e., when all userspace | |
668 | * processes have closed any open unifi devices. | |
669 | */ | |
670 | wait_event(Unifi_cleanup_wq, In_use[bus_id] == UNIFI_DEV_CLEANUP); | |
671 | unifi_trace(NULL, UDBG5, "Received clean up event\n"); | |
672 | ||
673 | /* Now we can free the private context and the char device nodes */ | |
674 | cleanup_unifi_sdio(priv); | |
675 | ||
676 | } /* unregister_unifi_sdio() */ | |
677 | ||
678 | ||
679 | /* | |
680 | * --------------------------------------------------------------------------- | |
681 | * uf_find_instance | |
682 | * | |
683 | * Find the context structure for a given UniFi device instance. | |
684 | * | |
685 | * Arguments: | |
686 | * inst The instance number to look for. | |
687 | * | |
688 | * Returns: | |
689 | * None. | |
690 | * --------------------------------------------------------------------------- | |
691 | */ | |
692 | unifi_priv_t * | |
693 | uf_find_instance(int inst) | |
694 | { | |
695 | if ((inst < 0) || (inst >= MAX_UNIFI_DEVS)) { | |
696 | return NULL; | |
697 | } | |
698 | return Unifi_instances[inst]; | |
699 | } /* uf_find_instance() */ | |
700 | ||
701 | ||
702 | /* | |
703 | * --------------------------------------------------------------------------- | |
704 | * uf_find_priv | |
705 | * | |
706 | * Find the device instance for a given context structure. | |
707 | * | |
708 | * Arguments: | |
709 | * priv The context structure pointer to look for. | |
710 | * | |
711 | * Returns: | |
712 | * index of instance, -1 otherwise. | |
713 | * --------------------------------------------------------------------------- | |
714 | */ | |
715 | int | |
716 | uf_find_priv(unifi_priv_t *priv) | |
717 | { | |
718 | int inst; | |
719 | ||
720 | if (!priv) { | |
721 | return -1; | |
722 | } | |
723 | ||
724 | for (inst = 0; inst < MAX_UNIFI_DEVS; inst++) { | |
725 | if (Unifi_instances[inst] == priv) { | |
726 | return inst; | |
727 | } | |
728 | } | |
729 | ||
730 | return -1; | |
731 | } /* uf_find_priv() */ | |
732 | ||
733 | /* | |
734 | * --------------------------------------------------------------------------- | |
735 | * uf_find_netdev_priv | |
736 | * | |
737 | * Find the device instance for a given netdev context structure. | |
738 | * | |
739 | * Arguments: | |
740 | * priv The context structure pointer to look for. | |
741 | * | |
742 | * Returns: | |
743 | * index of instance, -1 otherwise. | |
744 | * --------------------------------------------------------------------------- | |
745 | */ | |
746 | int | |
747 | uf_find_netdev_priv(netInterface_priv_t *priv) | |
748 | { | |
749 | int inst; | |
750 | ||
751 | if (!priv) { | |
752 | return -1; | |
753 | } | |
754 | ||
755 | for (inst = 0; inst < MAX_UNIFI_DEVS * CSR_WIFI_NUM_INTERFACES; inst++) { | |
756 | if (Unifi_netdev_instances[inst] == priv) { | |
757 | return inst; | |
758 | } | |
759 | } | |
760 | ||
761 | return -1; | |
762 | } /* uf_find_netdev_priv() */ | |
763 | ||
764 | /* | |
765 | * --------------------------------------------------------------------------- | |
766 | * uf_get_instance | |
767 | * | |
768 | * Find the context structure for a given UniFi device instance | |
769 | * and increment the reference count. | |
770 | * | |
771 | * Arguments: | |
772 | * inst The instance number to look for. | |
773 | * | |
774 | * Returns: | |
775 | * Pointer to the instance or NULL if no instance exists. | |
776 | * --------------------------------------------------------------------------- | |
777 | */ | |
778 | unifi_priv_t * | |
779 | uf_get_instance(int inst) | |
780 | { | |
781 | unifi_priv_t *priv; | |
782 | ||
783 | down(&Unifi_instance_mutex); | |
784 | ||
785 | priv = uf_find_instance(inst); | |
786 | if (priv) { | |
787 | priv->ref_count++; | |
788 | } | |
789 | ||
790 | up(&Unifi_instance_mutex); | |
791 | ||
792 | return priv; | |
793 | } | |
794 | ||
795 | /* | |
796 | * --------------------------------------------------------------------------- | |
797 | * uf_put_instance | |
798 | * | |
799 | * Decrement the context reference count, freeing resources and | |
800 | * shutting down the driver when the count reaches zero. | |
801 | * | |
802 | * Arguments: | |
803 | * inst The instance number to look for. | |
804 | * | |
805 | * Returns: | |
806 | * Pointer to the instance or NULL if no instance exists. | |
807 | * --------------------------------------------------------------------------- | |
808 | */ | |
809 | void | |
810 | uf_put_instance(int inst) | |
811 | { | |
812 | unifi_priv_t *priv; | |
813 | ||
814 | down(&Unifi_instance_mutex); | |
815 | ||
816 | priv = uf_find_instance(inst); | |
817 | if (priv) { | |
818 | priv->ref_count--; | |
819 | if (priv->ref_count == 0) { | |
820 | ask_unifi_sdio_cleanup(priv); | |
821 | } | |
822 | } | |
823 | ||
824 | up(&Unifi_instance_mutex); | |
825 | } | |
826 | ||
827 | ||
828 | /* | |
829 | * --------------------------------------------------------------------------- | |
830 | * uf_read_proc | |
831 | * | |
832 | * Read method for driver node in /proc/driver/unifi0 | |
833 | * | |
834 | * Arguments: | |
835 | * page | |
836 | * start | |
837 | * offset | |
838 | * count | |
839 | * eof | |
840 | * data | |
841 | * | |
842 | * Returns: | |
843 | * None. | |
844 | * --------------------------------------------------------------------------- | |
845 | */ | |
846 | #ifdef CONFIG_PROC_FS | |
847 | static int | |
848 | uf_read_proc(char *page, char **start, off_t offset, int count, | |
849 | int *eof, void *data) | |
850 | { | |
851 | #define UNIFI_DEBUG_TXT_BUFFER 8*1024 | |
852 | unifi_priv_t *priv; | |
853 | int actual_amount_to_copy; | |
854 | char *p, *orig_p; | |
95e326c2 GKH |
855 | s32 remain = UNIFI_DEBUG_TXT_BUFFER; |
856 | s32 written; | |
635d2b00 GKH |
857 | int i; |
858 | ||
859 | /* | |
860 | * The following complex casting is in place in order to eliminate 64-bit compilation warning | |
861 | * "cast to/from pointer from/to integer of different size" | |
862 | */ | |
863 | priv = uf_find_instance((int)(long)data); | |
864 | if (!priv) { | |
865 | return 0; | |
866 | } | |
867 | ||
868 | p = kmalloc( UNIFI_DEBUG_TXT_BUFFER, GFP_KERNEL ); | |
869 | ||
870 | orig_p = p; | |
871 | ||
c4f9e644 | 872 | written = scnprintf(p, remain, "UniFi SDIO Driver: %s %s %s\n", |
635d2b00 GKH |
873 | CSR_WIFI_VERSION, __DATE__, __TIME__); |
874 | UNIFI_SNPRINTF_RET(p, remain, written); | |
875 | #ifdef CSR_SME_USERSPACE | |
c4f9e644 | 876 | written = scnprintf(p, remain, "SME: CSR userspace "); |
635d2b00 GKH |
877 | UNIFI_SNPRINTF_RET(p, remain, written); |
878 | #ifdef CSR_SUPPORT_WEXT | |
c4f9e644 | 879 | written = scnprintf(p, remain, "with WEXT support\n"); |
635d2b00 | 880 | #else |
c4f9e644 | 881 | written = scnprintf(p, remain, "\n"); |
635d2b00 GKH |
882 | #endif /* CSR_SUPPORT_WEXT */ |
883 | UNIFI_SNPRINTF_RET(p, remain, written); | |
884 | #endif /* CSR_SME_USERSPACE */ | |
885 | #ifdef CSR_NATIVE_LINUX | |
c4f9e644 | 886 | written = scnprintf(p, remain, "SME: native\n"); |
635d2b00 GKH |
887 | UNIFI_SNPRINTF_RET(p, remain, written); |
888 | #endif | |
889 | ||
890 | #ifdef CSR_SUPPORT_SME | |
c4f9e644 DN |
891 | written = scnprintf(p, remain, |
892 | "Firmware (ROM) build:%u, Patch:%u\n", | |
635d2b00 GKH |
893 | priv->card_info.fw_build, |
894 | priv->sme_versions.firmwarePatch); | |
895 | UNIFI_SNPRINTF_RET(p, remain, written); | |
896 | #endif | |
897 | p += unifi_print_status(priv->card, p, &remain); | |
898 | ||
c4f9e644 | 899 | written = scnprintf(p, remain, "Last dbg str: %s\n", |
635d2b00 GKH |
900 | priv->last_debug_string); |
901 | UNIFI_SNPRINTF_RET(p, remain, written); | |
902 | ||
c4f9e644 | 903 | written = scnprintf(p, remain, "Last dbg16:"); |
635d2b00 GKH |
904 | UNIFI_SNPRINTF_RET(p, remain, written); |
905 | for (i = 0; i < 8; i++) { | |
c4f9e644 | 906 | written = scnprintf(p, remain, " %04X", |
635d2b00 GKH |
907 | priv->last_debug_word16[i]); |
908 | UNIFI_SNPRINTF_RET(p, remain, written); | |
909 | } | |
c4f9e644 | 910 | written = scnprintf(p, remain, "\n"); |
635d2b00 | 911 | UNIFI_SNPRINTF_RET(p, remain, written); |
c4f9e644 | 912 | written = scnprintf(p, remain, " "); |
635d2b00 GKH |
913 | UNIFI_SNPRINTF_RET(p, remain, written); |
914 | for (; i < 16; i++) { | |
c4f9e644 | 915 | written = scnprintf(p, remain, " %04X", |
635d2b00 GKH |
916 | priv->last_debug_word16[i]); |
917 | UNIFI_SNPRINTF_RET(p, remain, written); | |
918 | } | |
c4f9e644 | 919 | written = scnprintf(p, remain, "\n"); |
635d2b00 GKH |
920 | UNIFI_SNPRINTF_RET(p, remain, written); |
921 | *start = page; | |
922 | ||
923 | written = UNIFI_DEBUG_TXT_BUFFER - remain; | |
924 | ||
925 | if( offset >= written ) | |
926 | { | |
927 | *eof = 1; | |
928 | kfree( orig_p ); | |
929 | return(0); | |
930 | } | |
931 | ||
932 | if( offset + count > written ) | |
933 | { | |
934 | actual_amount_to_copy = written - offset; | |
935 | *eof = 1; | |
936 | } | |
937 | else | |
938 | { | |
939 | actual_amount_to_copy = count; | |
940 | } | |
941 | ||
942 | memcpy( page, &(orig_p[offset]), actual_amount_to_copy ); | |
943 | ||
944 | kfree( orig_p ); | |
945 | ||
946 | return( actual_amount_to_copy ); | |
947 | } /* uf_read_proc() */ | |
948 | #endif | |
949 | ||
950 | ||
951 | ||
952 | ||
953 | static void | |
954 | uf_lx_suspend(CsrSdioFunction *sdio_ctx) | |
955 | { | |
956 | unifi_priv_t *priv = sdio_ctx->driverData; | |
957 | unifi_suspend(priv); | |
958 | ||
959 | CsrSdioSuspendAcknowledge(sdio_ctx, CSR_RESULT_SUCCESS); | |
960 | } | |
961 | ||
962 | static void | |
963 | uf_lx_resume(CsrSdioFunction *sdio_ctx) | |
964 | { | |
965 | unifi_priv_t *priv = sdio_ctx->driverData; | |
966 | unifi_resume(priv); | |
967 | ||
968 | CsrSdioResumeAcknowledge(sdio_ctx, CSR_RESULT_SUCCESS); | |
969 | } | |
970 | ||
971 | static int active_slot = MAX_UNIFI_DEVS; | |
972 | static struct device *os_devices[MAX_UNIFI_DEVS]; | |
973 | ||
974 | void | |
975 | uf_add_os_device(int bus_id, struct device *os_device) | |
976 | { | |
977 | if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { | |
978 | unifi_error(NULL, "uf_add_os_device: invalid device %d\n", | |
979 | bus_id); | |
980 | return; | |
981 | } | |
982 | ||
983 | active_slot = bus_id; | |
984 | os_devices[bus_id] = os_device; | |
985 | } /* uf_add_os_device() */ | |
986 | ||
987 | void | |
988 | uf_remove_os_device(int bus_id) | |
989 | { | |
990 | if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { | |
991 | unifi_error(NULL, "uf_remove_os_device: invalid device %d\n", | |
992 | bus_id); | |
993 | return; | |
994 | } | |
995 | ||
996 | active_slot = bus_id; | |
997 | os_devices[bus_id] = NULL; | |
998 | } /* uf_remove_os_device() */ | |
999 | ||
1000 | static void | |
1001 | uf_sdio_inserted(CsrSdioFunction *sdio_ctx) | |
1002 | { | |
f145b4f4 | 1003 | unifi_priv_t *priv; |
635d2b00 | 1004 | |
f145b4f4 DN |
1005 | unifi_trace(NULL, UDBG5, "uf_sdio_inserted(0x%p), slot_id=%d, dev=%p\n", |
1006 | sdio_ctx, active_slot, os_devices[active_slot]); | |
635d2b00 | 1007 | |
f145b4f4 DN |
1008 | priv = register_unifi_sdio(sdio_ctx, active_slot, os_devices[active_slot]); |
1009 | if (priv == NULL) { | |
1010 | CsrSdioInsertedAcknowledge(sdio_ctx, CSR_RESULT_FAILURE); | |
1011 | return; | |
1012 | } | |
635d2b00 | 1013 | |
f145b4f4 | 1014 | sdio_ctx->driverData = priv; |
635d2b00 | 1015 | |
f145b4f4 | 1016 | CsrSdioInsertedAcknowledge(sdio_ctx, CSR_RESULT_SUCCESS); |
635d2b00 GKH |
1017 | } /* uf_sdio_inserted() */ |
1018 | ||
1019 | ||
1020 | static void | |
1021 | uf_sdio_removed(CsrSdioFunction *sdio_ctx) | |
1022 | { | |
f145b4f4 DN |
1023 | unregister_unifi_sdio(active_slot); |
1024 | CsrSdioRemovedAcknowledge(sdio_ctx); | |
635d2b00 GKH |
1025 | } /* uf_sdio_removed() */ |
1026 | ||
1027 | ||
1028 | static void | |
1029 | uf_sdio_dsr_handler(CsrSdioFunction *sdio_ctx) | |
1030 | { | |
f145b4f4 | 1031 | unifi_priv_t *priv = sdio_ctx->driverData; |
635d2b00 | 1032 | |
f145b4f4 | 1033 | unifi_sdio_interrupt_handler(priv->card); |
635d2b00 GKH |
1034 | } /* uf_sdio_dsr_handler() */ |
1035 | ||
1036 | /* | |
1037 | * --------------------------------------------------------------------------- | |
1038 | * uf_sdio_int_handler | |
1039 | * | |
1040 | * Interrupt callback function for SDIO interrupts. | |
1041 | * This is called in kernel context (i.e. not interrupt context). | |
1042 | * We retrieve the unifi context pointer and call the main UniFi | |
1043 | * interrupt handler. | |
1044 | * | |
1045 | * Arguments: | |
1046 | * fdev SDIO context pointer | |
1047 | * | |
1048 | * Returns: | |
1049 | * None. | |
1050 | * --------------------------------------------------------------------------- | |
1051 | */ | |
1052 | static CsrSdioInterruptDsrCallback | |
1053 | uf_sdio_int_handler(CsrSdioFunction *sdio_ctx) | |
1054 | { | |
f145b4f4 | 1055 | return uf_sdio_dsr_handler; |
635d2b00 GKH |
1056 | } /* uf_sdio_int_handler() */ |
1057 | ||
1058 | ||
1059 | ||
1060 | ||
1061 | static CsrSdioFunctionId unifi_ids[] = | |
1062 | { | |
f145b4f4 DN |
1063 | { |
1064 | .manfId = SDIO_MANF_ID_CSR, | |
1065 | .cardId = SDIO_CARD_ID_UNIFI_3, | |
1066 | .sdioFunction = SDIO_WLAN_FUNC_ID_UNIFI_3, | |
1067 | .sdioInterface = CSR_SDIO_ANY_SDIO_INTERFACE, | |
1068 | }, | |
1069 | { | |
1070 | .manfId = SDIO_MANF_ID_CSR, | |
1071 | .cardId = SDIO_CARD_ID_UNIFI_4, | |
1072 | .sdioFunction = SDIO_WLAN_FUNC_ID_UNIFI_4, | |
1073 | .sdioInterface = CSR_SDIO_ANY_SDIO_INTERFACE, | |
1074 | } | |
635d2b00 GKH |
1075 | }; |
1076 | ||
1077 | ||
1078 | /* | |
1079 | * Structure to register with the glue layer. | |
1080 | */ | |
1081 | static CsrSdioFunctionDriver unifi_sdioFunction_drv = | |
1082 | { | |
f145b4f4 DN |
1083 | .inserted = uf_sdio_inserted, |
1084 | .removed = uf_sdio_removed, | |
1085 | .intr = uf_sdio_int_handler, | |
1086 | .suspend = uf_lx_suspend, | |
1087 | .resume = uf_lx_resume, | |
1088 | ||
1089 | .ids = unifi_ids, | |
1090 | .idsCount = sizeof(unifi_ids) / sizeof(unifi_ids[0]) | |
635d2b00 GKH |
1091 | }; |
1092 | ||
1093 | ||
1094 | /* | |
1095 | * --------------------------------------------------------------------------- | |
1096 | * uf_sdio_load | |
1097 | * uf_sdio_unload | |
1098 | * | |
1099 | * These functions are called from the main module load and unload | |
1100 | * functions. They perform the appropriate operations for the monolithic | |
1101 | * driver. | |
1102 | * | |
1103 | * Arguments: | |
1104 | * None. | |
1105 | * | |
1106 | * Returns: | |
1107 | * None. | |
1108 | * --------------------------------------------------------------------------- | |
1109 | */ | |
1110 | int __init | |
1111 | uf_sdio_load(void) | |
1112 | { | |
f145b4f4 | 1113 | CsrResult csrResult; |
635d2b00 | 1114 | |
f145b4f4 DN |
1115 | csrResult = CsrSdioFunctionDriverRegister(&unifi_sdioFunction_drv); |
1116 | if (csrResult != CSR_RESULT_SUCCESS) { | |
1117 | unifi_error(NULL, "Failed to register UniFi SDIO driver: csrResult=%d\n", csrResult); | |
1118 | return -EIO; | |
1119 | } | |
635d2b00 | 1120 | |
f145b4f4 | 1121 | return 0; |
635d2b00 GKH |
1122 | } /* uf_sdio_load() */ |
1123 | ||
1124 | ||
1125 | ||
1126 | void __exit | |
1127 | uf_sdio_unload(void) | |
1128 | { | |
f145b4f4 | 1129 | CsrSdioFunctionDriverUnregister(&unifi_sdioFunction_drv); |
635d2b00 GKH |
1130 | } /* uf_sdio_unload() */ |
1131 |