diff options
Diffstat (limited to 'drivers/net/wireless/rtlwifi/base.c')
-rw-r--r-- | drivers/net/wireless/rtlwifi/base.c | 956 |
1 files changed, 956 insertions, 0 deletions
diff --git a/drivers/net/wireless/rtlwifi/base.c b/drivers/net/wireless/rtlwifi/base.c new file mode 100644 index 000000000000..cf0b73e51fc2 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/base.c | |||
@@ -0,0 +1,956 @@ | |||
1 | /****************************************************************************** | ||
2 | * | ||
3 | * Copyright(c) 2009-2010 Realtek Corporation. | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of version 2 of the GNU General Public License as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program; if not, write to the Free Software Foundation, Inc., | ||
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA | ||
17 | * | ||
18 | * The full GNU General Public License is included in this distribution in the | ||
19 | * file called LICENSE. | ||
20 | * | ||
21 | * Contact Information: | ||
22 | * wlanfae <wlanfae@realtek.com> | ||
23 | * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, | ||
24 | * Hsinchu 300, Taiwan. | ||
25 | * | ||
26 | * Larry Finger <Larry.Finger@lwfinger.net> | ||
27 | * | ||
28 | *****************************************************************************/ | ||
29 | |||
30 | #include <linux/ip.h> | ||
31 | #include "wifi.h" | ||
32 | #include "rc.h" | ||
33 | #include "base.h" | ||
34 | #include "efuse.h" | ||
35 | #include "cam.h" | ||
36 | #include "ps.h" | ||
37 | #include "regd.h" | ||
38 | |||
39 | /* | ||
40 | *NOTICE!!!: This file will be very big, we hsould | ||
41 | *keep it clear under follwing roles: | ||
42 | * | ||
43 | *This file include follwing part, so, if you add new | ||
44 | *functions into this file, please check which part it | ||
45 | *should includes. or check if you should add new part | ||
46 | *for this file: | ||
47 | * | ||
48 | *1) mac80211 init functions | ||
49 | *2) tx information functions | ||
50 | *3) functions called by core.c | ||
51 | *4) wq & timer callback functions | ||
52 | *5) frame process functions | ||
53 | *6) sysfs functions | ||
54 | *7) ... | ||
55 | */ | ||
56 | |||
57 | /********************************************************* | ||
58 | * | ||
59 | * mac80211 init functions | ||
60 | * | ||
61 | *********************************************************/ | ||
62 | static struct ieee80211_channel rtl_channeltable[] = { | ||
63 | {.center_freq = 2412, .hw_value = 1,}, | ||
64 | {.center_freq = 2417, .hw_value = 2,}, | ||
65 | {.center_freq = 2422, .hw_value = 3,}, | ||
66 | {.center_freq = 2427, .hw_value = 4,}, | ||
67 | {.center_freq = 2432, .hw_value = 5,}, | ||
68 | {.center_freq = 2437, .hw_value = 6,}, | ||
69 | {.center_freq = 2442, .hw_value = 7,}, | ||
70 | {.center_freq = 2447, .hw_value = 8,}, | ||
71 | {.center_freq = 2452, .hw_value = 9,}, | ||
72 | {.center_freq = 2457, .hw_value = 10,}, | ||
73 | {.center_freq = 2462, .hw_value = 11,}, | ||
74 | {.center_freq = 2467, .hw_value = 12,}, | ||
75 | {.center_freq = 2472, .hw_value = 13,}, | ||
76 | {.center_freq = 2484, .hw_value = 14,}, | ||
77 | }; | ||
78 | |||
79 | static struct ieee80211_rate rtl_ratetable[] = { | ||
80 | {.bitrate = 10, .hw_value = 0x00,}, | ||
81 | {.bitrate = 20, .hw_value = 0x01,}, | ||
82 | {.bitrate = 55, .hw_value = 0x02,}, | ||
83 | {.bitrate = 110, .hw_value = 0x03,}, | ||
84 | {.bitrate = 60, .hw_value = 0x04,}, | ||
85 | {.bitrate = 90, .hw_value = 0x05,}, | ||
86 | {.bitrate = 120, .hw_value = 0x06,}, | ||
87 | {.bitrate = 180, .hw_value = 0x07,}, | ||
88 | {.bitrate = 240, .hw_value = 0x08,}, | ||
89 | {.bitrate = 360, .hw_value = 0x09,}, | ||
90 | {.bitrate = 480, .hw_value = 0x0a,}, | ||
91 | {.bitrate = 540, .hw_value = 0x0b,}, | ||
92 | }; | ||
93 | |||
94 | static const struct ieee80211_supported_band rtl_band_2ghz = { | ||
95 | .band = IEEE80211_BAND_2GHZ, | ||
96 | |||
97 | .channels = rtl_channeltable, | ||
98 | .n_channels = ARRAY_SIZE(rtl_channeltable), | ||
99 | |||
100 | .bitrates = rtl_ratetable, | ||
101 | .n_bitrates = ARRAY_SIZE(rtl_ratetable), | ||
102 | |||
103 | .ht_cap = {0}, | ||
104 | }; | ||
105 | |||
106 | static void _rtl_init_hw_ht_capab(struct ieee80211_hw *hw, | ||
107 | struct ieee80211_sta_ht_cap *ht_cap) | ||
108 | { | ||
109 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
110 | struct rtl_phy *rtlphy = &(rtlpriv->phy); | ||
111 | |||
112 | ht_cap->ht_supported = true; | ||
113 | ht_cap->cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | | ||
114 | IEEE80211_HT_CAP_SGI_40 | | ||
115 | IEEE80211_HT_CAP_SGI_20 | | ||
116 | IEEE80211_HT_CAP_DSSSCCK40 | IEEE80211_HT_CAP_MAX_AMSDU; | ||
117 | |||
118 | /* | ||
119 | *Maximum length of AMPDU that the STA can receive. | ||
120 | *Length = 2 ^ (13 + max_ampdu_length_exp) - 1 (octets) | ||
121 | */ | ||
122 | ht_cap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; | ||
123 | |||
124 | /*Minimum MPDU start spacing , */ | ||
125 | ht_cap->ampdu_density = IEEE80211_HT_MPDU_DENSITY_16; | ||
126 | |||
127 | ht_cap->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; | ||
128 | |||
129 | /* | ||
130 | *hw->wiphy->bands[IEEE80211_BAND_2GHZ] | ||
131 | *base on ant_num | ||
132 | *rx_mask: RX mask | ||
133 | *if rx_ant =1 rx_mask[0]=0xff;==>MCS0-MCS7 | ||
134 | *if rx_ant =2 rx_mask[1]=0xff;==>MCS8-MCS15 | ||
135 | *if rx_ant >=3 rx_mask[2]=0xff; | ||
136 | *if BW_40 rx_mask[4]=0x01; | ||
137 | *highest supported RX rate | ||
138 | */ | ||
139 | if (get_rf_type(rtlphy) == RF_1T2R || get_rf_type(rtlphy) == RF_2T2R) { | ||
140 | |||
141 | RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, ("1T2R or 2T2R\n")); | ||
142 | |||
143 | ht_cap->mcs.rx_mask[0] = 0xFF; | ||
144 | ht_cap->mcs.rx_mask[1] = 0xFF; | ||
145 | ht_cap->mcs.rx_mask[4] = 0x01; | ||
146 | |||
147 | ht_cap->mcs.rx_highest = MAX_BIT_RATE_40MHZ_MCS15; | ||
148 | } else if (get_rf_type(rtlphy) == RF_1T1R) { | ||
149 | |||
150 | RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, ("1T1R\n")); | ||
151 | |||
152 | ht_cap->mcs.rx_mask[0] = 0xFF; | ||
153 | ht_cap->mcs.rx_mask[1] = 0x00; | ||
154 | ht_cap->mcs.rx_mask[4] = 0x01; | ||
155 | |||
156 | ht_cap->mcs.rx_highest = MAX_BIT_RATE_40MHZ_MCS7; | ||
157 | } | ||
158 | } | ||
159 | |||
160 | static void _rtl_init_mac80211(struct ieee80211_hw *hw) | ||
161 | { | ||
162 | struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw)); | ||
163 | struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); | ||
164 | struct ieee80211_supported_band *sband; | ||
165 | |||
166 | /* <1> use mac->bands as mem for hw->wiphy->bands */ | ||
167 | sband = &(rtlmac->bands[IEEE80211_BAND_2GHZ]); | ||
168 | |||
169 | /* | ||
170 | * <2> set hw->wiphy->bands[IEEE80211_BAND_2GHZ] | ||
171 | * to default value(1T1R) | ||
172 | */ | ||
173 | memcpy(&(rtlmac->bands[IEEE80211_BAND_2GHZ]), &rtl_band_2ghz, | ||
174 | sizeof(struct ieee80211_supported_band)); | ||
175 | |||
176 | /* <3> init ht cap base on ant_num */ | ||
177 | _rtl_init_hw_ht_capab(hw, &sband->ht_cap); | ||
178 | |||
179 | /* <4> set mac->sband to wiphy->sband */ | ||
180 | hw->wiphy->bands[IEEE80211_BAND_2GHZ] = sband; | ||
181 | |||
182 | /* <5> set hw caps */ | ||
183 | hw->flags = IEEE80211_HW_SIGNAL_DBM | | ||
184 | IEEE80211_HW_RX_INCLUDES_FCS | | ||
185 | IEEE80211_HW_BEACON_FILTER | IEEE80211_HW_AMPDU_AGGREGATION | /*PS*/ | ||
186 | /*IEEE80211_HW_SUPPORTS_PS | */ | ||
187 | /*IEEE80211_HW_PS_NULLFUNC_STACK | */ | ||
188 | /*IEEE80211_HW_SUPPORTS_DYNAMIC_PS | */ | ||
189 | IEEE80211_HW_REPORTS_TX_ACK_STATUS | 0; | ||
190 | |||
191 | hw->wiphy->interface_modes = | ||
192 | BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC); | ||
193 | |||
194 | hw->wiphy->rts_threshold = 2347; | ||
195 | |||
196 | hw->queues = AC_MAX; | ||
197 | hw->extra_tx_headroom = RTL_TX_HEADER_SIZE; | ||
198 | |||
199 | /* TODO: Correct this value for our hw */ | ||
200 | /* TODO: define these hard code value */ | ||
201 | hw->channel_change_time = 100; | ||
202 | hw->max_listen_interval = 5; | ||
203 | hw->max_rate_tries = 4; | ||
204 | /* hw->max_rates = 1; */ | ||
205 | |||
206 | /* <6> mac address */ | ||
207 | if (is_valid_ether_addr(rtlefuse->dev_addr)) { | ||
208 | SET_IEEE80211_PERM_ADDR(hw, rtlefuse->dev_addr); | ||
209 | } else { | ||
210 | u8 rtlmac[] = { 0x00, 0xe0, 0x4c, 0x81, 0x92, 0x00 }; | ||
211 | get_random_bytes((rtlmac + (ETH_ALEN - 1)), 1); | ||
212 | SET_IEEE80211_PERM_ADDR(hw, rtlmac); | ||
213 | } | ||
214 | |||
215 | } | ||
216 | |||
217 | static void _rtl_init_deferred_work(struct ieee80211_hw *hw) | ||
218 | { | ||
219 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
220 | |||
221 | /* <1> timer */ | ||
222 | init_timer(&rtlpriv->works.watchdog_timer); | ||
223 | setup_timer(&rtlpriv->works.watchdog_timer, | ||
224 | rtl_watch_dog_timer_callback, (unsigned long)hw); | ||
225 | |||
226 | /* <2> work queue */ | ||
227 | rtlpriv->works.hw = hw; | ||
228 | rtlpriv->works.rtl_wq = alloc_workqueue(rtlpriv->cfg->name, 0, 0); | ||
229 | INIT_DELAYED_WORK(&rtlpriv->works.watchdog_wq, | ||
230 | (void *)rtl_watchdog_wq_callback); | ||
231 | INIT_DELAYED_WORK(&rtlpriv->works.ips_nic_off_wq, | ||
232 | (void *)rtl_ips_nic_off_wq_callback); | ||
233 | |||
234 | } | ||
235 | |||
236 | void rtl_deinit_deferred_work(struct ieee80211_hw *hw) | ||
237 | { | ||
238 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
239 | |||
240 | del_timer_sync(&rtlpriv->works.watchdog_timer); | ||
241 | |||
242 | cancel_delayed_work(&rtlpriv->works.watchdog_wq); | ||
243 | cancel_delayed_work(&rtlpriv->works.ips_nic_off_wq); | ||
244 | } | ||
245 | |||
246 | void rtl_init_rfkill(struct ieee80211_hw *hw) | ||
247 | { | ||
248 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
249 | |||
250 | bool radio_state; | ||
251 | bool blocked; | ||
252 | u8 valid = 0; | ||
253 | |||
254 | radio_state = rtlpriv->cfg->ops->radio_onoff_checking(hw, &valid); | ||
255 | |||
256 | /*set init state to that of switch */ | ||
257 | rtlpriv->rfkill.rfkill_state = radio_state; | ||
258 | printk(KERN_INFO "rtlwifi: wireless switch is %s\n", | ||
259 | rtlpriv->rfkill.rfkill_state ? "on" : "off"); | ||
260 | |||
261 | if (valid) { | ||
262 | rtlpriv->rfkill.rfkill_state = radio_state; | ||
263 | |||
264 | blocked = (rtlpriv->rfkill.rfkill_state == 1) ? 0 : 1; | ||
265 | wiphy_rfkill_set_hw_state(hw->wiphy, blocked); | ||
266 | } | ||
267 | |||
268 | wiphy_rfkill_start_polling(hw->wiphy); | ||
269 | } | ||
270 | |||
271 | void rtl_deinit_rfkill(struct ieee80211_hw *hw) | ||
272 | { | ||
273 | wiphy_rfkill_stop_polling(hw->wiphy); | ||
274 | } | ||
275 | |||
276 | int rtl_init_core(struct ieee80211_hw *hw) | ||
277 | { | ||
278 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
279 | struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw)); | ||
280 | |||
281 | /* <1> init mac80211 */ | ||
282 | _rtl_init_mac80211(hw); | ||
283 | rtlmac->hw = hw; | ||
284 | |||
285 | /* <2> rate control register */ | ||
286 | if (rtl_rate_control_register()) { | ||
287 | RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, | ||
288 | ("rtl: Unable to register rtl_rc," | ||
289 | "use default RC !!\n")); | ||
290 | } else { | ||
291 | hw->rate_control_algorithm = "rtl_rc"; | ||
292 | } | ||
293 | |||
294 | /* | ||
295 | * <3> init CRDA must come after init | ||
296 | * mac80211 hw in _rtl_init_mac80211. | ||
297 | */ | ||
298 | if (rtl_regd_init(hw, rtl_reg_notifier)) { | ||
299 | RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, ("REGD init failed\n")); | ||
300 | return 1; | ||
301 | } else { | ||
302 | /* CRDA regd hint must after init CRDA */ | ||
303 | if (regulatory_hint(hw->wiphy, rtlpriv->regd.alpha2)) { | ||
304 | RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, | ||
305 | ("regulatory_hint fail\n")); | ||
306 | } | ||
307 | } | ||
308 | |||
309 | /* <4> locks */ | ||
310 | mutex_init(&rtlpriv->locks.conf_mutex); | ||
311 | spin_lock_init(&rtlpriv->locks.ips_lock); | ||
312 | spin_lock_init(&rtlpriv->locks.irq_th_lock); | ||
313 | spin_lock_init(&rtlpriv->locks.h2c_lock); | ||
314 | spin_lock_init(&rtlpriv->locks.rf_ps_lock); | ||
315 | spin_lock_init(&rtlpriv->locks.rf_lock); | ||
316 | spin_lock_init(&rtlpriv->locks.lps_lock); | ||
317 | |||
318 | rtlmac->link_state = MAC80211_NOLINK; | ||
319 | |||
320 | /* <5> init deferred work */ | ||
321 | _rtl_init_deferred_work(hw); | ||
322 | |||
323 | return 0; | ||
324 | } | ||
325 | |||
326 | void rtl_deinit_core(struct ieee80211_hw *hw) | ||
327 | { | ||
328 | /*RC*/ | ||
329 | rtl_rate_control_unregister(); | ||
330 | } | ||
331 | |||
332 | void rtl_init_rx_config(struct ieee80211_hw *hw) | ||
333 | { | ||
334 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
335 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
336 | |||
337 | rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_RCR, (u8 *) (&mac->rx_conf)); | ||
338 | rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_MGT_FILTER, | ||
339 | (u8 *) (&mac->rx_mgt_filter)); | ||
340 | rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_CTRL_FILTER, | ||
341 | (u8 *) (&mac->rx_ctrl_filter)); | ||
342 | rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_DATA_FILTER, | ||
343 | (u8 *) (&mac->rx_data_filter)); | ||
344 | } | ||
345 | |||
346 | /********************************************************* | ||
347 | * | ||
348 | * tx information functions | ||
349 | * | ||
350 | *********************************************************/ | ||
351 | static void _rtl_qurey_shortpreamble_mode(struct ieee80211_hw *hw, | ||
352 | struct rtl_tcb_desc *tcb_desc, | ||
353 | struct ieee80211_tx_info *info) | ||
354 | { | ||
355 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
356 | u8 rate_flag = info->control.rates[0].flags; | ||
357 | |||
358 | tcb_desc->use_shortpreamble = false; | ||
359 | |||
360 | /* 1M can only use Long Preamble. 11B spec */ | ||
361 | if (tcb_desc->hw_rate == rtlpriv->cfg->maps[RTL_RC_CCK_RATE1M]) | ||
362 | return; | ||
363 | else if (rate_flag & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) | ||
364 | tcb_desc->use_shortpreamble = true; | ||
365 | |||
366 | return; | ||
367 | } | ||
368 | |||
369 | static void _rtl_query_shortgi(struct ieee80211_hw *hw, | ||
370 | struct rtl_tcb_desc *tcb_desc, | ||
371 | struct ieee80211_tx_info *info) | ||
372 | { | ||
373 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
374 | u8 rate_flag = info->control.rates[0].flags; | ||
375 | |||
376 | tcb_desc->use_shortgi = false; | ||
377 | |||
378 | if (!mac->ht_enable) | ||
379 | return; | ||
380 | |||
381 | if (!mac->sgi_40 && !mac->sgi_20) | ||
382 | return; | ||
383 | |||
384 | if ((mac->bw_40 == true) && mac->sgi_40) | ||
385 | tcb_desc->use_shortgi = true; | ||
386 | else if ((mac->bw_40 == false) && mac->sgi_20) | ||
387 | tcb_desc->use_shortgi = true; | ||
388 | |||
389 | if (!(rate_flag & IEEE80211_TX_RC_SHORT_GI)) | ||
390 | tcb_desc->use_shortgi = false; | ||
391 | |||
392 | } | ||
393 | |||
394 | static void _rtl_query_protection_mode(struct ieee80211_hw *hw, | ||
395 | struct rtl_tcb_desc *tcb_desc, | ||
396 | struct ieee80211_tx_info *info) | ||
397 | { | ||
398 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
399 | u8 rate_flag = info->control.rates[0].flags; | ||
400 | |||
401 | /* Common Settings */ | ||
402 | tcb_desc->b_rts_stbc = false; | ||
403 | tcb_desc->b_cts_enable = false; | ||
404 | tcb_desc->rts_sc = 0; | ||
405 | tcb_desc->b_rts_bw = false; | ||
406 | tcb_desc->b_rts_use_shortpreamble = false; | ||
407 | tcb_desc->b_rts_use_shortgi = false; | ||
408 | |||
409 | if (rate_flag & IEEE80211_TX_RC_USE_CTS_PROTECT) { | ||
410 | /* Use CTS-to-SELF in protection mode. */ | ||
411 | tcb_desc->b_rts_enable = true; | ||
412 | tcb_desc->b_cts_enable = true; | ||
413 | tcb_desc->rts_rate = rtlpriv->cfg->maps[RTL_RC_OFDM_RATE24M]; | ||
414 | } else if (rate_flag & IEEE80211_TX_RC_USE_RTS_CTS) { | ||
415 | /* Use RTS-CTS in protection mode. */ | ||
416 | tcb_desc->b_rts_enable = true; | ||
417 | tcb_desc->rts_rate = rtlpriv->cfg->maps[RTL_RC_OFDM_RATE24M]; | ||
418 | } | ||
419 | |||
420 | } | ||
421 | |||
422 | static void _rtl_txrate_selectmode(struct ieee80211_hw *hw, | ||
423 | struct rtl_tcb_desc *tcb_desc) | ||
424 | { | ||
425 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
426 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
427 | |||
428 | if (!tcb_desc->disable_ratefallback || !tcb_desc->use_driver_rate) { | ||
429 | if (mac->opmode == NL80211_IFTYPE_STATION) | ||
430 | tcb_desc->ratr_index = 0; | ||
431 | else if (mac->opmode == NL80211_IFTYPE_ADHOC) { | ||
432 | if (tcb_desc->b_multicast || tcb_desc->b_broadcast) { | ||
433 | tcb_desc->hw_rate = | ||
434 | rtlpriv->cfg->maps[RTL_RC_CCK_RATE2M]; | ||
435 | tcb_desc->use_driver_rate = 1; | ||
436 | } else { | ||
437 | /* TODO */ | ||
438 | } | ||
439 | } | ||
440 | } | ||
441 | |||
442 | if (rtlpriv->dm.b_useramask) { | ||
443 | /* TODO we will differentiate adhoc and station futrue */ | ||
444 | tcb_desc->mac_id = 0; | ||
445 | |||
446 | if ((mac->mode == WIRELESS_MODE_N_24G) || | ||
447 | (mac->mode == WIRELESS_MODE_N_5G)) { | ||
448 | tcb_desc->ratr_index = RATR_INX_WIRELESS_NGB; | ||
449 | } else if (mac->mode & WIRELESS_MODE_G) { | ||
450 | tcb_desc->ratr_index = RATR_INX_WIRELESS_GB; | ||
451 | } else if (mac->mode & WIRELESS_MODE_B) { | ||
452 | tcb_desc->ratr_index = RATR_INX_WIRELESS_B; | ||
453 | } | ||
454 | } | ||
455 | |||
456 | } | ||
457 | |||
458 | static void _rtl_query_bandwidth_mode(struct ieee80211_hw *hw, | ||
459 | struct rtl_tcb_desc *tcb_desc) | ||
460 | { | ||
461 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
462 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
463 | |||
464 | tcb_desc->b_packet_bw = false; | ||
465 | |||
466 | if (!mac->bw_40 || !mac->ht_enable) | ||
467 | return; | ||
468 | |||
469 | if (tcb_desc->b_multicast || tcb_desc->b_broadcast) | ||
470 | return; | ||
471 | |||
472 | /*use legency rate, shall use 20MHz */ | ||
473 | if (tcb_desc->hw_rate <= rtlpriv->cfg->maps[RTL_RC_OFDM_RATE54M]) | ||
474 | return; | ||
475 | |||
476 | tcb_desc->b_packet_bw = true; | ||
477 | } | ||
478 | |||
479 | static u8 _rtl_get_highest_n_rate(struct ieee80211_hw *hw) | ||
480 | { | ||
481 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
482 | struct rtl_phy *rtlphy = &(rtlpriv->phy); | ||
483 | u8 hw_rate; | ||
484 | |||
485 | if (get_rf_type(rtlphy) == RF_2T2R) | ||
486 | hw_rate = rtlpriv->cfg->maps[RTL_RC_HT_RATEMCS15]; | ||
487 | else | ||
488 | hw_rate = rtlpriv->cfg->maps[RTL_RC_HT_RATEMCS7]; | ||
489 | |||
490 | return hw_rate; | ||
491 | } | ||
492 | |||
493 | void rtl_get_tcb_desc(struct ieee80211_hw *hw, | ||
494 | struct ieee80211_tx_info *info, | ||
495 | struct sk_buff *skb, struct rtl_tcb_desc *tcb_desc) | ||
496 | { | ||
497 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
498 | struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw)); | ||
499 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); | ||
500 | struct ieee80211_rate *txrate; | ||
501 | u16 fc = le16_to_cpu(hdr->frame_control); | ||
502 | |||
503 | memset(tcb_desc, 0, sizeof(struct rtl_tcb_desc)); | ||
504 | |||
505 | if (ieee80211_is_data(fc)) { | ||
506 | txrate = ieee80211_get_tx_rate(hw, info); | ||
507 | tcb_desc->hw_rate = txrate->hw_value; | ||
508 | |||
509 | /* | ||
510 | *we set data rate RTL_RC_CCK_RATE1M | ||
511 | *in rtl_rc.c if skb is special data or | ||
512 | *mgt which need low data rate. | ||
513 | */ | ||
514 | |||
515 | /* | ||
516 | *So tcb_desc->hw_rate is just used for | ||
517 | *special data and mgt frames | ||
518 | */ | ||
519 | if (tcb_desc->hw_rate < rtlpriv->cfg->maps[RTL_RC_CCK_RATE11M]) { | ||
520 | tcb_desc->use_driver_rate = true; | ||
521 | tcb_desc->ratr_index = 7; | ||
522 | |||
523 | tcb_desc->hw_rate = | ||
524 | rtlpriv->cfg->maps[RTL_RC_CCK_RATE1M]; | ||
525 | tcb_desc->disable_ratefallback = 1; | ||
526 | } else { | ||
527 | /* | ||
528 | *because hw will nerver use hw_rate | ||
529 | *when tcb_desc->use_driver_rate = false | ||
530 | *so we never set highest N rate here, | ||
531 | *and N rate will all be controled by FW | ||
532 | *when tcb_desc->use_driver_rate = false | ||
533 | */ | ||
534 | if (rtlmac->ht_enable) { | ||
535 | tcb_desc->hw_rate = _rtl_get_highest_n_rate(hw); | ||
536 | } else { | ||
537 | if (rtlmac->mode == WIRELESS_MODE_B) { | ||
538 | tcb_desc->hw_rate = | ||
539 | rtlpriv->cfg->maps[RTL_RC_CCK_RATE11M]; | ||
540 | } else { | ||
541 | tcb_desc->hw_rate = | ||
542 | rtlpriv->cfg->maps[RTL_RC_OFDM_RATE54M]; | ||
543 | } | ||
544 | } | ||
545 | } | ||
546 | |||
547 | if (is_multicast_ether_addr(ieee80211_get_DA(hdr))) | ||
548 | tcb_desc->b_multicast = 1; | ||
549 | else if (is_broadcast_ether_addr(ieee80211_get_DA(hdr))) | ||
550 | tcb_desc->b_broadcast = 1; | ||
551 | |||
552 | _rtl_txrate_selectmode(hw, tcb_desc); | ||
553 | _rtl_query_bandwidth_mode(hw, tcb_desc); | ||
554 | _rtl_qurey_shortpreamble_mode(hw, tcb_desc, info); | ||
555 | _rtl_query_shortgi(hw, tcb_desc, info); | ||
556 | _rtl_query_protection_mode(hw, tcb_desc, info); | ||
557 | } else { | ||
558 | tcb_desc->use_driver_rate = true; | ||
559 | tcb_desc->ratr_index = 7; | ||
560 | tcb_desc->disable_ratefallback = 1; | ||
561 | tcb_desc->mac_id = 0; | ||
562 | |||
563 | tcb_desc->hw_rate = rtlpriv->cfg->maps[RTL_RC_CCK_RATE1M]; | ||
564 | } | ||
565 | } | ||
566 | EXPORT_SYMBOL(rtl_get_tcb_desc); | ||
567 | |||
568 | bool rtl_tx_mgmt_proc(struct ieee80211_hw *hw, struct sk_buff *skb) | ||
569 | { | ||
570 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
571 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
572 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); | ||
573 | u16 fc = le16_to_cpu(hdr->frame_control); | ||
574 | |||
575 | if (ieee80211_is_auth(fc)) { | ||
576 | RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, ("MAC80211_LINKING\n")); | ||
577 | rtl_ips_nic_on(hw); | ||
578 | |||
579 | mac->link_state = MAC80211_LINKING; | ||
580 | } | ||
581 | |||
582 | return true; | ||
583 | } | ||
584 | |||
585 | bool rtl_action_proc(struct ieee80211_hw *hw, struct sk_buff *skb, u8 is_tx) | ||
586 | { | ||
587 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
588 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); | ||
589 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
590 | u16 fc = le16_to_cpu(hdr->frame_control); | ||
591 | u8 *act = (u8 *) (((u8 *) skb->data + MAC80211_3ADDR_LEN)); | ||
592 | u8 category; | ||
593 | |||
594 | if (!ieee80211_is_action(fc)) | ||
595 | return true; | ||
596 | |||
597 | category = *act; | ||
598 | act++; | ||
599 | switch (category) { | ||
600 | case ACT_CAT_BA: | ||
601 | switch (*act) { | ||
602 | case ACT_ADDBAREQ: | ||
603 | if (mac->act_scanning) | ||
604 | return false; | ||
605 | |||
606 | RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, | ||
607 | ("%s ACT_ADDBAREQ From :" MAC_FMT "\n", | ||
608 | is_tx ? "Tx" : "Rx", MAC_ARG(hdr->addr2))); | ||
609 | break; | ||
610 | case ACT_ADDBARSP: | ||
611 | RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, | ||
612 | ("%s ACT_ADDBARSP From :" MAC_FMT "\n", | ||
613 | is_tx ? "Tx" : "Rx", MAC_ARG(hdr->addr2))); | ||
614 | break; | ||
615 | case ACT_DELBA: | ||
616 | RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, | ||
617 | ("ACT_ADDBADEL From :" MAC_FMT "\n", | ||
618 | MAC_ARG(hdr->addr2))); | ||
619 | break; | ||
620 | } | ||
621 | break; | ||
622 | default: | ||
623 | break; | ||
624 | } | ||
625 | |||
626 | return true; | ||
627 | } | ||
628 | |||
629 | /*should call before software enc*/ | ||
630 | u8 rtl_is_special_data(struct ieee80211_hw *hw, struct sk_buff *skb, u8 is_tx) | ||
631 | { | ||
632 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
633 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); | ||
634 | struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); | ||
635 | u16 fc = le16_to_cpu(hdr->frame_control); | ||
636 | u16 ether_type; | ||
637 | u8 mac_hdr_len = ieee80211_get_hdrlen_from_skb(skb); | ||
638 | const struct iphdr *ip; | ||
639 | |||
640 | if (!ieee80211_is_data(fc)) | ||
641 | goto end; | ||
642 | |||
643 | if (ieee80211_is_nullfunc(fc)) | ||
644 | return true; | ||
645 | |||
646 | ip = (struct iphdr *)((u8 *) skb->data + mac_hdr_len + | ||
647 | SNAP_SIZE + PROTOC_TYPE_SIZE); | ||
648 | ether_type = *(u16 *) ((u8 *) skb->data + mac_hdr_len + SNAP_SIZE); | ||
649 | ether_type = ntohs(ether_type); | ||
650 | |||
651 | if (ETH_P_IP == ether_type) { | ||
652 | if (IPPROTO_UDP == ip->protocol) { | ||
653 | struct udphdr *udp = (struct udphdr *)((u8 *) ip + | ||
654 | (ip->ihl << 2)); | ||
655 | if (((((u8 *) udp)[1] == 68) && | ||
656 | (((u8 *) udp)[3] == 67)) || | ||
657 | ((((u8 *) udp)[1] == 67) && | ||
658 | (((u8 *) udp)[3] == 68))) { | ||
659 | /* | ||
660 | * 68 : UDP BOOTP client | ||
661 | * 67 : UDP BOOTP server | ||
662 | */ | ||
663 | RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), | ||
664 | DBG_DMESG, ("dhcp %s !!\n", | ||
665 | (is_tx) ? "Tx" : "Rx")); | ||
666 | |||
667 | if (is_tx) { | ||
668 | rtl_lps_leave(hw); | ||
669 | ppsc->last_delaylps_stamp_jiffies = | ||
670 | jiffies; | ||
671 | } | ||
672 | |||
673 | return true; | ||
674 | } | ||
675 | } | ||
676 | } else if (ETH_P_ARP == ether_type) { | ||
677 | if (is_tx) { | ||
678 | rtl_lps_leave(hw); | ||
679 | ppsc->last_delaylps_stamp_jiffies = jiffies; | ||
680 | } | ||
681 | |||
682 | return true; | ||
683 | } else if (ETH_P_PAE == ether_type) { | ||
684 | RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, | ||
685 | ("802.1X %s EAPOL pkt!!\n", (is_tx) ? "Tx" : "Rx")); | ||
686 | |||
687 | if (is_tx) { | ||
688 | rtl_lps_leave(hw); | ||
689 | ppsc->last_delaylps_stamp_jiffies = jiffies; | ||
690 | } | ||
691 | |||
692 | return true; | ||
693 | } else if (0x86DD == ether_type) { | ||
694 | return true; | ||
695 | } | ||
696 | |||
697 | end: | ||
698 | return false; | ||
699 | } | ||
700 | |||
701 | /********************************************************* | ||
702 | * | ||
703 | * functions called by core.c | ||
704 | * | ||
705 | *********************************************************/ | ||
706 | int rtl_tx_agg_start(struct ieee80211_hw *hw, const u8 *ra, u16 tid, u16 *ssn) | ||
707 | { | ||
708 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
709 | struct rtl_tid_data *tid_data; | ||
710 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
711 | |||
712 | RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, | ||
713 | ("on ra = %pM tid = %d\n", ra, tid)); | ||
714 | |||
715 | if (unlikely(tid >= MAX_TID_COUNT)) | ||
716 | return -EINVAL; | ||
717 | |||
718 | if (mac->tids[tid].agg.agg_state != RTL_AGG_OFF) { | ||
719 | RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, | ||
720 | ("Start AGG when state is not RTL_AGG_OFF !\n")); | ||
721 | return -ENXIO; | ||
722 | } | ||
723 | |||
724 | tid_data = &mac->tids[tid]; | ||
725 | *ssn = SEQ_TO_SN(tid_data->seq_number); | ||
726 | |||
727 | RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, | ||
728 | ("HW queue is empty tid:%d\n", tid)); | ||
729 | tid_data->agg.agg_state = RTL_AGG_ON; | ||
730 | |||
731 | ieee80211_start_tx_ba_cb_irqsafe(mac->vif, ra, tid); | ||
732 | |||
733 | return 0; | ||
734 | } | ||
735 | |||
736 | int rtl_tx_agg_stop(struct ieee80211_hw *hw, const u8 * ra, u16 tid) | ||
737 | { | ||
738 | int ssn = -1; | ||
739 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
740 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
741 | struct rtl_tid_data *tid_data; | ||
742 | |||
743 | if (!ra) { | ||
744 | RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, ("ra = NULL\n")); | ||
745 | return -EINVAL; | ||
746 | } | ||
747 | |||
748 | if (unlikely(tid >= MAX_TID_COUNT)) | ||
749 | return -EINVAL; | ||
750 | |||
751 | if (mac->tids[tid].agg.agg_state != RTL_AGG_ON) | ||
752 | RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, | ||
753 | ("Stopping AGG while state not ON or starting\n")); | ||
754 | |||
755 | tid_data = &mac->tids[tid]; | ||
756 | ssn = (tid_data->seq_number & IEEE80211_SCTL_SEQ) >> 4; | ||
757 | |||
758 | mac->tids[tid].agg.agg_state = RTL_AGG_OFF; | ||
759 | |||
760 | ieee80211_stop_tx_ba_cb_irqsafe(mac->vif, ra, tid); | ||
761 | |||
762 | return 0; | ||
763 | } | ||
764 | |||
765 | /********************************************************* | ||
766 | * | ||
767 | * wq & timer callback functions | ||
768 | * | ||
769 | *********************************************************/ | ||
770 | void rtl_watchdog_wq_callback(void *data) | ||
771 | { | ||
772 | struct rtl_works *rtlworks = container_of_dwork_rtl(data, | ||
773 | struct rtl_works, | ||
774 | watchdog_wq); | ||
775 | struct ieee80211_hw *hw = rtlworks->hw; | ||
776 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
777 | struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); | ||
778 | struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); | ||
779 | |||
780 | bool b_busytraffic = false; | ||
781 | bool b_higher_busytraffic = false; | ||
782 | bool b_higher_busyrxtraffic = false; | ||
783 | bool b_higher_busytxtraffic = false; | ||
784 | |||
785 | u8 idx = 0; | ||
786 | u32 rx_cnt_inp4eriod = 0; | ||
787 | u32 tx_cnt_inp4eriod = 0; | ||
788 | u32 aver_rx_cnt_inperiod = 0; | ||
789 | u32 aver_tx_cnt_inperiod = 0; | ||
790 | |||
791 | bool benter_ps = false; | ||
792 | |||
793 | if (is_hal_stop(rtlhal)) | ||
794 | return; | ||
795 | |||
796 | /* <1> Determine if action frame is allowed */ | ||
797 | if (mac->link_state > MAC80211_NOLINK) { | ||
798 | if (mac->cnt_after_linked < 20) | ||
799 | mac->cnt_after_linked++; | ||
800 | } else { | ||
801 | mac->cnt_after_linked = 0; | ||
802 | } | ||
803 | |||
804 | /* <2> DM */ | ||
805 | rtlpriv->cfg->ops->dm_watchdog(hw); | ||
806 | |||
807 | /* | ||
808 | *<3> to check if traffic busy, if | ||
809 | * busytraffic we don't change channel | ||
810 | */ | ||
811 | if (mac->link_state >= MAC80211_LINKED) { | ||
812 | |||
813 | /* (1) get aver_rx_cnt_inperiod & aver_tx_cnt_inperiod */ | ||
814 | for (idx = 0; idx <= 2; idx++) { | ||
815 | rtlpriv->link_info.num_rx_in4period[idx] = | ||
816 | rtlpriv->link_info.num_rx_in4period[idx + 1]; | ||
817 | rtlpriv->link_info.num_tx_in4period[idx] = | ||
818 | rtlpriv->link_info.num_tx_in4period[idx + 1]; | ||
819 | } | ||
820 | rtlpriv->link_info.num_rx_in4period[3] = | ||
821 | rtlpriv->link_info.num_rx_inperiod; | ||
822 | rtlpriv->link_info.num_tx_in4period[3] = | ||
823 | rtlpriv->link_info.num_tx_inperiod; | ||
824 | for (idx = 0; idx <= 3; idx++) { | ||
825 | rx_cnt_inp4eriod += | ||
826 | rtlpriv->link_info.num_rx_in4period[idx]; | ||
827 | tx_cnt_inp4eriod += | ||
828 | rtlpriv->link_info.num_tx_in4period[idx]; | ||
829 | } | ||
830 | aver_rx_cnt_inperiod = rx_cnt_inp4eriod / 4; | ||
831 | aver_tx_cnt_inperiod = tx_cnt_inp4eriod / 4; | ||
832 | |||
833 | /* (2) check traffic busy */ | ||
834 | if (aver_rx_cnt_inperiod > 100 || aver_tx_cnt_inperiod > 100) | ||
835 | b_busytraffic = true; | ||
836 | |||
837 | /* Higher Tx/Rx data. */ | ||
838 | if (aver_rx_cnt_inperiod > 4000 || | ||
839 | aver_tx_cnt_inperiod > 4000) { | ||
840 | b_higher_busytraffic = true; | ||
841 | |||
842 | /* Extremely high Rx data. */ | ||
843 | if (aver_rx_cnt_inperiod > 5000) | ||
844 | b_higher_busyrxtraffic = true; | ||
845 | else | ||
846 | b_higher_busytxtraffic = false; | ||
847 | } | ||
848 | |||
849 | if (((rtlpriv->link_info.num_rx_inperiod + | ||
850 | rtlpriv->link_info.num_tx_inperiod) > 8) || | ||
851 | (rtlpriv->link_info.num_rx_inperiod > 2)) | ||
852 | benter_ps = false; | ||
853 | else | ||
854 | benter_ps = true; | ||
855 | |||
856 | /* LeisurePS only work in infra mode. */ | ||
857 | if (benter_ps) | ||
858 | rtl_lps_enter(hw); | ||
859 | else | ||
860 | rtl_lps_leave(hw); | ||
861 | } | ||
862 | |||
863 | rtlpriv->link_info.num_rx_inperiod = 0; | ||
864 | rtlpriv->link_info.num_tx_inperiod = 0; | ||
865 | |||
866 | rtlpriv->link_info.b_busytraffic = b_busytraffic; | ||
867 | rtlpriv->link_info.b_higher_busytraffic = b_higher_busytraffic; | ||
868 | rtlpriv->link_info.b_higher_busyrxtraffic = b_higher_busyrxtraffic; | ||
869 | |||
870 | } | ||
871 | |||
872 | void rtl_watch_dog_timer_callback(unsigned long data) | ||
873 | { | ||
874 | struct ieee80211_hw *hw = (struct ieee80211_hw *)data; | ||
875 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
876 | |||
877 | queue_delayed_work(rtlpriv->works.rtl_wq, | ||
878 | &rtlpriv->works.watchdog_wq, 0); | ||
879 | |||
880 | mod_timer(&rtlpriv->works.watchdog_timer, | ||
881 | jiffies + MSECS(RTL_WATCH_DOG_TIME)); | ||
882 | } | ||
883 | |||
884 | /********************************************************* | ||
885 | * | ||
886 | * sysfs functions | ||
887 | * | ||
888 | *********************************************************/ | ||
889 | static ssize_t rtl_show_debug_level(struct device *d, | ||
890 | struct device_attribute *attr, char *buf) | ||
891 | { | ||
892 | struct ieee80211_hw *hw = dev_get_drvdata(d); | ||
893 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
894 | |||
895 | return sprintf(buf, "0x%08X\n", rtlpriv->dbg.global_debuglevel); | ||
896 | } | ||
897 | |||
898 | static ssize_t rtl_store_debug_level(struct device *d, | ||
899 | struct device_attribute *attr, | ||
900 | const char *buf, size_t count) | ||
901 | { | ||
902 | struct ieee80211_hw *hw = dev_get_drvdata(d); | ||
903 | struct rtl_priv *rtlpriv = rtl_priv(hw); | ||
904 | unsigned long val; | ||
905 | int ret; | ||
906 | |||
907 | ret = strict_strtoul(buf, 0, &val); | ||
908 | if (ret) { | ||
909 | printk(KERN_DEBUG "%s is not in hex or decimal form.\n", buf); | ||
910 | } else { | ||
911 | rtlpriv->dbg.global_debuglevel = val; | ||
912 | printk(KERN_DEBUG "debuglevel:%x\n", | ||
913 | rtlpriv->dbg.global_debuglevel); | ||
914 | } | ||
915 | |||
916 | return strnlen(buf, count); | ||
917 | } | ||
918 | |||
919 | static DEVICE_ATTR(debug_level, S_IWUSR | S_IRUGO, | ||
920 | rtl_show_debug_level, rtl_store_debug_level); | ||
921 | |||
922 | static struct attribute *rtl_sysfs_entries[] = { | ||
923 | |||
924 | &dev_attr_debug_level.attr, | ||
925 | |||
926 | NULL | ||
927 | }; | ||
928 | |||
929 | /* | ||
930 | * "name" is folder name witch will be | ||
931 | * put in device directory like : | ||
932 | * sys/devices/pci0000:00/0000:00:1c.4/ | ||
933 | * 0000:06:00.0/rtl_sysfs | ||
934 | */ | ||
935 | struct attribute_group rtl_attribute_group = { | ||
936 | .name = "rtlsysfs", | ||
937 | .attrs = rtl_sysfs_entries, | ||
938 | }; | ||
939 | |||
940 | MODULE_AUTHOR("lizhaoming <chaoming_li@realsil.com.cn>"); | ||
941 | MODULE_AUTHOR("Realtek WlanFAE <wlanfae@realtek.com>"); | ||
942 | MODULE_AUTHOR("Larry Finger <Larry.FInger@lwfinger.net>"); | ||
943 | MODULE_LICENSE("GPL"); | ||
944 | MODULE_DESCRIPTION("Realtek 802.11n PCI wireless core"); | ||
945 | |||
946 | static int __init rtl_core_module_init(void) | ||
947 | { | ||
948 | return 0; | ||
949 | } | ||
950 | |||
951 | static void __exit rtl_core_module_exit(void) | ||
952 | { | ||
953 | } | ||
954 | |||
955 | module_init(rtl_core_module_init); | ||
956 | module_exit(rtl_core_module_exit); | ||