rt2x00: Move intf_work to mac82011 workqueue
PATCH 1/3 rt2x00: Move intf_work to mac82011 workqueue
ieee80211_iterate_active_interfaces() no longer acquires the RTNL lock which means the intf_work handler can be safely used from the mac80211 workqueue again.
--- drivers/net/wireless/rt2x00/rt2x00dev.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
drivers/net/wireless/rt2x00/rt2x00dev.c +1 -1
@@ -215,7 +215,7 @@ void rt2x00lib_beacondone(struct rt2x00_dev *rt2x00dev) rt2x00lib_beacondone_iter, rt2x00dev); -schedule_work (&rt2x00dev->intf_work); +queue_work (rt2x00dev->hw->workqueue, &rt2x00dev->intf_work); }EXPORT_SYMBOL_GPL (rt2x00lib_beacondone);
Signature · 1 line
1.5.6.1
PATCH 2/3 rt2x00: Add kill_tx_queue callback function
provide rt2x00lib the possibility to kill a particular TX queue. This can be useful when disabling the radio, but more importantly will allow beaconing to be disabled when mac80211 requests this (during scanning for example)
--- drivers/net/wireless/rt2x00/rt2400pci.c | 30 ++++++++------ drivers/net/wireless/rt2x00/rt2500pci.c | 30 ++++++++------ drivers/net/wireless/rt2x00/rt2500usb.c | 1 + drivers/net/wireless/rt2x00/rt2x00.h | 2 + drivers/net/wireless/rt2x00/rt2x00dev.c | 5 +- drivers/net/wireless/rt2x00/rt2x00lib.h | 13 ++++++- drivers/net/wireless/rt2x00/rt2x00mac.c | 6 ++- drivers/net/wireless/rt2x00/rt2x00queue.c | 19 ++++++++- drivers/net/wireless/rt2x00/rt2x00usb.c | 62 +++++++++++++++++----------- drivers/net/wireless/rt2x00/rt2x00usb.h | 11 +++++ drivers/net/wireless/rt2x00/rt61pci.c | 37 ++++++++++------- drivers/net/wireless/rt2x00/rt73usb.c | 1 + 12 files changed, 145 insertions(+), 72 deletions(-)
drivers/net/wireless/rt2x00/rt2400pci.c +17 -13
@@ -934,21 +934,10 @@ static int rt2400pci_enable_radio(struct rt2x00_dev *rt2x00dev)static void rt2400pci_disable_radio (struct rt2x00_dev *rt2x00dev) { -u32 reg; - -rt2x00pci_register_write (rt2x00dev,PWRCSR0 ,0 ); -/* -* Disable synchronisation. +* Disable power */ -rt2x00pci_register_write (rt2x00dev,CSR14 ,0 ); - -/* -* Cancel RX and TX. -*/ -rt2x00pci_register_read (rt2x00dev,TXCSR0 , ®); -rt2x00_set_field32 (®,TXCSR0_ABORT ,1 ); -rt2x00pci_register_write (rt2x00dev,TXCSR0 , reg); +rt2x00pci_register_write (rt2x00dev,PWRCSR0 ,0 ); } static int rt2400pci_set_state(struct rt2x00_dev *rt2x00dev,
@@ -1145,6 +1134,20 @@ static void rt2400pci_kick_tx_queue(struct rt2x00_dev *rt2x00dev,rt2x00pci_register_write (rt2x00dev,TXCSR0 , reg); } +static void rt2400pci_kill_tx_queue (struct rt2x00_dev *rt2x00dev, +const enum data_queue_qid qid) +{ +u32 reg; + +if (qid ==QID_BEACON ) { +rt2x00pci_register_write (rt2x00dev,CSR14 ,0 ); + }else { +rt2x00pci_register_read (rt2x00dev,TXCSR0 , ®); +rt2x00_set_field32 (®,TXCSR0_ABORT ,1 ); +rt2x00pci_register_write (rt2x00dev,TXCSR0 , reg); + } +} +/* * RX control handlers */
@@ -1606,6 +1609,7 @@ static const struct rt2x00lib_ops rt2400pci_rt2x00_ops = { .write_tx_data = rt2x00pci_write_tx_data, .write_beacon = rt2400pci_write_beacon, .kick_tx_queue = rt2400pci_kick_tx_queue, + .kill_tx_queue = rt2400pci_kill_tx_queue, .fill_rxdone = rt2400pci_fill_rxdone, .config_filter = rt2400pci_config_filter, .config_intf = rt2400pci_config_intf,
drivers/net/wireless/rt2x00/rt2500pci.c +17 -13
@@ -1093,21 +1093,10 @@ static int rt2500pci_enable_radio(struct rt2x00_dev *rt2x00dev)static void rt2500pci_disable_radio (struct rt2x00_dev *rt2x00dev) { -u32 reg; - -rt2x00pci_register_write (rt2x00dev,PWRCSR0 ,0 ); -/* -* Disable synchronisation. +* Disable power */ -rt2x00pci_register_write (rt2x00dev,CSR14 ,0 ); - -/* -* Cancel RX and TX. -*/ -rt2x00pci_register_read (rt2x00dev,TXCSR0 , ®); -rt2x00_set_field32 (®,TXCSR0_ABORT ,1 ); -rt2x00pci_register_write (rt2x00dev,TXCSR0 , reg); +rt2x00pci_register_write (rt2x00dev,PWRCSR0 ,0 ); } static int rt2500pci_set_state(struct rt2x00_dev *rt2x00dev,
@@ -1303,6 +1292,20 @@ static void rt2500pci_kick_tx_queue(struct rt2x00_dev *rt2x00dev,rt2x00pci_register_write (rt2x00dev,TXCSR0 , reg); } +static void rt2500pci_kill_tx_queue (struct rt2x00_dev *rt2x00dev, +const enum data_queue_qid qid) +{ +u32 reg; + +if (qid ==QID_BEACON ) { +rt2x00pci_register_write (rt2x00dev,CSR14 ,0 ); + }else { +rt2x00pci_register_read (rt2x00dev,TXCSR0 , ®); +rt2x00_set_field32 (®,TXCSR0_ABORT ,1 ); +rt2x00pci_register_write (rt2x00dev,TXCSR0 , reg); + } +} +/* * RX control handlers */
@@ -1905,6 +1908,7 @@ static const struct rt2x00lib_ops rt2500pci_rt2x00_ops = { .write_tx_data = rt2x00pci_write_tx_data, .write_beacon = rt2500pci_write_beacon, .kick_tx_queue = rt2500pci_kick_tx_queue, + .kill_tx_queue = rt2500pci_kill_tx_queue, .fill_rxdone = rt2500pci_fill_rxdone, .config_filter = rt2500pci_config_filter, .config_intf = rt2500pci_config_intf,
drivers/net/wireless/rt2x00/rt2500usb.c +1 -0
@@ -1935,6 +1935,7 @@ static const struct rt2x00lib_ops rt2500usb_rt2x00_ops = { .write_beacon = rt2500usb_write_beacon, .get_tx_data_len = rt2500usb_get_tx_data_len, .kick_tx_queue = rt2500usb_kick_tx_queue, + .kill_tx_queue = rt2x00usb_kill_tx_queue, .fill_rxdone = rt2500usb_fill_rxdone, .config_shared_key = rt2500usb_config_key, .config_pairwise_key = rt2500usb_config_key,
drivers/net/wireless/rt2x00/rt2x00.h +2 -0
@@ -508,6 +508,8 @@ struct rt2x00lib_ops {int (*get_tx_data_len) (struct queue_entry *entry);void (*kick_tx_queue) (struct rt2x00_dev *rt2x00dev,const enum data_queue_qid queue); +void (*kill_tx_queue) (struct rt2x00_dev *rt2x00dev, +const enum data_queue_qid queue);/* * RX control handlers
drivers/net/wireless/rt2x00/rt2x00dev.c +3 -2
@@ -83,9 +83,10 @@ void rt2x00lib_disable_radio(struct rt2x00_dev *rt2x00dev)return ;/* -* Stop the TX queues. +* Stop the TX queues in mac80211. */ ieee80211_stop_queues (rt2x00dev->hw); +rt2x00queue_stop_queues (rt2x00dev);/* * Disable RX.
@@ -157,7 +158,7 @@ static void rt2x00lib_intf_scheduled_iter(void *data, u8 *mac,return ;if (delayed_flags &DELAYED_UPDATE_BEACON ) -rt2x00queue_update_beacon (rt2x00dev, vif); +rt2x00queue_update_beacon (rt2x00dev, vif, true);if (delayed_flags &DELAYED_CONFIG_ERP )rt2x00lib_config_erp (rt2x00dev, intf, &conf);
drivers/net/wireless/rt2x00/rt2x00lib.h +12 -1
@@ -123,9 +123,11 @@ int rt2x00queue_write_tx_frame(struct data_queue *queue, struct sk_buff *skb);* rt2x00queue_update_beacon - Send new beacon from mac80211 to hardware * @rt2x00dev: Pointer to &struct rt2x00_dev. * @vif: Interface for which the beacon should be updated. +* @enable_beacon: Enable beaconing */ int rt2x00queue_update_beacon (struct rt2x00_dev *rt2x00dev, -struct ieee80211_vif *vif); +struct ieee80211_vif *vif, +const bool enable_beacon);/** * rt2x00queue_index_inc - Index incrementation function
@@ -139,6 +141,15 @@ int rt2x00queue_update_beacon(struct rt2x00_dev *rt2x00dev,void rt2x00queue_index_inc (struct data_queue *queue,enum queue_index index);/** +* rt2x00queue_stop_queues - Halt all data queues +* @rt2x00dev: Pointer to &struct rt2x00_dev. +* +* This function will loop through all available queues to stop +* any pending outgoing frames. +*/ +void rt2x00queue_stop_queues (struct rt2x00_dev *rt2x00dev); + +/** * rt2x00queue_init_queues - Initialize all data queues * @rt2x00dev: Pointer to &struct rt2x00_dev. *
drivers/net/wireless/rt2x00/rt2x00mac.c +4 -2
@@ -431,8 +431,10 @@ int rt2x00mac_config_interface(struct ieee80211_hw *hw,/* * Update the beacon. */ -if (conf->changed &IEEE80211_IFCC_BEACON ) - status =rt2x00queue_update_beacon (rt2x00dev, vif); +if (conf->changed & (IEEE80211_IFCC_BEACON | +IEEE80211_IFCC_BEACON_ENABLED )) + status =rt2x00queue_update_beacon (rt2x00dev, vif, + conf->enable_beacon);return status; }
drivers/net/wireless/rt2x00/rt2x00queue.c +18 -1
@@ -443,7 +443,8 @@ int rt2x00queue_write_tx_frame(struct data_queue *queue, struct sk_buff *skb) }int rt2x00queue_update_beacon (struct rt2x00_dev *rt2x00dev, -struct ieee80211_vif *vif) +struct ieee80211_vif *vif, +const bool enable_beacon) {struct rt2x00_intf *intf =vif_to_intf (vif);struct skb_frame_desc *skbdesc;
@@ -453,6 +454,11 @@ int rt2x00queue_update_beacon(struct rt2x00_dev *rt2x00dev,if (unlikely (!intf->beacon))return -ENOBUFS ; +if (!enable_beacon) { + rt2x00dev->ops->lib->kill_tx_queue (rt2x00dev,QID_BEACON ); +return 0 ; + } + intf->beacon->skb =ieee80211_beacon_get (rt2x00dev->hw, vif);if (!intf->beacon->skb)return -ENOMEM ;
@@ -501,6 +507,9 @@ struct data_queue *rt2x00queue_get_queue(struct rt2x00_dev *rt2x00dev, {int atim =test_bit (DRIVER_REQUIRE_ATIM_QUEUE , &rt2x00dev->flags); +if (queue ==QID_RX ) +return rt2x00dev->rx; +if (queue < rt2x00dev->ops->tx_queues && rt2x00dev->tx)return &rt2x00dev->tx[queue];
@@ -577,6 +586,14 @@ static void rt2x00queue_reset(struct data_queue *queue)spin_unlock_irqrestore (&queue->lock, irqflags); } +void rt2x00queue_stop_queues (struct rt2x00_dev *rt2x00dev) +{ +struct data_queue *queue; + +txall_queue_for_each (rt2x00dev, queue) + rt2x00dev->ops->lib->kill_tx_queue (rt2x00dev, queue->qid); +} +void rt2x00queue_init_queues (struct rt2x00_dev *rt2x00dev) {struct data_queue *queue;
drivers/net/wireless/rt2x00/rt2x00usb.c +38 -24
79 lines not on this page · raw message
drivers/net/wireless/rt2x00/rt2x00usb.h +11 -0
@@ -419,6 +419,17 @@ struct queue_entry_priv_usb_bcn {void rt2x00usb_kick_tx_queue (struct rt2x00_dev *rt2x00dev,const enum data_queue_qid qid); +/** +* rt2x00usb_kill_tx_queue - Kill data queue +* @rt2x00dev: Pointer to &struct rt2x00_dev +* @qid: Data queue to kill +* +* This will walk through all entries of the queue and kill all +* previously kicked frames before they can be send. +*/ +void rt2x00usb_kill_tx_queue (struct rt2x00_dev *rt2x00dev, +const enum data_queue_qid qid); +/* * Device initialization handlers. */
drivers/net/wireless/rt2x00/rt61pci.c +21 -16
@@ -1696,24 +1696,10 @@ static int rt61pci_enable_radio(struct rt2x00_dev *rt2x00dev)static void rt61pci_disable_radio (struct rt2x00_dev *rt2x00dev) { -u32 reg; - -rt2x00pci_register_write (rt2x00dev,MAC_CSR10 ,0x00001818 ); - -/* -* Disable synchronisation. -*/ -rt2x00pci_register_write (rt2x00dev,TXRX_CSR9 ,0 ); -/* -* Cancel RX and TX. +* Disable power */ -rt2x00pci_register_read (rt2x00dev,TX_CNTL_CSR , ®); -rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC0 ,1 ); -rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC1 ,1 ); -rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC2 ,1 ); -rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC3 ,1 ); -rt2x00pci_register_write (rt2x00dev,TX_CNTL_CSR , reg); +rt2x00pci_register_write (rt2x00dev,MAC_CSR10 ,0x00001818 ); }static int rt61pci_set_state (struct rt2x00_dev *rt2x00dev,enum dev_state state)
@@ -1936,6 +1922,24 @@ static void rt61pci_kick_tx_queue(struct rt2x00_dev *rt2x00dev,rt2x00pci_register_write (rt2x00dev,TX_CNTL_CSR , reg); } +static void rt61pci_kill_tx_queue (struct rt2x00_dev *rt2x00dev, +const enum data_queue_qid qid) +{ +u32 reg; + +if (qid ==QID_BEACON ) { +rt2x00pci_register_write (rt2x00dev,TXRX_CSR9 ,0 ); +return ; + } + +rt2x00pci_register_read (rt2x00dev,TX_CNTL_CSR , ®); +rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC0 , (qid ==QID_AC_BE )); +rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC1 , (qid ==QID_AC_BK )); +rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC2 , (qid ==QID_AC_VI )); +rt2x00_set_field32 (®,TX_CNTL_CSR_ABORT_TX_AC3 , (qid ==QID_AC_VO )); +rt2x00pci_register_write (rt2x00dev,TX_CNTL_CSR , reg); +} +/* * RX control handlers */
@@ -2761,6 +2765,7 @@ static const struct rt2x00lib_ops rt61pci_rt2x00_ops = { .write_tx_data = rt2x00pci_write_tx_data, .write_beacon = rt61pci_write_beacon, .kick_tx_queue = rt61pci_kick_tx_queue, + .kill_tx_queue = rt61pci_kill_tx_queue, .fill_rxdone = rt61pci_fill_rxdone, .config_shared_key = rt61pci_config_shared_key, .config_pairwise_key = rt61pci_config_pairwise_key,
drivers/net/wireless/rt2x00/rt73usb.c +1 -0
@@ -2293,6 +2293,7 @@ static const struct rt2x00lib_ops rt73usb_rt2x00_ops = { .write_beacon = rt73usb_write_beacon, .get_tx_data_len = rt73usb_get_tx_data_len, .kick_tx_queue = rt73usb_kick_tx_queue, + .kill_tx_queue = rt2x00usb_kill_tx_queue, .fill_rxdone = rt73usb_fill_rxdone, .config_shared_key = rt73usb_config_shared_key, .config_pairwise_key = rt73usb_config_pairwise_key,
Signature · 1 line
1.5.6.1
PATCH 3/3 rt2x00: Validate firmware in driver
The get_firmware_crc() callback function isn't flexible enough when dealing with multiple firmware versions. It might in some cases be possible that the firmware file contains multiple CRC checksums.
Create the check_firmware() callback function where the driver has complete freedom in how to validate the firmware.
--- drivers/net/wireless/rt2x00/rt2x00.h | 7 +++-- drivers/net/wireless/rt2x00/rt2x00firmware.c | 27 ++++++++++++++++-------- drivers/net/wireless/rt2x00/rt2x00reg.h | 10 +++++++++ drivers/net/wireless/rt2x00/rt61pci.c | 29 ++++++++++++++++--------- drivers/net/wireless/rt2x00/rt73usb.c | 29 ++++++++++++++++--------- 5 files changed, 68 insertions(+), 34 deletions(-)
drivers/net/wireless/rt2x00/rt2x00.h +4 -3
@@ -468,9 +468,10 @@ struct rt2x00lib_ops {*/ int (*probe_hw) (struct rt2x00_dev *rt2x00dev);char *(*get_firmware_name) (struct rt2x00_dev *rt2x00dev); -u16 (*get_firmware_crc) (const void *data,const size_t len); -int (*load_firmware) (struct rt2x00_dev *rt2x00dev,const void *data, -const size_t len); +int (*check_firmware) (struct rt2x00_dev *rt2x00dev, +const u8 *data,const size_t len); +int (*load_firmware) (struct rt2x00_dev *rt2x00dev, +const u8 *data,const size_t len);/* * Device initialization/deinitialization handlers.
drivers/net/wireless/rt2x00/rt2x00firmware.c +18 -9
@@ -35,7 +35,6 @@ static int rt2x00lib_request_firmware(struct rt2x00_dev *rt2x00dev)const struct firmware *fw;char *fw_name;int retval; -u16 crc;/* * Read correct firmware from harddisk.
@@ -61,16 +60,26 @@ static int rt2x00lib_request_firmware(struct rt2x00_dev *rt2x00dev)return -ENOENT ; } - crc = rt2x00dev->ops->lib->get_firmware_crc (fw->data, fw->size); -if (crc != (fw->data[fw->size -2 ] <<8 | fw->data[fw->size -1 ])) { -ERROR (rt2x00dev,"Firmware checksum error.\n" ); - retval = -ENOENT ; - goto exit; - } -INFO (rt2x00dev,"Firmware detected - version: %d.%d.\n" , fw->data[fw->size -4 ], fw->data[fw->size -3 ]); + retval = rt2x00dev->ops->lib->check_firmware (rt2x00dev, fw->data, fw->size); +switch (retval) { +case FW_OK : +break ; +case FW_BAD_CRC : +ERROR (rt2x00dev,"Firmware checksum error.\n" ); + goto exit; +case FW_BAD_LENGTH : +ERROR (rt2x00dev, +"Invalid firmware file length (len=%zu)\n" , fw->size); + goto exit; +case FW_BAD_VERSION : +ERROR (rt2x00dev, +"Current firmware does not support detected chipset.\n" ); + goto exit; + }; + rt2x00dev->fw = fw;return 0 ;
@@ -78,7 +87,7 @@ static int rt2x00lib_request_firmware(struct rt2x00_dev *rt2x00dev) exit:release_firmware (fw); -return retval; +return -ENOENT ; }int rt2x00lib_load_firmware (struct rt2x00_dev *rt2x00dev)
drivers/net/wireless/rt2x00/rt2x00reg.h +10 -0
@@ -135,6 +135,16 @@ enum rate_modulation { };/* +* Firmware validation error codes +*/ +enum firmware_errors { +FW_OK , +FW_BAD_CRC , +FW_BAD_LENGTH , +FW_BAD_VERSION , +}; + +/* * Register handlers. * We store the position of a register field inside a field structure, * This will simplify the process of setting and reading a certain field
drivers/net/wireless/rt2x00/rt61pci.c +18 -11
@@ -1176,34 +1176,41 @@ static char *rt61pci_get_firmware_name(struct rt2x00_dev *rt2x00dev)return fw_name; } -static u16 rt61pci_get_firmware_crc (const void *data,const size_t len) +static int rt61pci_check_firmware (struct rt2x00_dev *rt2x00dev, +const u8 *data,const size_t len) { +u16 fw_crc;u16 crc;/* -* Use the crc itu-t algorithm. +* Only support 8kb firmware files. +*/ +if (len !=8192 ) +return FW_BAD_LENGTH ; + +/* * The last 2 bytes in the firmware array are the crc checksum itself, * this means that we should never pass those 2 bytes to the crc * algorithm. */ + fw_crc = (data[len -2 ] <<8 | data[len -1 ]); + +/* +* Use the crc itu-t algorithm. +*/ crc =crc_itu_t (0 , data, len -2 ); crc =crc_itu_t_byte (crc,0 ); crc =crc_itu_t_byte (crc,0 ); -return crc; +return (fw_crc == crc) ?FW_OK :FW_BAD_CRC ; } -static int rt61pci_load_firmware (struct rt2x00_dev *rt2x00dev,const void *data, -const size_t len) +static int rt61pci_load_firmware (struct rt2x00_dev *rt2x00dev, +const u8 *data,const size_t len) {int i;u32 reg; -if (len !=8192 ) { -ERROR (rt2x00dev,"Invalid firmware file length (len=%zu)\n" , len); -return -ENOENT ; - } -/* * Wait for stable hardware. */
@@ -2750,7 +2757,7 @@ static const struct rt2x00lib_ops rt61pci_rt2x00_ops = { .irq_handler = rt61pci_interrupt, .probe_hw = rt61pci_probe_hw, .get_firmware_name = rt61pci_get_firmware_name, - .get_firmware_crc = rt61pci_get_firmware_crc, + .check_firmware = rt61pci_check_firmware, .load_firmware = rt61pci_load_firmware, .initialize = rt2x00pci_initialize, .uninitialize = rt2x00pci_uninitialize,
drivers/net/wireless/rt2x00/rt73usb.c +18 -11
@@ -1061,35 +1061,42 @@ static char *rt73usb_get_firmware_name(struct rt2x00_dev *rt2x00dev)return FIRMWARE_RT2571 ; } -static u16 rt73usb_get_firmware_crc (const void *data,const size_t len) +static int rt73usb_check_firmware (struct rt2x00_dev *rt2x00dev, +const u8 *data,const size_t len) { +u16 fw_crc;u16 crc;/* -* Use the crc itu-t algorithm. +* Only support 2kb firmware files. +*/ +if (len !=2048 ) +return FW_BAD_LENGTH ; + +/* * The last 2 bytes in the firmware array are the crc checksum itself, * this means that we should never pass those 2 bytes to the crc * algorithm. */ + fw_crc = (data[len -2 ] <<8 | data[len -1 ]); + +/* +* Use the crc itu-t algorithm. +*/ crc =crc_itu_t (0 , data, len -2 ); crc =crc_itu_t_byte (crc,0 ); crc =crc_itu_t_byte (crc,0 ); -return crc; +return (fw_crc == crc) ?FW_OK :FW_BAD_CRC ; } -static int rt73usb_load_firmware (struct rt2x00_dev *rt2x00dev,const void *data, -const size_t len) +static int rt73usb_load_firmware (struct rt2x00_dev *rt2x00dev, +const u8 *data,const size_t len) {unsigned int i;int status;u32 reg; -if (len !=2048 ) { -ERROR (rt2x00dev,"Invalid firmware file length (len=%zu)\n" , len); -return -ENOENT ; - } -/* * Wait for stable hardware. */
@@ -2278,7 +2285,7 @@ static const struct ieee80211_ops rt73usb_mac80211_ops = {static const struct rt2x00lib_ops rt73usb_rt2x00_ops = { .probe_hw = rt73usb_probe_hw, .get_firmware_name = rt73usb_get_firmware_name, - .get_firmware_crc = rt73usb_get_firmware_crc, + .check_firmware = rt73usb_check_firmware, .load_firmware = rt73usb_load_firmware, .initialize = rt2x00usb_initialize, .uninitialize = rt2x00usb_uninitialize,
Signature · 1 line
1.5.6.1