| /* | |
| * --------------------------------------------------------------------------- | |
| * FILE: io.c | |
| * | |
| * PURPOSE: | |
| * This file contains routines that the SDIO driver can call when a | |
| * UniFi card is first inserted (or detected) and removed. | |
| * | |
| * When used with sdioemb, the udev scripts (at least on Ubuntu) don't | |
| * recognise a UniFi being added to the system. This is because sdioemb | |
| * does not register itself as a device_driver, it uses it's own code | |
| * to handle insert and remove. | |
| * To have Ubuntu recognise UniFi, edit /etc/udev/rules.d/85-ifupdown.rules | |
| * to change this line: | |
| * SUBSYSTEM=="net", DRIVERS=="?*", GOTO="net_start" | |
| * to these: | |
| * #SUBSYSTEM=="net", DRIVERS=="?*", GOTO="net_start" | |
| * SUBSYSTEM=="net", GOTO="net_start" | |
| * | |
| * Then you can add a stanza to /etc/network/interfaces like this: | |
| * auto eth1 | |
| * iface eth1 inet dhcp | |
| * wpa-conf /etc/wpa_supplicant.conf | |
| * This will then automatically associate when a car dis inserted. | |
| * | |
| * Copyright (C) 2006-2009 by Cambridge Silicon Radio Ltd. | |
| * | |
| * Refer to LICENSE.txt included with this source code for details on | |
| * the license terms. | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| #include <linux/proc_fs.h> | |
| #include "csr_wifi_hip_unifi.h" | |
| #include "csr_wifi_hip_unifiversion.h" | |
| #include "csr_wifi_hip_unifi_udi.h" /* for unifi_print_status() */ | |
| #include "unifiio.h" | |
| #include "unifi_priv.h" | |
| /* | |
| * Array of pointers to context structs for unifi devices that are present. | |
| * The index in the array corresponds to the wlan interface number | |
| * (if "wlan*" is used). If "eth*" is used, the eth* numbers are allocated | |
| * after any Ethernet cards. | |
| * | |
| * The Arasan PCI-SDIO controller card supported by this driver has 2 slots, | |
| * hence a max of 2 devices. | |
| */ | |
| static unifi_priv_t *Unifi_instances[MAX_UNIFI_DEVS]; | |
| /* Array of pointers to netdev objects used by the UniFi driver, as there | |
| * are now many per instance. This is used to determine which netdev events | |
| * are for UniFi as opposed to other net interfaces. | |
| */ | |
| static netInterface_priv_t *Unifi_netdev_instances[MAX_UNIFI_DEVS * CSR_WIFI_NUM_INTERFACES]; | |
| /* | |
| * Array to hold the status of each unifi device in each slot. | |
| * We only process an insert event when In_use[] for the slot is | |
| * UNIFI_DEV_NOT_IN_USE. Otherwise, it means that the slot is in use or | |
| * we are in the middle of a cleanup (the action on unplug). | |
| */ | |
| #define UNIFI_DEV_NOT_IN_USE 0 | |
| #define UNIFI_DEV_IN_USE 1 | |
| #define UNIFI_DEV_CLEANUP 2 | |
| static int In_use[MAX_UNIFI_DEVS]; | |
| /* | |
| * Mutex to prevent UDI clients to open the character device before the priv | |
| * is created and initialised. | |
| */ | |
| DEFINE_SEMAPHORE(Unifi_instance_mutex); | |
| /* | |
| * When the device is removed, unregister waits on Unifi_cleanup_wq | |
| * until all the UDI clients release the character device. | |
| */ | |
| DECLARE_WAIT_QUEUE_HEAD(Unifi_cleanup_wq); | |
| static int uf_read_proc(char *page, char **start, off_t offset, int count, | |
| int *eof, void *data); | |
| #ifdef CSR_WIFI_RX_PATH_SPLIT | |
| static CsrResult signal_buffer_init(unifi_priv_t * priv, int size) | |
| { | |
| int i; | |
| priv->rxSignalBuffer.writePointer = | |
| priv->rxSignalBuffer.readPointer = 0; | |
| priv->rxSignalBuffer.size = size; | |
| /* Allocating Memory for Signal primitive pointer */ | |
| for(i=0; i<size; i++) | |
| { | |
| priv->rxSignalBuffer.rx_buff[i].sig_len=0; | |
| priv->rxSignalBuffer.rx_buff[i].bufptr = kmalloc(UNIFI_PACKED_SIGBUF_SIZE, GFP_KERNEL); | |
| if (priv->rxSignalBuffer.rx_buff[i].bufptr == NULL) | |
| { | |
| int j; | |
| unifi_error(priv,"signal_buffer_init:Failed to Allocate shared memory for T-H signals \n"); | |
| for(j=0;j<i;j++) | |
| { | |
| priv->rxSignalBuffer.rx_buff[j].sig_len=0; | |
| kfree(priv->rxSignalBuffer.rx_buff[j].bufptr); | |
| priv->rxSignalBuffer.rx_buff[j].bufptr = NULL; | |
| } | |
| return -1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void signal_buffer_free(unifi_priv_t * priv, int size) | |
| { | |
| int i; | |
| for(i=0; i<size; i++) | |
| { | |
| priv->rxSignalBuffer.rx_buff[i].sig_len=0; | |
| kfree(priv->rxSignalBuffer.rx_buff[i].bufptr); | |
| priv->rxSignalBuffer.rx_buff[i].bufptr = NULL; | |
| } | |
| } | |
| #endif | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_register_netdev | |
| * | |
| * Registers the network interface, installes the qdisc, | |
| * and registers the inet handler. | |
| * In the porting exercise, register the driver to the network | |
| * stack if necessary. | |
| * | |
| * Arguments: | |
| * priv Pointer to driver context. | |
| * | |
| * Returns: | |
| * O on success, non-zero otherwise. | |
| * | |
| * Notes: | |
| * We will only unregister when the card is ejected, so we must | |
| * only do it once. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int | |
| uf_register_netdev(unifi_priv_t *priv, int interfaceTag) | |
| { | |
| int r; | |
| netInterface_priv_t *interfacePriv = priv->interfacePriv[interfaceTag]; | |
| if (interfaceTag >= CSR_WIFI_NUM_INTERFACES) { | |
| unifi_error(priv, "uf_register_netdev bad interfaceTag\n"); | |
| return -EINVAL; | |
| } | |
| /* | |
| * Allocates a device number and registers device with the network | |
| * stack. | |
| */ | |
| unifi_trace(priv, UDBG5, "uf_register_netdev: netdev %d - 0x%p\n", | |
| interfaceTag, priv->netdev[interfaceTag]); | |
| r = register_netdev(priv->netdev[interfaceTag]); | |
| if (r) { | |
| unifi_error(priv, "Failed to register net device\n"); | |
| return -EINVAL; | |
| } | |
| /* The device is registed */ | |
| interfacePriv->netdev_registered = 1; | |
| #ifdef CSR_SUPPORT_SME | |
| /* | |
| * Register the inet handler; it notifies us for changes in the IP address. | |
| */ | |
| uf_register_inet_notifier(); | |
| #endif /* CSR_SUPPORT_SME */ | |
| unifi_notice(priv, "unifi%d is %s\n", | |
| priv->instance, priv->netdev[interfaceTag]->name); | |
| return 0; | |
| } /* uf_register_netdev */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_unregister_netdev | |
| * | |
| * Unregisters the network interface and the inet handler. | |
| * | |
| * Arguments: | |
| * priv Pointer to driver context. | |
| * | |
| * Returns: | |
| * None. | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| void | |
| uf_unregister_netdev(unifi_priv_t *priv) | |
| { | |
| int i=0; | |
| #ifdef CSR_SUPPORT_SME | |
| /* Unregister the inet handler... */ | |
| uf_unregister_inet_notifier(); | |
| #endif /* CSR_SUPPORT_SME */ | |
| for (i=0; i<CSR_WIFI_NUM_INTERFACES; i++) { | |
| netInterface_priv_t *interfacePriv = priv->interfacePriv[i]; | |
| if (interfacePriv->netdev_registered) { | |
| unifi_trace(priv, UDBG5, | |
| "uf_unregister_netdev: netdev %d - 0x%p\n", | |
| i, priv->netdev[i]); | |
| /* ... and the netdev */ | |
| unregister_netdev(priv->netdev[i]); | |
| interfacePriv->netdev_registered = 0; | |
| } | |
| interfacePriv->interfaceMode = 0; | |
| /* Enable all queues by default */ | |
| interfacePriv->queueEnabled[0] = 1; | |
| interfacePriv->queueEnabled[1] = 1; | |
| interfacePriv->queueEnabled[2] = 1; | |
| interfacePriv->queueEnabled[3] = 1; | |
| } | |
| priv->totalInterfaceCount = 0; | |
| } /* uf_unregister_netdev() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * register_unifi_sdio | |
| * | |
| * This function is called from the Probe (or equivalent) method of | |
| * the SDIO driver when a UniFi card is detected. | |
| * We allocate the Linux net_device struct, initialise the HIP core | |
| * lib, create the char device nodes and start the userspace helper | |
| * to initialise the device. | |
| * | |
| * Arguments: | |
| * sdio_dev Pointer to SDIO context handle to use for all | |
| * SDIO ops. | |
| * bus_id A small number indicating the SDIO card position on the | |
| * bus. Typically this is the slot number, e.g. 0, 1 etc. | |
| * Valid values are 0 to MAX_UNIFI_DEVS-1. | |
| * dev Pointer to kernel device manager struct. | |
| * | |
| * Returns: | |
| * Pointer to the unifi instance, or NULL on error. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| static unifi_priv_t * | |
| register_unifi_sdio(CsrSdioFunction *sdio_dev, int bus_id, struct device *dev) | |
| { | |
| unifi_priv_t *priv = NULL; | |
| int r = -1; | |
| CsrResult csrResult; | |
| if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { | |
| unifi_error(priv, "register_unifi_sdio: invalid device %d\n", | |
| bus_id); | |
| return NULL; | |
| } | |
| down(&Unifi_instance_mutex); | |
| if (In_use[bus_id] != UNIFI_DEV_NOT_IN_USE) { | |
| unifi_error(priv, "register_unifi_sdio: device %d is already in use\n", | |
| bus_id); | |
| goto failed0; | |
| } | |
| /* Allocate device private and net_device structs */ | |
| priv = uf_alloc_netdevice(sdio_dev, bus_id); | |
| if (priv == NULL) { | |
| unifi_error(priv, "Failed to allocate driver private\n"); | |
| goto failed0; | |
| } | |
| priv->unifi_device = dev; | |
| SET_NETDEV_DEV(priv->netdev[0], dev); | |
| /* We are not ready to send data yet. */ | |
| netif_carrier_off(priv->netdev[0]); | |
| /* Allocate driver context. */ | |
| priv->card = unifi_alloc_card(priv->sdio, priv); | |
| if (priv->card == NULL) { | |
| unifi_error(priv, "Failed to allocate UniFi driver card struct.\n"); | |
| goto failed1; | |
| } | |
| if (Unifi_instances[bus_id]) { | |
| unifi_error(priv, "Internal error: instance for slot %d is already taken\n", | |
| bus_id); | |
| } | |
| Unifi_instances[bus_id] = priv; | |
| In_use[bus_id] = UNIFI_DEV_IN_USE; | |
| /* Save the netdev_priv for use by the netdev event callback mechanism */ | |
| Unifi_netdev_instances[bus_id * CSR_WIFI_NUM_INTERFACES] = netdev_priv(priv->netdev[0]); | |
| /* Initialise the mini-coredump capture buffers */ | |
| csrResult = unifi_coredump_init(priv->card, (u16)coredump_max); | |
| if (csrResult != CSR_RESULT_SUCCESS) { | |
| unifi_error(priv, "Couldn't allocate mini-coredump buffers\n"); | |
| } | |
| /* Create the character device nodes */ | |
| r = uf_create_device_nodes(priv, bus_id); | |
| if (r) { | |
| goto failed1; | |
| } | |
| /* | |
| * We use the slot number as unifi device index. | |
| */ | |
| scnprintf(priv->proc_entry_name, 64, "driver/unifi%d", priv->instance); | |
| /* | |
| * The following complex casting is in place in order to eliminate 64-bit compilation warning | |
| * "cast to/from pointer from/to integer of different size" | |
| */ | |
| if (!create_proc_read_entry(priv->proc_entry_name, 0, 0, | |
| uf_read_proc, (void *)(long)priv->instance)) | |
| { | |
| unifi_error(priv, "unifi: can't create /proc/driver/unifi\n"); | |
| } | |
| /* Allocate the net_device for interfaces other than 0. */ | |
| { | |
| int i; | |
| priv->totalInterfaceCount =0; | |
| for(i=1;i<CSR_WIFI_NUM_INTERFACES;i++) | |
| { | |
| if( !uf_alloc_netdevice_for_other_interfaces(priv,i) ) | |
| { | |
| /* error occured while allocating the net_device for interface[i]. The net_device are | |
| * allocated for the interfaces with id<i. Dont worry, all the allocated net_device will | |
| * be releasing chen the control goes to the label failed0. | |
| */ | |
| unifi_error(priv, "Failed to allocate driver private for interface[%d]\n",i); | |
| goto failed0; | |
| } | |
| else | |
| { | |
| SET_NETDEV_DEV(priv->netdev[i], dev); | |
| /* We are not ready to send data yet. */ | |
| netif_carrier_off(priv->netdev[i]); | |
| /* Save the netdev_priv for use by the netdev event callback mechanism */ | |
| Unifi_netdev_instances[bus_id * CSR_WIFI_NUM_INTERFACES + i] = netdev_priv(priv->netdev[i]); | |
| } | |
| } | |
| for(i=0;i<CSR_WIFI_NUM_INTERFACES;i++) | |
| { | |
| netInterface_priv_t *interfacePriv = priv->interfacePriv[i]; | |
| interfacePriv->netdev_registered=0; | |
| } | |
| } | |
| #ifdef CSR_WIFI_RX_PATH_SPLIT | |
| if (signal_buffer_init(priv, CSR_WIFI_RX_SIGNAL_BUFFER_SIZE)) | |
| { | |
| unifi_error(priv,"Failed to allocate shared memory for T-H signals\n"); | |
| goto failed2; | |
| } | |
| priv->rx_workqueue = create_singlethread_workqueue("rx_workq"); | |
| if (priv->rx_workqueue == NULL) { | |
| unifi_error(priv,"create_singlethread_workqueue failed \n"); | |
| goto failed3; | |
| } | |
| INIT_WORK(&priv->rx_work_struct, rx_wq_handler); | |
| #endif | |
| #ifdef CSR_WIFI_HIP_DEBUG_OFFLINE | |
| if (log_hip_signals) | |
| { | |
| uf_register_hip_offline_debug(priv); | |
| } | |
| #endif | |
| /* Initialise the SME related threads and parameters */ | |
| r = uf_sme_init(priv); | |
| if (r) { | |
| unifi_error(priv, "SME initialisation failed.\n"); | |
| goto failed4; | |
| } | |
| /* | |
| * Run the userspace helper program (unififw) to perform | |
| * the device initialisation. | |
| */ | |
| unifi_trace(priv, UDBG1, "run UniFi helper app...\n"); | |
| r = uf_run_unifihelper(priv); | |
| if (r) { | |
| unifi_notice(priv, "unable to run UniFi helper app\n"); | |
| /* Not a fatal error. */ | |
| } | |
| up(&Unifi_instance_mutex); | |
| return priv; | |
| failed4: | |
| #ifdef CSR_WIFI_HIP_DEBUG_OFFLINE | |
| if (log_hip_signals) | |
| { | |
| uf_unregister_hip_offline_debug(priv); | |
| } | |
| #endif | |
| #ifdef CSR_WIFI_RX_PATH_SPLIT | |
| flush_workqueue(priv->rx_workqueue); | |
| destroy_workqueue(priv->rx_workqueue); | |
| failed3: | |
| signal_buffer_free(priv,CSR_WIFI_RX_SIGNAL_BUFFER_SIZE); | |
| failed2: | |
| #endif | |
| /* Remove the device nodes */ | |
| uf_destroy_device_nodes(priv); | |
| failed1: | |
| /* Deregister priv->netdev_client */ | |
| ul_deregister_client(priv->netdev_client); | |
| failed0: | |
| if (priv && priv->card) { | |
| unifi_coredump_free(priv->card); | |
| unifi_free_card(priv->card); | |
| } | |
| if (priv) { | |
| uf_free_netdevice(priv); | |
| } | |
| up(&Unifi_instance_mutex); | |
| return NULL; | |
| } /* register_unifi_sdio() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * ask_unifi_sdio_cleanup | |
| * | |
| * We can not free our private context, until all the char device | |
| * clients have closed the file handles. unregister_unifi_sdio() which | |
| * is called when a card is removed, waits on Unifi_cleanup_wq until | |
| * the reference count becomes zero. It is time to wake it up now. | |
| * | |
| * Arguments: | |
| * priv Pointer to driver context. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| static void | |
| ask_unifi_sdio_cleanup(unifi_priv_t *priv) | |
| { | |
| /* | |
| * Now clear the flag that says the old instance is in use. | |
| * This is used to prevent a new instance being started before old | |
| * one has finshed closing down, for example if bounce makes the card | |
| * appear to be ejected and re-inserted quickly. | |
| */ | |
| In_use[priv->instance] = UNIFI_DEV_CLEANUP; | |
| unifi_trace(NULL, UDBG5, "ask_unifi_sdio_cleanup: wake up cleanup workqueue.\n"); | |
| wake_up(&Unifi_cleanup_wq); | |
| } /* ask_unifi_sdio_cleanup() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * cleanup_unifi_sdio | |
| * | |
| * Release any resources owned by a unifi instance. | |
| * | |
| * Arguments: | |
| * priv Pointer to the instance to free. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| static void | |
| cleanup_unifi_sdio(unifi_priv_t *priv) | |
| { | |
| int priv_instance; | |
| int i; | |
| static const CsrWifiMacAddress broadcast_address = {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; | |
| /* Remove the device nodes */ | |
| uf_destroy_device_nodes(priv); | |
| /* Mark this device as gone away by NULLing the entry in Unifi_instances */ | |
| Unifi_instances[priv->instance] = NULL; | |
| unifi_trace(priv, UDBG5, "cleanup_unifi_sdio: remove_proc_entry\n"); | |
| /* | |
| * Free the children of priv before unifi_free_netdevice() frees | |
| * the priv struct | |
| */ | |
| remove_proc_entry(priv->proc_entry_name, 0); | |
| /* Unregister netdev as a client. */ | |
| if (priv->netdev_client) { | |
| unifi_trace(priv, UDBG2, "Netdev client (id:%d s:0x%X) is unregistered\n", | |
| priv->netdev_client->client_id, priv->netdev_client->sender_id); | |
| ul_deregister_client(priv->netdev_client); | |
| } | |
| /* Destroy the SME related threads and parameters */ | |
| uf_sme_deinit(priv); | |
| #ifdef CSR_SME_USERSPACE | |
| priv->smepriv = NULL; | |
| #endif | |
| #ifdef CSR_WIFI_HIP_DEBUG_OFFLINE | |
| if (log_hip_signals) | |
| { | |
| uf_unregister_hip_offline_debug(priv); | |
| } | |
| #endif | |
| /* Free any packets left in the Rx queues */ | |
| for(i=0;i<CSR_WIFI_NUM_INTERFACES;i++) | |
| { | |
| uf_free_pending_rx_packets(priv, UF_UNCONTROLLED_PORT_Q, broadcast_address,i); | |
| uf_free_pending_rx_packets(priv, UF_CONTROLLED_PORT_Q, broadcast_address,i); | |
| } | |
| /* | |
| * We need to free the resources held by the core, which include tx skbs, | |
| * otherwise we can not call unregister_netdev(). | |
| */ | |
| if (priv->card) { | |
| unifi_trace(priv, UDBG5, "cleanup_unifi_sdio: free card\n"); | |
| unifi_coredump_free(priv->card); | |
| unifi_free_card(priv->card); | |
| priv->card = NULL; | |
| } | |
| /* | |
| * Unregister the network device. | |
| * We can not unregister the netdev before we release | |
| * all pending packets in the core. | |
| */ | |
| uf_unregister_netdev(priv); | |
| priv->totalInterfaceCount = 0; | |
| /* Clear the table of registered netdev_priv's */ | |
| for (i = 0; i < CSR_WIFI_NUM_INTERFACES; i++) { | |
| Unifi_netdev_instances[priv->instance * CSR_WIFI_NUM_INTERFACES + i] = NULL; | |
| } | |
| unifi_trace(priv, UDBG5, "cleanup_unifi_sdio: uf_free_netdevice\n"); | |
| /* | |
| * When uf_free_netdevice() returns, the priv is invalid | |
| * so we need to remember the instance to clear the global flag later. | |
| */ | |
| priv_instance = priv->instance; | |
| #ifdef CSR_WIFI_RX_PATH_SPLIT | |
| flush_workqueue(priv->rx_workqueue); | |
| destroy_workqueue(priv->rx_workqueue); | |
| signal_buffer_free(priv,CSR_WIFI_RX_SIGNAL_BUFFER_SIZE); | |
| #endif | |
| /* Priv is freed as part of the net_device */ | |
| uf_free_netdevice(priv); | |
| /* | |
| * Now clear the flag that says the old instance is in use. | |
| * This is used to prevent a new instance being started before old | |
| * one has finshed closing down, for example if bounce makes the card | |
| * appear to be ejected and re-inserted quickly. | |
| */ | |
| In_use[priv_instance] = UNIFI_DEV_NOT_IN_USE; | |
| unifi_trace(NULL, UDBG5, "cleanup_unifi_sdio: DONE.\n"); | |
| } /* cleanup_unifi_sdio() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unregister_unifi_sdio | |
| * | |
| * Call from SDIO driver when it detects that UniFi has been removed. | |
| * | |
| * Arguments: | |
| * bus_id Number of the card that was ejected. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| static void | |
| unregister_unifi_sdio(int bus_id) | |
| { | |
| unifi_priv_t *priv; | |
| int interfaceTag=0; | |
| u8 reason = CONFIG_IND_EXIT; | |
| if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { | |
| unifi_error(NULL, "unregister_unifi_sdio: invalid device %d\n", | |
| bus_id); | |
| return; | |
| } | |
| priv = Unifi_instances[bus_id]; | |
| if (priv == NULL) { | |
| unifi_error(priv, "unregister_unifi_sdio: device %d is not registered\n", | |
| bus_id); | |
| return; | |
| } | |
| /* Stop the network traffic before freeing the core. */ | |
| for(interfaceTag=0;interfaceTag<priv->totalInterfaceCount;interfaceTag++) | |
| { | |
| netInterface_priv_t *interfacePriv = priv->interfacePriv[interfaceTag]; | |
| if(interfacePriv->netdev_registered) | |
| { | |
| netif_carrier_off(priv->netdev[interfaceTag]); | |
| netif_tx_stop_all_queues(priv->netdev[interfaceTag]); | |
| } | |
| } | |
| #ifdef CSR_NATIVE_LINUX | |
| /* | |
| * If the unifi thread was started, signal it to stop. This | |
| * should cause any userspace processes with open unifi device to | |
| * close them. | |
| */ | |
| uf_stop_thread(priv, &priv->bh_thread); | |
| /* Unregister the interrupt handler */ | |
| if (csr_sdio_linux_remove_irq(priv->sdio)) { | |
| unifi_notice(priv, | |
| "csr_sdio_linux_remove_irq failed to talk to card.\n"); | |
| } | |
| /* Ensure no MLME functions are waiting on a the mlme_event semaphore. */ | |
| uf_abort_mlme(priv); | |
| #endif /* CSR_NATIVE_LINUX */ | |
| ul_log_config_ind(priv, &reason, sizeof(u8)); | |
| /* Deregister the UDI hook from the core. */ | |
| unifi_remove_udi_hook(priv->card, logging_handler); | |
| uf_put_instance(bus_id); | |
| /* | |
| * Wait until the device is cleaned up. i.e., when all userspace | |
| * processes have closed any open unifi devices. | |
| */ | |
| wait_event(Unifi_cleanup_wq, In_use[bus_id] == UNIFI_DEV_CLEANUP); | |
| unifi_trace(NULL, UDBG5, "Received clean up event\n"); | |
| /* Now we can free the private context and the char device nodes */ | |
| cleanup_unifi_sdio(priv); | |
| } /* unregister_unifi_sdio() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_find_instance | |
| * | |
| * Find the context structure for a given UniFi device instance. | |
| * | |
| * Arguments: | |
| * inst The instance number to look for. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| unifi_priv_t * | |
| uf_find_instance(int inst) | |
| { | |
| if ((inst < 0) || (inst >= MAX_UNIFI_DEVS)) { | |
| return NULL; | |
| } | |
| return Unifi_instances[inst]; | |
| } /* uf_find_instance() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_find_priv | |
| * | |
| * Find the device instance for a given context structure. | |
| * | |
| * Arguments: | |
| * priv The context structure pointer to look for. | |
| * | |
| * Returns: | |
| * index of instance, -1 otherwise. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int | |
| uf_find_priv(unifi_priv_t *priv) | |
| { | |
| int inst; | |
| if (!priv) { | |
| return -1; | |
| } | |
| for (inst = 0; inst < MAX_UNIFI_DEVS; inst++) { | |
| if (Unifi_instances[inst] == priv) { | |
| return inst; | |
| } | |
| } | |
| return -1; | |
| } /* uf_find_priv() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_find_netdev_priv | |
| * | |
| * Find the device instance for a given netdev context structure. | |
| * | |
| * Arguments: | |
| * priv The context structure pointer to look for. | |
| * | |
| * Returns: | |
| * index of instance, -1 otherwise. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int | |
| uf_find_netdev_priv(netInterface_priv_t *priv) | |
| { | |
| int inst; | |
| if (!priv) { | |
| return -1; | |
| } | |
| for (inst = 0; inst < MAX_UNIFI_DEVS * CSR_WIFI_NUM_INTERFACES; inst++) { | |
| if (Unifi_netdev_instances[inst] == priv) { | |
| return inst; | |
| } | |
| } | |
| return -1; | |
| } /* uf_find_netdev_priv() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_get_instance | |
| * | |
| * Find the context structure for a given UniFi device instance | |
| * and increment the reference count. | |
| * | |
| * Arguments: | |
| * inst The instance number to look for. | |
| * | |
| * Returns: | |
| * Pointer to the instance or NULL if no instance exists. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| unifi_priv_t * | |
| uf_get_instance(int inst) | |
| { | |
| unifi_priv_t *priv; | |
| down(&Unifi_instance_mutex); | |
| priv = uf_find_instance(inst); | |
| if (priv) { | |
| priv->ref_count++; | |
| } | |
| up(&Unifi_instance_mutex); | |
| return priv; | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_put_instance | |
| * | |
| * Decrement the context reference count, freeing resources and | |
| * shutting down the driver when the count reaches zero. | |
| * | |
| * Arguments: | |
| * inst The instance number to look for. | |
| * | |
| * Returns: | |
| * Pointer to the instance or NULL if no instance exists. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| void | |
| uf_put_instance(int inst) | |
| { | |
| unifi_priv_t *priv; | |
| down(&Unifi_instance_mutex); | |
| priv = uf_find_instance(inst); | |
| if (priv) { | |
| priv->ref_count--; | |
| if (priv->ref_count == 0) { | |
| ask_unifi_sdio_cleanup(priv); | |
| } | |
| } | |
| up(&Unifi_instance_mutex); | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_read_proc | |
| * | |
| * Read method for driver node in /proc/driver/unifi0 | |
| * | |
| * Arguments: | |
| * page | |
| * start | |
| * offset | |
| * count | |
| * eof | |
| * data | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| #ifdef CONFIG_PROC_FS | |
| static int | |
| uf_read_proc(char *page, char **start, off_t offset, int count, | |
| int *eof, void *data) | |
| { | |
| #define UNIFI_DEBUG_TXT_BUFFER 8*1024 | |
| unifi_priv_t *priv; | |
| int actual_amount_to_copy; | |
| char *p, *orig_p; | |
| s32 remain = UNIFI_DEBUG_TXT_BUFFER; | |
| s32 written; | |
| int i; | |
| /* | |
| * The following complex casting is in place in order to eliminate 64-bit compilation warning | |
| * "cast to/from pointer from/to integer of different size" | |
| */ | |
| priv = uf_find_instance((int)(long)data); | |
| if (!priv) { | |
| return 0; | |
| } | |
| p = kmalloc( UNIFI_DEBUG_TXT_BUFFER, GFP_KERNEL ); | |
| orig_p = p; | |
| written = scnprintf(p, remain, "UniFi SDIO Driver: %s %s %s\n", | |
| CSR_WIFI_VERSION, __DATE__, __TIME__); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| #ifdef CSR_SME_USERSPACE | |
| written = scnprintf(p, remain, "SME: CSR userspace "); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| #ifdef CSR_SUPPORT_WEXT | |
| written = scnprintf(p, remain, "with WEXT support\n"); | |
| #else | |
| written = scnprintf(p, remain, "\n"); | |
| #endif /* CSR_SUPPORT_WEXT */ | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| #endif /* CSR_SME_USERSPACE */ | |
| #ifdef CSR_NATIVE_LINUX | |
| written = scnprintf(p, remain, "SME: native\n"); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| #endif | |
| #ifdef CSR_SUPPORT_SME | |
| written = scnprintf(p, remain, | |
| "Firmware (ROM) build:%u, Patch:%u\n", | |
| priv->card_info.fw_build, | |
| priv->sme_versions.firmwarePatch); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| #endif | |
| p += unifi_print_status(priv->card, p, &remain); | |
| written = scnprintf(p, remain, "Last dbg str: %s\n", | |
| priv->last_debug_string); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| written = scnprintf(p, remain, "Last dbg16:"); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| for (i = 0; i < 8; i++) { | |
| written = scnprintf(p, remain, " %04X", | |
| priv->last_debug_word16[i]); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| } | |
| written = scnprintf(p, remain, "\n"); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| written = scnprintf(p, remain, " "); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| for (; i < 16; i++) { | |
| written = scnprintf(p, remain, " %04X", | |
| priv->last_debug_word16[i]); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| } | |
| written = scnprintf(p, remain, "\n"); | |
| UNIFI_SNPRINTF_RET(p, remain, written); | |
| *start = page; | |
| written = UNIFI_DEBUG_TXT_BUFFER - remain; | |
| if( offset >= written ) | |
| { | |
| *eof = 1; | |
| kfree( orig_p ); | |
| return(0); | |
| } | |
| if( offset + count > written ) | |
| { | |
| actual_amount_to_copy = written - offset; | |
| *eof = 1; | |
| } | |
| else | |
| { | |
| actual_amount_to_copy = count; | |
| } | |
| memcpy( page, &(orig_p[offset]), actual_amount_to_copy ); | |
| kfree( orig_p ); | |
| return( actual_amount_to_copy ); | |
| } /* uf_read_proc() */ | |
| #endif | |
| static void | |
| uf_lx_suspend(CsrSdioFunction *sdio_ctx) | |
| { | |
| unifi_priv_t *priv = sdio_ctx->driverData; | |
| unifi_suspend(priv); | |
| CsrSdioSuspendAcknowledge(sdio_ctx, CSR_RESULT_SUCCESS); | |
| } | |
| static void | |
| uf_lx_resume(CsrSdioFunction *sdio_ctx) | |
| { | |
| unifi_priv_t *priv = sdio_ctx->driverData; | |
| unifi_resume(priv); | |
| CsrSdioResumeAcknowledge(sdio_ctx, CSR_RESULT_SUCCESS); | |
| } | |
| static int active_slot = MAX_UNIFI_DEVS; | |
| static struct device *os_devices[MAX_UNIFI_DEVS]; | |
| void | |
| uf_add_os_device(int bus_id, struct device *os_device) | |
| { | |
| if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { | |
| unifi_error(NULL, "uf_add_os_device: invalid device %d\n", | |
| bus_id); | |
| return; | |
| } | |
| active_slot = bus_id; | |
| os_devices[bus_id] = os_device; | |
| } /* uf_add_os_device() */ | |
| void | |
| uf_remove_os_device(int bus_id) | |
| { | |
| if ((bus_id < 0) || (bus_id >= MAX_UNIFI_DEVS)) { | |
| unifi_error(NULL, "uf_remove_os_device: invalid device %d\n", | |
| bus_id); | |
| return; | |
| } | |
| active_slot = bus_id; | |
| os_devices[bus_id] = NULL; | |
| } /* uf_remove_os_device() */ | |
| static void | |
| uf_sdio_inserted(CsrSdioFunction *sdio_ctx) | |
| { | |
| unifi_priv_t *priv; | |
| unifi_trace(NULL, UDBG5, "uf_sdio_inserted(0x%p), slot_id=%d, dev=%p\n", | |
| sdio_ctx, active_slot, os_devices[active_slot]); | |
| priv = register_unifi_sdio(sdio_ctx, active_slot, os_devices[active_slot]); | |
| if (priv == NULL) { | |
| CsrSdioInsertedAcknowledge(sdio_ctx, CSR_RESULT_FAILURE); | |
| return; | |
| } | |
| sdio_ctx->driverData = priv; | |
| CsrSdioInsertedAcknowledge(sdio_ctx, CSR_RESULT_SUCCESS); | |
| } /* uf_sdio_inserted() */ | |
| static void | |
| uf_sdio_removed(CsrSdioFunction *sdio_ctx) | |
| { | |
| unregister_unifi_sdio(active_slot); | |
| CsrSdioRemovedAcknowledge(sdio_ctx); | |
| } /* uf_sdio_removed() */ | |
| static void | |
| uf_sdio_dsr_handler(CsrSdioFunction *sdio_ctx) | |
| { | |
| unifi_priv_t *priv = sdio_ctx->driverData; | |
| unifi_sdio_interrupt_handler(priv->card); | |
| } /* uf_sdio_dsr_handler() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_sdio_int_handler | |
| * | |
| * Interrupt callback function for SDIO interrupts. | |
| * This is called in kernel context (i.e. not interrupt context). | |
| * We retrieve the unifi context pointer and call the main UniFi | |
| * interrupt handler. | |
| * | |
| * Arguments: | |
| * fdev SDIO context pointer | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| static CsrSdioInterruptDsrCallback | |
| uf_sdio_int_handler(CsrSdioFunction *sdio_ctx) | |
| { | |
| return uf_sdio_dsr_handler; | |
| } /* uf_sdio_int_handler() */ | |
| static CsrSdioFunctionId unifi_ids[] = | |
| { | |
| { | |
| .manfId = SDIO_MANF_ID_CSR, | |
| .cardId = SDIO_CARD_ID_UNIFI_3, | |
| .sdioFunction = SDIO_WLAN_FUNC_ID_UNIFI_3, | |
| .sdioInterface = CSR_SDIO_ANY_SDIO_INTERFACE, | |
| }, | |
| { | |
| .manfId = SDIO_MANF_ID_CSR, | |
| .cardId = SDIO_CARD_ID_UNIFI_4, | |
| .sdioFunction = SDIO_WLAN_FUNC_ID_UNIFI_4, | |
| .sdioInterface = CSR_SDIO_ANY_SDIO_INTERFACE, | |
| } | |
| }; | |
| /* | |
| * Structure to register with the glue layer. | |
| */ | |
| static CsrSdioFunctionDriver unifi_sdioFunction_drv = | |
| { | |
| .inserted = uf_sdio_inserted, | |
| .removed = uf_sdio_removed, | |
| .intr = uf_sdio_int_handler, | |
| .suspend = uf_lx_suspend, | |
| .resume = uf_lx_resume, | |
| .ids = unifi_ids, | |
| .idsCount = sizeof(unifi_ids) / sizeof(unifi_ids[0]) | |
| }; | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_sdio_load | |
| * uf_sdio_unload | |
| * | |
| * These functions are called from the main module load and unload | |
| * functions. They perform the appropriate operations for the monolithic | |
| * driver. | |
| * | |
| * Arguments: | |
| * None. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int __init | |
| uf_sdio_load(void) | |
| { | |
| CsrResult csrResult; | |
| csrResult = CsrSdioFunctionDriverRegister(&unifi_sdioFunction_drv); | |
| if (csrResult != CSR_RESULT_SUCCESS) { | |
| unifi_error(NULL, "Failed to register UniFi SDIO driver: csrResult=%d\n", csrResult); | |
| return -EIO; | |
| } | |
| return 0; | |
| } /* uf_sdio_load() */ | |
| void __exit | |
| uf_sdio_unload(void) | |
| { | |
| CsrSdioFunctionDriverUnregister(&unifi_sdioFunction_drv); | |
| } /* uf_sdio_unload() */ | |