Logo Search packages:      
Sourcecode: linux-qcm-msm version File versions  Download package

wrapndis.c

/*
 *  Copyright (C) 2003-2005 Pontus Fuchs, Giridhar Pemmasani
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 */

#include "ndis.h"
#include "iw_ndis.h"
#include "pnp.h"
#include "loader.h"
#include "wrapndis.h"
#include <linux/inetdevice.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/in.h>
#include "wrapper.h"

/* Functions callable from the NDIS driver */
wstdcall NTSTATUS NdisDispatchDeviceControl(struct device_object *fdo,
                                  struct irp *irp);
wstdcall NTSTATUS NdisDispatchPnp(struct device_object *fdo, struct irp *irp);
wstdcall NTSTATUS NdisDispatchPower(struct device_object *fdo, struct irp *irp);

workqueue_struct_t *wrapndis_wq;
static struct nt_thread *wrapndis_worker_thread;

static int set_packet_filter(struct ndis_device *wnd,
                       ULONG packet_filter);
static void add_iw_stats_timer(struct ndis_device *wnd);
static void del_iw_stats_timer(struct ndis_device *wnd);
static NDIS_STATUS ndis_start_device(struct ndis_device *wnd);
static int ndis_remove_device(struct ndis_device *wnd);
static void set_multicast_list(struct ndis_device *wnd);
static int ndis_net_dev_open(struct net_device *net_dev);
static int ndis_net_dev_close(struct net_device *net_dev);

/* MiniportReset */
NDIS_STATUS mp_reset(struct ndis_device *wnd)
{
      NDIS_STATUS res;
      struct miniport *mp;
      BOOLEAN reset_address;
      KIRQL irql;

      ENTER2("wnd: %p", wnd);
      if (down_interruptible(&wnd->tx_ring_mutex))
            EXIT3(return NDIS_STATUS_FAILURE);
      if (down_interruptible(&wnd->ndis_req_mutex)) {
            up(&wnd->tx_ring_mutex);
            EXIT3(return NDIS_STATUS_FAILURE);
      }
      mp = &wnd->wd->driver->ndis_driver->mp;
      prepare_wait_condition(wnd->ndis_req_task, wnd->ndis_req_done, 0);
      WARNING("%s is being reset", wnd->net_dev->name);
      irql = serialize_lock_irql(wnd);
      assert_irql(_irql_ == DISPATCH_LEVEL);
      res = LIN2WIN2(mp->reset, &reset_address, wnd->nmb->mp_ctx);
      serialize_unlock_irql(wnd, irql);

      TRACE2("%08X, %08X", res, reset_address);
      if (res == NDIS_STATUS_PENDING) {
            /* wait for NdisMResetComplete */
            if (wait_condition((wnd->ndis_req_done > 0), 0,
                           TASK_INTERRUPTIBLE) < 0)
                  res = NDIS_STATUS_FAILURE;
            else {
                  res = wnd->ndis_req_status;
                  reset_address = wnd->ndis_req_done - 1;
            }
            TRACE2("%08X, %08X", res, reset_address);
      }
      up(&wnd->ndis_req_mutex);
      if (res == NDIS_STATUS_SUCCESS && reset_address) {
            set_packet_filter(wnd, wnd->packet_filter);
            set_multicast_list(wnd);
      }
      up(&wnd->tx_ring_mutex);
      EXIT3(return res);
}

/* MiniportRequest(Query/Set)Information */
NDIS_STATUS mp_request(enum ndis_request_type request,
                   struct ndis_device *wnd, ndis_oid oid,
                   void *buf, ULONG buflen, ULONG *written, ULONG *needed)
{
      NDIS_STATUS res;
      ULONG w, n;
      struct miniport *mp;
      KIRQL irql;

      if (down_interruptible(&wnd->ndis_req_mutex))
            EXIT3(return NDIS_STATUS_FAILURE);
      if (!written)
            written = &w;
      if (!needed)
            needed = &n;
      mp = &wnd->wd->driver->ndis_driver->mp;
      prepare_wait_condition(wnd->ndis_req_task, wnd->ndis_req_done, 0);
      irql = serialize_lock_irql(wnd);
      assert_irql(_irql_ == DISPATCH_LEVEL);
      switch (request) {
      case NdisRequestQueryInformation:
            TRACE2("%p, %08X, %p", mp->queryinfo, oid, wnd->nmb->mp_ctx);
            res = LIN2WIN6(mp->queryinfo, wnd->nmb->mp_ctx, oid, buf,
                         buflen, written, needed);
            break;
      case NdisRequestSetInformation:
            TRACE2("%p, %08X, %p", mp->setinfo, oid, wnd->nmb->mp_ctx);
            res = LIN2WIN6(mp->setinfo, wnd->nmb->mp_ctx, oid, buf,
                         buflen, written, needed);
            break;
      default:
            WARNING("invalid request %d, %08X", request, oid);
            res = NDIS_STATUS_NOT_SUPPORTED;
            break;
      }
      serialize_unlock_irql(wnd, irql);
      TRACE2("%08X, %08X", res, oid);
      if (res == NDIS_STATUS_PENDING) {
            /* wait for NdisMQueryInformationComplete */
            if (wait_condition((wnd->ndis_req_done > 0), 0,
                           TASK_INTERRUPTIBLE) < 0)
                  res = NDIS_STATUS_FAILURE;
            else
                  res = wnd->ndis_req_status;
            TRACE2("%08X, %08X", res, oid);
      }
      up(&wnd->ndis_req_mutex);
      DBG_BLOCK(2) {
            if (res || needed)
                  TRACE2("%08X, %d, %d, %d", res, buflen, *written,
                         *needed);
      }
      EXIT3(return res);
}

/* MiniportPnPEventNotify */
static NDIS_STATUS mp_pnp_event(struct ndis_device *wnd,
                        enum ndis_device_pnp_event event,
                        ULONG power_profile)
{
      struct miniport *mp;

      ENTER1("%p, %d", wnd, event);
      mp = &wnd->wd->driver->ndis_driver->mp;
      if (!mp->pnp_event_notify) {
            TRACE1("Windows driver %s doesn't support "
                   "MiniportPnpEventNotify", wnd->wd->driver->name);
            return NDIS_STATUS_FAILURE;
      }
      /* RNDIS driver doesn't like to be notified if device is
       * already halted */
      if (!test_bit(HW_INITIALIZED, &wnd->wd->hw_status))
            EXIT1(return NDIS_STATUS_SUCCESS);
      switch (event) {
      case NdisDevicePnPEventSurpriseRemoved:
            TRACE1("%u, %p",
                   (wnd->attributes & NDIS_ATTRIBUTE_SURPRISE_REMOVE_OK),
                   mp->pnp_event_notify);
            if ((wnd->attributes & NDIS_ATTRIBUTE_SURPRISE_REMOVE_OK) &&
                !test_bit(HW_PRESENT, &wnd->wd->hw_status) &&
                mp->pnp_event_notify) {
                  TRACE1("calling surprise_removed");
                  LIN2WIN4(mp->pnp_event_notify, wnd->nmb->mp_ctx,
                         NdisDevicePnPEventSurpriseRemoved, NULL, 0);
            } else
                  TRACE1("Windows driver %s doesn't support "
                         "MiniportPnpEventNotify for safe unplugging",
                         wnd->wd->driver->name);
            return NDIS_STATUS_SUCCESS;
      case NdisDevicePnPEventPowerProfileChanged:
            if (power_profile)
                  power_profile = NdisPowerProfileAcOnLine;
            LIN2WIN4(mp->pnp_event_notify, wnd->nmb->mp_ctx,
                   NdisDevicePnPEventPowerProfileChanged,
                   &power_profile, (ULONG)sizeof(power_profile));
            return NDIS_STATUS_SUCCESS;
      default:
            WARNING("event %d not yet implemented", event);
            return NDIS_STATUS_SUCCESS;
      }
}

/* MiniportInitialize */
static NDIS_STATUS mp_init(struct ndis_device *wnd)
{
      NDIS_STATUS error_status, status;
      UINT medium_index;
      enum ndis_medium medium_array[] = {NdisMedium802_3};
      struct miniport *mp;

      ENTER1("irql: %d", current_irql());
      if (test_bit(HW_INITIALIZED, &wnd->wd->hw_status)) {
            WARNING("device %p already initialized!", wnd);
            return NDIS_STATUS_FAILURE;
      }

      if (!wnd->wd->driver->ndis_driver ||
          !wnd->wd->driver->ndis_driver->mp.init) {
            WARNING("assuming WDM (non-NDIS) driver");
            EXIT1(return NDIS_STATUS_NOT_RECOGNIZED);
      }
      mp = &wnd->wd->driver->ndis_driver->mp;
      status = LIN2WIN6(mp->init, &error_status, &medium_index, medium_array,
                    sizeof(medium_array) / sizeof(medium_array[0]),
                    wnd->nmb, wnd->nmb);
      TRACE1("init returns: %08X, irql: %d", status, current_irql());
      if (status != NDIS_STATUS_SUCCESS) {
            WARNING("couldn't initialize device: %08X", status);
            EXIT1(return NDIS_STATUS_FAILURE);
      }

      /* Wait a little to let card power up otherwise ifup might
       * fail after boot */
      sleep_hz(HZ / 5);
      status = mp_pnp_event(wnd, NdisDevicePnPEventPowerProfileChanged,
                        NdisPowerProfileAcOnLine);
      if (status != NDIS_STATUS_SUCCESS)
            TRACE1("setting power failed: %08X", status);
      set_bit(HW_INITIALIZED, &wnd->wd->hw_status);
      /* the description about NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND is
       * misleading/confusing */
      status = mp_query(wnd, OID_PNP_CAPABILITIES,
                    &wnd->pnp_capa, sizeof(wnd->pnp_capa));
      if (status == NDIS_STATUS_SUCCESS) {
            TRACE1("%d, %d", wnd->pnp_capa.wakeup.min_magic_packet_wakeup,
                   wnd->pnp_capa.wakeup.min_pattern_wakeup);
            wnd->attributes |= NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND;
            status = mp_query_int(wnd, OID_PNP_ENABLE_WAKE_UP,
                              &wnd->ndis_wolopts);
            TRACE1("%08X, %x", status, wnd->ndis_wolopts);
      } else if (status == NDIS_STATUS_NOT_SUPPORTED)
            wnd->attributes &= ~NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND;
      TRACE1("%d", wnd->pnp_capa.wakeup.min_magic_packet_wakeup);
      /* although some NDIS drivers support suspend, Linux kernel
       * has issues with suspending USB devices */
      if (wrap_is_usb_bus(wnd->wd->dev_bus)) {
            wnd->attributes &= ~NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND;
            wnd->ndis_wolopts = 0;
      }
      mp_set_int(wnd, OID_802_11_POWER_MODE, NDIS_POWER_OFF);
      EXIT1(return NDIS_STATUS_SUCCESS);
}

/* MiniportHalt */
static void mp_halt(struct ndis_device *wnd)
{
      struct miniport *mp;

      ENTER1("%p", wnd);
      if (!test_and_clear_bit(HW_INITIALIZED, &wnd->wd->hw_status)) {
            WARNING("device %p is not initialized - not halting", wnd);
            return;
      }
      hangcheck_del(wnd);
      del_iw_stats_timer(wnd);
      if (wnd->physical_medium == NdisPhysicalMediumWirelessLan &&
          wrap_is_pci_bus(wnd->wd->dev_bus)) {
            up(&wnd->ndis_req_mutex);
            disassociate(wnd, 0);
            if (down_interruptible(&wnd->ndis_req_mutex))
                  WARNING("couldn't obtain ndis_req_mutex");
      }
      mp = &wnd->wd->driver->ndis_driver->mp;
      TRACE1("halt: %p", mp->mp_halt);
      LIN2WIN1(mp->mp_halt, wnd->nmb->mp_ctx);
      /* if a driver doesn't call NdisMDeregisterInterrupt during
       * halt, deregister it now */
      if (wnd->mp_interrupt)
            NdisMDeregisterInterrupt(wnd->mp_interrupt);
      /* cancel any timers left by bugyy windows driver; also free
       * the memory for timers */
      while (1) {
            struct nt_slist *slist;
            struct wrap_timer *wrap_timer;

            spin_lock_bh(&ntoskernel_lock);
            if ((slist = wnd->wrap_timer_slist.next))
                  wnd->wrap_timer_slist.next = slist->next;
            spin_unlock_bh(&ntoskernel_lock);
            TIMERTRACE("%p", slist);
            if (!slist)
                  break;
            wrap_timer = container_of(slist, struct wrap_timer, slist);
            wrap_timer->repeat = 0;
            /* ktimer that this wrap_timer is associated to can't
             * be touched, as it may have been freed by the driver
             * already */
            if (del_timer_sync(&wrap_timer->timer))
                  WARNING("Buggy Windows driver left timer %p "
                        "running", wrap_timer->nt_timer);
            memset(wrap_timer, 0, sizeof(*wrap_timer));
            kfree(wrap_timer);
      }
      EXIT1(return);
}

static NDIS_STATUS mp_set_power_state(struct ndis_device *wnd,
                              enum ndis_power_state state)
{
      NDIS_STATUS status;

      TRACE1("%d", state);
      if (state == NdisDeviceStateD0) {
            status = NDIS_STATUS_SUCCESS;
            up(&wnd->ndis_req_mutex);
            if (test_and_clear_bit(HW_HALTED, &wnd->wd->hw_status)) {
                  status = mp_init(wnd);
                  if (status == NDIS_STATUS_SUCCESS) {
                        set_packet_filter(wnd, wnd->packet_filter);
                        set_multicast_list(wnd);
                  }
            } else if (test_and_clear_bit(HW_SUSPENDED,
                                    &wnd->wd->hw_status)) {
                  status = mp_set_int(wnd, OID_PNP_SET_POWER, state);
                  if (status != NDIS_STATUS_SUCCESS)
                        WARNING("%s: setting power to state %d failed? "
                              "%08X", wnd->net_dev->name, state,
                              status);
            } else
                  return NDIS_STATUS_FAILURE;

            if (wrap_is_pci_bus(wnd->wd->dev_bus)) {
                  pci_enable_wake(wnd->wd->pci.pdev, PCI_D3hot, 0);
                  pci_enable_wake(wnd->wd->pci.pdev, PCI_D3cold, 0);
            }
            if (status == NDIS_STATUS_SUCCESS) {
                  up(&wnd->tx_ring_mutex);
                  netif_device_attach(wnd->net_dev);
                  hangcheck_add(wnd);
                  add_iw_stats_timer(wnd);
            } else
                  WARNING("%s: couldn't set power to state %d; device not"
                        " resumed", wnd->net_dev->name, state);
            EXIT1(return status);
      } else {
            if (down_interruptible(&wnd->tx_ring_mutex))
                  EXIT1(return NDIS_STATUS_FAILURE);
            netif_device_detach(wnd->net_dev);
            hangcheck_del(wnd);
            del_iw_stats_timer(wnd);
            status = NDIS_STATUS_NOT_SUPPORTED;
            if (wnd->attributes & NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND) {
                  status = mp_set_int(wnd, OID_PNP_ENABLE_WAKE_UP,
                                  wnd->ndis_wolopts);
                  TRACE2("0x%x, 0x%x", status, wnd->ndis_wolopts);
                  if (status == NDIS_STATUS_SUCCESS) {
                        if (wnd->ndis_wolopts)
                              wnd->wd->pci.wake_state =
                                    PowerDeviceD3;
                        else
                              wnd->wd->pci.wake_state =
                                    PowerDeviceUnspecified;
                  } else
                        WARNING("couldn't set wake-on-lan options: "
                              "0x%x, %08X", wnd->ndis_wolopts, status);
                  status = mp_set_int(wnd, OID_PNP_SET_POWER, state);
                  if (status == NDIS_STATUS_SUCCESS)
                        set_bit(HW_SUSPENDED, &wnd->wd->hw_status);
                  else
                        WARNING("suspend failed: %08X", status);
            }
            if (status != NDIS_STATUS_SUCCESS) {
                  WARNING("%s does not support power management; "
                        "halting the device", wnd->net_dev->name);
                  mp_halt(wnd);
                  set_bit(HW_HALTED, &wnd->wd->hw_status);
                  status = STATUS_SUCCESS;
            }
            if (down_interruptible(&wnd->ndis_req_mutex))
                  WARNING("couldn't lock ndis_req_mutex");
            EXIT1(return status);
      }
}

static int ndis_set_mac_address(struct net_device *dev, void *p)
{
      struct ndis_device *wnd = netdev_priv(dev);
      struct sockaddr *addr = p;
      struct ndis_configuration_parameter param;
      struct unicode_string key;
      struct ansi_string ansi;
      NDIS_STATUS res;
      unsigned char mac_string[2 * ETH_ALEN + 1];
      mac_address mac;

      memcpy(mac, addr->sa_data, sizeof(mac));
      memset(mac_string, 0, sizeof(mac_string));
      res = snprintf(mac_string, sizeof(mac_string), MACSTR, MAC2STR(mac));
      if (res != (sizeof(mac_string) - 1))
            EXIT1(return -EINVAL);
      TRACE1("new mac: %s", mac_string);

      RtlInitAnsiString(&ansi, mac_string);
      if (RtlAnsiStringToUnicodeString(&param.data.string, &ansi,
                               TRUE) != STATUS_SUCCESS)
            EXIT1(return -EINVAL);

      param.type = NdisParameterString;
      RtlInitAnsiString(&ansi, "NetworkAddress");
      if (RtlAnsiStringToUnicodeString(&key, &ansi, TRUE) != STATUS_SUCCESS) {
            RtlFreeUnicodeString(&param.data.string);
            EXIT1(return -EINVAL);
      }
      NdisWriteConfiguration(&res, wnd->nmb, &key, &param);
      RtlFreeUnicodeString(&key);
      RtlFreeUnicodeString(&param.data.string);

      if (res != NDIS_STATUS_SUCCESS)
            EXIT1(return -EFAULT);
      if (ndis_reinit(wnd) == NDIS_STATUS_SUCCESS) {
            res = mp_query(wnd, OID_802_3_CURRENT_ADDRESS,
                         mac, sizeof(mac));
            if (res == NDIS_STATUS_SUCCESS) {
                  TRACE1("mac:" MACSTRSEP, MAC2STR(mac));
                  memcpy(dev->dev_addr, mac, sizeof(mac));
            } else
                  ERROR("couldn't get mac address: %08X", res);
      }
      EXIT1(return 0);
}

static int setup_tx_sg_list(struct ndis_device *wnd, struct sk_buff *skb,
                      struct ndis_packet_oob_data *oob_data)
{
      struct ndis_sg_element *sg_element;
      struct ndis_sg_list *sg_list;
      int i;

      ENTER3("%p, %d", skb, skb_shinfo(skb)->nr_frags);
      if (skb_shinfo(skb)->nr_frags <= 1) {
            sg_element = &oob_data->wrap_tx_sg_list.elements[0];
            sg_element->address =
                  PCI_DMA_MAP_SINGLE(wnd->wd->pci.pdev, skb->data,
                                 skb->len, PCI_DMA_TODEVICE);
            sg_element->length = skb->len;
            oob_data->wrap_tx_sg_list.nent = 1;
            oob_data->ext.info[ScatterGatherListPacketInfo] =
                  &oob_data->wrap_tx_sg_list;
            TRACE3("%Lx, %u", sg_element->address, sg_element->length);
            return 0;
      }
      sg_list = kmalloc(sizeof(*sg_list) +
                    (skb_shinfo(skb)->nr_frags + 1) * sizeof(*sg_element),
                    GFP_ATOMIC);
      if (!sg_list)
            return -ENOMEM;
      sg_list->nent = skb_shinfo(skb)->nr_frags + 1;
      TRACE3("%p, %d", sg_list, sg_list->nent);
      sg_element = sg_list->elements;
      sg_element->length = skb_headlen(skb);
      sg_element->address =
            PCI_DMA_MAP_SINGLE(wnd->wd->pci.pdev, skb->data,
                           skb_headlen(skb), PCI_DMA_TODEVICE);
      for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
            skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
            sg_element++;
            sg_element->length = frag->size;
            sg_element->address =
                  pci_map_page(wnd->wd->pci.pdev, frag->page,
                             frag->page_offset, frag->size,
                             PCI_DMA_TODEVICE);
            TRACE3("%Lx, %u", sg_element->address, sg_element->length);
      }
      oob_data->ext.info[ScatterGatherListPacketInfo] = sg_list;
      return 0;
}

static void free_tx_sg_list(struct ndis_device *wnd,
                      struct ndis_packet_oob_data *oob_data)
{
      int i;
      struct ndis_sg_element *sg_element;
      struct ndis_sg_list *sg_list =
            oob_data->ext.info[ScatterGatherListPacketInfo];
      sg_element = sg_list->elements;
      TRACE3("%p, %d", sg_list, sg_list->nent);
      PCI_DMA_UNMAP_SINGLE(wnd->wd->pci.pdev, sg_element->address,
                       sg_element->length, PCI_DMA_TODEVICE);
      if (sg_list->nent == 1)
            EXIT3(return);
      for (i = 1; i < sg_list->nent; i++, sg_element++) {
            TRACE3("%Lx, %u", sg_element->address, sg_element->length);
            pci_unmap_page(wnd->wd->pci.pdev, sg_element->address,
                         sg_element->length, PCI_DMA_TODEVICE);
      }
      TRACE3("%p", sg_list);
      kfree(sg_list);
}

static struct ndis_packet *alloc_tx_packet(struct ndis_device *wnd,
                                 struct sk_buff *skb)
{
      struct ndis_packet *packet;
      ndis_buffer *buffer;
      struct ndis_packet_oob_data *oob_data;
      NDIS_STATUS status;

      NdisAllocatePacket(&status, &packet, wnd->tx_packet_pool);
      if (status != NDIS_STATUS_SUCCESS)
            return NULL;
      NdisAllocateBuffer(&status, &buffer, wnd->tx_buffer_pool,
                     skb->data, skb->len);
      if (status != NDIS_STATUS_SUCCESS) {
            NdisFreePacket(packet);
            return NULL;
      }
      packet->private.buffer_head = buffer;
      packet->private.buffer_tail = buffer;

      oob_data = NDIS_PACKET_OOB_DATA(packet);
      oob_data->tx_skb = skb;
      if (wnd->sg_dma_size) {
            if (setup_tx_sg_list(wnd, skb, oob_data)) {
                  NdisFreeBuffer(buffer);
                  NdisFreePacket(packet);
                  return NULL;
            }
      }
      if (skb->ip_summed == CHECKSUM_PARTIAL) {
            struct ndis_tcp_ip_checksum_packet_info csum;
            int protocol;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,21)
            protocol = ntohs(skb->protocol);
#else
            protocol = skb->nh.iph->protocol;
#endif
            csum.value = 0;
            csum.tx.v4 = 1;
            if (protocol == IPPROTO_TCP)
                  csum.tx.tcp = 1;
            else if (protocol == IPPROTO_UDP)
                  csum.tx.udp = 1;
//          csum->tx.ip = 1;
            packet->private.flags |= NDIS_PROTOCOL_ID_TCP_IP;
            oob_data->ext.info[TcpIpChecksumPacketInfo] =
                  (void *)(ULONG_PTR)csum.value;
      }
      DBG_BLOCK(4) {
            dump_bytes(__func__, skb->data, skb->len);
      }
      TRACE4("%p, %p, %p", packet, buffer, skb);
      return packet;
}

void free_tx_packet(struct ndis_device *wnd, struct ndis_packet *packet,
                NDIS_STATUS status)
{
      ndis_buffer *buffer;
      struct ndis_packet_oob_data *oob_data;
      struct sk_buff *skb;
      struct ndis_packet_pool *pool;

      assert_irql(_irql_ <= DISPATCH_LEVEL);
      assert(packet->private.packet_flags);
      oob_data = NDIS_PACKET_OOB_DATA(packet);
      skb = oob_data->tx_skb;
      buffer = packet->private.buffer_head;
      TRACE4("%p, %p, %p, %08X", packet, buffer, skb, status);
      if (status == NDIS_STATUS_SUCCESS) {
            pre_atomic_add(wnd->net_stats.tx_bytes, packet->private.len);
            atomic_inc_var(wnd->net_stats.tx_packets);
      } else {
            TRACE1("packet dropped: %08X", status);
            atomic_inc_var(wnd->net_stats.tx_dropped);
      }
      if (wnd->sg_dma_size)
            free_tx_sg_list(wnd, oob_data);
      NdisFreeBuffer(buffer);
      dev_kfree_skb_any(skb);
      pool = packet->private.pool;
      NdisFreePacket(packet);
      if (netif_queue_stopped(wnd->net_dev) &&
          ((pool->max_descr - pool->num_used_descr) >=
           (wnd->max_tx_packets / 4))) {
            set_bit(NETIF_WAKEQ, &wnd->ndis_pending_work);
            schedule_wrapndis_work(&wnd->ndis_work);
      }
      EXIT4(return);
}

/* MiniportSend and MiniportSendPackets */
/* this function is called holding tx_ring_mutex. start and n are such
 * that start + n < TX_RING_SIZE; i.e., packets don't wrap around
 * ring */
static u8 mp_tx_packets(struct ndis_device *wnd, u8 start, u8 n)
{
      NDIS_STATUS res;
      struct miniport *mp;
      struct ndis_packet *packet;
      u8 sent;
      KIRQL irql;

      ENTER3("%d, %d", start, n);
      mp = &wnd->wd->driver->ndis_driver->mp;
      if (mp->send_packets) {
            if (deserialized_driver(wnd)) {
                  LIN2WIN3(mp->send_packets, wnd->nmb->mp_ctx,
                         &wnd->tx_ring[start], n);
                  sent = n;
            } else {
                  irql = serialize_lock_irql(wnd);
                  LIN2WIN3(mp->send_packets, wnd->nmb->mp_ctx,
                         &wnd->tx_ring[start], n);
                  serialize_unlock_irql(wnd, irql);
                  for (sent = 0; sent < n && wnd->tx_ok; sent++) {
                        struct ndis_packet_oob_data *oob_data;
                        packet = wnd->tx_ring[start + sent];
                        oob_data = NDIS_PACKET_OOB_DATA(packet);
                        switch ((res =
                               xchg(&oob_data->status,
                                    NDIS_STATUS_NOT_RECOGNIZED))) {
                        case NDIS_STATUS_SUCCESS:
                              free_tx_packet(wnd, packet,
                                           NDIS_STATUS_SUCCESS);
                              break;
                        case NDIS_STATUS_PENDING:
                              break;
                        case NDIS_STATUS_RESOURCES:
                              wnd->tx_ok = 0;
                              /* resubmit this packet and
                               * the rest when resources
                               * become available */
                              sent--;
                              break;
                        case NDIS_STATUS_FAILURE:
                              free_tx_packet(wnd, packet,
                                           NDIS_STATUS_FAILURE);
                              break;
                        default:
                              ERROR("%p: invalid status: %08X",
                                    packet, res);
                              free_tx_packet(wnd, packet,
                                           oob_data->status);
                              break;
                        }
                        TRACE3("%p, %d", packet, res);
                  }
            }
            TRACE3("sent: %d(%d)", sent, n);
      } else {
            for (sent = 0; sent < n && wnd->tx_ok; sent++) {
                  struct ndis_packet_oob_data *oob_data;
                  packet = wnd->tx_ring[start + sent];
                  oob_data = NDIS_PACKET_OOB_DATA(packet);
                  oob_data->status = NDIS_STATUS_NOT_RECOGNIZED;
                  irql = serialize_lock_irql(wnd);
                  res = LIN2WIN3(mp->send, wnd->nmb->mp_ctx,
                               packet, packet->private.flags);
                  serialize_unlock_irql(wnd, irql);
                  switch (res) {
                  case NDIS_STATUS_SUCCESS:
                        free_tx_packet(wnd, packet, res);
                        break;
                  case NDIS_STATUS_PENDING:
                        break;
                  case NDIS_STATUS_RESOURCES:
                        wnd->tx_ok = 0;
                        /* resend this packet when resources
                         * become available */
                        sent--;
                        break;
                  case NDIS_STATUS_FAILURE:
                        free_tx_packet(wnd, packet, res);
                        break;
                  default:
                        ERROR("packet %p: invalid status: %08X",
                              packet, res);
                        break;
                  }
            }
      }
      EXIT3(return sent);
}

static void tx_worker(worker_param_t param)
{
      struct ndis_device *wnd;
      s8 n;

      wnd = worker_param_data(param, struct ndis_device, tx_work);
      ENTER3("tx_ok %d", wnd->tx_ok);
      while (wnd->tx_ok) {
            if (down_interruptible(&wnd->tx_ring_mutex))
                  break;
            spin_lock_bh(&wnd->tx_ring_lock);
            n = wnd->tx_ring_end - wnd->tx_ring_start;
            TRACE3("%d, %d, %d", wnd->tx_ring_start, wnd->tx_ring_end, n);
            /* end == start if either ring is empty or full; in
             * the latter case is_tx_ring_full is set */
            if (n == 0) {
                  if (wnd->is_tx_ring_full)
                        n = TX_RING_SIZE - wnd->tx_ring_start;
                  else {
                        spin_unlock_bh(&wnd->tx_ring_lock);
                        up(&wnd->tx_ring_mutex);
                        break;
                  }
            } else if (n < 0)
                  n = TX_RING_SIZE - wnd->tx_ring_start;
            spin_unlock_bh(&wnd->tx_ring_lock);
            if (unlikely(n > wnd->max_tx_packets))
                  n = wnd->max_tx_packets;
            n = mp_tx_packets(wnd, wnd->tx_ring_start, n);
            if (n) {
                  wnd->net_dev->trans_start = jiffies;
                  wnd->tx_ring_start =
                        (wnd->tx_ring_start + n) % TX_RING_SIZE;
                  wnd->is_tx_ring_full = 0;
            }
            up(&wnd->tx_ring_mutex);
            TRACE3("%d, %d, %d", wnd->tx_ring_start, wnd->tx_ring_end, n);
      }
      EXIT3(return);
}

static int tx_skbuff(struct sk_buff *skb, struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      struct ndis_packet *packet;

      packet = alloc_tx_packet(wnd, skb);
      if (!packet) {
            TRACE2("couldn't allocate packet");
            netif_tx_lock(dev);
            netif_stop_queue(dev);
            netif_tx_unlock(dev);
            return NETDEV_TX_BUSY;
      }
      spin_lock(&wnd->tx_ring_lock);
      wnd->tx_ring[wnd->tx_ring_end++] = packet;
      if (wnd->tx_ring_end == TX_RING_SIZE)
            wnd->tx_ring_end = 0;
      if (wnd->tx_ring_end == wnd->tx_ring_start) {
            netif_tx_lock(dev);
            wnd->is_tx_ring_full = 1;
            netif_stop_queue(dev);
            netif_tx_unlock(dev);
      }
      spin_unlock(&wnd->tx_ring_lock);
      TRACE4("ring: %d, %d", wnd->tx_ring_start, wnd->tx_ring_end);
      schedule_wrapndis_work(&wnd->tx_work);
      return NETDEV_TX_OK;
}

static int set_packet_filter(struct ndis_device *wnd, ULONG packet_filter)
{
      NDIS_STATUS res;

      while (1) {
            res = mp_set_int(wnd, OID_GEN_CURRENT_PACKET_FILTER,
                         packet_filter);
            if (res == NDIS_STATUS_SUCCESS)
                  break;
            TRACE2("couldn't set filter 0x%08x", packet_filter);
            /* NDIS_PACKET_TYPE_PROMISCUOUS may not work with 802.11 */
            if (packet_filter & NDIS_PACKET_TYPE_PROMISCUOUS) {
                  packet_filter &= ~NDIS_PACKET_TYPE_PROMISCUOUS;
                  continue;
            }
            if (packet_filter & NDIS_PACKET_TYPE_ALL_LOCAL) {
                  packet_filter &= ~NDIS_PACKET_TYPE_ALL_LOCAL;
                  continue;
            }
            if (packet_filter & NDIS_PACKET_TYPE_ALL_FUNCTIONAL) {
                  packet_filter &= ~NDIS_PACKET_TYPE_ALL_FUNCTIONAL;
                  continue;
            }
            if (packet_filter & NDIS_PACKET_TYPE_MULTICAST) {
                  packet_filter &= ~NDIS_PACKET_TYPE_MULTICAST;
                  packet_filter |= NDIS_PACKET_TYPE_ALL_MULTICAST;
                  continue;
            }
            if (packet_filter & NDIS_PACKET_TYPE_ALL_MULTICAST) {
                  packet_filter &= ~NDIS_PACKET_TYPE_ALL_MULTICAST;
                  continue;
            }
            break;
      }

      wnd->packet_filter = packet_filter;
      res = mp_query_int(wnd, OID_GEN_CURRENT_PACKET_FILTER, &packet_filter);
      if (packet_filter != wnd->packet_filter) {
            WARNING("filter not set: 0x%08x, 0x%08x",
                  packet_filter, wnd->packet_filter);
            wnd->packet_filter = packet_filter;
      }
      if (wnd->packet_filter)
            EXIT3(return 0);
      else
            EXIT3(return -1);
}

void set_media_state(struct ndis_device *wnd, enum ndis_media_state state)
{
      ENTER2("state: 0x%x", state);
      if (state == NdisMediaStateConnected) {
            netif_carrier_on(wnd->net_dev);
            wnd->tx_ok = 1;
            if (netif_queue_stopped(wnd->net_dev))
                  netif_wake_queue(wnd->net_dev);
            if (wnd->physical_medium == NdisPhysicalMediumWirelessLan) {
                  set_bit(LINK_STATUS_ON, &wnd->ndis_pending_work);
                  schedule_wrapndis_work(&wnd->ndis_work);
            }
      } else if (state == NdisMediaStateDisconnected) {
            netif_carrier_off(wnd->net_dev);
            netif_stop_queue(wnd->net_dev);
            wnd->tx_ok = 0;
            if (wnd->physical_medium == NdisPhysicalMediumWirelessLan) {
                  memset(&wnd->essid, 0, sizeof(wnd->essid));
                  set_bit(LINK_STATUS_OFF, &wnd->ndis_pending_work);
                  schedule_wrapndis_work(&wnd->ndis_work);
            }
      } else {
            WARNING("invalid media state: 0x%x", state);
      }
}

static int ndis_net_dev_open(struct net_device *net_dev)
{
      ENTER1("%p", netdev_priv(net_dev));
      netif_start_queue(net_dev);
      netif_poll_enable(net_dev);
      EXIT1(return 0);
}

static int ndis_net_dev_close(struct net_device *net_dev)
{
      ENTER1("%p", netdev_priv(net_dev));
      netif_poll_disable(net_dev);
      netif_tx_disable(net_dev);
      EXIT1(return 0);
}

static int ndis_change_mtu(struct net_device *net_dev, int mtu)
{
      struct ndis_device *wnd = netdev_priv(net_dev);
      int max;

      if (mtu < ETH_ZLEN)
            return -EINVAL;
      if (mp_query_int(wnd, OID_GEN_MAXIMUM_TOTAL_SIZE, &max) !=
          NDIS_STATUS_SUCCESS)
            return -EOPNOTSUPP;
      TRACE1("%d", max);
      max -= ETH_HLEN;
      if (max <= ETH_ZLEN)
            return -EINVAL;
      if (mtu + ETH_HLEN > max)
            return -EINVAL;
      net_dev->mtu = mtu;
      return 0;
}

#ifdef CONFIG_NET_POLL_CONTROLLER
static void ndis_poll_controller(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);

      disable_irq(dev->irq);
      ndis_isr(wnd->mp_interrupt->kinterrupt, wnd->mp_interrupt);
      enable_irq(dev->irq);
}
#endif

/* called from BH context */
static struct net_device_stats *ndis_get_stats(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      return &wnd->net_stats;
}

/* called from BH context */
static void ndis_set_multicast_list(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      set_bit(SET_MULTICAST_LIST, &wnd->ndis_pending_work);
      schedule_wrapndis_work(&wnd->ndis_work);
}

/* called from BH context */
struct iw_statistics *get_iw_stats(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      return &wnd->iw_stats;
}

static void update_iw_stats(struct ndis_device *wnd)
{
      struct iw_statistics *iw_stats = &wnd->iw_stats;
      struct ndis_wireless_stats ndis_stats;
      NDIS_STATUS res;
      ndis_rssi rssi;
      int qual;

      ENTER2("%p", wnd);
      if (wnd->iw_stats_enabled == FALSE || !netif_carrier_ok(wnd->net_dev)) {
            memset(iw_stats, 0, sizeof(*iw_stats));
            EXIT2(return);
      }
      res = mp_query(wnd, OID_802_11_RSSI, &rssi, sizeof(rssi));
      if (res == NDIS_STATUS_SUCCESS)
            iw_stats->qual.level = rssi;

      qual = 100 * (rssi - WL_NOISE) / (WL_SIGMAX - WL_NOISE);
      if (qual < 0)
            qual = 0;
      else if (qual > 100)
            qual = 100;

      iw_stats->qual.noise = WL_NOISE;
      iw_stats->qual.qual  = qual;

      res = mp_query(wnd, OID_802_11_STATISTICS,
                   &ndis_stats, sizeof(ndis_stats));
      if (res != NDIS_STATUS_SUCCESS)
            EXIT2(return);
      iw_stats->discard.retries = (unsigned long)ndis_stats.retry +
            (unsigned long)ndis_stats.multi_retry;
      iw_stats->discard.misc = (unsigned long)ndis_stats.fcs_err +
            (unsigned long)ndis_stats.rtss_fail +
            (unsigned long)ndis_stats.ack_fail +
            (unsigned long)ndis_stats.frame_dup;

      EXIT2(return);
}

static void set_multicast_list(struct ndis_device *wnd)
{
      struct net_device *net_dev;
      ULONG packet_filter;
      NDIS_STATUS res;

      net_dev = wnd->net_dev;
      packet_filter = wnd->packet_filter;

      TRACE2("0x%08x", packet_filter);
      if (net_dev->flags & IFF_PROMISC) {
            packet_filter |= NDIS_PACKET_TYPE_PROMISCUOUS |
                  NDIS_PACKET_TYPE_ALL_LOCAL;
      } else if (net_dev->flags & IFF_ALLMULTI ||
               net_dev->mc_count > wnd->multicast_size) {
            packet_filter |= NDIS_PACKET_TYPE_ALL_MULTICAST;
            TRACE2("0x%08x", packet_filter);
      } else if (net_dev->mc_count > 0) {
            int i, size;
            char *buf;
            struct dev_mc_list *mclist;
            size = min(wnd->multicast_size, net_dev->mc_count);
            TRACE2("%d, %d", wnd->multicast_size, net_dev->mc_count);
            buf = kmalloc(size * ETH_ALEN, GFP_KERNEL);
            if (!buf) {
                  WARNING("couldn't allocate memory");
                  EXIT2(return);
            }
            mclist = net_dev->mc_list;
            for (i = 0; i < size && mclist; mclist = mclist->next) {
                  if (mclist->dmi_addrlen != ETH_ALEN)
                        continue;
                  memcpy(buf + i * ETH_ALEN, mclist->dmi_addr, ETH_ALEN);
                  TRACE2(MACSTRSEP, MAC2STR(mclist->dmi_addr));
                  i++;
            }
            res = mp_set(wnd, OID_802_3_MULTICAST_LIST, buf, i * ETH_ALEN);
            if (res == NDIS_STATUS_SUCCESS && i > 0)
                  packet_filter |= NDIS_PACKET_TYPE_MULTICAST;
            else
                  packet_filter |= NDIS_PACKET_TYPE_ALL_MULTICAST;
            kfree(buf);
      }
      TRACE2("0x%08x", packet_filter);
      res = set_packet_filter(wnd, packet_filter);
      if (res)
            TRACE1("couldn't set packet filter (%08X)", res);
      EXIT2(return);
}

static void link_status_off(struct ndis_device *wnd)
{
#ifdef CONFIG_WIRELESS_EXT
      union iwreq_data wrqu;

      memset(&wrqu, 0, sizeof(wrqu));
      wrqu.ap_addr.sa_family = ARPHRD_ETHER;
      wireless_send_event(wnd->net_dev, SIOCGIWAP, &wrqu, NULL);
#endif
      EXIT2(return);
}

static void link_status_on(struct ndis_device *wnd)
{
#ifdef CONFIG_WIRELESS_EXT
      struct ndis_assoc_info *ndis_assoc_info;
      union iwreq_data wrqu;
      NDIS_STATUS res;
      const int assoc_size = sizeof(*ndis_assoc_info) + IW_CUSTOM_MAX + 32;
#endif

      ENTER2("");
#ifdef CONFIG_WIRELESS_EXT
      memset(&wrqu, 0, sizeof(wrqu));
      ndis_assoc_info = kzalloc(assoc_size, GFP_KERNEL);
      if (!ndis_assoc_info) {
            ERROR("couldn't allocate memory");
            goto send_assoc_event;
      }
      res = mp_query(wnd, OID_802_11_ASSOCIATION_INFORMATION,
                   ndis_assoc_info, assoc_size);
      if (res) {
            TRACE2("query assoc_info failed (%08X)", res);
            kfree(ndis_assoc_info);
            goto send_assoc_event;
      }
      TRACE2("%u, 0x%x, %u, 0x%x, %u", ndis_assoc_info->length,
             ndis_assoc_info->req_ies, ndis_assoc_info->req_ie_length,
             ndis_assoc_info->resp_ies, ndis_assoc_info->resp_ie_length);
      if (ndis_assoc_info->req_ie_length > 0) {
            wrqu.data.length = ndis_assoc_info->req_ie_length;
            wireless_send_event(wnd->net_dev, IWEVASSOCREQIE, &wrqu,
                            ((char *)ndis_assoc_info) +
                            ndis_assoc_info->offset_req_ies);
      }
      if (ndis_assoc_info->resp_ie_length > 0) {
            wrqu.data.length = ndis_assoc_info->resp_ie_length;
            wireless_send_event(wnd->net_dev, IWEVASSOCRESPIE, &wrqu,
                            ((char *)ndis_assoc_info) +
                            ndis_assoc_info->offset_resp_ies);
      }
      kfree(ndis_assoc_info);

send_assoc_event:
      get_ap_address(wnd, wrqu.ap_addr.sa_data);
      wrqu.ap_addr.sa_family = ARPHRD_ETHER;
      TRACE2(MACSTRSEP, MAC2STR(wrqu.ap_addr.sa_data));
      wireless_send_event(wnd->net_dev, SIOCGIWAP, &wrqu, NULL);
#endif
      EXIT2(return);
}

static void iw_stats_timer_proc(unsigned long data)
{
      struct ndis_device *wnd = (struct ndis_device *)data;

      ENTER2("%d", wnd->iw_stats_interval);
      if (wnd->iw_stats_interval > 0) {
            set_bit(COLLECT_IW_STATS, &wnd->ndis_pending_work);
            schedule_wrapndis_work(&wnd->ndis_work);
      }
      mod_timer(&wnd->iw_stats_timer, jiffies + wnd->iw_stats_interval);
}

static void add_iw_stats_timer(struct ndis_device *wnd)
{
      if (wnd->physical_medium != NdisPhysicalMediumWirelessLan)
            return;
      if (wnd->iw_stats_interval < 0)
            wnd->iw_stats_interval *= -1;
      wnd->iw_stats_timer.data = (unsigned long)wnd;
      wnd->iw_stats_timer.function = iw_stats_timer_proc;
      mod_timer(&wnd->iw_stats_timer, jiffies + wnd->iw_stats_interval);
}

static void del_iw_stats_timer(struct ndis_device *wnd)
{
      ENTER2("%d", wnd->iw_stats_interval);
      wnd->iw_stats_interval *= -1;
      del_timer_sync(&wnd->iw_stats_timer);
      EXIT2(return);
}

static void hangcheck_proc(unsigned long data)
{
      struct ndis_device *wnd = (struct ndis_device *)data;

      ENTER3("%d", wnd->hangcheck_interval);
      if (wnd->hangcheck_interval > 0) {
            set_bit(HANGCHECK, &wnd->ndis_pending_work);
            schedule_wrapndis_work(&wnd->ndis_work);
      }
      mod_timer(&wnd->hangcheck_timer, jiffies + wnd->hangcheck_interval);
      EXIT3(return);
}

void hangcheck_add(struct ndis_device *wnd)
{
      if (!wnd->wd->driver->ndis_driver->mp.hangcheck ||
          hangcheck_interval < 0)
            EXIT2(return);

      if (hangcheck_interval > 0)
            wnd->hangcheck_interval = hangcheck_interval * HZ;
      if (wnd->hangcheck_interval < 0)
            wnd->hangcheck_interval *= -1;
      wnd->hangcheck_timer.data = (unsigned long)wnd;
      wnd->hangcheck_timer.function = hangcheck_proc;
      mod_timer(&wnd->hangcheck_timer, jiffies + wnd->hangcheck_interval);
      EXIT2(return);
}

void hangcheck_del(struct ndis_device *wnd)
{
      ENTER2("%d", wnd->hangcheck_interval);
      if (wnd->hangcheck_interval > 0)
            wnd->hangcheck_interval *= -1;
      del_timer_sync(&wnd->hangcheck_timer);
      EXIT2(return);
}

/* worker procedure to take care of setting/checking various states */
static void ndis_worker(worker_param_t param)
{
      struct ndis_device *wnd;

      wnd = worker_param_data(param, struct ndis_device, ndis_work);
      WORKTRACE("0x%lx", wnd->ndis_pending_work);

      if (test_and_clear_bit(NETIF_WAKEQ, &wnd->ndis_pending_work)) {
            netif_tx_lock_bh(wnd->net_dev);
            netif_wake_queue(wnd->net_dev);
            netif_tx_unlock_bh(wnd->net_dev);
      }

      if (test_and_clear_bit(LINK_STATUS_OFF, &wnd->ndis_pending_work))
            link_status_off(wnd);

      if (test_and_clear_bit(LINK_STATUS_ON, &wnd->ndis_pending_work))
            link_status_on(wnd);

      if (test_and_clear_bit(COLLECT_IW_STATS, &wnd->ndis_pending_work))
            update_iw_stats(wnd);

      if (test_and_clear_bit(SET_MULTICAST_LIST,
                         &wnd->ndis_pending_work))
            set_multicast_list(wnd);

      if (test_and_clear_bit(HANGCHECK, &wnd->ndis_pending_work)) {
            struct miniport *mp;
            BOOLEAN reset;
            KIRQL irql;

            mp = &wnd->wd->driver->ndis_driver->mp;
            irql = serialize_lock_irql(wnd);
            reset = LIN2WIN1(mp->hangcheck, wnd->nmb->mp_ctx);
            serialize_unlock_irql(wnd, irql);
            if (reset) {
                  TRACE2("%s needs reset", wnd->net_dev->name);
                  mp_reset(wnd);
            }
      }
      WORKEXIT(return);
}

NDIS_STATUS ndis_reinit(struct ndis_device *wnd)
{
      NDIS_STATUS status;

      wnd->attributes &= ~NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND;
      status = mp_set_power_state(wnd, NdisDeviceStateD3);
      if (status != NDIS_STATUS_SUCCESS) {
            ERROR("halting device %s failed: %08X", wnd->net_dev->name,
                  status);
            return status;
      }
      status = mp_set_power_state(wnd, NdisDeviceStateD0);
      if (status != NDIS_STATUS_SUCCESS)
            ERROR("starting device %s failed: %08X", wnd->net_dev->name,
                  status);
      return status;
}

static void get_encryption_capa(struct ndis_device *wnd, char *buf,
                        const int buf_len)
{
      int i, mode;
      NDIS_STATUS res;
      struct ndis_assoc_info ndis_assoc_info;
      struct ndis_add_key ndis_key;
      struct ndis_capability *c;

      ENTER1("%p", wnd);
      /* set network type to g, b, or a, in that order */
      res = mp_query(wnd, OID_802_11_NETWORK_TYPES_SUPPORTED, buf, buf_len);
      if (res == NDIS_STATUS_SUCCESS) {
            struct network_type_list *net_types;
            unsigned long types = 0;
            net_types = (typeof(net_types))buf;
            for (i = 0; i < net_types->num; i++) {
                  TRACE2("%d", net_types->types[i]);
                  set_bit(net_types->types[i], &types);
            }
            if (types & Ndis802_11OFDM24)
                  mode = Ndis802_11OFDM24;
            else if (types & Ndis802_11DS)
                  mode = Ndis802_11DS;
            else if (types & Ndis802_11OFDM5)
                  mode = Ndis802_11OFDM5;
            else
                  mode = Ndis802_11DS;
            mp_set_int(wnd, OID_802_11_NETWORK_TYPE_IN_USE, mode);
      }
      /* check if WEP is supported */
      if (set_iw_encr_mode(wnd, IW_AUTH_CIPHER_WEP104,
                       IW_AUTH_CIPHER_NONE) == 0 &&
          get_ndis_encr_mode(wnd) == Ndis802_11Encryption1KeyAbsent)
            set_bit(Ndis802_11Encryption1Enabled, &wnd->capa.encr);

      /* check if WPA is supported */
      if (set_ndis_auth_mode(wnd, Ndis802_11AuthModeWPA) == 0 &&
          get_ndis_auth_mode(wnd) == Ndis802_11AuthModeWPA)
            set_bit(Ndis802_11AuthModeWPA, &wnd->capa.encr);
      else
            EXIT1(return);

      if (set_ndis_auth_mode(wnd, Ndis802_11AuthModeWPAPSK) == 0 &&
          get_ndis_auth_mode(wnd) == Ndis802_11AuthModeWPAPSK)
            set_bit(Ndis802_11AuthModeWPAPSK, &wnd->capa.encr);

      /* check for highest encryption */
      mode = 0;
      if (set_iw_encr_mode(wnd, IW_AUTH_CIPHER_CCMP,
                       IW_AUTH_CIPHER_NONE) == 0 &&
          (i = get_ndis_encr_mode(wnd)) > 0 &&
          (i == Ndis802_11Encryption3KeyAbsent ||
           i == Ndis802_11Encryption3Enabled))
            mode = Ndis802_11Encryption3Enabled;
      else if (set_iw_encr_mode(wnd, IW_AUTH_CIPHER_TKIP,
                          IW_AUTH_CIPHER_NONE) == 0 &&
             (i = get_ndis_encr_mode(wnd)) > 0 &&
             (i == Ndis802_11Encryption2KeyAbsent ||
              i == Ndis802_11Encryption2Enabled))
            mode = Ndis802_11Encryption2Enabled;
      else if (set_iw_encr_mode(wnd, IW_AUTH_CIPHER_WEP104,
                          IW_AUTH_CIPHER_NONE) == 0 &&
             (i = get_ndis_encr_mode(wnd)) > 0 &&
             (i == Ndis802_11Encryption1KeyAbsent ||
              i == Ndis802_11Encryption1Enabled))
            mode = Ndis802_11Encryption1Enabled;

      TRACE1("mode: %d", mode);
      if (mode == 0)
            EXIT1(return);
      set_bit(Ndis802_11Encryption1Enabled, &wnd->capa.encr);
      if (mode == Ndis802_11Encryption1Enabled)
            EXIT1(return);

      ndis_key.length = 32;
      ndis_key.index = 0xC0000001;
      ndis_key.struct_size = sizeof(ndis_key);
      res = mp_set(wnd, OID_802_11_ADD_KEY, &ndis_key, ndis_key.struct_size);
      TRACE2("%08X, %lu", res, (unsigned long)sizeof(ndis_key));
      if (res && res != NDIS_STATUS_INVALID_DATA)
            EXIT1(return);
      res = mp_query(wnd, OID_802_11_ASSOCIATION_INFORMATION,
                   &ndis_assoc_info, sizeof(ndis_assoc_info));
      TRACE1("%08X", res);
      if (res == NDIS_STATUS_NOT_SUPPORTED)
            EXIT1(return);

      set_bit(Ndis802_11Encryption2Enabled, &wnd->capa.encr);
      if (mode == Ndis802_11Encryption3Enabled)
            set_bit(Ndis802_11Encryption3Enabled, &wnd->capa.encr);
      /* not all drivers support OID_802_11_CAPABILITY, so we don't
       * know for sure if driver support WPA or WPAPSK; assume
       * WPAPSK */
      set_bit(Ndis802_11AuthModeWPAPSK, &wnd->capa.auth);
      wnd->max_pmkids = 1;

      memset(buf, 0, buf_len);
      c = (struct ndis_capability *)buf;
      res = mp_query(wnd, OID_802_11_CAPABILITY, buf, buf_len);
      if (!(res == NDIS_STATUS_SUCCESS && c->version == 2))
            EXIT1(return);
      wnd->max_pmkids = c->num_PMKIDs;

      for (i = 0; i < c->num_auth_encr_pair; i++) {
            struct ndis_auth_encr_pair *ae;

            ae = &c->auth_encr_pair[i];
            if ((char *)(ae + 1) > buf + buf_len)
                  break;
            switch (ae->auth_mode) {
            case Ndis802_11AuthModeOpen:
            case Ndis802_11AuthModeShared:
            case Ndis802_11AuthModeWPA:
            case Ndis802_11AuthModeWPAPSK:
            case Ndis802_11AuthModeWPANone:
            case Ndis802_11AuthModeWPA2:
            case Ndis802_11AuthModeWPA2PSK:
                  set_bit(ae->auth_mode, &wnd->capa.auth);
                  break;
            default:
                  WARNING("unknown auth_mode: %d", ae->auth_mode);
                  break;
            }
            switch (ae->encr_mode) {
            case Ndis802_11EncryptionDisabled:
            case Ndis802_11Encryption1Enabled:
            case Ndis802_11Encryption2Enabled:
            case Ndis802_11Encryption3Enabled:
                  set_bit(ae->encr_mode, &wnd->capa.encr);
                  break;
            default:
                  WARNING("unknown encr_mode: %d", ae->encr_mode);
                  break;
            }
      }
      EXIT1(return);
}

wstdcall NTSTATUS NdisDispatchDeviceControl(struct device_object *fdo,
                                  struct irp *irp)
{
      struct ndis_device *wnd;

      TRACE3("fdo: %p", fdo);
      /* for now, we don't have anything intresting here, so pass it
       * down to bus driver */
      wnd = fdo->reserved;
      return IoPassIrpDown(wnd->nmb->pdo, irp);
}
WIN_FUNC_DECL(NdisDispatchDeviceControl,2)

wstdcall NTSTATUS NdisDispatchPower(struct device_object *fdo, struct irp *irp)
{
      struct io_stack_location *irp_sl;
      struct ndis_device *wnd;
      enum ndis_power_state state;
      NTSTATUS status;
      NDIS_STATUS ndis_status;

      irp_sl = IoGetCurrentIrpStackLocation(irp);
      wnd = fdo->reserved;
      IOTRACE("fdo: %p, fn: %d:%d, wnd: %p", fdo, irp_sl->major_fn,
            irp_sl->minor_fn, wnd);
      if ((irp_sl->params.power.type == SystemPowerState &&
           irp_sl->params.power.state.system_state > PowerSystemWorking) ||
          (irp_sl->params.power.type == DevicePowerState &&
           irp_sl->params.power.state.device_state > PowerDeviceD0))
            state = NdisDeviceStateD3;
      else
            state = NdisDeviceStateD0;
      switch (irp_sl->minor_fn) {
      case IRP_MN_SET_POWER:
            if (state == NdisDeviceStateD0) {
                  status = IoSyncForwardIrp(wnd->nmb->pdo, irp);
                  if (status != STATUS_SUCCESS)
                        break;
                  ndis_status = mp_set_power_state(wnd, state);
                  if (ndis_status != NDIS_STATUS_SUCCESS)
                        WARNING("couldn't set power to %d: %08X",
                              state, ndis_status);
                  TRACE2("%s: device resumed", wnd->net_dev->name);
                  irp->io_status.status = status = STATUS_SUCCESS;
                  IoCompleteRequest(irp, IO_NO_INCREMENT);
                  break;
            } else {
                  ndis_status = mp_set_power_state(wnd, state);
                  /* TODO: handle error case */
                  if (ndis_status != NDIS_STATUS_SUCCESS)
                        WARNING("setting power to %d failed: %08X",
                              state, ndis_status);
                  status = IoAsyncForwardIrp(wnd->nmb->pdo, irp);
            }
            break;
      case IRP_MN_QUERY_POWER:
            if (wnd->attributes & NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND) {
                  ndis_status = mp_query(wnd, OID_PNP_QUERY_POWER,
                                     &state, sizeof(state));
                  TRACE2("%d, %08X", state, ndis_status);
                  /* this OID must always succeed */
                  if (ndis_status != NDIS_STATUS_SUCCESS)
                        TRACE1("query power returns %08X", ndis_status);
                  irp->io_status.status = STATUS_SUCCESS;
            } else
                  irp->io_status.status = STATUS_SUCCESS;
            status = IoPassIrpDown(wnd->nmb->pdo, irp);
            break;
      case IRP_MN_WAIT_WAKE:
      case IRP_MN_POWER_SEQUENCE:
            /* TODO: implement WAIT_WAKE */
            status = IoPassIrpDown(wnd->nmb->pdo, irp);
            break;
      default:
            status = IoPassIrpDown(wnd->nmb->pdo, irp);
            break;
      }
      IOEXIT(return status);
}
WIN_FUNC_DECL(NdisDispatchPower,2)

wstdcall NTSTATUS NdisDispatchPnp(struct device_object *fdo, struct irp *irp)
{
      struct io_stack_location *irp_sl;
      struct ndis_device *wnd;
      struct device_object *pdo;
      NTSTATUS status;

      IOTRACE("fdo: %p, irp: %p", fdo, irp);
      irp_sl = IoGetCurrentIrpStackLocation(irp);
      wnd = fdo->reserved;
      pdo = wnd->nmb->pdo;
      switch (irp_sl->minor_fn) {
      case IRP_MN_START_DEVICE:
            status = IoSyncForwardIrp(pdo, irp);
            if (status != STATUS_SUCCESS)
                  break;
            if (ndis_start_device(wnd) == NDIS_STATUS_SUCCESS)
                  status = STATUS_SUCCESS;
            else
                  status = STATUS_FAILURE;
            irp->io_status.status = status;
            IoCompleteRequest(irp, IO_NO_INCREMENT);
            break;
      case IRP_MN_QUERY_STOP_DEVICE:
            /* TODO: implement in NDIS */
            status = IoPassIrpDown(wnd->nmb->pdo, irp);
            break;
      case IRP_MN_STOP_DEVICE:
            mp_halt(wnd);
            irp->io_status.status = STATUS_SUCCESS;
            status = IoAsyncForwardIrp(pdo, irp);
            break;
      case IRP_MN_REMOVE_DEVICE:
            TRACE1("%s", wnd->net_dev->name);
            mp_pnp_event(wnd, NdisDevicePnPEventSurpriseRemoved, 0);
            if (ndis_remove_device(wnd)) {
                  status = STATUS_FAILURE;
                  break;
            }
            /* wnd is already freed */
            status = IoAsyncForwardIrp(pdo, irp);
            IoDetachDevice(fdo);
            IoDeleteDevice(fdo);
            break;
      default:
            status = IoAsyncForwardIrp(pdo, irp);
            break;
      }
      IOTRACE("status: %08X", status);
      IOEXIT(return status);
}
WIN_FUNC_DECL(NdisDispatchPnp,2)

static void set_task_offload(struct ndis_device *wnd, void *buf,
                       const int buf_size)
{
      struct ndis_task_offload_header *task_offload_header;
      struct ndis_task_offload *task_offload;
      struct ndis_task_tcp_ip_checksum *csum = NULL;
      struct ndis_task_tcp_large_send *tso = NULL;
      NDIS_STATUS status;

      memset(buf, 0, buf_size);
      task_offload_header = buf;
      task_offload_header->version = NDIS_TASK_OFFLOAD_VERSION;
      task_offload_header->size = sizeof(*task_offload_header);
      task_offload_header->encap_format.flags.fixed_header_size = 1;
      task_offload_header->encap_format.header_size = sizeof(struct ethhdr);
      task_offload_header->encap_format.encap = IEEE_802_3_Encapsulation;
      status = mp_query(wnd, OID_TCP_TASK_OFFLOAD, buf, buf_size);
      TRACE1("%08X", status);
      if (status != NDIS_STATUS_SUCCESS)
            EXIT1(return);
      if (task_offload_header->offset_first_task == 0)
            EXIT1(return);
      task_offload = ((void *)task_offload_header +
                  task_offload_header->offset_first_task);
      while (1) {
            TRACE1("%d, %d", task_offload->version, task_offload->task);
            switch(task_offload->task) {
            case TcpIpChecksumNdisTask:
                  csum = (void *)task_offload->task_buf;
                  break;
            case TcpLargeSendNdisTask:
                  tso = (void *)task_offload->task_buf;
                  break;
            default:
                  TRACE1("%d", task_offload->task);
                  break;
            }
            if (task_offload->offset_next_task == 0)
                  break;
            task_offload = (void *)task_offload +
                  task_offload->offset_next_task;
      }
      if (tso)
            TRACE1("%u, %u, %d, %d", tso->max_size, tso->min_seg_count,
                   tso->tcp_opts, tso->ip_opts);
      if (!csum)
            EXIT1(return);
      TRACE1("%08x, %08x", csum->v4_tx.value, csum->v4_rx.value);
      task_offload_header->encap_format.flags.fixed_header_size = 1;
      task_offload_header->encap_format.header_size = sizeof(struct ethhdr);
      task_offload_header->offset_first_task = sizeof(*task_offload_header);
      task_offload = ((void *)task_offload_header +
                  task_offload_header->offset_first_task);
      task_offload->offset_next_task = 0;
      task_offload->size = sizeof(*task_offload);
      task_offload->task = TcpIpChecksumNdisTask;
      memcpy(task_offload->task_buf, csum, sizeof(*csum));
      task_offload->task_buf_length = sizeof(*csum);
      status = mp_set(wnd, OID_TCP_TASK_OFFLOAD, task_offload_header,
                  sizeof(*task_offload_header) +
                  sizeof(*task_offload) + sizeof(*csum));
      TRACE1("%08X", status);
      if (status != NDIS_STATUS_SUCCESS)
            EXIT2(return);
      wnd->tx_csum = csum->v4_tx;
      if (csum->v4_tx.tcp_csum && csum->v4_tx.udp_csum) {
            if (csum->v4_tx.ip_csum) {
                  wnd->net_dev->features |= NETIF_F_HW_CSUM;
                  TRACE1("hw checksum enabled");
            } else {
                  wnd->net_dev->features |= NETIF_F_IP_CSUM;
                  TRACE1("IP checksum enabled");
            }
            if (wnd->sg_dma_size)
                  wnd->net_dev->features |= NETIF_F_SG;
      }
      wnd->rx_csum = csum->v4_rx;
      EXIT1(return);
}

static void get_supported_oids(struct ndis_device *wnd)
{
      NDIS_STATUS res;
      int i, n, needed;
      ndis_oid *oids;

      res = mp_query_info(wnd, OID_GEN_SUPPORTED_LIST, NULL, 0, NULL,
                      &needed);
      if (!(res == NDIS_STATUS_BUFFER_TOO_SHORT ||
            res == NDIS_STATUS_INVALID_LENGTH))
            EXIT1(return);
      oids = kmalloc(needed, GFP_KERNEL);
      if (!oids) {
            TRACE1("couldn't allocate memory");
            EXIT1(return);
      }
      res = mp_query(wnd, OID_GEN_SUPPORTED_LIST, oids, needed);
      if (res) {
            TRACE1("failed: %08X", res);
            kfree(oids);
            EXIT1(return);
      }
      for (i = 0, n = needed / sizeof(*oids); i < n; i++) {
            TRACE1("oid: %08X", oids[i]);
            /* if a wireless device didn't say so for
             * OID_GEN_PHYSICAL_MEDIUM (they should, but in case) */
            if (wnd->physical_medium != NdisPhysicalMediumWirelessLan &&
                oids[i] == OID_802_11_SSID)
                  wnd->physical_medium = NdisPhysicalMediumWirelessLan;
      }
      kfree(oids);
      EXIT1(return);
}

static void ndis_get_drvinfo(struct net_device *dev,
                       struct ethtool_drvinfo *info)
{
      struct ndis_device *wnd = netdev_priv(dev);
      strncpy(info->driver, DRIVER_NAME, sizeof(info->driver) - 2);
      strcat(info->driver, "+");
      strncat(info->driver, wnd->wd->driver->name,
            sizeof(info->driver) - strlen(DRIVER_NAME) - 1);
      strncpy(info->version, DRIVER_VERSION, sizeof(info->version) - 2);
      strcat(info->version, "+");
      strncat(info->version, wnd->wd->driver->version,
            sizeof(info->version) - strlen(DRIVER_VERSION) - 1);
      if (wrap_is_pci_bus(wnd->wd->dev_bus))
            strncpy(info->bus_info, pci_name(wnd->wd->pci.pdev),
                  sizeof(info->bus_info) - 1);
#ifdef ENABLE_USB
      else
            usb_make_path(wnd->wd->usb.udev, info->bus_info,
                        sizeof(info->bus_info) - 1);
#endif
      return;
}

static u32 ndis_get_link(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      return netif_carrier_ok(wnd->net_dev);
}

static void ndis_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
      struct ndis_device *wnd = netdev_priv(dev);

      wol->supported = 0;
      wol->wolopts = 0;
      if (!(wnd->attributes & NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND))
            EXIT2(return);
      if (!wrap_is_pci_bus(wnd->wd->dev_bus))
            EXIT2(return);
      /* we always suspend to D3 */
      if (wnd->pnp_capa.wakeup.min_magic_packet_wakeup < NdisDeviceStateD3)
            return;
      wol->supported |= WAKE_MAGIC;
      if (wnd->ndis_wolopts & NDIS_PNP_WAKE_UP_MAGIC_PACKET)
            wol->wolopts |= WAKE_MAGIC;
      return;
}

static int ndis_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
      struct ndis_device *wnd = netdev_priv(dev);

      if (!(wnd->attributes & NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND))
            return -EOPNOTSUPP;
      if (wnd->pnp_capa.wakeup.min_magic_packet_wakeup < NdisDeviceStateD3)
            EXIT2(return -EOPNOTSUPP);
      TRACE2("0x%x", wol->wolopts);
      if (wol->wolopts & WAKE_MAGIC) {
            wnd->ndis_wolopts |= NDIS_PNP_WAKE_UP_MAGIC_PACKET;
            if (wol->wolopts != WAKE_MAGIC)
                  WARNING("ignored wake-on-lan options: 0x%x",
                        wol->wolopts & ~WAKE_MAGIC);
      } else if (!wol->wolopts)
            wnd->ndis_wolopts = 0;
      else
            return -EOPNOTSUPP;
      TRACE2("0x%x", wnd->ndis_wolopts);
      return 0;
}

static u32 ndis_get_tx_csum(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      if (wnd->tx_csum.tcp_csum && wnd->tx_csum.udp_csum)
            return 1;
      else
            return 0;
}

static u32 ndis_get_rx_csum(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      if (wnd->rx_csum.value)
            return 1;
      else
            return 0;
}

static int ndis_set_tx_csum(struct net_device *dev, u32 data)
{
      struct ndis_device *wnd = netdev_priv(dev);

      if (data && (wnd->tx_csum.value == 0))
            return -EOPNOTSUPP;

      if (wnd->tx_csum.ip_csum)
            ethtool_op_set_tx_hw_csum(dev, data);
      else
            ethtool_op_set_tx_csum(dev, data);
      return 0;
}

static int ndis_set_rx_csum(struct net_device *dev, u32 data)
{
      struct ndis_device *wnd = netdev_priv(dev);

      if (data && (wnd->tx_csum.value == 0))
            return -EOPNOTSUPP;

      /* TODO: enable/disable rx csum through NDIS */
      return 0;
}

static u32 ndis_get_sg(struct net_device *dev)
{
      struct ndis_device *wnd = netdev_priv(dev);
      if (wnd->sg_dma_size)
            return ethtool_op_get_sg(dev);
      else
            return 0;
}

static int ndis_set_sg(struct net_device *dev, u32 data)
{
      struct ndis_device *wnd = netdev_priv(dev);
      if (wnd->sg_dma_size)
            return ethtool_op_set_sg(dev, data);
      else
            return -EOPNOTSUPP;
}

static struct ethtool_ops ndis_ethtool_ops = {
      .get_drvinfo      = ndis_get_drvinfo,
      .get_link   = ndis_get_link,
      .get_wol    = ndis_get_wol,
      .set_wol    = ndis_set_wol,
      .get_tx_csum      = ndis_get_tx_csum,
      .get_rx_csum      = ndis_get_rx_csum,
      .set_tx_csum      = ndis_set_tx_csum,
      .set_rx_csum      = ndis_set_rx_csum,
      .get_sg           = ndis_get_sg,
      .set_sg           = ndis_set_sg,
};

static int notifier_event(struct notifier_block *notifier, unsigned long event,
                    void *ptr)
{
      struct net_device *net_dev = ptr;

      ENTER2("0x%lx", event);
      if (net_dev->ethtool_ops == &ndis_ethtool_ops
          && event == NETDEV_CHANGENAME) {
            struct ndis_device *wnd = netdev_priv(net_dev);
            /* called with rtnl lock held, so no need to lock */
            wrap_procfs_remove_ndis_device(wnd);
            printk(KERN_INFO "%s: changing interface name from '%s' to "
                   "'%s'\n", DRIVER_NAME, wnd->netdev_name, net_dev->name);
            memcpy(wnd->netdev_name, net_dev->name,
                   sizeof(wnd->netdev_name));
            wrap_procfs_add_ndis_device(wnd);
      }
      return NOTIFY_DONE;
}

static struct notifier_block netdev_notifier = {
      .notifier_call = notifier_event,
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
static const struct net_device_ops ndis_netdev_ops = {
      .ndo_open = ndis_net_dev_open,
      .ndo_stop = ndis_net_dev_close,
      .ndo_start_xmit = tx_skbuff,
      .ndo_change_mtu = ndis_change_mtu,
      .ndo_set_multicast_list = ndis_set_multicast_list,
      .ndo_set_mac_address = ndis_set_mac_address,
      .ndo_get_stats = ndis_get_stats,
#ifdef CONFIG_NET_POLL_CONTROLLER
      .ndo_poll_controller = ndis_poll_controller,
#endif
};
#endif

static NDIS_STATUS ndis_start_device(struct ndis_device *wnd)
{
      struct wrap_device *wd;
      struct net_device *net_dev;
      NDIS_STATUS status;
      char *buf;
      const int buf_len = 256;
      mac_address mac;
      struct transport_header_offset *tx_header_offset;
      int n;

      ENTER2("%d", in_atomic());
      status = mp_init(wnd);
      if (status == NDIS_STATUS_NOT_RECOGNIZED)
            EXIT1(return NDIS_STATUS_SUCCESS);
      if (status != NDIS_STATUS_SUCCESS)
            EXIT1(return status);
      wd = wnd->wd;
      net_dev = wnd->net_dev;

      get_supported_oids(wnd);
      memset(mac, 0, sizeof(mac));
      status = mp_query(wnd, OID_802_3_CURRENT_ADDRESS, mac, sizeof(mac));
      if (memcmp(mac, "\x00\x00\x00\x00\x00\x00", sizeof(mac)) == 0) {
            status = mp_query(wnd, OID_802_3_PERMANENT_ADDRESS, mac,
                          sizeof(mac));
            if (status != NDIS_STATUS_SUCCESS) {
                  ERROR("couldn't get mac address: %08X", status);
                  goto err_start;
            }
      }
      TRACE1("mac:" MACSTRSEP, MAC2STR(mac));
      memcpy(net_dev->dev_addr, mac, ETH_ALEN);

      strncpy(net_dev->name, if_name, IFNAMSIZ - 1);
      net_dev->name[IFNAMSIZ - 1] = 0;

      wnd->packet_filter = NDIS_PACKET_TYPE_DIRECTED |
            NDIS_PACKET_TYPE_BROADCAST | NDIS_PACKET_TYPE_MULTICAST;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
      net_dev->netdev_ops = &ndis_netdev_ops;
#else
      net_dev->open = ndis_net_dev_open;
      net_dev->hard_start_xmit = tx_skbuff;
      net_dev->stop = ndis_net_dev_close;
      net_dev->get_stats = ndis_get_stats;
      net_dev->change_mtu = ndis_change_mtu;
      net_dev->set_multicast_list = ndis_set_multicast_list;
      net_dev->set_mac_address = ndis_set_mac_address;
#ifdef CONFIG_NET_POLL_CONTROLLER
      net_dev->poll_controller = ndis_poll_controller;
#endif
#endif
      if (wnd->physical_medium == NdisPhysicalMediumWirelessLan) {
            net_dev->wireless_handlers = &ndis_handler_def;
      }
      net_dev->ethtool_ops = &ndis_ethtool_ops;
      if (wnd->mp_interrupt)
            net_dev->irq = wnd->mp_interrupt->irq;
      net_dev->mem_start = wnd->mem_start;
      net_dev->mem_end = wnd->mem_end;
      status = mp_query_int(wnd, OID_802_3_MAXIMUM_LIST_SIZE,
                        &wnd->multicast_size);
      if (status != NDIS_STATUS_SUCCESS || wnd->multicast_size < 0)
            wnd->multicast_size = 0;
      if (wnd->multicast_size > 0)
            net_dev->flags |= IFF_MULTICAST;
      else
            net_dev->flags &= ~IFF_MULTICAST;

      buf = kmalloc(buf_len, GFP_KERNEL);
      if (!buf) {
            WARNING("couldn't allocate memory");
            goto err_start;
      }

      set_task_offload(wnd, buf, buf_len);
#ifdef NETIF_F_LLTX
      net_dev->features |= NETIF_F_LLTX;
#endif

      if (register_netdev(net_dev)) {
            ERROR("cannot register net device %s", net_dev->name);
            goto err_register;
      }
      memcpy(wnd->netdev_name, net_dev->name, sizeof(wnd->netdev_name));
      memset(buf, 0, buf_len);
      status = mp_query(wnd, OID_GEN_VENDOR_DESCRIPTION, buf, buf_len);
      if (status != NDIS_STATUS_SUCCESS) {
            WARNING("couldn't get vendor information: 0x%x", status);
            buf[0] = 0;
      }
      wnd->drv_ndis_version = n = 0;
      mp_query_int(wnd, OID_GEN_DRIVER_VERSION, &wnd->drv_ndis_version);
      mp_query_int(wnd, OID_GEN_VENDOR_DRIVER_VERSION, &n);

      printk(KERN_INFO "%s: ethernet device " MACSTRSEP " using %sNDIS "
             "driver: %s, version: 0x%x, NDIS version: 0x%x, vendor: '%s', "
             "%s\n", net_dev->name, MAC2STR(net_dev->dev_addr),
             deserialized_driver(wnd) ? "" : "serialized ",
             wnd->wd->driver->name, n, wnd->drv_ndis_version, buf,
             wnd->wd->conf_file_name);

      if (deserialized_driver(wnd)) {
            /* deserialized drivers don't have a limit, but we
             * keep max at TX_RING_SIZE */
            wnd->max_tx_packets = TX_RING_SIZE;
      } else {
            status = mp_query_int(wnd, OID_GEN_MAXIMUM_SEND_PACKETS,
                              &wnd->max_tx_packets);
            if (status != NDIS_STATUS_SUCCESS)
                  wnd->max_tx_packets = 1;
            if (wnd->max_tx_packets > TX_RING_SIZE)
                  wnd->max_tx_packets = TX_RING_SIZE;
      }
      TRACE2("maximum send packets: %d", wnd->max_tx_packets);
      NdisAllocatePacketPoolEx(&status, &wnd->tx_packet_pool,
                         wnd->max_tx_packets, 0,
                         PROTOCOL_RESERVED_SIZE_IN_PACKET);
      if (status != NDIS_STATUS_SUCCESS) {
            ERROR("couldn't allocate packet pool");
            goto packet_pool_err;
      }
      NdisAllocateBufferPool(&status, &wnd->tx_buffer_pool,
                         wnd->max_tx_packets + 4);
      if (status != NDIS_STATUS_SUCCESS) {
            ERROR("couldn't allocate buffer pool");
            goto buffer_pool_err;
      }
      TRACE1("pool: %p", wnd->tx_buffer_pool);

      if (mp_query_int(wnd, OID_GEN_MAXIMUM_TOTAL_SIZE, &n) ==
          NDIS_STATUS_SUCCESS && n > ETH_HLEN)
            ndis_change_mtu(wnd->net_dev, n - ETH_HLEN);

      if (mp_query_int(wnd, OID_GEN_MAC_OPTIONS, &n) == NDIS_STATUS_SUCCESS)
            TRACE2("mac options supported: 0x%x", n);

      tx_header_offset = (typeof(tx_header_offset))buf;
      tx_header_offset->protocol_type = NDIS_PROTOCOL_ID_TCP_IP;
      tx_header_offset->header_offset = sizeof(ETH_HLEN);
      status = mp_set(wnd, OID_GEN_TRANSPORT_HEADER_OFFSET,
                  tx_header_offset, sizeof(*tx_header_offset));
      TRACE2("%08X", status);

      status = mp_query_int(wnd, OID_GEN_PHYSICAL_MEDIUM,
                        &wnd->physical_medium);
      if (status != NDIS_STATUS_SUCCESS)
            wnd->physical_medium = NdisPhysicalMediumUnspecified;

      if (wnd->physical_medium == NdisPhysicalMediumWirelessLan) {
            mp_set_int(wnd, OID_802_11_POWER_MODE, NDIS_POWER_OFF);
            get_encryption_capa(wnd, buf, buf_len);
            TRACE1("capbilities = %ld", wnd->capa.encr);
            printk(KERN_INFO "%s: encryption modes supported: "
                   "%s%s%s%s%s%s%s\n", net_dev->name,
                   test_bit(Ndis802_11Encryption1Enabled, &wnd->capa.encr) ?
                   "WEP" : "none",

                   test_bit(Ndis802_11Encryption2Enabled, &wnd->capa.encr) ?
                   "; TKIP with WPA" : "",
                   test_bit(Ndis802_11AuthModeWPA2, &wnd->capa.auth) ?
                   ", WPA2" : "",
                   test_bit(Ndis802_11AuthModeWPA2PSK, &wnd->capa.auth) ?
                   ", WPA2PSK" : "",

                   test_bit(Ndis802_11Encryption3Enabled, &wnd->capa.encr) ?
                   "; AES/CCMP with WPA" : "",
                   test_bit(Ndis802_11AuthModeWPA2, &wnd->capa.auth) ?
                   ", WPA2" : "",
                   test_bit(Ndis802_11AuthModeWPA2PSK, &wnd->capa.auth) ?
                   ", WPA2PSK" : "");

            set_default_iw_params(wnd);
      }
      status = mp_query_int(wnd, OID_GEN_MEDIA_CONNECT_STATUS, (int *)buf);
      if (status == NDIS_STATUS_SUCCESS)
            set_media_state(wnd, *((int *)buf));
      kfree(buf);
      wrap_procfs_add_ndis_device(wnd);
      hangcheck_add(wnd);
      add_iw_stats_timer(wnd);
      EXIT1(return NDIS_STATUS_SUCCESS);

buffer_pool_err:
      wnd->tx_buffer_pool = NULL;
      if (wnd->tx_packet_pool) {
            NdisFreePacketPool(wnd->tx_packet_pool);
            wnd->tx_packet_pool = NULL;
      }
packet_pool_err:
err_register:
      kfree(buf);
err_start:
      ndis_remove_device(wnd);
      EXIT1(return NDIS_STATUS_FAILURE);
}

static int ndis_remove_device(struct ndis_device *wnd)
{
      s8 tx_pending;

      /* prevent setting essid during disassociation */
      memset(&wnd->essid, 0, sizeof(wnd->essid));
      wnd->tx_ok = 0;
      if (wnd->max_tx_packets)
            unregister_netdev(wnd->net_dev);
      netif_carrier_off(wnd->net_dev);
      /* if device is suspended, but resume failed, tx_ring_mutex
       * may already be locked */
      if (down_trylock(&wnd->tx_ring_mutex))
            WARNING("couldn't obtain tx_ring_mutex");
      spin_lock_bh(&wnd->tx_ring_lock);
      tx_pending = wnd->tx_ring_end - wnd->tx_ring_start;
      if (tx_pending < 0)
            tx_pending += TX_RING_SIZE;
      else if (tx_pending == 0 && wnd->is_tx_ring_full)
            tx_pending = TX_RING_SIZE - 1;
      wnd->is_tx_ring_full = 0;
      /* throw away pending packets */
      while (tx_pending-- > 0) {
            struct ndis_packet *packet;

            packet = wnd->tx_ring[wnd->tx_ring_start];
            free_tx_packet(wnd, packet, NDIS_STATUS_CLOSING);
            wnd->tx_ring_start = (wnd->tx_ring_start + 1) % TX_RING_SIZE;
      }
      spin_unlock_bh(&wnd->tx_ring_lock);
      up(&wnd->tx_ring_mutex);
      wrap_procfs_remove_ndis_device(wnd);
      mp_halt(wnd);
      ndis_exit_device(wnd);

      if (wnd->tx_packet_pool) {
            NdisFreePacketPool(wnd->tx_packet_pool);
            wnd->tx_packet_pool = NULL;
      }
      if (wnd->tx_buffer_pool) {
            NdisFreeBufferPool(wnd->tx_buffer_pool);
            wnd->tx_buffer_pool = NULL;
      }
      if (wnd->pmkids)
            kfree(wnd->pmkids);
      printk(KERN_INFO "%s: device %s removed\n", DRIVER_NAME,
             wnd->net_dev->name);
      kfree(wnd->nmb);
      free_netdev(wnd->net_dev);
      EXIT2(return 0);
}

static wstdcall NTSTATUS NdisAddDevice(struct driver_object *drv_obj,
                               struct device_object *pdo)
{
      struct device_object *fdo;
      struct ndis_mp_block *nmb;
      NTSTATUS status;
      struct ndis_device *wnd;
      struct net_device *net_dev;
      struct wrap_device *wd;
      unsigned long i;

      ENTER2("%p, %p", drv_obj, pdo);
      if (strlen(if_name) >= IFNAMSIZ) {
            ERROR("interface name '%s' is too long", if_name);
            return STATUS_INVALID_PARAMETER;
      }
      net_dev = alloc_etherdev(sizeof(*wnd));
      if (!net_dev) {
            ERROR("couldn't allocate device");
            return STATUS_RESOURCES;
      }
      wd = pdo->reserved;
      if (wrap_is_pci_bus(wd->dev_bus))
            SET_NETDEV_DEV(net_dev, &wd->pci.pdev->dev);
      if (wrap_is_usb_bus(wd->dev_bus))
            SET_NETDEV_DEV(net_dev, &wd->usb.intf->dev);
      status = IoCreateDevice(drv_obj, 0, NULL, FILE_DEVICE_UNKNOWN, 0,
                        FALSE, &fdo);
      if (status != STATUS_SUCCESS) {
            free_netdev(net_dev);
            EXIT2(return status);
      }
      wnd = netdev_priv(net_dev);
      TRACE1("wnd: %p", wnd);

      nmb = kmalloc(sizeof(*nmb), GFP_KERNEL);
      if (!nmb) {
            WARNING("couldn't allocate memory");
            IoDeleteDevice(fdo);
            free_netdev(net_dev);
            return STATUS_RESOURCES;
      }
#if defined(DEBUG) && DEBUG >= 6
      /* poison nmb so if a driver accesses uninitialized pointers, we
       * know what it is */
      for (i = 0; i < sizeof(*nmb) / sizeof(unsigned long); i++)
            ((unsigned long *)nmb)[i] = i + 0x8a3fc1;
#endif

      wnd->nmb = nmb;
      nmb->wnd = wnd;
      nmb->pdo = pdo;
      wd->wnd = wnd;
      wnd->wd = wd;
      wnd->net_dev = net_dev;
      fdo->reserved = wnd;
      nmb->fdo = fdo;
      if (ndis_init_device(wnd)) {
            IoDeleteDevice(fdo);
            kfree(nmb);
            free_netdev(net_dev);
            EXIT1(return STATUS_RESOURCES);
      }
      nmb->next_device = IoAttachDeviceToDeviceStack(fdo, pdo);
      spin_lock_init(&wnd->tx_ring_lock);
      init_MUTEX(&wnd->tx_ring_mutex);
      init_MUTEX(&wnd->ndis_req_mutex);
      wnd->ndis_req_done = 0;
      initialize_work(&wnd->tx_work, tx_worker, wnd);
      wnd->tx_ring_start = 0;
      wnd->tx_ring_end = 0;
      wnd->is_tx_ring_full = 0;
      wnd->capa.encr = 0;
      wnd->capa.auth = 0;
      wnd->attributes = 0;
      wnd->dma_map_count = 0;
      wnd->dma_map_addr = NULL;
      wnd->nick[0] = 0;
      init_timer(&wnd->hangcheck_timer);
      wnd->scan_timestamp = 0;
      init_timer(&wnd->iw_stats_timer);
      wnd->iw_stats_interval = 10 * HZ;
      wnd->ndis_pending_work = 0;
      memset(&wnd->essid, 0, sizeof(wnd->essid));
      memset(&wnd->encr_info, 0, sizeof(wnd->encr_info));
      wnd->infrastructure_mode = Ndis802_11Infrastructure;
      initialize_work(&wnd->ndis_work, ndis_worker, wnd);
      wnd->iw_stats_enabled = TRUE;

      TRACE1("nmb: %p, pdo: %p, fdo: %p, attached: %p, next: %p",
             nmb, pdo, fdo, fdo->attached, nmb->next_device);

      /* dispatch routines are called as Windows functions */
      for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
            drv_obj->major_func[i] = WIN_FUNC_PTR(IoPassIrpDown,2);

      drv_obj->major_func[IRP_MJ_PNP] = WIN_FUNC_PTR(NdisDispatchPnp,2);
      drv_obj->major_func[IRP_MJ_POWER] = WIN_FUNC_PTR(NdisDispatchPower,2);
      drv_obj->major_func[IRP_MJ_INTERNAL_DEVICE_CONTROL] =
            WIN_FUNC_PTR(NdisDispatchDeviceControl,2);
//    drv_obj->major_func[IRP_MJ_DEVICE_CONTROL] =
//          WIN_FUNC_PTR(NdisDispatchDeviceControl,2);
      EXIT2(return STATUS_SUCCESS);
}

int init_ndis_driver(struct driver_object *drv_obj)
{
      ENTER1("%p", drv_obj);
      drv_obj->drv_ext->add_device = NdisAddDevice;
      return 0;
}

int wrapndis_init(void)
{
      wrapndis_wq = create_singlethread_workqueue("wrapndis_wq");
      if (!wrapndis_wq)
            EXIT1(return -ENOMEM);
      wrapndis_worker_thread = wrap_worker_init(wrapndis_wq);
      TRACE1("%p", wrapndis_worker_thread);
      register_netdevice_notifier(&netdev_notifier);
      return 0;
}

void wrapndis_exit(void)
{
      unregister_netdevice_notifier(&netdev_notifier);
      if (wrapndis_wq)
            destroy_workqueue(wrapndis_wq);
      TRACE1("%p", wrapndis_worker_thread);
      if (wrapndis_worker_thread)
            ObDereferenceObject(wrapndis_worker_thread);
}

Generated by  Doxygen 1.6.0   Back to index