3993 lines · 137.8 KB · C
RawDownload
/* * --------------------------------------------------------------------------- * FILE:     netdev.c * * PURPOSE: *      This file provides the upper edge interface to the linux netdevice *      and wireless extensions. *      It is part of the porting exercise. * * Copyright (C) 2005-2010 by Cambridge Silicon Radio Ltd. * * Refer to LICENSE.txt included with this source code for details on * the license terms. * * --------------------------------------------------------------------------- */  /* * Porting Notes: * This file implements the data plane of the UniFi linux driver. * * All the Tx packets are passed to the HIP core lib, using the * unifi_send_signal() API. For EAPOL packets use the MLME-EAPOL.req * signal, for all other use the MLME-UNITDATA.req. The unifi_send_signal() * expects the wire-formatted (packed) signal. For convenience, in the OS * layer we only use the native (unpacked) signal structures. The HIP core lib * provides the write_pack() helper function to convert to the packed signal. * The packet is stored in the bulk data of the signal. We do not need to * allocate new memory to store the packet, because unifi_net_data_malloc() * is implemented to return a skb, which is the format of packet in Linux. * The HIP core lib frees the bulk data buffers, so we do not need to do * this in the OS layer. * * All the Rx packets are MLME-UNITDATA.ind signals, passed by the HIP core lib * in unifi_receive_event(). We do not need to allocate an skb and copy the * received packet because the HIP core lib has stored in memory allocated by * unifi_net_data_malloc(). Also, we can perform the 802.11 to Ethernet * translation in-place because we allocate the extra memory allocated in * unifi_net_data_malloc(). * * If possible, the porting exercise should appropriately implement * unifi_net_data_malloc() and unifi_net_data_free() to save copies between * network and driver buffers. */ #include <linux/types.h>#include <linux/etherdevice.h>#include <linux/mutex.h>#include <linux/semaphore.h> #include <linux/vmalloc.h>#include "csr_wifi_hip_unifi.h"#include "csr_wifi_hip_conversions.h"#include "unifi_priv.h"#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)#include <net/iw_handler.h>#endif#include <net/pkt_sched.h>  /* ALLOW_Q_PAUSE: Pre 2.6.28 kernels do not support multiple driver queues (required for QoS). * In order to support QoS in these kernels, multiple queues are implemented in the driver. But since * there is only a single queue in the kernel (leading to multiple queues in the driver) there is no possibility * of stopping a particular queue in the kernel. Stopping the single kernel queue leads to undesirable starvation * of driver queues. One of the proposals is to not stop the kernel queue but to prevent dequeuing from the * 'stopped' driver queue. Allow q pause is an experimental implementation of this scheme for pre 2.6.28 kernels. * When NOT defined, queues are paused locally in the driver and packets are dequeued for transmission only from the * unpaused queues. When Allow q pause is defined the kernel queue is stopped whenever any driver queue is paused. */#define ALLOW_Q_PAUSE #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)#ifdef UNIFI_NET_NAME#define UF_ALLOC_NETDEV(_dev, _size, _name, _setup, _num_of_queues)     \    do {                                                                \        static char name[8];                                           \        sprintf(name, "%s%s", UNIFI_NET_NAME, _name);                   \        _dev = alloc_netdev_mq(_size, name, _setup, _num_of_queues);    \    } while (0);#else#define UF_ALLOC_NETDEV(_dev, _size, _name, _setup, _num_of_queues)     \    do {                                                                \        _dev = alloc_etherdev_mq(_size, _num_of_queues);                \    } while (0);#endif /* UNIFI_NET_NAME */#else#ifdef UNIFI_NET_NAME#define UF_ALLOC_NETDEV(_dev, _size, _name, _setup, _num_of_queues)     \    do {                                                                \        static char name[8];                                           \        sprintf(name, "%s%s", UNIFI_NET_NAME, _name);                   \        _dev = alloc_netdev(_size, name, _setup);                       \    } while (0);#else#define UF_ALLOC_NETDEV(_dev, _size, _name, _setup, _num_of_queues)     \    do {                                                                \        _dev = alloc_etherdev(_size);                                   \    } while (0);#endif /* UNIFI_NET_NAME */#endif /* LINUX_VERSION_CODE */  /* Wext handler is suported only if CSR_SUPPORT_WEXT is defined */#ifdef CSR_SUPPORT_WEXTextern struct iw_handler_def unifi_iw_handler_def;#endif /* CSR_SUPPORT_WEXT */static void check_ba_frame_age_timeout( unifi_priv_t *priv,                                            netInterface_priv_t *interfacePriv,                                            ba_session_rx_struct *ba_session);static void process_ba_frame(unifi_priv_t *priv,                             netInterface_priv_t *interfacePriv,                             ba_session_rx_struct *ba_session,                             frame_desc_struct *frame_desc);static void process_ba_complete(unifi_priv_t *priv, netInterface_priv_t *interfacePriv);static void process_ma_packet_error_ind(unifi_priv_t *priv, CSR_SIGNAL *signal, bulk_data_param_t *bulkdata);static void process_amsdu(unifi_priv_t *priv, CSR_SIGNAL *signal, bulk_data_param_t *bulkdata);static int uf_net_open(struct net_device *dev);static int uf_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);static int uf_net_stop(struct net_device *dev);static struct net_device_stats *uf_net_get_stats(struct net_device *dev);#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)static u16 uf_net_select_queue(struct net_device *dev, struct sk_buff *skb);#endif#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)static netdev_tx_t uf_net_xmit(struct sk_buff *skb, struct net_device *dev);#elsestatic int uf_net_xmit(struct sk_buff *skb, struct net_device *dev);#ifndef NETDEV_TX_OK#define NETDEV_TX_OK        0#endif#ifndef NETDEV_TX_BUSY#define NETDEV_TX_BUSY      1#endif#endifstatic void uf_set_multicast_list(struct net_device *dev);  typedef int (*tx_signal_handler)(unifi_priv_t *priv, struct sk_buff *skb, const struct ethhdr *ehdr, CSR_PRIORITY priority); #ifdef CONFIG_NET_SCHED/* * Queueing Discipline Interface * Only used if kernel is configured with CONFIG_NET_SCHED */ /* * The driver uses the qdisc interface to buffer and control all * outgoing traffic. We create a root qdisc, register our qdisc operations * and later we create two subsiduary pfifo queues for the uncontrolled * and controlled ports. * * The network stack delivers all outgoing packets in our enqueue handler. * There, we classify the packet and decide whether to store it or drop it * (if the controlled port state is set to "discard"). * If the packet is enqueued, the network stack call our dequeue handler. * There, we decide whether we can send the packet, delay it or drop it * (the controlled port configuration might have changed meanwhile). * If a packet is dequeued, then the network stack calls our hard_start_xmit * handler where finally we send the packet. * * If the hard_start_xmit handler fails to send the packet, we return * NETDEV_TX_BUSY and the network stack call our requeue handler where * we put the packet back in the same queue in came from. * */ struct uf_sched_data{    /* Traffic Classifier TBD */    struct tcf_proto *filter_list;    /* Our two queues */    struct Qdisc *queues[UNIFI_TRAFFIC_Q_MAX];}; struct uf_tx_packet_data {    /* Queue the packet is stored in */    unifi_TrafficQueue queue;    /* QoS Priority determined when enqueing packet */    CSR_PRIORITY priority;    /* Debug */    unsigned long host_tag;}; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)static int uf_qdiscop_enqueue(struct sk_buff *skb, struct Qdisc* qd);static int uf_qdiscop_requeue(struct sk_buff *skb, struct Qdisc* qd);static struct sk_buff *uf_qdiscop_dequeue(struct Qdisc* qd);static void uf_qdiscop_reset(struct Qdisc* qd);static void uf_qdiscop_destroy(struct Qdisc* qd);static int uf_qdiscop_dump(struct Qdisc *qd, struct sk_buff *skb);#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)static int uf_qdiscop_tune(struct Qdisc *qd, struct nlattr *opt);static int uf_qdiscop_init(struct Qdisc *qd, struct nlattr *opt);#elsestatic int uf_qdiscop_tune(struct Qdisc *qd, struct rtattr *opt);static int uf_qdiscop_init(struct Qdisc *qd, struct rtattr *opt);#endif#endif  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)/* queueing discipline operations */static struct Qdisc_ops uf_qdisc_ops ={    .next = NULL,    .cl_ops = NULL,    .id = "UniFi Qdisc",    .priv_size = sizeof(struct uf_sched_data),     .enqueue = uf_qdiscop_enqueue,    .dequeue = uf_qdiscop_dequeue,    .requeue = uf_qdiscop_requeue,    .drop = NULL, /* drop not needed since we are always the root qdisc */     .init = uf_qdiscop_init,    .reset = uf_qdiscop_reset,    .destroy = uf_qdiscop_destroy,    .change = uf_qdiscop_tune,     .dump = uf_qdiscop_dump,};#endif /* LINUX_VERSION_CODE */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)#define UF_QDISC_CREATE_DFLT(_dev, _ops, _root)#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)#define UF_QDISC_CREATE_DFLT(_dev, _ops, _root)         \    qdisc_create_dflt(dev, netdev_get_tx_queue(_dev, 0), _ops, _root)#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)#define UF_QDISC_CREATE_DFLT(_dev, _ops, _root)         \    qdisc_create_dflt(dev, _ops, _root)#else#define UF_QDISC_CREATE_DFLT(_dev, _ops, _root)         \    qdisc_create_dflt(dev, _ops)#endif /* LINUX_VERSION_CODE */ #endif /* CONFIG_NET_SCHED */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)static const struct net_device_ops uf_netdev_ops ={    .ndo_open = uf_net_open,    .ndo_stop = uf_net_stop,    .ndo_start_xmit = uf_net_xmit,    .ndo_do_ioctl = uf_net_ioctl,    .ndo_get_stats = uf_net_get_stats, /* called by /proc/net/dev */    .ndo_set_rx_mode = uf_set_multicast_list,    .ndo_select_queue = uf_net_select_queue,};#endif static u8 oui_rfc1042[P80211_OUI_LEN] = { 0x00, 0x00, 0x00 };static u8 oui_8021h[P80211_OUI_LEN]   = { 0x00, 0x00, 0xf8 };  /* Callback for event logging to blocking clients */static void netdev_mlme_event_handler(ul_client_t  *client,                                      const u8 *sig_packed, int sig_len,                                      const bulk_data_param_t *bulkdata,                                      int dir); #ifdef CSR_SUPPORT_WEXT/* Declare netdev_notifier block which will contain the state change * handler callback function */static struct notifier_block uf_netdev_notifier;#endif /* * --------------------------------------------------------------------------- *  uf_alloc_netdevice * *      Allocate memory for the net_device and device private structs *      for this interface. *      Fill in the fields, but don't register the interface yet. *      We need to configure the UniFi first. * *  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. * *  Returns: *      Pointer to device private struct. * *  Notes: *      The net_device and device private structs are allocated together *      and should be freed by freeing the net_device pointer. * --------------------------------------------------------------------------- */unifi_priv_t *uf_alloc_netdevice(CsrSdioFunction *sdio_dev, int bus_id){    struct net_device *dev;    unifi_priv_t *priv;    netInterface_priv_t *interfacePriv;#ifdef CSR_SUPPORT_WEXT    int rc;#endif    unsigned char i; /* loop index */     /*     * Allocate netdevice struct, assign name template and     * setup as an ethernet device.     * The net_device and private structs are zeroed. Ether_setup() then     * sets up ethernet handlers and values.     * The RedHat 9 redhat-config-network tool doesn't recognise wlan* devices,     * so use "eth*" (like other wireless extns drivers).     */    UF_ALLOC_NETDEV(dev, sizeof(unifi_priv_t)+sizeof(netInterface_priv_t), "%d", ether_setup, UNIFI_TRAFFIC_Q_MAX);     if (dev == NULL) {        return NULL;    }     /* Set up back pointer from priv to netdev */    interfacePriv = (netInterface_priv_t *)netdev_priv(dev);    priv = (unifi_priv_t *)(interfacePriv + 1);    interfacePriv->privPtr = priv;    interfacePriv->InterfaceTag = 0;      /* Initialize all supported netdev interface to be NULL */    for(i=0; i<CSR_WIFI_NUM_INTERFACES; i++) {        priv->netdev[i] = NULL;        priv->interfacePriv[i] = NULL;    }    priv->netdev[0] = dev;    priv->interfacePriv[0] = interfacePriv;     /* Setup / override net_device fields */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)    dev->netdev_ops = &uf_netdev_ops;#else    dev->open             = uf_net_open;    dev->stop             = uf_net_stop;    dev->hard_start_xmit  = uf_net_xmit;    dev->do_ioctl         = uf_net_ioctl;     /* called by /proc/net/dev */    dev->get_stats = uf_net_get_stats;     dev->set_multicast_list = uf_set_multicast_list;#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)    dev->select_queue       = uf_net_select_queue;#endif#endif #ifdef CSR_SUPPORT_WEXT    dev->wireless_handlers = &unifi_iw_handler_def;#if IW_HANDLER_VERSION < 6    dev->get_wireless_stats = unifi_get_wireless_stats;#endif /* IW_HANDLER_VERSION */#endif /* CSR_SUPPORT_WEXT */     /* This gives us enough headroom to add the 802.11 header */    dev->needed_headroom = 32;     /* Use bus_id as instance number */    priv->instance = bus_id;    /* Store SDIO pointer to pass in the core */    priv->sdio = sdio_dev;     sdio_dev->driverData = (void*)priv;    /* Consider UniFi to be uninitialised */    priv->init_progress = UNIFI_INIT_NONE;     priv->prev_queue = 0;     /*     * Initialise the clients structure array.     * We do not need protection around ul_init_clients() because     * the character device can not be used until uf_alloc_netdevice()     * returns and Unifi_instances[bus_id]=priv is set, since unifi_open()     * will return -ENODEV.     */    ul_init_clients(priv);     /*     * Register a new ul client to send the multicast list signals.     * Note: priv->instance must be set before calling this.     */    priv->netdev_client = ul_register_client(priv,            0,            netdev_mlme_event_handler);    if (priv->netdev_client == NULL) {        unifi_error(priv,                "Failed to register a unifi client for background netdev processing\n");        free_netdev(priv->netdev[0]);        return NULL;    }    unifi_trace(priv, UDBG2, "Netdev %p client (id:%d s:0x%X) is registered\n",            dev, priv->netdev_client->client_id, priv->netdev_client->sender_id);     priv->sta_wmm_capabilities = 0; #if (defined(CSR_WIFI_SECURITY_WAPI_ENABLE) && defined(CSR_SUPPORT_SME))    priv->wapi_multicast_filter = 0;    priv->wapi_unicast_filter = 0;    priv->wapi_unicast_queued_pkt_filter = 0;#ifdef CSR_WIFI_SECURITY_WAPI_QOSCTRL_MIC_WORKAROUND    priv->isWapiConnection = FALSE;#endif#endif     /* Enable all queues by default */    interfacePriv->queueEnabled[0] = 1;    interfacePriv->queueEnabled[1] = 1;    interfacePriv->queueEnabled[2] = 1;    interfacePriv->queueEnabled[3] = 1; #ifdef CSR_SUPPORT_SME    priv->allPeerDozing = 0;#endif    /*     * Initialise the OS private struct.     */    /*     * Instead of deciding in advance to use 11bg or 11a, we could do a more     * clever scan on both radios.     */    if (use_5g) {        priv->if_index = CSR_INDEX_5G;        unifi_info(priv, "Using the 802.11a radio\n");    } else {        priv->if_index = CSR_INDEX_2G4;    }     /* Initialise bh thread structure */    priv->bh_thread.thread_task = NULL;    priv->bh_thread.block_thread = 1;    init_waitqueue_head(&priv->bh_thread.wakeup_q);    priv->bh_thread.wakeup_flag = 0;    sprintf(priv->bh_thread.name, "uf_bh_thread");     /* reset the connected state for the interface */    interfacePriv->connected = UnifiConnectedUnknown;  /* -1 unknown, 0 no, 1 yes */ #ifdef USE_DRIVER_LOCK#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)    sema_init(&priv->lock, 1);#else    init_MUTEX(&priv->lock);#endif#endif /* USE_DRIVER_LOCK */     spin_lock_init(&priv->send_signal_lock);     spin_lock_init(&priv->m4_lock);#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)    sema_init(&priv->ba_mutex, 1);#else    init_MUTEX(&priv->ba_mutex);#endif #if (defined(CSR_WIFI_SECURITY_WAPI_ENABLE) && defined(CSR_WIFI_SECURITY_WAPI_SW_ENCRYPTION))    spin_lock_init(&priv->wapi_lock);#endif #ifdef CSR_SUPPORT_SME    spin_lock_init(&priv->staRecord_lock);    spin_lock_init(&priv->tx_q_lock);#endif     /* Create the Traffic Analysis workqueue */    priv->unifi_workqueue = create_singlethread_workqueue("unifi_workq");    if (priv->unifi_workqueue == NULL) {        /* Deregister priv->netdev_client */        ul_deregister_client(priv->netdev_client);        free_netdev(priv->netdev[0]);        return NULL;    } #ifdef CSR_SUPPORT_SME    /* Create the Multicast Addresses list work structure */    INIT_WORK(&priv->multicast_list_task, uf_multicast_list_wq);     /* Create m4 buffering work structure */    INIT_WORK(&interfacePriv->send_m4_ready_task, uf_send_m4_ready_wq); #if (defined(CSR_WIFI_SECURITY_WAPI_ENABLE) && defined(CSR_WIFI_SECURITY_WAPI_SW_ENCRYPTION))    /* Create work structure to buffer the WAPI data packets to be sent to SME for encryption */    INIT_WORK(&interfacePriv->send_pkt_to_encrypt, uf_send_pkt_to_encrypt);#endif#endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)#ifdef CONFIG_NET_SCHED    /* Register the qdisc operations */    register_qdisc(&uf_qdisc_ops);#endif /* CONFIG_NET_SCHED */#endif /* LINUX_VERSION_CODE */     priv->ref_count = 1;      priv->amp_client = NULL;

Lines 1–500 of 3993.