aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvo van Doorn <ivdoorn@gmail.com>2010-08-06 14:45:38 -0400
committerJohn W. Linville <linville@tuxdriver.com>2010-08-16 15:26:41 -0400
commit7e613e1666d59b5364f7918b3427bf328ac5f9ca (patch)
tree147a7175901e86129dbe442095816da0f8eb7969
parentc17512d846a4b063c8d3e708d82c0664d9c7182e (diff)
rt2x00: Move USB tx/rx done handling to workqueue
Move all TX and RX completion handling into a work structure, which is handeled on the mac80211 workqueue. This simplifies the code in rt2x00lib since it no longer needs to check if the device is USB or PCI to decide which mac80211 function should be used. In the watchdog some changes are needed since it can no longer rely on the TX completion function to be run while looping through the entries. (Both functions now work on the same workqueue, so this would deadlock). So the watchdog now waits for the URB to return, and handle the TX status report directly. As a side-effect, the debugfs entry for the RX queue now correctly displays the positions of the INDEX and INDEX_DONE counters. This also implies that it is not possible to perform checks like queue_empty() and queue_full() on the RX queue. Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00.h9
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00dev.c35
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00queue.c7
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00queue.h6
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00usb.c133
5 files changed, 128 insertions, 62 deletions
diff --git a/drivers/net/wireless/rt2x00/rt2x00.h b/drivers/net/wireless/rt2x00/rt2x00.h
index c21af38cc5af..5276c19f380a 100644
--- a/drivers/net/wireless/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/rt2x00/rt2x00.h
@@ -1,5 +1,6 @@
1/* 1/*
2 Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> 2 Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
3 Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
3 Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com> 4 Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com>
4 <http://rt2x00.serialmonkey.com> 5 <http://rt2x00.serialmonkey.com>
5 6
@@ -862,6 +863,12 @@ struct rt2x00_dev {
862 */ 863 */
863 struct work_struct intf_work; 864 struct work_struct intf_work;
864 865
866 /**
867 * Scheduled work for TX/RX done handling (USB devices)
868 */
869 struct work_struct rxdone_work;
870 struct work_struct txdone_work;
871
865 /* 872 /*
866 * Data queue arrays for RX, TX and Beacon. 873 * Data queue arrays for RX, TX and Beacon.
867 * The Beacon array also contains the Atim queue 874 * The Beacon array also contains the Atim queue
diff --git a/drivers/net/wireless/rt2x00/rt2x00dev.c b/drivers/net/wireless/rt2x00/rt2x00dev.c
index 585e8166f22a..94621bd4fb9b 100644
--- a/drivers/net/wireless/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/rt2x00/rt2x00dev.c
@@ -1,5 +1,6 @@
1/* 1/*
2 Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> 2 Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
3 Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
3 <http://rt2x00.serialmonkey.com> 4 <http://rt2x00.serialmonkey.com>
4 5
5 This program is free software; you can redistribute it and/or modify 6 This program is free software; you can redistribute it and/or modify
@@ -383,15 +384,7 @@ void rt2x00lib_txdone(struct queue_entry *entry,
383 * send the status report back. 384 * send the status report back.
384 */ 385 */
385 if (!(skbdesc_flags & SKBDESC_NOT_MAC80211)) 386 if (!(skbdesc_flags & SKBDESC_NOT_MAC80211))
386 /* 387 ieee80211_tx_status(rt2x00dev->hw, entry->skb);
387 * Only PCI and SOC devices process the tx status in process
388 * context. Hence use ieee80211_tx_status for PCI and SOC
389 * devices and stick to ieee80211_tx_status_irqsafe for USB.
390 */
391 if (rt2x00_is_usb(rt2x00dev))
392 ieee80211_tx_status_irqsafe(rt2x00dev->hw, entry->skb);
393 else
394 ieee80211_tx_status(rt2x00dev->hw, entry->skb);
395 else 388 else
396 dev_kfree_skb_any(entry->skb); 389 dev_kfree_skb_any(entry->skb);
397 390
@@ -403,7 +396,6 @@ void rt2x00lib_txdone(struct queue_entry *entry,
403 396
404 rt2x00dev->ops->lib->clear_entry(entry); 397 rt2x00dev->ops->lib->clear_entry(entry);
405 398
406 clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags);
407 rt2x00queue_index_inc(entry->queue, Q_INDEX_DONE); 399 rt2x00queue_index_inc(entry->queue, Q_INDEX_DONE);
408 400
409 /* 401 /*
@@ -463,6 +455,10 @@ void rt2x00lib_rxdone(struct rt2x00_dev *rt2x00dev,
463 struct ieee80211_rx_status *rx_status = &rt2x00dev->rx_status; 455 struct ieee80211_rx_status *rx_status = &rt2x00dev->rx_status;
464 unsigned int header_length; 456 unsigned int header_length;
465 int rate_idx; 457 int rate_idx;
458
459 if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags))
460 goto submit_entry;
461
466 /* 462 /*
467 * Allocate a new sk_buffer. If no new buffer available, drop the 463 * Allocate a new sk_buffer. If no new buffer available, drop the
468 * received frame and reuse the existing buffer. 464 * received frame and reuse the existing buffer.
@@ -540,26 +536,17 @@ void rt2x00lib_rxdone(struct rt2x00_dev *rt2x00dev,
540 */ 536 */
541 rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_RXDONE, entry->skb); 537 rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_RXDONE, entry->skb);
542 memcpy(IEEE80211_SKB_RXCB(entry->skb), rx_status, sizeof(*rx_status)); 538 memcpy(IEEE80211_SKB_RXCB(entry->skb), rx_status, sizeof(*rx_status));
543 539 ieee80211_rx_ni(rt2x00dev->hw, entry->skb);
544 /*
545 * Currently only PCI and SOC devices handle rx interrupts in process
546 * context. Hence, use ieee80211_rx_irqsafe for USB and ieee80211_rx_ni
547 * for PCI and SOC devices.
548 */
549 if (rt2x00_is_usb(rt2x00dev))
550 ieee80211_rx_irqsafe(rt2x00dev->hw, entry->skb);
551 else
552 ieee80211_rx_ni(rt2x00dev->hw, entry->skb);
553 540
554 /* 541 /*
555 * Replace the skb with the freshly allocated one. 542 * Replace the skb with the freshly allocated one.
556 */ 543 */
557 entry->skb = skb; 544 entry->skb = skb;
558 entry->flags = 0;
559 545
546submit_entry:
560 rt2x00dev->ops->lib->clear_entry(entry); 547 rt2x00dev->ops->lib->clear_entry(entry);
561
562 rt2x00queue_index_inc(entry->queue, Q_INDEX); 548 rt2x00queue_index_inc(entry->queue, Q_INDEX);
549 rt2x00queue_index_inc(entry->queue, Q_INDEX_DONE);
563} 550}
564EXPORT_SYMBOL_GPL(rt2x00lib_rxdone); 551EXPORT_SYMBOL_GPL(rt2x00lib_rxdone);
565 552
@@ -1017,6 +1004,8 @@ void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev)
1017 * Stop all work. 1004 * Stop all work.
1018 */ 1005 */
1019 cancel_work_sync(&rt2x00dev->intf_work); 1006 cancel_work_sync(&rt2x00dev->intf_work);
1007 cancel_work_sync(&rt2x00dev->rxdone_work);
1008 cancel_work_sync(&rt2x00dev->txdone_work);
1020 1009
1021 /* 1010 /*
1022 * Uninitialize device. 1011 * Uninitialize device.
diff --git a/drivers/net/wireless/rt2x00/rt2x00queue.c b/drivers/net/wireless/rt2x00/rt2x00queue.c
index a3401d301058..18220953851e 100644
--- a/drivers/net/wireless/rt2x00/rt2x00queue.c
+++ b/drivers/net/wireless/rt2x00/rt2x00queue.c
@@ -1,5 +1,6 @@
1/* 1/*
2 Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> 2 Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
3 Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
3 Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com> 4 Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com>
4 <http://rt2x00.serialmonkey.com> 5 <http://rt2x00.serialmonkey.com>
5 6
@@ -730,9 +731,9 @@ void rt2x00queue_init_queues(struct rt2x00_dev *rt2x00dev)
730 rt2x00queue_reset(queue); 731 rt2x00queue_reset(queue);
731 732
732 for (i = 0; i < queue->limit; i++) { 733 for (i = 0; i < queue->limit; i++) {
733 queue->entries[i].flags = 0;
734
735 rt2x00dev->ops->lib->clear_entry(&queue->entries[i]); 734 rt2x00dev->ops->lib->clear_entry(&queue->entries[i]);
735 if (queue->qid == QID_RX)
736 rt2x00queue_index_inc(queue, Q_INDEX);
736 } 737 }
737 } 738 }
738} 739}
diff --git a/drivers/net/wireless/rt2x00/rt2x00queue.h b/drivers/net/wireless/rt2x00/rt2x00queue.h
index 191e7775a9c0..38b47919c868 100644
--- a/drivers/net/wireless/rt2x00/rt2x00queue.h
+++ b/drivers/net/wireless/rt2x00/rt2x00queue.h
@@ -1,5 +1,5 @@
1/* 1/*
2 Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> 2 Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
3 <http://rt2x00.serialmonkey.com> 3 <http://rt2x00.serialmonkey.com>
4 4
5 This program is free software; you can redistribute it and/or modify 5 This program is free software; you can redistribute it and/or modify
@@ -363,12 +363,16 @@ struct txentry_desc {
363 * the device has signaled it is done with it. 363 * the device has signaled it is done with it.
364 * @ENTRY_DATA_PENDING: This entry contains a valid frame and is waiting 364 * @ENTRY_DATA_PENDING: This entry contains a valid frame and is waiting
365 * for the signal to start sending. 365 * for the signal to start sending.
366 * @ENTRY_DATA_IO_FAILED: Hardware indicated that an IO error occured
367 * while transfering the data to the hardware. No TX status report will
368 * be expected from the hardware.
366 */ 369 */
367enum queue_entry_flags { 370enum queue_entry_flags {
368 ENTRY_BCN_ASSIGNED, 371 ENTRY_BCN_ASSIGNED,
369 ENTRY_OWNER_DEVICE_DATA, 372 ENTRY_OWNER_DEVICE_DATA,
370 ENTRY_OWNER_DEVICE_CRYPTO, 373 ENTRY_OWNER_DEVICE_CRYPTO,
371 ENTRY_DATA_PENDING, 374 ENTRY_DATA_PENDING,
375 ENTRY_DATA_IO_FAILED
372}; 376};
373 377
374/** 378/**
diff --git a/drivers/net/wireless/rt2x00/rt2x00usb.c b/drivers/net/wireless/rt2x00/rt2x00usb.c
index ff3a36622d1b..3f131017fd21 100644
--- a/drivers/net/wireless/rt2x00/rt2x00usb.c
+++ b/drivers/net/wireless/rt2x00/rt2x00usb.c
@@ -1,5 +1,6 @@
1/* 1/*
2 Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> 2 Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
3 Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
3 <http://rt2x00.serialmonkey.com> 4 <http://rt2x00.serialmonkey.com>
4 5
5 This program is free software; you can redistribute it and/or modify 6 This program is free software; you can redistribute it and/or modify
@@ -167,19 +168,12 @@ EXPORT_SYMBOL_GPL(rt2x00usb_regbusy_read);
167/* 168/*
168 * TX data handlers. 169 * TX data handlers.
169 */ 170 */
170static void rt2x00usb_interrupt_txdone(struct urb *urb) 171static void rt2x00usb_work_txdone_entry(struct queue_entry *entry)
171{ 172{
172 struct queue_entry *entry = (struct queue_entry *)urb->context;
173 struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
174 struct txdone_entry_desc txdesc; 173 struct txdone_entry_desc txdesc;
175 174
176 if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) ||
177 !test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
178 return;
179
180 /* 175 /*
181 * Obtain the status about this packet. 176 * If the transfer to hardware succeeded, it does not mean the
182 * Note that when the status is 0 it does not mean the
183 * frame was send out correctly. It only means the frame 177 * frame was send out correctly. It only means the frame
184 * was succesfully pushed to the hardware, we have no 178 * was succesfully pushed to the hardware, we have no
185 * way to determine the transmission status right now. 179 * way to determine the transmission status right now.
@@ -187,15 +181,56 @@ static void rt2x00usb_interrupt_txdone(struct urb *urb)
187 * in the register). 181 * in the register).
188 */ 182 */
189 txdesc.flags = 0; 183 txdesc.flags = 0;
190 if (!urb->status) 184 if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags))
191 __set_bit(TXDONE_UNKNOWN, &txdesc.flags);
192 else
193 __set_bit(TXDONE_FAILURE, &txdesc.flags); 185 __set_bit(TXDONE_FAILURE, &txdesc.flags);
186 else
187 __set_bit(TXDONE_UNKNOWN, &txdesc.flags);
194 txdesc.retry = 0; 188 txdesc.retry = 0;
195 189
196 rt2x00lib_txdone(entry, &txdesc); 190 rt2x00lib_txdone(entry, &txdesc);
197} 191}
198 192
193static void rt2x00usb_work_txdone(struct work_struct *work)
194{
195 struct rt2x00_dev *rt2x00dev =
196 container_of(work, struct rt2x00_dev, txdone_work);
197 struct data_queue *queue;
198 struct queue_entry *entry;
199
200 tx_queue_for_each(rt2x00dev, queue) {
201 while (!rt2x00queue_empty(queue)) {
202 entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
203
204 if (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
205 break;
206
207 rt2x00usb_work_txdone_entry(entry);
208 }
209 }
210}
211
212static void rt2x00usb_interrupt_txdone(struct urb *urb)
213{
214 struct queue_entry *entry = (struct queue_entry *)urb->context;
215 struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
216
217 if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) ||
218 !__test_and_clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
219 return;
220
221 /*
222 * Check if the frame was correctly uploaded
223 */
224 if (urb->status)
225 __set_bit(ENTRY_DATA_IO_FAILED, &entry->flags);
226
227 /*
228 * Schedule the delayed work for reading the TX status
229 * from the device.
230 */
231 ieee80211_queue_work(rt2x00dev->hw, &rt2x00dev->txdone_work);
232}
233
199static inline void rt2x00usb_kick_tx_entry(struct queue_entry *entry) 234static inline void rt2x00usb_kick_tx_entry(struct queue_entry *entry)
200{ 235{
201 struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; 236 struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
@@ -294,6 +329,7 @@ EXPORT_SYMBOL_GPL(rt2x00usb_kill_tx_queue);
294 329
295static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue) 330static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue)
296{ 331{
332 struct queue_entry *entry;
297 struct queue_entry_priv_usb *entry_priv; 333 struct queue_entry_priv_usb *entry_priv;
298 unsigned short threshold = queue->threshold; 334 unsigned short threshold = queue->threshold;
299 335
@@ -313,14 +349,22 @@ static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue)
313 * Reset all currently uploaded TX frames. 349 * Reset all currently uploaded TX frames.
314 */ 350 */
315 while (!rt2x00queue_empty(queue)) { 351 while (!rt2x00queue_empty(queue)) {
316 entry_priv = rt2x00queue_get_entry(queue, Q_INDEX_DONE)->priv_data; 352 entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
353 entry_priv = entry->priv_data;
317 usb_kill_urb(entry_priv->urb); 354 usb_kill_urb(entry_priv->urb);
318 355
319 /* 356 /*
320 * We need a short delay here to wait for 357 * We need a short delay here to wait for
321 * the URB to be canceled and invoked the tx_done handler. 358 * the URB to be canceled
322 */ 359 */
323 udelay(200); 360 do {
361 udelay(100);
362 } while (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags));
363
364 /*
365 * Invoke the TX done handler
366 */
367 rt2x00usb_work_txdone_entry(entry);
324 } 368 }
325 369
326 /* 370 /*
@@ -345,15 +389,41 @@ EXPORT_SYMBOL_GPL(rt2x00usb_watchdog);
345/* 389/*
346 * RX data handlers. 390 * RX data handlers.
347 */ 391 */
392static void rt2x00usb_work_rxdone(struct work_struct *work)
393{
394 struct rt2x00_dev *rt2x00dev =
395 container_of(work, struct rt2x00_dev, rxdone_work);
396 struct queue_entry *entry;
397 struct skb_frame_desc *skbdesc;
398 u8 rxd[32];
399
400 while (!rt2x00queue_empty(rt2x00dev->rx)) {
401 entry = rt2x00queue_get_entry(rt2x00dev->rx, Q_INDEX_DONE);
402
403 if (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
404 break;
405
406 /*
407 * Fill in desc fields of the skb descriptor
408 */
409 skbdesc = get_skb_frame_desc(entry->skb);
410 skbdesc->desc = rxd;
411 skbdesc->desc_len = entry->queue->desc_size;
412
413 /*
414 * Send the frame to rt2x00lib for further processing.
415 */
416 rt2x00lib_rxdone(rt2x00dev, entry);
417 }
418}
419
348static void rt2x00usb_interrupt_rxdone(struct urb *urb) 420static void rt2x00usb_interrupt_rxdone(struct urb *urb)
349{ 421{
350 struct queue_entry *entry = (struct queue_entry *)urb->context; 422 struct queue_entry *entry = (struct queue_entry *)urb->context;
351 struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; 423 struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
352 struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb);
353 u8 rxd[32];
354 424
355 if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) || 425 if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) ||
356 !test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags)) 426 !__test_and_clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
357 return; 427 return;
358 428
359 /* 429 /*
@@ -361,22 +431,14 @@ static void rt2x00usb_interrupt_rxdone(struct urb *urb)
361 * to be actually valid, or if the urb is signaling 431 * to be actually valid, or if the urb is signaling
362 * a problem. 432 * a problem.
363 */ 433 */
364 if (urb->actual_length < entry->queue->desc_size || urb->status) { 434 if (urb->actual_length < entry->queue->desc_size || urb->status)
365 set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags); 435 __set_bit(ENTRY_DATA_IO_FAILED, &entry->flags);
366 usb_submit_urb(urb, GFP_ATOMIC);
367 return;
368 }
369
370 /*
371 * Fill in desc fields of the skb descriptor
372 */
373 skbdesc->desc = rxd;
374 skbdesc->desc_len = entry->queue->desc_size;
375 436
376 /* 437 /*
377 * Send the frame to rt2x00lib for further processing. 438 * Schedule the delayed work for reading the RX status
439 * from the device.
378 */ 440 */
379 rt2x00lib_rxdone(rt2x00dev, entry); 441 ieee80211_queue_work(rt2x00dev->hw, &rt2x00dev->rxdone_work);
380} 442}
381 443
382/* 444/*
@@ -405,6 +467,8 @@ void rt2x00usb_clear_entry(struct queue_entry *entry)
405 struct queue_entry_priv_usb *entry_priv = entry->priv_data; 467 struct queue_entry_priv_usb *entry_priv = entry->priv_data;
406 int pipe; 468 int pipe;
407 469
470 entry->flags = 0;
471
408 if (entry->queue->qid == QID_RX) { 472 if (entry->queue->qid == QID_RX) {
409 pipe = usb_rcvbulkpipe(usb_dev, entry->queue->usb_endpoint); 473 pipe = usb_rcvbulkpipe(usb_dev, entry->queue->usb_endpoint);
410 usb_fill_bulk_urb(entry_priv->urb, usb_dev, pipe, 474 usb_fill_bulk_urb(entry_priv->urb, usb_dev, pipe,
@@ -413,8 +477,6 @@ void rt2x00usb_clear_entry(struct queue_entry *entry)
413 477
414 set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags); 478 set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags);
415 usb_submit_urb(entry_priv->urb, GFP_ATOMIC); 479 usb_submit_urb(entry_priv->urb, GFP_ATOMIC);
416 } else {
417 entry->flags = 0;
418 } 480 }
419} 481}
420EXPORT_SYMBOL_GPL(rt2x00usb_clear_entry); 482EXPORT_SYMBOL_GPL(rt2x00usb_clear_entry);
@@ -659,6 +721,9 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
659 721
660 rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_USB); 722 rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_USB);
661 723
724 INIT_WORK(&rt2x00dev->rxdone_work, rt2x00usb_work_rxdone);
725 INIT_WORK(&rt2x00dev->txdone_work, rt2x00usb_work_txdone);
726
662 retval = rt2x00usb_alloc_reg(rt2x00dev); 727 retval = rt2x00usb_alloc_reg(rt2x00dev);
663 if (retval) 728 if (retval)
664 goto exit_free_device; 729 goto exit_free_device;