Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/inaky/wimax
authorDavid S. Miller <davem@davemloft.net>
Sun, 16 May 2010 06:14:16 +0000 (23:14 -0700)
committerDavid S. Miller <davem@davemloft.net>
Sun, 16 May 2010 06:14:16 +0000 (23:14 -0700)
1  2 
drivers/net/wimax/i2400m/control.c
drivers/net/wimax/i2400m/driver.c
drivers/net/wimax/i2400m/netdev.c
drivers/net/wimax/i2400m/rx.c

index f8a9734243d015fdefd5543476764233149b6d4c,0c1aa886753141f97a499c695107667e88b9d8a4..ac5e2c4e517c97a35e8c5b94e15f482bb2c282ea
  #define D_SUBMODULE control
  #include "debug-levels.h"
  
+ static int i2400m_idle_mode_disabled;/* 0 (idle mode enabled) by default */
+ module_param_named(idle_mode_disabled, i2400m_idle_mode_disabled, int, 0644);
+ MODULE_PARM_DESC(idle_mode_disabled,
+                "If true, the device will not enable idle mode negotiation "
+                "with the base station (when connected) to save power.");
+ /* 0 (power saving enabled) by default */
+ static int i2400m_power_save_disabled;
+ module_param_named(power_save_disabled, i2400m_power_save_disabled, int, 0644);
+ MODULE_PARM_DESC(power_save_disabled,
+                "If true, the driver will not tell the device to enter "
+                "power saving mode when it reports it is ready for it. "
+                "False by default (so the device is told to do power "
+                "saving).");
  int i2400m_passive_mode;      /* 0 (passive mode disabled) by default */
  module_param_named(passive_mode, i2400m_passive_mode, int, 0644);
  MODULE_PARM_DESC(passive_mode,
@@@ -568,6 -583,7 +583,6 @@@ void i2400m_msg_ack_hook(struct i2400m 
                }
                break;
        };
 -      return;
  }
  
  
@@@ -1418,4 -1434,5 +1433,4 @@@ void i2400m_dev_shutdown(struct i2400m 
  
        d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
        d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
 -      return;
  }
index 0043cc1152bdc77b17c51f48a4a922ba850d2afa,66bdb5d6cd3446d120deccc5b43db24c1d180572..9c8b78d4abd2f2b416c15cf3511f3bcdc11ca25e
  #include "debug-levels.h"
  
  
- int i2400m_idle_mode_disabled;        /* 0 (idle mode enabled) by default */
- module_param_named(idle_mode_disabled, i2400m_idle_mode_disabled, int, 0644);
- MODULE_PARM_DESC(idle_mode_disabled,
-                "If true, the device will not enable idle mode negotiation "
-                "with the base station (when connected) to save power.");
- int i2400m_rx_reorder_disabled;       /* 0 (rx reorder enabled) by default */
- module_param_named(rx_reorder_disabled, i2400m_rx_reorder_disabled, int, 0644);
- MODULE_PARM_DESC(rx_reorder_disabled,
-                "If true, RX reordering will be disabled.");
- int i2400m_power_save_disabled;       /* 0 (power saving enabled) by default */
- module_param_named(power_save_disabled, i2400m_power_save_disabled, int, 0644);
- MODULE_PARM_DESC(power_save_disabled,
-                "If true, the driver will not tell the device to enter "
-                "power saving mode when it reports it is ready for it. "
-                "False by default (so the device is told to do power "
-                "saving).");
  static char i2400m_debug_params[128];
  module_param_string(debug, i2400m_debug_params, sizeof(i2400m_debug_params),
                    0644);
@@@ -395,6 -376,16 +376,16 @@@ retry
        result = i2400m_dev_initialize(i2400m);
        if (result < 0)
                goto error_dev_initialize;
+       /* We don't want any additional unwanted error recovery triggered
+        * from any other context so if anything went wrong before we come
+        * here, let's keep i2400m->error_recovery untouched and leave it to
+        * dev_reset_handle(). See dev_reset_handle(). */
+       atomic_dec(&i2400m->error_recovery);
+       /* Every thing works so far, ok, now we are ready to
+        * take error recovery if it's required. */
        /* At this point, reports will come for the device and set it
         * to the right state if it is different than UNINITIALIZED */
        d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n",
  
  error_dev_initialize:
  error_check_mac_addr:
+ error_fw_check:
        i2400m->ready = 0;
        wmb();          /* see i2400m->ready's documentation  */
        flush_workqueue(i2400m->work_queue);
- error_fw_check:
        if (i2400m->bus_dev_stop)
                i2400m->bus_dev_stop(i2400m);
  error_bus_dev_start:
@@@ -436,7 -427,8 +427,8 @@@ int i2400m_dev_start(struct i2400m *i24
                result = __i2400m_dev_start(i2400m, bm_flags);
                if (result >= 0) {
                        i2400m->updown = 1;
-                       wmb();  /* see i2400m->updown's documentation */
+                       i2400m->alive = 1;
+                       wmb();/* see i2400m->updown and i2400m->alive's doc */
                }
        }
        mutex_unlock(&i2400m->init_mutex);
@@@ -497,7 -489,8 +489,8 @@@ void i2400m_dev_stop(struct i2400m *i24
        if (i2400m->updown) {
                __i2400m_dev_stop(i2400m);
                i2400m->updown = 0;
-               wmb();  /* see i2400m->updown's documentation  */
+               i2400m->alive = 0;
+               wmb();  /* see i2400m->updown and i2400m->alive's doc */
        }
        mutex_unlock(&i2400m->init_mutex);
  }
@@@ -617,12 -610,12 +610,12 @@@ int i2400m_post_reset(struct i2400m *i2
  error_dev_start:
        if (i2400m->bus_release)
                i2400m->bus_release(i2400m);
- error_bus_setup:
        /* even if the device was up, it could not be recovered, so we
         * mark it as down. */
        i2400m->updown = 0;
        wmb();          /* see i2400m->updown's documentation  */
        mutex_unlock(&i2400m->init_mutex);
+ error_bus_setup:
        d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
        return result;
  }
@@@ -669,6 -662,9 +662,9 @@@ void __i2400m_dev_reset_handle(struct w
  
        d_fnstart(3, dev, "(ws %p i2400m %p reason %s)\n", ws, i2400m, reason);
  
+       i2400m->boot_mode = 1;
+       wmb();          /* Make sure i2400m_msg_to_dev() sees boot_mode */
        result = 0;
        if (mutex_trylock(&i2400m->init_mutex) == 0) {
                /* We are still in i2400m_dev_start() [let it fail] or
                complete(&i2400m->msg_completion);
                goto out;
        }
-       if (i2400m->updown == 0)  {
-               dev_info(dev, "%s: device is down, doing nothing\n", reason);
-               goto out_unlock;
-       }
        dev_err(dev, "%s: reinitializing driver\n", reason);
-       __i2400m_dev_stop(i2400m);
-       result = __i2400m_dev_start(i2400m,
-                                   I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT);
-       if (result < 0) {
+       rmb();
+       if (i2400m->updown) {
+               __i2400m_dev_stop(i2400m);
                i2400m->updown = 0;
                wmb();          /* see i2400m->updown's documentation  */
-               dev_err(dev, "%s: cannot start the device: %d\n",
-                       reason, result);
-               result = -EUCLEAN;
        }
- out_unlock:
+       if (i2400m->alive) {
+               result = __i2400m_dev_start(i2400m,
+                                   I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT);
+               if (result < 0) {
+                       dev_err(dev, "%s: cannot start the device: %d\n",
+                               reason, result);
+                       result = -EUCLEAN;
+                       if (atomic_read(&i2400m->bus_reset_retries)
+                                       >= I2400M_BUS_RESET_RETRIES) {
+                               result = -ENODEV;
+                               dev_err(dev, "tried too many times to "
+                                       "reset the device, giving up\n");
+                       }
+               }
+       }
        if (i2400m->reset_ctx) {
                ctx->result = result;
                complete(&ctx->completion);
        }
        mutex_unlock(&i2400m->init_mutex);
        if (result == -EUCLEAN) {
+               /*
+                * We come here because the reset during operational mode
+                * wasn't successully done and need to proceed to a bus
+                * reset. For the dev_reset_handle() to be able to handle
+                * the reset event later properly, we restore boot_mode back
+                * to the state before previous reset. ie: just like we are
+                * issuing the bus reset for the first time
+                */
+               i2400m->boot_mode = 0;
+               wmb();
+               atomic_inc(&i2400m->bus_reset_retries);
                /* ops, need to clean up [w/ init_mutex not held] */
                result = i2400m_reset(i2400m, I2400M_RT_BUS);
                if (result >= 0)
                        result = -ENODEV;
+       } else {
+               rmb();
+               if (i2400m->alive) {
+                       /* great, we expect the device state up and
+                        * dev_start() actually brings the device state up */
+                       i2400m->updown = 1;
+                       wmb();
+                       atomic_set(&i2400m->bus_reset_retries, 0);
+               }
        }
  out:
        i2400m_put(i2400m);
        kfree(iw);
        d_fnend(3, dev, "(ws %p i2400m %p reason %s) = void\n",
                ws, i2400m, reason);
 -      return;
  }
  
  
   */
  int i2400m_dev_reset_handle(struct i2400m *i2400m, const char *reason)
  {
-       i2400m->boot_mode = 1;
-       wmb();          /* Make sure i2400m_msg_to_dev() sees boot_mode */
        return i2400m_schedule_work(i2400m, __i2400m_dev_reset_handle,
                                    GFP_ATOMIC, &reason, sizeof(reason));
  }
  EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle);
  
  
+  /*
+  * The actual work of error recovery.
+  *
+  * The current implementation of error recovery is to trigger a bus reset.
+  */
+ static
+ void __i2400m_error_recovery(struct work_struct *ws)
+ {
+       struct i2400m_work *iw = container_of(ws, struct i2400m_work, ws);
+       struct i2400m *i2400m = iw->i2400m;
+       i2400m_reset(i2400m, I2400M_RT_BUS);
+       i2400m_put(i2400m);
+       kfree(iw);
+       return;
+ }
+ /*
+  * Schedule a work struct for error recovery.
+  *
+  * The intention of error recovery is to bring back the device to some
+  * known state whenever TX sees -110 (-ETIMEOUT) on copying the data to
+  * the device. The TX failure could mean a device bus stuck, so the current
+  * error recovery implementation is to trigger a bus reset to the device
+  * and hopefully it can bring back the device.
+  *
+  * The actual work of error recovery has to be in a thread context because
+  * it is kicked off in the TX thread (i2400ms->tx_workqueue) which is to be
+  * destroyed by the error recovery mechanism (currently a bus reset).
+  *
+  * Also, there may be already a queue of TX works that all hit
+  * the -ETIMEOUT error condition because the device is stuck already.
+  * Since bus reset is used as the error recovery mechanism and we don't
+  * want consecutive bus resets simply because the multiple TX works
+  * in the queue all hit the same device erratum, the flag "error_recovery"
+  * is introduced for preventing unwanted consecutive bus resets.
+  *
+  * Error recovery shall only be invoked again if previous one was completed.
+  * The flag error_recovery is set when error recovery mechanism is scheduled,
+  * and is checked when we need to schedule another error recovery. If it is
+  * in place already, then we shouldn't schedule another one.
+  */
+ void i2400m_error_recovery(struct i2400m *i2400m)
+ {
+       struct device *dev = i2400m_dev(i2400m);
+       if (atomic_add_return(1, &i2400m->error_recovery) == 1) {
+               if (i2400m_schedule_work(i2400m, __i2400m_error_recovery,
+                       GFP_ATOMIC, NULL, 0) < 0) {
+                       dev_err(dev, "run out of memory for "
+                               "scheduling an error recovery ?\n");
+                       atomic_dec(&i2400m->error_recovery);
+               }
+       } else
+               atomic_dec(&i2400m->error_recovery);
+       return;
+ }
+ EXPORT_SYMBOL_GPL(i2400m_error_recovery);
  /*
   * Alloc the command and ack buffers for boot mode
   *
@@@ -802,6 -887,13 +886,13 @@@ void i2400m_init(struct i2400m *i2400m
  
        mutex_init(&i2400m->init_mutex);
        /* wake_tx_ws is initialized in i2400m_tx_setup() */
+       atomic_set(&i2400m->bus_reset_retries, 0);
+       i2400m->alive = 0;
+       /* initialize error_recovery to 1 for denoting we
+        * are not yet ready to take any error recovery */
+       atomic_set(&i2400m->error_recovery, 1);
  }
  EXPORT_SYMBOL_GPL(i2400m_init);
  
@@@ -995,6 -1087,7 +1086,6 @@@ void __exit i2400m_driver_exit(void
        /* for scheds i2400m_dev_reset_handle() */
        flush_scheduled_work();
        i2400m_barker_db_exit();
 -      return;
  }
  module_exit(i2400m_driver_exit);
  
index 149b9f57e5ccb5d90cfa2c882c363e6299515087,7d7b5ef0531c28b4e3e70a2facd6db3ad4f3ec86..94742e1eafe079d4dd99a33e1e144642f3cb6ad7
  
  enum {
  /* netdev interface */
-       /*
-        * Out of NWG spec (R1_v1.2.2), 3.3.3 ASN Bearer Plane MTU Size
-        *
-        * The MTU is 1400 or less
-        */
-       I2400M_MAX_MTU = 1400,
        /* 20 secs? yep, this is the maximum timeout that the device
         * might take to get out of IDLE / negotiate it with the base
         * station. We add 1sec for good measure. */
        I2400M_TX_TIMEOUT = 21 * HZ,
-       I2400M_TX_QLEN = 5,
+       /*
+        * Experimentation has determined that, 20 to be a good value
+        * for minimizing the jitter in the throughput.
+        */
+       I2400M_TX_QLEN = 20,
  };
  
  
@@@ -255,6 -253,7 +253,6 @@@ void i2400m_net_wake_stop(struct i2400
                kfree_skb(wake_tx_skb);
        }
        d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
 -      return;
  }
  
  
@@@ -433,6 -432,7 +431,6 @@@ void i2400m_tx_timeout(struct net_devic
         * this, there might be data pending to be sent or not...
         */
        net_dev->stats.tx_errors++;
 -      return;
  }
  
  
index 5fa841d913b21935d925d8bad32d2c02ea6b17e4,c835ae8b89ce6c8555589ffc5245875ededdbda8..6537593fae66cfc6cfc02c4b5dc95c7b35d4e572
  #define D_SUBMODULE rx
  #include "debug-levels.h"
  
+ static int i2400m_rx_reorder_disabled;        /* 0 (rx reorder enabled) by default */
+ module_param_named(rx_reorder_disabled, i2400m_rx_reorder_disabled, int, 0644);
+ MODULE_PARM_DESC(rx_reorder_disabled,
+                "If true, RX reordering will be disabled.");
  struct i2400m_report_hook_args {
        struct sk_buff *skb_rx;
        const struct i2400m_l3l4_hdr *l3l4_hdr;
@@@ -300,19 -305,19 +305,18 @@@ void i2400m_rx_ctl_ack(struct i2400m *i
                d_printf(1, dev, "Huh? waiter for command reply cancelled\n");
                goto error_waiter_cancelled;
        }
-       if (ack_skb == NULL) {
+       if (IS_ERR(ack_skb))
                dev_err(dev, "CMD/GET/SET ack: cannot allocate SKB\n");
-               i2400m->ack_skb = ERR_PTR(-ENOMEM);
-       } else
-               i2400m->ack_skb = ack_skb;
+       i2400m->ack_skb = ack_skb;
        spin_unlock_irqrestore(&i2400m->rx_lock, flags);
        complete(&i2400m->msg_completion);
        return;
  
  error_waiter_cancelled:
-       kfree_skb(ack_skb);
+       if (!IS_ERR(ack_skb))
+               kfree_skb(ack_skb);
  error_no_waiter:
        spin_unlock_irqrestore(&i2400m->rx_lock, flags);
 -      return;
  }
  
  
@@@ -717,6 -722,7 +721,6 @@@ void __i2400m_roq_queue(struct i2400m *
  out:
        d_fnend(4, dev, "(i2400m %p roq %p skb %p sn %u nsn %d) = void\n",
                i2400m, roq, skb, sn, nsn);
 -      return;
  }
  
  
@@@ -741,12 -747,12 +745,12 @@@ unsigned __i2400m_roq_update_ws(struct 
        unsigned new_nws, nsn_itr;
  
        new_nws = __i2400m_roq_nsn(roq, sn);
-       if (unlikely(new_nws >= 1024) && d_test(1)) {
-               dev_err(dev, "SW BUG? __update_ws new_nws %u (sn %u ws %u)\n",
-                       new_nws, sn, roq->ws);
-               WARN_ON(1);
-               i2400m_roq_log_dump(i2400m, roq);
-       }
+       /*
+        * For type 2(update_window_start) rx messages, there is no
+        * need to check if the normalized sequence number is greater 1023.
+        * Simply insert and deliver all packets to the host up to the
+        * window start.
+        */
        skb_queue_walk_safe(&roq->queue, skb_itr, tmp_itr) {
                roq_data_itr = (struct i2400m_roq_data *) &skb_itr->cb;
                nsn_itr = __i2400m_roq_nsn(roq, roq_data_itr->sn);
@@@ -796,6 -802,7 +800,6 @@@ void i2400m_roq_reset(struct i2400m *i2
        }
        roq->ws = 0;
        d_fnend(2, dev, "(i2400m %p roq %p) = void\n", i2400m, roq);
 -      return;
  }
  
  
@@@ -834,6 -841,7 +838,6 @@@ void i2400m_roq_queue(struct i2400m *i2
        }
        d_fnend(2, dev, "(i2400m %p roq %p skb %p lbn %u) = void\n",
                i2400m, roq, skb, lbn);
 -      return;
  }
  
  
@@@ -859,6 -867,7 +863,6 @@@ void i2400m_roq_update_ws(struct i2400
        i2400m_roq_log_add(i2400m, roq, I2400M_RO_TYPE_WS,
                             old_ws, len, sn, nsn, roq->ws);
        d_fnstart(2, dev, "(i2400m %p roq %p sn %u) = void\n", i2400m, roq, sn);
 -      return;
  }
  
  
@@@ -885,31 -894,52 +889,51 @@@ void i2400m_roq_queue_update_ws(struct 
                  i2400m, roq, skb, sn);
        len = skb_queue_len(&roq->queue);
        nsn = __i2400m_roq_nsn(roq, sn);
+       /*
+        * For type 3(queue_update_window_start) rx messages, there is no
+        * need to check if the normalized sequence number is greater 1023.
+        * Simply insert and deliver all packets to the host up to the
+        * window start.
+        */
        old_ws = roq->ws;
-       if (unlikely(nsn >= 1024)) {
-               dev_err(dev, "SW BUG? queue_update_ws nsn %u (sn %u ws %u)\n",
-                       nsn, sn, roq->ws);
-               i2400m_roq_log_dump(i2400m, roq);
-               i2400m_reset(i2400m, I2400M_RT_WARM);
-       } else {
-               /* if the queue is empty, don't bother as we'd queue
-                * it and inmediately unqueue it -- just deliver it */
-               if (len == 0) {
-                       struct i2400m_roq_data *roq_data;
-                       roq_data = (struct i2400m_roq_data *) &skb->cb;
-                       i2400m_net_erx(i2400m, skb, roq_data->cs);
-               }
-               else
-                       __i2400m_roq_queue(i2400m, roq, skb, sn, nsn);
-               __i2400m_roq_update_ws(i2400m, roq, sn + 1);
-               i2400m_roq_log_add(i2400m, roq, I2400M_RO_TYPE_PACKET_WS,
-                                  old_ws, len, sn, nsn, roq->ws);
-       }
+       /* If the queue is empty, don't bother as we'd queue
+        * it and immediately unqueue it -- just deliver it.
+        */
+       if (len == 0) {
+               struct i2400m_roq_data *roq_data;
+               roq_data = (struct i2400m_roq_data *) &skb->cb;
+               i2400m_net_erx(i2400m, skb, roq_data->cs);
+       } else
+               __i2400m_roq_queue(i2400m, roq, skb, sn, nsn);
+       __i2400m_roq_update_ws(i2400m, roq, sn + 1);
+       i2400m_roq_log_add(i2400m, roq, I2400M_RO_TYPE_PACKET_WS,
+                          old_ws, len, sn, nsn, roq->ws);
        d_fnend(2, dev, "(i2400m %p roq %p skb %p sn %u) = void\n",
                i2400m, roq, skb, sn);
 -      return;
  }
  
  
+ /*
+  * This routine destroys the memory allocated for rx_roq, when no
+  * other thread is accessing it. Access to rx_roq is refcounted by
+  * rx_roq_refcount, hence memory allocated must be destroyed when
+  * rx_roq_refcount becomes zero. This routine gets executed when
+  * rx_roq_refcount becomes zero.
+  */
+ void i2400m_rx_roq_destroy(struct kref *ref)
+ {
+       unsigned itr;
+       struct i2400m *i2400m
+                       = container_of(ref, struct i2400m, rx_roq_refcount);
+       for (itr = 0; itr < I2400M_RO_CIN + 1; itr++)
+               __skb_queue_purge(&i2400m->rx_roq[itr].queue);
+       kfree(i2400m->rx_roq[0].log);
+       kfree(i2400m->rx_roq);
+       i2400m->rx_roq = NULL;
+ }
  /*
   * Receive and send up an extended data packet
   *
@@@ -963,6 -993,7 +987,7 @@@ void i2400m_rx_edata(struct i2400m *i24
        unsigned ro_needed, ro_type, ro_cin, ro_sn;
        struct i2400m_roq *roq;
        struct i2400m_roq_data *roq_data;
+       unsigned long flags;
  
        BUILD_BUG_ON(ETH_HLEN > sizeof(*hdr));
  
                ro_cin = (reorder >> I2400M_RO_CIN_SHIFT) & I2400M_RO_CIN;
                ro_sn = (reorder >> I2400M_RO_SN_SHIFT) & I2400M_RO_SN;
  
+               spin_lock_irqsave(&i2400m->rx_lock, flags);
                roq = &i2400m->rx_roq[ro_cin];
+               if (roq == NULL) {
+                       kfree_skb(skb); /* rx_roq is already destroyed */
+                       spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+                       goto error;
+               }
+               kref_get(&i2400m->rx_roq_refcount);
+               spin_unlock_irqrestore(&i2400m->rx_lock, flags);
                roq_data = (struct i2400m_roq_data *) &skb->cb;
                roq_data->sn = ro_sn;
                roq_data->cs = cs;
                default:
                        dev_err(dev, "HW BUG? unknown reorder type %u\n", ro_type);
                }
+               spin_lock_irqsave(&i2400m->rx_lock, flags);
+               kref_put(&i2400m->rx_roq_refcount, i2400m_rx_roq_destroy);
+               spin_unlock_irqrestore(&i2400m->rx_lock, flags);
        }
        else
                i2400m_net_erx(i2400m, skb, cs);
@@@ -1035,6 -1079,7 +1073,6 @@@ error_skb_clone
  error:
        d_fnend(2, dev, "(i2400m %p skb_rx %p single %u payload %p "
                "size %zu) = void\n", i2400m, skb_rx, single_last, payload, size);
 -      return;
  }
  
  
@@@ -1337,6 -1382,7 +1375,7 @@@ int i2400m_rx_setup(struct i2400m *i240
                        __i2400m_roq_init(&i2400m->rx_roq[itr]);
                        i2400m->rx_roq[itr].log = &rd[itr];
                }
+               kref_init(&i2400m->rx_roq_refcount);
        }
        return 0;
  
@@@ -1350,12 -1396,12 +1389,12 @@@ error_roq_alloc
  /* Tear down the RX queue and infrastructure */
  void i2400m_rx_release(struct i2400m *i2400m)
  {
+       unsigned long flags;
        if (i2400m->rx_reorder) {
-               unsigned itr;
-               for(itr = 0; itr < I2400M_RO_CIN + 1; itr++)
-                       __skb_queue_purge(&i2400m->rx_roq[itr].queue);
-               kfree(i2400m->rx_roq[0].log);
-               kfree(i2400m->rx_roq);
+               spin_lock_irqsave(&i2400m->rx_lock, flags);
+               kref_put(&i2400m->rx_roq_refcount, i2400m_rx_roq_destroy);
+               spin_unlock_irqrestore(&i2400m->rx_lock, flags);
        }
        /* at this point, nothing can be received... */
        i2400m_report_hook_flush(i2400m);