| /*********************************************************************** |
| ** Copyright (C) 2003 ACX100 Open Source Project |
| ** |
| ** The contents of this file are subject to the Mozilla Public |
| ** License Version 1.1 (the "License"); you may not use this file |
| ** except in compliance with the License. You may obtain a copy of |
| ** the License at http://www.mozilla.org/MPL/ |
| ** |
| ** Software distributed under the License is distributed on an "AS |
| ** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
| ** implied. See the License for the specific language governing |
| ** rights and limitations under the License. |
| ** |
| ** Alternatively, the contents of this file may be used under the |
| ** terms of the GNU Public License version 2 (the "GPL"), in which |
| ** case the provisions of the GPL are applicable instead of the |
| ** above. If you wish to allow the use of your version of this file |
| ** only under the terms of the GPL and not to allow others to use |
| ** your version of this file under the MPL, indicate your decision |
| ** by deleting the provisions above and replace them with the notice |
| ** and other provisions required by the GPL. If you do not delete |
| ** the provisions above, a recipient may use your version of this |
| ** file under either the MPL or the GPL. |
| ** --------------------------------------------------------------------- |
| ** Inquiries regarding the ACX100 Open Source Project can be |
| ** made directly to: |
| ** |
| ** acx100-users@lists.sf.net |
| ** http://acx100.sf.net |
| ** --------------------------------------------------------------------- |
| */ |
| |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/types.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/proc_fs.h> |
| #include <linux/if_arp.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/wireless.h> |
| #include <linux/pm.h> |
| #include <linux/vmalloc.h> |
| #include <linux/firmware.h> /* request_firmware() */ |
| #include <net/iw_handler.h> |
| |
| #include "acx.h" |
| |
| |
| /*********************************************************************** |
| */ |
| #if ACX_DEBUG |
| unsigned int acx_debug /* will add __read_mostly later */ = ACX_DEFAULT_MSG; |
| EXPORT_SYMBOL_GPL(acx_debug); |
| /* parameter is 'debug', corresponding var is acx_debug */ |
| module_param_named(debug, acx_debug, uint, 0); |
| MODULE_PARM_DESC(debug, "Debug level mask (see L_xxx constants)"); |
| #endif |
| |
| #ifdef MODULE_LICENSE |
| MODULE_LICENSE("Dual MPL/GPL"); |
| #endif |
| /* USB had this: MODULE_AUTHOR("Martin Wawro <martin.wawro AT uni-dortmund.de>"); */ |
| MODULE_AUTHOR("ACX100 Open Source Driver development team"); |
| MODULE_DESCRIPTION("Driver for TI ACX1xx based wireless cards (common)"); |
| |
| |
| /*********************************************************************** |
| */ |
| /* Probably a number of acx's intermediate buffers for USB transfers, |
| ** not to be confused with number of descriptors in tx/rx rings |
| ** (which are not directly accessible to host in USB devices) */ |
| #define USB_RX_CNT 10 |
| #define USB_TX_CNT 10 |
| |
| |
| /*********************************************************************** |
| */ |
| |
| /* minutes to wait until next radio recalibration: */ |
| #define RECALIB_PAUSE 5 |
| |
| /* Please keep acx_reg_domain_ids_len in sync... */ |
| const u8 acx_reg_domain_ids[acx_reg_domain_ids_len] = |
| { 0x10, 0x20, 0x30, 0x31, 0x32, 0x40, 0x41, 0x51 }; |
| static const u16 reg_domain_channel_masks[acx_reg_domain_ids_len] = |
| { 0x07ff, 0x07ff, 0x1fff, 0x0600, 0x1e00, 0x2000, 0x3fff, 0x01fc }; |
| const char * const |
| acx_reg_domain_strings[] = { |
| /* 0 */ " 1-11 FCC (USA)", |
| /* 1 */ " 1-11 DOC/IC (Canada)", |
| /* BTW: WLAN use in ETSI is regulated by ETSI standard EN 300 328-2 V1.1.2 */ |
| /* 2 */ " 1-13 ETSI (Europe)", |
| /* 3 */ "10-11 Spain", |
| /* 4 */ "10-13 France", |
| /* 5 */ " 14 MKK (Japan)", |
| /* 6 */ " 1-14 MKK1", |
| /* 7 */ " 3-9 Israel (not all firmware versions)", |
| NULL /* needs to remain as last entry */ |
| }; |
| |
| |
| |
| /*********************************************************************** |
| ** Debugging support |
| */ |
| #ifdef PARANOID_LOCKING |
| static unsigned max_lock_time; |
| static unsigned max_sem_time; |
| |
| void |
| acx_lock_unhold() { max_lock_time = 0; } |
| void |
| acx_sem_unhold() { max_sem_time = 0; } |
| |
| static inline const char* |
| sanitize_str(const char *s) |
| { |
| const char* t = strrchr(s, '/'); |
| if (t) return t + 1; |
| return s; |
| } |
| |
| void |
| acx_lock_debug(acx_device_t *adev, const char* where) |
| { |
| unsigned int count = 100*1000*1000; |
| where = sanitize_str(where); |
| while (--count) { |
| if (!spin_is_locked(&adev->lock)) break; |
| cpu_relax(); |
| } |
| if (!count) { |
| printk(KERN_EMERG "LOCKUP: already taken at %s!\n", adev->last_lock); |
| BUG(); |
| } |
| adev->last_lock = where; |
| rdtscl(adev->lock_time); |
| } |
| void |
| acx_unlock_debug(acx_device_t *adev, const char* where) |
| { |
| #ifdef SMP |
| if (!spin_is_locked(&adev->lock)) { |
| where = sanitize_str(where); |
| printk(KERN_EMERG "STRAY UNLOCK at %s!\n", where); |
| BUG(); |
| } |
| #endif |
| if (acx_debug & L_LOCK) { |
| unsigned long diff; |
| rdtscl(diff); |
| diff -= adev->lock_time; |
| if (diff > max_lock_time) { |
| where = sanitize_str(where); |
| printk("max lock hold time %ld CPU ticks from %s " |
| "to %s\n", diff, adev->last_lock, where); |
| max_lock_time = diff; |
| } |
| } |
| } |
| void |
| acx_down_debug(acx_device_t *adev, const char* where) |
| { |
| int sem_count; |
| unsigned long timeout = jiffies + 5*HZ; |
| |
| where = sanitize_str(where); |
| |
| for (;;) { |
| sem_count = atomic_read(&adev->sem.count); |
| if (sem_count) break; |
| if (time_after(jiffies, timeout)) |
| break; |
| msleep(5); |
| } |
| if (!sem_count) { |
| printk(KERN_EMERG "D STATE at %s! last sem at %s\n", |
| where, adev->last_sem); |
| dump_stack(); |
| } |
| adev->last_sem = where; |
| adev->sem_time = jiffies; |
| down(&adev->sem); |
| if (acx_debug & L_LOCK) { |
| printk("%s: sem_down %d -> %d\n", |
| where, sem_count, atomic_read(&adev->sem.count)); |
| } |
| } |
| void |
| acx_up_debug(acx_device_t *adev, const char* where) |
| { |
| int sem_count = atomic_read(&adev->sem.count); |
| if (sem_count) { |
| where = sanitize_str(where); |
| printk(KERN_EMERG "STRAY UP at %s! sem.count=%d\n", where, sem_count); |
| dump_stack(); |
| } |
| if (acx_debug & L_LOCK) { |
| unsigned long diff = jiffies - adev->sem_time; |
| if (diff > max_sem_time) { |
| where = sanitize_str(where); |
| printk("max sem hold time %ld jiffies from %s " |
| "to %s\n", diff, adev->last_sem, where); |
| max_sem_time = diff; |
| } |
| } |
| up(&adev->sem); |
| if (acx_debug & L_LOCK) { |
| where = sanitize_str(where); |
| printk("%s: sem_up %d -> %d\n", |
| where, sem_count, atomic_read(&adev->sem.count)); |
| } |
| } |
| #endif /* PARANOID_LOCKING */ |
| |
| |
| /*********************************************************************** |
| */ |
| #if ACX_DEBUG > 1 |
| |
| static int acx_debug_func_indent; |
| #define DEBUG_TSC 0 |
| #define FUNC_INDENT_INCREMENT 2 |
| |
| #if DEBUG_TSC |
| #define TIMESTAMP(d) unsigned long d; rdtscl(d) |
| #else |
| #define TIMESTAMP(d) unsigned long d = jiffies |
| #endif |
| |
| static const char |
| spaces[] = " " " "; /* Nx10 spaces */ |
| |
| void |
| acx_log_fn_enter(const char *funcname) |
| { |
| int indent; |
| TIMESTAMP(d); |
| |
| indent = acx_debug_func_indent; |
| if (indent >= sizeof(spaces)) |
| indent = sizeof(spaces)-1; |
| |
| printk("%08ld %s==> %s\n", |
| d % 100000000, |
| spaces + (sizeof(spaces)-1) - indent, |
| funcname |
| ); |
| |
| acx_debug_func_indent += FUNC_INDENT_INCREMENT; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_log_fn_enter); |
| |
| void |
| acx_log_fn_exit(const char *funcname) |
| { |
| int indent; |
| TIMESTAMP(d); |
| |
| acx_debug_func_indent -= FUNC_INDENT_INCREMENT; |
| |
| indent = acx_debug_func_indent; |
| if (indent >= sizeof(spaces)) |
| indent = sizeof(spaces)-1; |
| |
| printk("%08ld %s<== %s\n", |
| d % 100000000, |
| spaces + (sizeof(spaces)-1) - indent, |
| funcname |
| ); |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_log_fn_exit); |
| |
| void |
| acx_log_fn_exit_v(const char *funcname, int v) |
| { |
| int indent; |
| TIMESTAMP(d); |
| |
| acx_debug_func_indent -= FUNC_INDENT_INCREMENT; |
| |
| indent = acx_debug_func_indent; |
| if (indent >= sizeof(spaces)) |
| indent = sizeof(spaces)-1; |
| |
| printk("%08ld %s<== %s: %08X\n", |
| d % 100000000, |
| spaces + (sizeof(spaces)-1) - indent, |
| funcname, |
| v |
| ); |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_log_fn_exit_v); |
| |
| #endif /* ACX_DEBUG > 1 */ |
| |
| |
| /*********************************************************************** |
| ** Basically a msleep with logging |
| */ |
| void |
| acx_s_msleep(int ms) |
| { |
| FN_ENTER; |
| msleep(ms); |
| FN_EXIT0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_s_msleep); |
| |
| /*********************************************************************** |
| ** Not inlined: it's larger than it seems |
| */ |
| void |
| acx_print_mac(const char *head, const u8 *mac, const char *tail) |
| { |
| printk("%s"MACSTR"%s", head, MAC(mac), tail); |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_get_status_name |
| */ |
| static const char* |
| acx_get_status_name(u16 status) |
| { |
| static const char * const str[] = { |
| "STOPPED", "SCANNING", "WAIT_AUTH", |
| "AUTHENTICATED", "ASSOCIATED", "INVALID??" |
| }; |
| if (status > VEC_SIZE(str)-1) |
| status = VEC_SIZE(str)-1; |
| |
| return str[status]; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_get_packet_type_string |
| */ |
| #if ACX_DEBUG |
| const char* |
| acx_get_packet_type_string(u16 fc) |
| { |
| static const char * const mgmt_arr[] = { |
| "MGMT/AssocReq", "MGMT/AssocResp", "MGMT/ReassocReq", |
| "MGMT/ReassocResp", "MGMT/ProbeReq", "MGMT/ProbeResp", |
| "MGMT/UNKNOWN", "MGMT/UNKNOWN", "MGMT/Beacon", "MGMT/ATIM", |
| "MGMT/Disassoc", "MGMT/Authen", "MGMT/Deauthen" |
| }; |
| static const char * const ctl_arr[] = { |
| "CTL/PSPoll", "CTL/RTS", "CTL/CTS", "CTL/Ack", "CTL/CFEnd", |
| "CTL/CFEndCFAck" |
| }; |
| static const char * const data_arr[] = { |
| "DATA/DataOnly", "DATA/Data CFAck", "DATA/Data CFPoll", |
| "DATA/Data CFAck/CFPoll", "DATA/Null", "DATA/CFAck", |
| "DATA/CFPoll", "DATA/CFAck/CFPoll" |
| }; |
| const char *str; |
| u8 fstype = (WF_FC_FSTYPE & fc) >> 4; |
| u8 ctl; |
| |
| switch (WF_FC_FTYPE & fc) { |
| case WF_FTYPE_MGMT: |
| if (fstype < VEC_SIZE(mgmt_arr)) |
| str = mgmt_arr[fstype]; |
| else |
| str = "MGMT/UNKNOWN"; |
| break; |
| case WF_FTYPE_CTL: |
| ctl = fstype - 0x0a; |
| if (ctl < VEC_SIZE(ctl_arr)) |
| str = ctl_arr[ctl]; |
| else |
| str = "CTL/UNKNOWN"; |
| break; |
| case WF_FTYPE_DATA: |
| if (fstype < VEC_SIZE(data_arr)) |
| str = data_arr[fstype]; |
| else |
| str = "DATA/UNKNOWN"; |
| break; |
| default: |
| str = "UNKNOWN"; |
| break; |
| } |
| return str; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_get_packet_type_string); |
| #endif |
| |
| |
| /*********************************************************************** |
| ** acx_wlan_reason_str |
| */ |
| static inline const char* |
| acx_wlan_reason_str(u16 reason) |
| { |
| static const char* const reason_str[] = { |
| /* 0 */ "?", |
| /* 1 */ "unspecified", |
| /* 2 */ "prev auth is not valid", |
| /* 3 */ "leaving BBS", |
| /* 4 */ "due to inactivity", |
| /* 5 */ "AP is busy", |
| /* 6 */ "got class 2 frame from non-auth'ed STA", |
| /* 7 */ "got class 3 frame from non-assoc'ed STA", |
| /* 8 */ "STA has left BSS", |
| /* 9 */ "assoc without auth is not allowed", |
| /* 10 */ "bad power setting (802.11h)", |
| /* 11 */ "bad channel (802.11i)", |
| /* 12 */ "?", |
| /* 13 */ "invalid IE", |
| /* 14 */ "MIC failure", |
| /* 15 */ "four-way handshake timeout", |
| /* 16 */ "group key handshake timeout", |
| /* 17 */ "IE is different", |
| /* 18 */ "invalid group cipher", |
| /* 19 */ "invalid pairwise cipher", |
| /* 20 */ "invalid AKMP", |
| /* 21 */ "unsupported RSN version", |
| /* 22 */ "invalid RSN IE cap", |
| /* 23 */ "802.1x failed", |
| /* 24 */ "cipher suite rejected" |
| }; |
| return reason < VEC_SIZE(reason_str) ? reason_str[reason] : "?"; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_cmd_status_str |
| */ |
| const char* |
| acx_cmd_status_str(unsigned int state) |
| { |
| static const char * const cmd_error_strings[] = { |
| "Idle", |
| "Success", |
| "Unknown Command", |
| "Invalid Information Element", |
| "Channel rejected", |
| "Channel invalid in current regulatory domain", |
| "MAC invalid", |
| "Command rejected (read-only information element)", |
| "Command rejected", |
| "Already asleep", |
| "TX in progress", |
| "Already awake", |
| "Write only", |
| "RX in progress", |
| "Invalid parameter", |
| "Scan in progress", |
| "Failed" |
| }; |
| return state < VEC_SIZE(cmd_error_strings) ? |
| cmd_error_strings[state] : "?"; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_cmd_status_str); |
| |
| |
| /*********************************************************************** |
| ** get_status_string |
| */ |
| static inline const char* |
| get_status_string(unsigned int status) |
| { |
| /* A bit shortened, but hopefully still understandable */ |
| static const char * const status_str[] = { |
| /* 0 */ "Successful", |
| /* 1 */ "Unspecified failure", |
| /* 2 */ "reserved", |
| /* 3 */ "reserved", |
| /* 4 */ "reserved", |
| /* 5 */ "reserved", |
| /* 6 */ "reserved", |
| /* 7 */ "reserved", |
| /* 8 */ "reserved", |
| /* 9 */ "reserved", |
| /*10 */ "Cannot support all requested capabilities in Capability Information field", |
| /*11 */ "Reassoc denied (reason outside of 802.11b scope)", |
| /*12 */ "Assoc denied (reason outside of 802.11b scope), maybe MAC filtering by peer?", |
| /*13 */ "Responding station doesnt support specified auth algorithm", |
| /*14 */ "Auth rejected: wrong transaction sequence number", |
| /*15 */ "Auth rejected: challenge failure", |
| /*16 */ "Auth rejected: timeout for next frame in sequence", |
| /*17 */ "Assoc denied: too many STAs on this AP", |
| /*18 */ "Assoc denied: requesting STA doesnt support all data rates in basic set", |
| /*19 */ "Assoc denied: requesting STA doesnt support Short Preamble", |
| /*20 */ "Assoc denied: requesting STA doesnt support PBCC Modulation", |
| /*21 */ "Assoc denied: requesting STA doesnt support Channel Agility" |
| /*22 */ "reserved", |
| /*23 */ "reserved", |
| /*24 */ "reserved", |
| /*25 */ "Assoc denied: requesting STA doesnt support Short Slot Time", |
| /*26 */ "Assoc denied: requesting STA doesnt support DSSS-OFDM" |
| }; |
| |
| return status_str[status < VEC_SIZE(status_str) ? status : 2]; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| #if ACX_DEBUG |
| void |
| acx_dump_bytes(const void *data, int num) |
| { |
| const u8* ptr = (const u8*)data; |
| |
| if (num <= 0) { |
| printk("\n"); |
| return; |
| } |
| |
| while (num >= 16) { |
| printk( "%02X %02X %02X %02X %02X %02X %02X %02X " |
| "%02X %02X %02X %02X %02X %02X %02X %02X\n", |
| ptr[0], ptr[1], ptr[2], ptr[3], |
| ptr[4], ptr[5], ptr[6], ptr[7], |
| ptr[8], ptr[9], ptr[10], ptr[11], |
| ptr[12], ptr[13], ptr[14], ptr[15]); |
| num -= 16; |
| ptr += 16; |
| } |
| if (num > 0) { |
| while (--num > 0) |
| printk("%02X ", *ptr++); |
| printk("%02X\n", *ptr); |
| } |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_dump_bytes); |
| #endif |
| |
| |
| /*********************************************************************** |
| ** acx_s_get_firmware_version |
| */ |
| void |
| acx_s_get_firmware_version(acx_device_t *adev) |
| { |
| fw_ver_t fw; |
| u8 hexarr[4] = { 0, 0, 0, 0 }; |
| int hexidx = 0, val = 0; |
| const char *num; |
| char c; |
| |
| FN_ENTER; |
| |
| memset(fw.fw_id, 'E', FW_ID_SIZE); |
| acx_s_interrogate(adev, &fw, ACX1xx_IE_FWREV); |
| memcpy(adev->firmware_version, fw.fw_id, FW_ID_SIZE); |
| adev->firmware_version[FW_ID_SIZE] = '\0'; |
| |
| log(L_DEBUG, "fw_ver: fw_id='%s' hw_id=%08X\n", |
| adev->firmware_version, fw.hw_id); |
| |
| if (strncmp(fw.fw_id, "Rev ", 4) != 0) { |
| printk("acx: strange firmware version string " |
| "'%s', please report\n", adev->firmware_version); |
| adev->firmware_numver = 0x01090407; /* assume 1.9.4.7 */ |
| } else { |
| num = &fw.fw_id[4]; |
| while (1) { |
| c = *num++; |
| if ((c == '.') || (c == '\0')) { |
| hexarr[hexidx++] = val; |
| if ((hexidx > 3) || (c == '\0')) /* end? */ |
| break; |
| val = 0; |
| continue; |
| } |
| if ((c >= '0') && (c <= '9')) |
| c -= '0'; |
| else |
| c = c - 'a' + (char)10; |
| val = val*16 + c; |
| } |
| |
| adev->firmware_numver = (u32)( |
| (hexarr[0] << 24) + (hexarr[1] << 16) |
| + (hexarr[2] << 8) + hexarr[3]); |
| log(L_DEBUG, "firmware_numver 0x%08X\n", adev->firmware_numver); |
| } |
| if (IS_ACX111(adev)) { |
| if (adev->firmware_numver == 0x00010011) { |
| /* This one does not survive floodpinging */ |
| printk("acx: firmware '%s' is known to be buggy, " |
| "please upgrade\n", adev->firmware_version); |
| } |
| } |
| |
| adev->firmware_id = le32_to_cpu(fw.hw_id); |
| |
| /* we're able to find out more detailed chip names now */ |
| switch (adev->firmware_id & 0xffff0000) { |
| case 0x01010000: |
| case 0x01020000: |
| adev->chip_name = "TNETW1100A"; |
| break; |
| case 0x01030000: |
| adev->chip_name = "TNETW1100B"; |
| break; |
| case 0x03000000: |
| case 0x03010000: |
| adev->chip_name = "TNETW1130"; |
| break; |
| case 0x04030000: /* 0x04030101 is TNETW1450 */ |
| adev->chip_name = "TNETW1450"; |
| break; |
| default: |
| printk("acx: unknown chip ID 0x%08X, " |
| "please report\n", adev->firmware_id); |
| break; |
| } |
| |
| FN_EXIT0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_s_get_firmware_version); |
| |
| /*********************************************************************** |
| ** acx_display_hardware_details |
| ** |
| ** Displays hw/fw version, radio type etc... |
| */ |
| void |
| acx_display_hardware_details(acx_device_t *adev) |
| { |
| const char *radio_str, *form_str; |
| |
| FN_ENTER; |
| |
| switch (adev->radio_type) { |
| case RADIO_MAXIM_0D: |
| radio_str = "Maxim"; |
| break; |
| case RADIO_RFMD_11: |
| radio_str = "RFMD"; |
| break; |
| case RADIO_RALINK_15: |
| radio_str = "Ralink"; |
| break; |
| case RADIO_RADIA_16: |
| radio_str = "Radia"; |
| break; |
| case RADIO_UNKNOWN_17: |
| /* TI seems to have a radio which is |
| * additionally 802.11a capable, too */ |
| radio_str = "802.11a/b/g radio?! Please report"; |
| break; |
| case RADIO_UNKNOWN_19: |
| radio_str = "A radio used by Safecom cards?! Please report"; |
| break; |
| case RADIO_UNKNOWN_1B: |
| radio_str = "An unknown radio used by TNETW1450 USB adapters"; |
| break; |
| default: |
| radio_str = "UNKNOWN, please report radio type name!"; |
| break; |
| } |
| |
| switch (adev->form_factor) { |
| case 0x00: |
| form_str = "unspecified"; |
| break; |
| case 0x01: |
| form_str = "(mini-)PCI / CardBus"; |
| break; |
| case 0x02: |
| form_str = "USB"; |
| break; |
| case 0x03: |
| form_str = "Compact Flash"; |
| break; |
| default: |
| form_str = "UNKNOWN, please report"; |
| break; |
| } |
| |
| printk("acx: chipset %s, radio type 0x%02X (%s), " |
| "form factor 0x%02X (%s), EEPROM version 0x%02X: " |
| "uploaded firmware '%s'\n", |
| adev->chip_name, adev->radio_type, radio_str, |
| adev->form_factor, form_str, adev->eeprom_version, |
| adev->firmware_version); |
| |
| FN_EXIT0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_display_hardware_details); |
| |
| /*********************************************************************** |
| */ |
| int |
| acx_e_change_mtu(struct net_device *ndev, int mtu) |
| { |
| enum { |
| MIN_MTU = 256, |
| MAX_MTU = WLAN_DATA_MAXLEN - (ETH_HLEN) |
| }; |
| |
| if (mtu < MIN_MTU || mtu > MAX_MTU) |
| return -EINVAL; |
| |
| ndev->mtu = mtu; |
| return 0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_e_change_mtu); |
| |
| /*********************************************************************** |
| ** acx_e_get_stats, acx_e_get_wireless_stats |
| */ |
| struct net_device_stats* |
| acx_e_get_stats(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| return &adev->stats; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_e_get_stats); |
| |
| struct iw_statistics* |
| acx_e_get_wireless_stats(struct net_device *ndev) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| return &adev->wstats; |
| } |
| |
| |
| /*********************************************************************** |
| ** maps acx111 tx descr rate field to acx100 one |
| */ |
| const u8 |
| acx_bitpos2rate100[] = { |
| RATE100_1 ,/* 0 */ |
| RATE100_2 ,/* 1 */ |
| RATE100_5 ,/* 2 */ |
| RATE100_2 ,/* 3, should not happen */ |
| RATE100_2 ,/* 4, should not happen */ |
| RATE100_11 ,/* 5 */ |
| RATE100_2 ,/* 6, should not happen */ |
| RATE100_2 ,/* 7, should not happen */ |
| RATE100_22 ,/* 8 */ |
| RATE100_2 ,/* 9, should not happen */ |
| RATE100_2 ,/* 10, should not happen */ |
| RATE100_2 ,/* 11, should not happen */ |
| RATE100_2 ,/* 12, should not happen */ |
| RATE100_2 ,/* 13, should not happen */ |
| RATE100_2 ,/* 14, should not happen */ |
| RATE100_2 ,/* 15, should not happen */ |
| }; |
| |
| u8 |
| acx_rate111to100(u16 r) { |
| return acx_bitpos2rate100[highest_bit(r)]; |
| } |
| |
| |
| /*********************************************************************** |
| ** Calculate level like the feb 2003 windows driver seems to do |
| */ |
| static u8 |
| acx_signal_to_winlevel(u8 rawlevel) |
| { |
| /* u8 winlevel = (u8) (0.5 + 0.625 * rawlevel); */ |
| u8 winlevel = ((4 + (rawlevel * 5)) / 8); |
| |
| if (winlevel > 100) |
| winlevel = 100; |
| return winlevel; |
| } |
| |
| u8 |
| acx_signal_determine_quality(u8 signal, u8 noise) |
| { |
| int qual; |
| |
| qual = (((signal - 30) * 100 / 70) + (100 - noise * 4)) / 2; |
| |
| if (qual > 100) |
| return 100; |
| if (qual < 0) |
| return 0; |
| return qual; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_signal_determine_quality); |
| |
| /*********************************************************************** |
| ** Interrogate/configure commands |
| */ |
| |
| /* FIXME: the lengths given here probably aren't always correct. |
| * They should be gradually replaced by proper "sizeof(acx1XX_ie_XXXX)-4", |
| * unless the firmware actually expects a different length than the struct length */ |
| static const u16 |
| acx100_ie_len[] = { |
| 0, |
| ACX100_IE_ACX_TIMER_LEN, |
| sizeof(acx100_ie_powersave_t)-4, /* is that 6 or 8??? */ |
| ACX1xx_IE_QUEUE_CONFIG_LEN, |
| ACX100_IE_BLOCK_SIZE_LEN, |
| ACX1xx_IE_MEMORY_CONFIG_OPTIONS_LEN, |
| ACX1xx_IE_RATE_FALLBACK_LEN, |
| ACX100_IE_WEP_OPTIONS_LEN, |
| ACX1xx_IE_MEMORY_MAP_LEN, /* ACX1xx_IE_SSID_LEN, */ |
| 0, |
| ACX1xx_IE_ASSOC_ID_LEN, |
| 0, |
| ACX111_IE_CONFIG_OPTIONS_LEN, |
| ACX1xx_IE_FWREV_LEN, |
| ACX1xx_IE_FCS_ERROR_COUNT_LEN, |
| ACX1xx_IE_MEDIUM_USAGE_LEN, |
| ACX1xx_IE_RXCONFIG_LEN, |
| 0, |
| 0, |
| sizeof(fw_stats_t)-4, |
| 0, |
| ACX1xx_IE_FEATURE_CONFIG_LEN, |
| ACX111_IE_KEY_CHOOSE_LEN, |
| ACX1FF_IE_MISC_CONFIG_TABLE_LEN, |
| ACX1FF_IE_WONE_CONFIG_LEN, |
| 0, |
| ACX1FF_IE_TID_CONFIG_LEN, |
| 0, |
| 0, |
| 0, |
| ACX1FF_IE_CALIB_ASSESSMENT_LEN, |
| ACX1FF_IE_BEACON_FILTER_OPTIONS_LEN, |
| ACX1FF_IE_LOW_RSSI_THRESH_OPT_LEN, |
| ACX1FF_IE_NOISE_HISTOGRAM_RESULTS_LEN, |
| 0, |
| ACX1FF_IE_PACKET_DETECT_THRESH_LEN, |
| ACX1FF_IE_TX_CONFIG_OPTIONS_LEN, |
| ACX1FF_IE_CCA_THRESHOLD_LEN, |
| ACX1FF_IE_EVENT_MASK_LEN, |
| ACX1FF_IE_DTIM_PERIOD_LEN, |
| 0, |
| ACX1FF_IE_ACI_CONFIG_SET_LEN, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| ACX1FF_IE_EEPROM_VER_LEN, |
| }; |
| |
| static const u16 |
| acx100_ie_len_dot11[] = { |
| 0, |
| ACX1xx_IE_DOT11_STATION_ID_LEN, |
| 0, |
| ACX100_IE_DOT11_BEACON_PERIOD_LEN, |
| ACX1xx_IE_DOT11_DTIM_PERIOD_LEN, |
| ACX1xx_IE_DOT11_SHORT_RETRY_LIMIT_LEN, |
| ACX1xx_IE_DOT11_LONG_RETRY_LIMIT_LEN, |
| ACX100_IE_DOT11_WEP_DEFAULT_KEY_WRITE_LEN, |
| ACX1xx_IE_DOT11_MAX_XMIT_MSDU_LIFETIME_LEN, |
| 0, |
| ACX1xx_IE_DOT11_CURRENT_REG_DOMAIN_LEN, |
| ACX1xx_IE_DOT11_CURRENT_ANTENNA_LEN, |
| 0, |
| ACX1xx_IE_DOT11_TX_POWER_LEVEL_LEN, |
| ACX1xx_IE_DOT11_CURRENT_CCA_MODE_LEN, |
| ACX100_IE_DOT11_ED_THRESHOLD_LEN, |
| ACX1xx_IE_DOT11_WEP_DEFAULT_KEY_SET_LEN, |
| 0, |
| 0, |
| 0, |
| }; |
| |
| static const u16 |
| acx111_ie_len[] = { |
| 0, |
| ACX100_IE_ACX_TIMER_LEN, |
| sizeof(acx111_ie_powersave_t)-4, |
| ACX1xx_IE_QUEUE_CONFIG_LEN, |
| ACX100_IE_BLOCK_SIZE_LEN, |
| ACX1xx_IE_MEMORY_CONFIG_OPTIONS_LEN, |
| ACX1xx_IE_RATE_FALLBACK_LEN, |
| ACX100_IE_WEP_OPTIONS_LEN, |
| ACX1xx_IE_MEMORY_MAP_LEN, /* ACX1xx_IE_SSID_LEN, */ |
| 0, |
| ACX1xx_IE_ASSOC_ID_LEN, |
| 0, |
| ACX111_IE_CONFIG_OPTIONS_LEN, |
| ACX1xx_IE_FWREV_LEN, |
| ACX1xx_IE_FCS_ERROR_COUNT_LEN, |
| ACX1xx_IE_MEDIUM_USAGE_LEN, |
| ACX1xx_IE_RXCONFIG_LEN, |
| 0, |
| 0, |
| sizeof(fw_stats_t)-4, |
| 0, |
| ACX1xx_IE_FEATURE_CONFIG_LEN, |
| ACX111_IE_KEY_CHOOSE_LEN, |
| ACX1FF_IE_MISC_CONFIG_TABLE_LEN, |
| ACX1FF_IE_WONE_CONFIG_LEN, |
| 0, |
| ACX1FF_IE_TID_CONFIG_LEN, |
| 0, |
| 0, |
| 0, |
| ACX1FF_IE_CALIB_ASSESSMENT_LEN, |
| ACX1FF_IE_BEACON_FILTER_OPTIONS_LEN, |
| ACX1FF_IE_LOW_RSSI_THRESH_OPT_LEN, |
| ACX1FF_IE_NOISE_HISTOGRAM_RESULTS_LEN, |
| 0, |
| ACX1FF_IE_PACKET_DETECT_THRESH_LEN, |
| ACX1FF_IE_TX_CONFIG_OPTIONS_LEN, |
| ACX1FF_IE_CCA_THRESHOLD_LEN, |
| ACX1FF_IE_EVENT_MASK_LEN, |
| ACX1FF_IE_DTIM_PERIOD_LEN, |
| 0, |
| ACX1FF_IE_ACI_CONFIG_SET_LEN, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| ACX1FF_IE_EEPROM_VER_LEN, |
| }; |
| |
| static const u16 |
| acx111_ie_len_dot11[] = { |
| 0, |
| ACX1xx_IE_DOT11_STATION_ID_LEN, |
| 0, |
| ACX100_IE_DOT11_BEACON_PERIOD_LEN, |
| ACX1xx_IE_DOT11_DTIM_PERIOD_LEN, |
| ACX1xx_IE_DOT11_SHORT_RETRY_LIMIT_LEN, |
| ACX1xx_IE_DOT11_LONG_RETRY_LIMIT_LEN, |
| ACX100_IE_DOT11_WEP_DEFAULT_KEY_WRITE_LEN, |
| ACX1xx_IE_DOT11_MAX_XMIT_MSDU_LIFETIME_LEN, |
| 0, |
| ACX1xx_IE_DOT11_CURRENT_REG_DOMAIN_LEN, |
| ACX1xx_IE_DOT11_CURRENT_ANTENNA_LEN, |
| 0, |
| ACX1xx_IE_DOT11_TX_POWER_LEVEL_LEN, |
| ACX1xx_IE_DOT11_CURRENT_CCA_MODE_LEN, |
| ACX100_IE_DOT11_ED_THRESHOLD_LEN, |
| ACX1xx_IE_DOT11_WEP_DEFAULT_KEY_SET_LEN, |
| 0, |
| 0, |
| 0, |
| }; |
| |
| |
| #undef FUNC |
| #define FUNC "configure" |
| #if !ACX_DEBUG |
| int |
| acx_s_configure(acx_device_t *adev, void *pdr, int type) |
| #else |
| int |
| acx_s_configure_debug(acx_device_t *adev, void *pdr, int type, const char* typestr) |
| #endif |
| { |
| u16 len; |
| int res; |
| |
| if (type < 0x1000) |
| len = adev->ie_len[type]; |
| else |
| len = adev->ie_len_dot11[type - 0x1000]; |
| |
| log(L_CTL, FUNC"(type:%s,len:%u)\n", typestr, len); |
| if (unlikely(!len)) { |
| log(L_DEBUG, "zero-length type %s?!\n", typestr); |
| } |
| |
| ((acx_ie_generic_t *)pdr)->type = cpu_to_le16(type); |
| ((acx_ie_generic_t *)pdr)->len = cpu_to_le16(len); |
| res = acx_s_issue_cmd(adev, ACX1xx_CMD_CONFIGURE, pdr, len + 4); |
| if (unlikely(OK != res)) { |
| #if ACX_DEBUG |
| printk("%s: "FUNC"(type:%s) FAILED\n", adev->ndev->name, typestr); |
| #else |
| printk("%s: "FUNC"(type:0x%X) FAILED\n", adev->ndev->name, type); |
| #endif |
| /* dump_stack() is already done in issue_cmd() */ |
| } |
| return res; |
| } |
| |
| #undef FUNC |
| #define FUNC "interrogate" |
| #if !ACX_DEBUG |
| int |
| acx_s_interrogate(acx_device_t *adev, void *pdr, int type) |
| #else |
| int |
| acx_s_interrogate_debug(acx_device_t *adev, void *pdr, int type, |
| const char* typestr) |
| #endif |
| { |
| u16 len; |
| int res; |
| |
| /* FIXME: no check whether this exceeds the array yet. |
| * We should probably remember the number of entries... */ |
| if (type < 0x1000) |
| len = adev->ie_len[type]; |
| else |
| len = adev->ie_len_dot11[type-0x1000]; |
| |
| log(L_CTL, FUNC"(type:%s,len:%u)\n", typestr, len); |
| |
| ((acx_ie_generic_t *)pdr)->type = cpu_to_le16(type); |
| ((acx_ie_generic_t *)pdr)->len = cpu_to_le16(len); |
| res = acx_s_issue_cmd(adev, ACX1xx_CMD_INTERROGATE, pdr, len + 4); |
| if (unlikely(OK != res)) { |
| #if ACX_DEBUG |
| printk("%s: "FUNC"(type:%s) FAILED\n", adev->ndev->name, typestr); |
| #else |
| printk("%s: "FUNC"(type:0x%X) FAILED\n", adev->ndev->name, type); |
| #endif |
| /* dump_stack() is already done in issue_cmd() */ |
| } |
| return res; |
| } |
| |
| #if ACX_DEBUG |
| EXPORT_SYMBOL_GPL(acx_s_interrogate_debug); |
| #else |
| EXPORT_SYMBOL_GPL(acx_s_interrogate); |
| #endif /* ACX_DEBUG */ |
| |
| #if CMD_DISCOVERY |
| void |
| great_inquisitor(acx_device_t *adev) |
| { |
| static struct { |
| u16 type; |
| u16 len; |
| /* 0x200 was too large here: */ |
| u8 data[0x100 - 4]; |
| } ACX_PACKED ie; |
| u16 type; |
| |
| FN_ENTER; |
| |
| /* 0..0x20, 0x1000..0x1020 */ |
| for (type = 0; type <= 0x1020; type++) { |
| if (type == 0x21) |
| type = 0x1000; |
| ie.type = cpu_to_le16(type); |
| ie.len = cpu_to_le16(sizeof(ie) - 4); |
| acx_s_issue_cmd(adev, ACX1xx_CMD_INTERROGATE, &ie, sizeof(ie)); |
| } |
| FN_EXIT0; |
| } |
| #endif |
| |
| |
| #ifdef CONFIG_PROC_FS |
| /*********************************************************************** |
| ** /proc files |
| */ |
| /*********************************************************************** |
| ** acx_l_proc_output |
| ** Generate content for our /proc entry |
| ** |
| ** Arguments: |
| ** buf is a pointer to write output to |
| ** adev is the usual pointer to our private struct acx_device |
| ** Returns: |
| ** number of bytes actually written to buf |
| ** Side effects: |
| ** none |
| */ |
| static int |
| acx_l_proc_output(char *buf, acx_device_t *adev) |
| { |
| char *p = buf; |
| int i; |
| |
| FN_ENTER; |
| |
| p += sprintf(p, |
| "acx driver version:\t\t" ACX_RELEASE "\n" |
| "Wireless extension version:\t" STRING(WIRELESS_EXT) "\n" |
| "chip name:\t\t\t%s (0x%08X)\n" |
| "radio type:\t\t\t0x%02X\n" |
| "form factor:\t\t\t0x%02X\n" |
| "EEPROM version:\t\t\t0x%02X\n" |
| "firmware version:\t\t%s (0x%08X)\n", |
| adev->chip_name, adev->firmware_id, |
| adev->radio_type, |
| adev->form_factor, |
| adev->eeprom_version, |
| adev->firmware_version, adev->firmware_numver); |
| |
| for (i = 0; i < VEC_SIZE(adev->sta_list); i++) { |
| struct client *bss = &adev->sta_list[i]; |
| if (!bss->used) continue; |
| p += sprintf(p, "BSS %u BSSID "MACSTR" ESSID %s channel %u " |
| "Cap 0x%X SIR %u SNR %u\n", |
| i, MAC(bss->bssid), (char*)bss->essid, bss->channel, |
| bss->cap_info, bss->sir, bss->snr); |
| } |
| p += sprintf(p, "status:\t\t\t%u (%s)\n", |
| adev->status, acx_get_status_name(adev->status)); |
| |
| FN_EXIT1(p - buf); |
| return p - buf; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| static int |
| acx_s_proc_diag_output(char *buf, acx_device_t *adev) |
| { |
| char *p = buf; |
| unsigned long flags; |
| unsigned int len = 0, partlen; |
| u32 temp1, temp2; |
| u8 *st, *st_end; |
| #ifdef __BIG_ENDIAN |
| u8 *st2; |
| #endif |
| fw_stats_t *fw_stats; |
| char *part_str = NULL; |
| fw_stats_tx_t *tx = NULL; |
| fw_stats_rx_t *rx = NULL; |
| fw_stats_dma_t *dma = NULL; |
| fw_stats_irq_t *irq = NULL; |
| fw_stats_wep_t *wep = NULL; |
| fw_stats_pwr_t *pwr = NULL; |
| fw_stats_mic_t *mic = NULL; |
| fw_stats_aes_t *aes = NULL; |
| fw_stats_event_t *evt = NULL; |
| |
| FN_ENTER; |
| |
| acx_lock(adev, flags); |
| |
| if (IS_PCI(adev)) |
| p = acxpci_s_proc_diag_output(p, adev); |
| |
| p += sprintf(p, |
| "\n" |
| "** network status **\n" |
| "dev_state_mask 0x%04X\n" |
| "status %u (%s), " |
| "mode %u, channel %u, " |
| "reg_dom_id 0x%02X, reg_dom_chanmask 0x%04X, ", |
| adev->dev_state_mask, |
| adev->status, acx_get_status_name(adev->status), |
| adev->mode, adev->channel, |
| adev->reg_dom_id, adev->reg_dom_chanmask |
| ); |
| p += sprintf(p, |
| "ESSID \"%s\", essid_active %d, essid_len %d, " |
| "essid_for_assoc \"%s\", nick \"%s\"\n" |
| "WEP ena %d, restricted %d, idx %d\n", |
| adev->essid, adev->essid_active, (int)adev->essid_len, |
| adev->essid_for_assoc, adev->nick, |
| adev->ieee->sec.enabled, adev->ieee->sec.auth_mode, |
| adev->ieee->sec.active_key); |
| p += sprintf(p, "dev_addr "MACSTR"\n", MAC(adev->dev_addr)); |
| p += sprintf(p, "bssid "MACSTR"\n", MAC(adev->bssid)); |
| p += sprintf(p, "ap_filter "MACSTR"\n", MAC(adev->ap)); |
| |
| p += sprintf(p, |
| "\n" |
| "** PHY status **\n" |
| "tx_disabled %d, tx_level_dbm %d\n" /* "tx_level_val %d, tx_level_auto %d\n" */ |
| "sensitivity %d, antenna 0x%02X, ed_threshold %d, cca %d, preamble_mode %d\n" |
| "rts_threshold %d, frag_threshold %d, short_retry %d, long_retry %d\n" |
| "msdu_lifetime %d, listen_interval %d, beacon_interval %d\n", |
| adev->tx_disabled, adev->tx_level_dbm, /* adev->tx_level_val, adev->tx_level_auto, */ |
| adev->sensitivity, adev->antenna, adev->ed_threshold, adev->cca, adev->preamble_mode, |
| adev->rts_threshold, adev->frag_threshold, adev->short_retry, adev->long_retry, |
| adev->msdu_lifetime, adev->listen_interval, adev->beacon_interval); |
| |
| acx_unlock(adev, flags); |
| |
| p += sprintf(p, |
| "\n" |
| "** Firmware **\n" |
| "NOTE: version dependent statistics layout, " |
| "please report if you suspect wrong parsing!\n" |
| "\n" |
| "version \"%s\"\n", adev->firmware_version); |
| |
| /* TODO: may replace kmalloc/memset with kzalloc once |
| * Linux 2.6.14 is widespread */ |
| fw_stats = kmalloc(sizeof(*fw_stats), GFP_KERNEL); |
| if (!fw_stats) { |
| FN_EXIT1(0); |
| return 0; |
| } |
| memset(fw_stats, 0, sizeof(*fw_stats)); |
| |
| st = (u8 *)fw_stats; |
| |
| part_str = "statistics query command"; |
| |
| if (OK != acx_s_interrogate(adev, st, ACX1xx_IE_FIRMWARE_STATISTICS)) |
| goto fw_stats_end; |
| |
| st += sizeof(u16); |
| len = *(u16 *)st; |
| |
| if (len > sizeof(*fw_stats)) { |
| p += sprintf(p, |
| "firmware version with bigger fw_stats struct detected\n" |
| "(%u vs. %u), please report\n", len, sizeof(fw_stats_t)); |
| if (len > sizeof(*fw_stats)) { |
| p += sprintf(p, "struct size exceeded allocation!\n"); |
| len = sizeof(*fw_stats); |
| } |
| } |
| st += sizeof(u16); |
| st_end = st - 2*sizeof(u16) + len; |
| |
| #ifdef __BIG_ENDIAN |
| /* let's make one bold assumption here: |
| * (hopefully!) *all* statistics fields are u32 only, |
| * thus if we need to make endianness corrections |
| * we can simply do them in one go, in advance */ |
| st2 = (u8 *)fw_stats; |
| for (temp1 = 0; temp1 < len; temp1 += 4, st2 += 4) |
| *(u32 *)st2 = le32_to_cpu(*(u32 *)st2); |
| #endif |
| |
| part_str = "Rx/Tx"; |
| |
| /* directly at end of a struct part? --> no error! */ |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| tx = (fw_stats_tx_t *)st; |
| st += sizeof(fw_stats_tx_t); |
| rx = (fw_stats_rx_t *)st; |
| st += sizeof(fw_stats_rx_t); |
| partlen = sizeof(fw_stats_tx_t) + sizeof(fw_stats_rx_t); |
| |
| if (IS_ACX100(adev)) { |
| /* at least ACX100 PCI F/W 1.9.8.b |
| * and ACX100 USB F/W 1.0.7-USB |
| * don't have those two fields... */ |
| st -= 2*sizeof(u32); |
| |
| /* our parsing doesn't quite match this firmware yet, |
| * log failure */ |
| if (st > st_end) |
| goto fw_stats_fail; |
| temp1 = temp2 = 999999999; |
| } else { |
| if (st > st_end) |
| goto fw_stats_fail; |
| temp1 = rx->rx_aci_events; |
| temp2 = rx->rx_aci_resets; |
| } |
| |
| p += sprintf(p, |
| "%s:\n" |
| " tx_desc_overfl %u\n" |
| " rx_OutOfMem %u, rx_hdr_overfl %u, rx_hw_stuck %u\n" |
| " rx_dropped_frame %u, rx_frame_ptr_err %u, rx_xfr_hint_trig %u\n" |
| " rx_aci_events %u, rx_aci_resets %u\n", |
| part_str, |
| tx->tx_desc_of, |
| rx->rx_oom, |
| rx->rx_hdr_of, |
| rx->rx_hw_stuck, |
| rx->rx_dropped_frame, |
| rx->rx_frame_ptr_err, |
| rx->rx_xfr_hint_trig, |
| temp1, |
| temp2); |
| |
| part_str = "DMA"; |
| |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| dma = (fw_stats_dma_t *)st; |
| partlen = sizeof(fw_stats_dma_t); |
| st += partlen; |
| |
| if (st > st_end) |
| goto fw_stats_fail; |
| |
| p += sprintf(p, |
| "%s:\n" |
| " rx_dma_req %u, rx_dma_err %u, tx_dma_req %u, tx_dma_err %u\n", |
| part_str, |
| dma->rx_dma_req, |
| dma->rx_dma_err, |
| dma->tx_dma_req, |
| dma->tx_dma_err); |
| |
| part_str = "IRQ"; |
| |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| irq = (fw_stats_irq_t *)st; |
| partlen = sizeof(fw_stats_irq_t); |
| st += partlen; |
| |
| if (st > st_end) |
| goto fw_stats_fail; |
| |
| p += sprintf(p, |
| "%s:\n" |
| " cmd_cplt %u, fiq %u\n" |
| " rx_hdrs %u, rx_cmplt %u, rx_mem_overfl %u, rx_rdys %u\n" |
| " irqs %u, tx_procs %u, decrypt_done %u\n" |
| " dma_0_done %u, dma_1_done %u, tx_exch_complet %u\n" |
| " commands %u, rx_procs %u, hw_pm_mode_changes %u\n" |
| " host_acks %u, pci_pm %u, acm_wakeups %u\n", |
| part_str, |
| irq->cmd_cplt, |
| irq->fiq, |
| irq->rx_hdrs, |
| irq->rx_cmplt, |
| irq->rx_mem_of, |
| irq->rx_rdys, |
| irq->irqs, |
| irq->tx_procs, |
| irq->decrypt_done, |
| irq->dma_0_done, |
| irq->dma_1_done, |
| irq->tx_exch_complet, |
| irq->commands, |
| irq->rx_procs, |
| irq->hw_pm_mode_changes, |
| irq->host_acks, |
| irq->pci_pm, |
| irq->acm_wakeups); |
| |
| part_str = "WEP"; |
| |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| wep = (fw_stats_wep_t *)st; |
| partlen = sizeof(fw_stats_wep_t); |
| st += partlen; |
| |
| if ( |
| (IS_PCI(adev) && IS_ACX100(adev)) |
| || (IS_USB(adev) && IS_ACX100(adev)) |
| ) { |
| /* at least ACX100 PCI F/W 1.9.8.b |
| * and ACX100 USB F/W 1.0.7-USB |
| * don't have those two fields... */ |
| st -= 2*sizeof(u32); |
| if (st > st_end) |
| goto fw_stats_fail; |
| temp1 = temp2 = 999999999; |
| } else { |
| if (st > st_end) |
| goto fw_stats_fail; |
| temp1 = wep->wep_pkt_decrypt; |
| temp2 = wep->wep_decrypt_irqs; |
| } |
| |
| p += sprintf(p, |
| "%s:\n" |
| " wep_key_count %u, wep_default_key_count %u, dot11_def_key_mib %u\n" |
| " wep_key_not_found %u, wep_decrypt_fail %u\n" |
| " wep_pkt_decrypt %u, wep_decrypt_irqs %u\n", |
| part_str, |
| wep->wep_key_count, |
| wep->wep_default_key_count, |
| wep->dot11_def_key_mib, |
| wep->wep_key_not_found, |
| wep->wep_decrypt_fail, |
| temp1, |
| temp2); |
| |
| part_str = "power"; |
| |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| pwr = (fw_stats_pwr_t *)st; |
| partlen = sizeof(fw_stats_pwr_t); |
| st += partlen; |
| |
| if (st > st_end) |
| goto fw_stats_fail; |
| |
| p += sprintf(p, |
| "%s:\n" |
| " tx_start_ctr %u, no_ps_tx_too_short %u\n" |
| " rx_start_ctr %u, no_ps_rx_too_short %u\n" |
| " lppd_started %u\n" |
| " no_lppd_too_noisy %u, no_lppd_too_short %u, no_lppd_matching_frame %u\n", |
| part_str, |
| pwr->tx_start_ctr, |
| pwr->no_ps_tx_too_short, |
| pwr->rx_start_ctr, |
| pwr->no_ps_rx_too_short, |
| pwr->lppd_started, |
| pwr->no_lppd_too_noisy, |
| pwr->no_lppd_too_short, |
| pwr->no_lppd_matching_frame); |
| |
| part_str = "MIC"; |
| |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| mic = (fw_stats_mic_t *)st; |
| partlen = sizeof(fw_stats_mic_t); |
| st += partlen; |
| |
| if (st > st_end) |
| goto fw_stats_fail; |
| |
| p += sprintf(p, |
| "%s:\n" |
| " mic_rx_pkts %u, mic_calc_fail %u\n", |
| part_str, |
| mic->mic_rx_pkts, |
| mic->mic_calc_fail); |
| |
| part_str = "AES"; |
| |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| aes = (fw_stats_aes_t *)st; |
| partlen = sizeof(fw_stats_aes_t); |
| st += partlen; |
| |
| if (st > st_end) |
| goto fw_stats_fail; |
| |
| p += sprintf(p, |
| "%s:\n" |
| " aes_enc_fail %u, aes_dec_fail %u\n" |
| " aes_enc_pkts %u, aes_dec_pkts %u\n" |
| " aes_enc_irq %u, aes_dec_irq %u\n", |
| part_str, |
| aes->aes_enc_fail, |
| aes->aes_dec_fail, |
| aes->aes_enc_pkts, |
| aes->aes_dec_pkts, |
| aes->aes_enc_irq, |
| aes->aes_dec_irq); |
| |
| part_str = "event"; |
| |
| if (st == st_end) |
| goto fw_stats_end; |
| |
| evt = (fw_stats_event_t *)st; |
| partlen = sizeof(fw_stats_event_t); |
| st += partlen; |
| |
| if (st > st_end) |
| goto fw_stats_fail; |
| |
| p += sprintf(p, |
| "%s:\n" |
| " heartbeat %u, calibration %u\n" |
| " rx_mismatch %u, rx_mem_empty %u, rx_pool %u\n" |
| " oom_late %u\n" |
| " phy_tx_err %u, tx_stuck %u\n", |
| part_str, |
| evt->heartbeat, |
| evt->calibration, |
| evt->rx_mismatch, |
| evt->rx_mem_empty, |
| evt->rx_pool, |
| evt->oom_late, |
| evt->phy_tx_err, |
| evt->tx_stuck); |
| |
| if (st < st_end) |
| goto fw_stats_bigger; |
| |
| goto fw_stats_end; |
| |
| fw_stats_fail: |
| st -= partlen; |
| p += sprintf(p, |
| "failed at %s part (size %u), offset %u (struct size %u), " |
| "please report\n", part_str, partlen, |
| (int)st - (int)fw_stats, len); |
| |
| fw_stats_bigger: |
| for (; st < st_end; st += 4) |
| p += sprintf(p, |
| "UNKN%3d: %u\n", (int)st - (int)fw_stats, *(u32 *)st); |
| |
| fw_stats_end: |
| kfree(fw_stats); |
| |
| FN_EXIT1(p - buf); |
| return p - buf; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| static int |
| acx_s_proc_phy_output(char *buf, acx_device_t *adev) |
| { |
| char *p = buf; |
| int i; |
| |
| FN_ENTER; |
| |
| /* |
| if (RADIO_RFMD_11 != adev->radio_type) { |
| printk("sorry, not yet adapted for radio types " |
| "other than RFMD, please verify " |
| "PHY size etc. first!\n"); |
| goto end; |
| } |
| */ |
| |
| /* The PHY area is only 0x80 bytes long; further pages after that |
| * only have some page number registers with altered value, |
| * all other registers remain the same. */ |
| for (i = 0; i < 0x80; i++) { |
| acx_s_read_phy_reg(adev, i, p++); |
| } |
| |
| FN_EXIT1(p - buf); |
| return p - buf; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_e_read_proc_XXXX |
| ** Handle our /proc entry |
| ** |
| ** Arguments: |
| ** standard kernel read_proc interface |
| ** Returns: |
| ** number of bytes written to buf |
| ** Side effects: |
| ** none |
| */ |
| static int |
| acx_e_read_proc(char *buf, char **start, off_t offset, int count, |
| int *eof, void *data) |
| { |
| acx_device_t *adev = (acx_device_t*)data; |
| unsigned long flags; |
| int length; |
| |
| FN_ENTER; |
| |
| acx_sem_lock(adev); |
| acx_lock(adev, flags); |
| /* fill buf */ |
| length = acx_l_proc_output(buf, adev); |
| acx_unlock(adev, flags); |
| acx_sem_unlock(adev); |
| |
| /* housekeeping */ |
| if (length <= offset + count) |
| *eof = 1; |
| *start = buf + offset; |
| length -= offset; |
| if (length > count) |
| length = count; |
| if (length < 0) |
| length = 0; |
| FN_EXIT1(length); |
| return length; |
| } |
| |
| static int |
| acx_e_read_proc_diag(char *buf, char **start, off_t offset, int count, |
| int *eof, void *data) |
| { |
| acx_device_t *adev = (acx_device_t*)data; |
| int length; |
| |
| FN_ENTER; |
| |
| acx_sem_lock(adev); |
| /* fill buf */ |
| length = acx_s_proc_diag_output(buf, adev); |
| acx_sem_unlock(adev); |
| |
| /* housekeeping */ |
| if (length <= offset + count) |
| *eof = 1; |
| *start = buf + offset; |
| length -= offset; |
| if (length > count) |
| length = count; |
| if (length < 0) |
| length = 0; |
| FN_EXIT1(length); |
| return length; |
| } |
| |
| static int |
| acx_e_read_proc_eeprom(char *buf, char **start, off_t offset, int count, |
| int *eof, void *data) |
| { |
| acx_device_t *adev = (acx_device_t*)data; |
| int length; |
| |
| FN_ENTER; |
| |
| /* fill buf */ |
| length = 0; |
| if (IS_PCI(adev)) { |
| acx_sem_lock(adev); |
| length = acxpci_proc_eeprom_output(buf, adev); |
| acx_sem_unlock(adev); |
| } |
| |
| /* housekeeping */ |
| if (length <= offset + count) |
| *eof = 1; |
| *start = buf + offset; |
| length -= offset; |
| if (length > count) |
| length = count; |
| if (length < 0) |
| length = 0; |
| FN_EXIT1(length); |
| return length; |
| } |
| |
| static int |
| acx_e_read_proc_phy(char *buf, char **start, off_t offset, int count, |
| int *eof, void *data) |
| { |
| acx_device_t *adev = (acx_device_t*)data; |
| int length; |
| |
| FN_ENTER; |
| |
| acx_sem_lock(adev); |
| /* fill buf */ |
| length = acx_s_proc_phy_output(buf, adev); |
| acx_sem_unlock(adev); |
| |
| /* housekeeping */ |
| if (length <= offset + count) |
| *eof = 1; |
| *start = buf + offset; |
| length -= offset; |
| if (length > count) |
| length = count; |
| if (length < 0) |
| length = 0; |
| FN_EXIT1(length); |
| return length; |
| } |
| |
| |
| /*********************************************************************** |
| ** /proc files registration |
| */ |
| static const char * const |
| proc_files[] = { "", "_diag", "_eeprom", "_phy" }; |
| |
| static read_proc_t * const |
| proc_funcs[] = { |
| acx_e_read_proc, |
| acx_e_read_proc_diag, |
| acx_e_read_proc_eeprom, |
| acx_e_read_proc_phy |
| }; |
| |
| static int |
| manage_proc_entries(const struct net_device *ndev, int remove) |
| { |
| acx_device_t *adev = ndev2adev((struct net_device *)ndev); |
| char procbuf[80]; |
| int i; |
| |
| for (i = 0; i < VEC_SIZE(proc_files); i++) { |
| snprintf(procbuf, sizeof(procbuf), |
| "driver/acx_%s%s", ndev->name, proc_files[i]); |
| log(L_INIT, "%sing /proc entry %s\n", |
| remove ? "remov" : "creat", procbuf); |
| if (!remove) { |
| if (!create_proc_read_entry(procbuf, 0, NULL, proc_funcs[i], adev)) { |
| printk("acx: cannot register /proc entry %s\n", procbuf); |
| return NOT_OK; |
| } |
| } else { |
| remove_proc_entry(procbuf, NULL); |
| } |
| } |
| return OK; |
| } |
| |
| int |
| acx_proc_register_entries(const struct net_device *ndev) |
| { |
| return manage_proc_entries(ndev, 0); |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_proc_register_entries); |
| |
| int |
| acx_proc_unregister_entries(const struct net_device *ndev) |
| { |
| return manage_proc_entries(ndev, 1); |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_proc_unregister_entries); |
| #endif /* CONFIG_PROC_FS */ |
| |
| |
| /*********************************************************************** |
| ** acx_cmd_join_bssid |
| ** |
| ** Common code for both acx100 and acx111. |
| */ |
| /* NB: does NOT match RATE100_nn but matches ACX[111]_SCAN_RATE_n */ |
| static const u8 |
| bitpos2genframe_txrate[] = { |
| 10, /* 0. 1 Mbit/s */ |
| 20, /* 1. 2 Mbit/s */ |
| 55, /* 2. 5.5 Mbit/s */ |
| 0x0B, /* 3. 6 Mbit/s */ |
| 0x0F, /* 4. 9 Mbit/s */ |
| 110, /* 5. 11 Mbit/s */ |
| 0x0A, /* 6. 12 Mbit/s */ |
| 0x0E, /* 7. 18 Mbit/s */ |
| 220, /* 8. 22 Mbit/s */ |
| 0x09, /* 9. 24 Mbit/s */ |
| 0x0D, /* 10. 36 Mbit/s */ |
| 0x08, /* 11. 48 Mbit/s */ |
| 0x0C, /* 12. 54 Mbit/s */ |
| 10, /* 13. 1 Mbit/s, should never happen */ |
| 10, /* 14. 1 Mbit/s, should never happen */ |
| 10, /* 15. 1 Mbit/s, should never happen */ |
| }; |
| |
| /* Looks scary, eh? |
| ** Actually, each one compiled into one AND and one SHIFT, |
| ** 31 bytes in x86 asm (more if uints are replaced by u16/u8) */ |
| static inline unsigned int |
| rate111to5bits(unsigned int rate) |
| { |
| return (rate & 0x7) |
| | ( (rate & RATE111_11) / (RATE111_11/JOINBSS_RATES_11) ) |
| | ( (rate & RATE111_22) / (RATE111_22/JOINBSS_RATES_22) ) |
| ; |
| } |
| |
| static void |
| acx_s_cmd_join_bssid(acx_device_t *adev, const u8 *bssid) |
| { |
| acx_joinbss_t tmp; |
| int dtim_interval; |
| int i; |
| |
| if (mac_is_zero(bssid)) |
| return; |
| |
| FN_ENTER; |
| |
| dtim_interval = (ACX_MODE_0_ADHOC == adev->mode) ? |
| 1 : adev->dtim_interval; |
| |
| memset(&tmp, 0, sizeof(tmp)); |
| |
| for (i = 0; i < ETH_ALEN; i++) { |
| tmp.bssid[i] = bssid[ETH_ALEN-1 - i]; |
| } |
| |
| tmp.beacon_interval = cpu_to_le16(adev->beacon_interval); |
| |
| /* Basic rate set. Control frame responses (such as ACK or CTS frames) |
| ** are sent with one of these rates */ |
| if (IS_ACX111(adev)) { |
| /* It was experimentally determined that rates_basic |
| ** can take 11g rates as well, not only rates |
| ** defined with JOINBSS_RATES_BASIC111_nnn. |
| ** Just use RATE111_nnn constants... */ |
| tmp.u.acx111.dtim_interval = dtim_interval; |
| tmp.u.acx111.rates_basic = cpu_to_le16(adev->rate_basic); |
| log(L_ASSOC, "rates_basic:%04X, rates_supported:%04X\n", |
| adev->rate_basic, adev->rate_oper); |
| } else { |
| tmp.u.acx100.dtim_interval = dtim_interval; |
| tmp.u.acx100.rates_basic = rate111to5bits(adev->rate_basic); |
| tmp.u.acx100.rates_supported = rate111to5bits(adev->rate_oper); |
| log(L_ASSOC, "rates_basic:%04X->%02X, " |
| "rates_supported:%04X->%02X\n", |
| adev->rate_basic, tmp.u.acx100.rates_basic, |
| adev->rate_oper, tmp.u.acx100.rates_supported); |
| } |
| |
| /* Setting up how Beacon, Probe Response, RTS, and PS-Poll frames |
| ** will be sent (rate/modulation/preamble) */ |
| tmp.genfrm_txrate = bitpos2genframe_txrate[lowest_bit(adev->rate_basic)]; |
| tmp.genfrm_mod_pre = 0; /* FIXME: was = adev->capab_short (which was always 0); */ |
| /* we can use short pre *if* all peers can understand it */ |
| /* FIXME #2: we need to correctly set PBCC/OFDM bits here too */ |
| |
| /* we switch fw to STA mode in MONITOR mode, it seems to be |
| ** the only mode where fw does not emit beacons by itself |
| ** but allows us to send anything (we really want to retain |
| ** ability to tx arbitrary frames in MONITOR mode) |
| */ |
| tmp.macmode = (adev->mode != ACX_MODE_MONITOR ? adev->mode : ACX_MODE_2_STA); |
| tmp.channel = adev->channel; |
| tmp.essid_len = adev->essid_len; |
| /* NOTE: the code memcpy'd essid_len + 1 before, which is WRONG! */ |
| memcpy(tmp.essid, adev->essid, tmp.essid_len); |
| acx_s_issue_cmd(adev, ACX1xx_CMD_JOIN, &tmp, tmp.essid_len + 0x11); |
| |
| log(L_ASSOC|L_DEBUG, "BSS_Type = %u\n", tmp.macmode); |
| acxlog_mac(L_ASSOC|L_DEBUG, "JoinBSSID MAC:", adev->bssid, "\n"); |
| |
| acx_update_capabilities(adev); |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_s_cmd_start_scan |
| ** |
| ** Issue scan command to the hardware |
| ** |
| ** unified function for both ACX111 and ACX100 |
| */ |
| static void |
| acx_s_scan_chan(acx_device_t *adev) |
| { |
| union { |
| acx111_scan_t acx111; |
| acx100_scan_t acx100; |
| } s; |
| |
| FN_ENTER; |
| |
| memset(&s, 0, sizeof(s)); |
| |
| /* first common positions... */ |
| |
| s.acx111.count = cpu_to_le16(adev->scan_count); |
| s.acx111.rate = adev->scan_rate; |
| s.acx111.options = adev->scan_mode; |
| s.acx111.chan_duration = cpu_to_le16(adev->scan_duration); |
| s.acx111.max_probe_delay = cpu_to_le16(adev->scan_probe_delay); |
| |
| /* ...then differences */ |
| |
| if (IS_ACX111(adev)) { |
| s.acx111.channel_list_select = 0; /* scan every allowed channel */ |
| /*s.acx111.channel_list_select = 1;*/ /* scan given channels */ |
| /*s.acx111.modulation = 0x40;*/ /* long preamble? OFDM? -> only for active scan */ |
| s.acx111.modulation = 0; |
| /*s.acx111.channel_list[0] = 6; |
| s.acx111.channel_list[1] = 4;*/ |
| } else { |
| s.acx100.start_chan = cpu_to_le16(1); |
| s.acx100.flags = cpu_to_le16(0x8000); |
| } |
| |
| acx_s_issue_cmd(adev, ACX1xx_CMD_SCAN, &s, sizeof(s)); |
| FN_EXIT0; |
| } |
| |
| |
| void |
| acx_s_cmd_start_scan(acx_device_t *adev) |
| { |
| /* time_before check is 'just in case' thing */ |
| if (!(adev->irq_status & HOST_INT_SCAN_COMPLETE) |
| && time_before(jiffies, adev->scan_start + 10*HZ) |
| ) { |
| log(L_INIT, "start_scan: seems like previous scan " |
| "is still running. Not starting anew. Please report\n"); |
| return; |
| } |
| |
| log(L_INIT, "starting radio scan\n"); |
| /* remember that fw is commanded to do scan */ |
| adev->scan_start = jiffies; |
| CLEAR_BIT(adev->irq_status, HOST_INT_SCAN_COMPLETE); |
| /* issue it */ |
| acx_s_scan_chan(adev); |
| } |
| |
| |
| /*********************************************************************** |
| ** acx111 feature config |
| */ |
| static int |
| acx111_s_get_feature_config(acx_device_t *adev, |
| u32 *feature_options, u32 *data_flow_options) |
| { |
| struct acx111_ie_feature_config feat; |
| |
| if (!IS_ACX111(adev)) { |
| return NOT_OK; |
| } |
| |
| memset(&feat, 0, sizeof(feat)); |
| |
| if (OK != acx_s_interrogate(adev, &feat, ACX1xx_IE_FEATURE_CONFIG)) { |
| return NOT_OK; |
| } |
| log(L_DEBUG, |
| "got Feature option:0x%X, DataFlow option: 0x%X\n", |
| feat.feature_options, |
| feat.data_flow_options); |
| |
| if (feature_options) |
| *feature_options = le32_to_cpu(feat.feature_options); |
| if (data_flow_options) |
| *data_flow_options = le32_to_cpu(feat.data_flow_options); |
| |
| return OK; |
| } |
| |
| static int |
| acx111_s_set_feature_config(acx_device_t *adev, |
| u32 feature_options, u32 data_flow_options, |
| unsigned int mode /* 0 == remove, 1 == add, 2 == set */) |
| { |
| struct acx111_ie_feature_config feat; |
| |
| if (!IS_ACX111(adev)) { |
| return NOT_OK; |
| } |
| |
| if ((mode < 0) || (mode > 2)) |
| return NOT_OK; |
| |
| if (mode != 2) |
| /* need to modify old data */ |
| acx111_s_get_feature_config(adev, &feat.feature_options, &feat.data_flow_options); |
| else { |
| /* need to set a completely new value */ |
| feat.feature_options = 0; |
| feat.data_flow_options = 0; |
| } |
| |
| if (mode == 0) { /* remove */ |
| CLEAR_BIT(feat.feature_options, cpu_to_le32(feature_options)); |
| CLEAR_BIT(feat.data_flow_options, cpu_to_le32(data_flow_options)); |
| } else { /* add or set */ |
| SET_BIT(feat.feature_options, cpu_to_le32(feature_options)); |
| SET_BIT(feat.data_flow_options, cpu_to_le32(data_flow_options)); |
| } |
| |
| log(L_DEBUG, |
| "old: feature 0x%08X dataflow 0x%08X. mode: %u\n" |
| "new: feature 0x%08X dataflow 0x%08X\n", |
| feature_options, data_flow_options, mode, |
| le32_to_cpu(feat.feature_options), |
| le32_to_cpu(feat.data_flow_options)); |
| |
| if (OK != acx_s_configure(adev, &feat, ACX1xx_IE_FEATURE_CONFIG)) { |
| return NOT_OK; |
| } |
| |
| return OK; |
| } |
| |
| static inline int |
| acx111_s_feature_off(acx_device_t *adev, u32 f, u32 d) |
| { |
| return acx111_s_set_feature_config(adev, f, d, 0); |
| } |
| static inline int |
| acx111_s_feature_on(acx_device_t *adev, u32 f, u32 d) |
| { |
| return acx111_s_set_feature_config(adev, f, d, 1); |
| } |
| static inline int |
| acx111_s_feature_set(acx_device_t *adev, u32 f, u32 d) |
| { |
| return acx111_s_set_feature_config(adev, f, d, 2); |
| } |
| |
| |
| /*********************************************************************** |
| ** acx100_s_init_memory_pools |
| */ |
| static int |
| acx100_s_init_memory_pools(acx_device_t *adev, const acx_ie_memmap_t *mmt) |
| { |
| acx100_ie_memblocksize_t MemoryBlockSize; |
| acx100_ie_memconfigoption_t MemoryConfigOption; |
| int TotalMemoryBlocks; |
| int RxBlockNum; |
| int TotalRxBlockSize; |
| int TxBlockNum; |
| int TotalTxBlockSize; |
| |
| FN_ENTER; |
| |
| /* Let's see if we can follow this: |
| first we select our memory block size (which I think is |
| completely arbitrary) */ |
| MemoryBlockSize.size = cpu_to_le16(adev->memblocksize); |
| |
| /* Then we alert the card to our decision of block size */ |
| if (OK != acx_s_configure(adev, &MemoryBlockSize, ACX100_IE_BLOCK_SIZE)) { |
| goto bad; |
| } |
| |
| /* We figure out how many total blocks we can create, using |
| the block size we chose, and the beginning and ending |
| memory pointers, i.e.: end-start/size */ |
| TotalMemoryBlocks = (le32_to_cpu(mmt->PoolEnd) - le32_to_cpu(mmt->PoolStart)) / adev->memblocksize; |
| |
| log(L_DEBUG, "TotalMemoryBlocks=%u (%u bytes)\n", |
| TotalMemoryBlocks, TotalMemoryBlocks*adev->memblocksize); |
| |
| /* MemoryConfigOption.DMA_config bitmask: |
| access to ACX memory is to be done: |
| 0x00080000 using PCI conf space?! |
| 0x00040000 using IO instructions? |
| 0x00000000 using memory access instructions |
| 0x00020000 using local memory block linked list (else what?) |
| 0x00010000 using host indirect descriptors (else host must access ACX memory?) |
| */ |
| if (IS_PCI(adev)) { |
| MemoryConfigOption.DMA_config = cpu_to_le32(0x30000); |
| /* Declare start of the Rx host pool */ |
| MemoryConfigOption.pRxHostDesc = cpu2acx(adev->rxhostdesc_startphy); |
| log(L_DEBUG, "pRxHostDesc 0x%08X, rxhostdesc_startphy 0x%lX\n", |
| acx2cpu(MemoryConfigOption.pRxHostDesc), |
| (long)adev->rxhostdesc_startphy); |
| } else { |
| MemoryConfigOption.DMA_config = cpu_to_le32(0x20000); |
| } |
| |
| /* 50% of the allotment of memory blocks go to tx descriptors */ |
| TxBlockNum = TotalMemoryBlocks / 2; |
| MemoryConfigOption.TxBlockNum = cpu_to_le16(TxBlockNum); |
| |
| /* and 50% go to the rx descriptors */ |
| RxBlockNum = TotalMemoryBlocks - TxBlockNum; |
| MemoryConfigOption.RxBlockNum = cpu_to_le16(RxBlockNum); |
| |
| /* size of the tx and rx descriptor queues */ |
| TotalTxBlockSize = TxBlockNum * adev->memblocksize; |
| TotalRxBlockSize = RxBlockNum * adev->memblocksize; |
| log(L_DEBUG, "TxBlockNum %u RxBlockNum %u TotalTxBlockSize %u " |
| "TotalTxBlockSize %u\n", TxBlockNum, RxBlockNum, |
| TotalTxBlockSize, TotalRxBlockSize); |
| |
| |
| /* align the tx descriptor queue to an alignment of 0x20 (32 bytes) */ |
| MemoryConfigOption.rx_mem = |
| cpu_to_le32((le32_to_cpu(mmt->PoolStart) + 0x1f) & ~0x1f); |
| |
| /* align the rx descriptor queue to units of 0x20 |
| * and offset it by the tx descriptor queue */ |
| MemoryConfigOption.tx_mem = |
| cpu_to_le32((le32_to_cpu(mmt->PoolStart) + TotalRxBlockSize + 0x1f) & ~0x1f); |
| log(L_DEBUG, "rx_mem %08X rx_mem %08X\n", |
| MemoryConfigOption.tx_mem, MemoryConfigOption.rx_mem); |
| |
| /* alert the device to our decision */ |
| if (OK != acx_s_configure(adev, &MemoryConfigOption, ACX1xx_IE_MEMORY_CONFIG_OPTIONS)) { |
| goto bad; |
| } |
| |
| /* and tell the device to kick it into gear */ |
| if (OK != acx_s_issue_cmd(adev, ACX100_CMD_INIT_MEMORY, NULL, 0)) { |
| goto bad; |
| } |
| FN_EXIT1(OK); |
| return OK; |
| bad: |
| FN_EXIT1(NOT_OK); |
| return NOT_OK; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx100_s_create_dma_regions |
| ** |
| ** Note that this fn messes up heavily with hardware, but we cannot |
| ** lock it (we need to sleep). Not a problem since IRQs can't happen |
| */ |
| int |
| acx100_s_create_dma_regions(acx_device_t *adev) |
| { |
| acx100_ie_queueconfig_t queueconf; |
| acx_ie_memmap_t memmap; |
| int res = NOT_OK; |
| u32 tx_queue_start, rx_queue_start; |
| |
| FN_ENTER; |
| |
| /* read out the acx100 physical start address for the queues */ |
| if (OK != acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP)) { |
| goto fail; |
| } |
| |
| tx_queue_start = le32_to_cpu(memmap.QueueStart); |
| rx_queue_start = tx_queue_start + TX_CNT * sizeof(txdesc_t); |
| |
| log(L_DEBUG, "initializing Queue Indicator\n"); |
| |
| memset(&queueconf, 0, sizeof(queueconf)); |
| |
| /* Not needed for PCI, so we can avoid setting them altogether */ |
| if (IS_USB(adev)) { |
| queueconf.NumTxDesc = USB_TX_CNT; |
| queueconf.NumRxDesc = USB_RX_CNT; |
| } |
| |
| /* calculate size of queues */ |
| queueconf.AreaSize = cpu_to_le32( |
| TX_CNT * sizeof(txdesc_t) + |
| RX_CNT * sizeof(rxdesc_t) + 8 |
| ); |
| queueconf.NumTxQueues = 1; /* number of tx queues */ |
| /* sets the beginning of the tx descriptor queue */ |
| queueconf.TxQueueStart = memmap.QueueStart; |
| /* done by memset: queueconf.TxQueuePri = 0; */ |
| queueconf.RxQueueStart = cpu_to_le32(rx_queue_start); |
| queueconf.QueueOptions = 1; /* auto reset descriptor */ |
| /* sets the end of the rx descriptor queue */ |
| queueconf.QueueEnd = cpu_to_le32( |
| rx_queue_start + RX_CNT * sizeof(rxdesc_t) |
| ); |
| /* sets the beginning of the next queue */ |
| queueconf.HostQueueEnd = cpu_to_le32(le32_to_cpu(queueconf.QueueEnd) + 8); |
| if (OK != acx_s_configure(adev, &queueconf, ACX1xx_IE_QUEUE_CONFIG)) { |
| goto fail; |
| } |
| |
| if (IS_PCI(adev)) { |
| /* sets the beginning of the rx descriptor queue, after the tx descrs */ |
| if (OK != acxpci_s_create_hostdesc_queues(adev)) |
| goto fail; |
| acxpci_create_desc_queues(adev, tx_queue_start, rx_queue_start); |
| } |
| |
| if (OK != acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP)) { |
| goto fail; |
| } |
| |
| memmap.PoolStart = cpu_to_le32( |
| (le32_to_cpu(memmap.QueueEnd) + 4 + 0x1f) & ~0x1f |
| ); |
| |
| if (OK != acx_s_configure(adev, &memmap, ACX1xx_IE_MEMORY_MAP)) { |
| goto fail; |
| } |
| |
| if (OK != acx100_s_init_memory_pools(adev, &memmap)) { |
| goto fail; |
| } |
| |
| res = OK; |
| goto end; |
| |
| fail: |
| acx_s_msleep(1000); /* ? */ |
| if (IS_PCI(adev)) |
| acxpci_free_desc_queues(adev); |
| end: |
| FN_EXIT1(res); |
| return res; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx100_s_create_dma_regions); |
| |
| /*********************************************************************** |
| ** acx111_s_create_dma_regions |
| ** |
| ** Note that this fn messes heavily with hardware, but we cannot |
| ** lock it (we need to sleep). Not a problem since IRQs can't happen |
| */ |
| #define ACX111_PERCENT(percent) ((percent)/5) |
| |
| int |
| acx111_s_create_dma_regions(acx_device_t *adev) |
| { |
| struct acx111_ie_memoryconfig memconf; |
| struct acx111_ie_queueconfig queueconf; |
| u32 tx_queue_start, rx_queue_start; |
| |
| FN_ENTER; |
| |
| /* Calculate memory positions and queue sizes */ |
| |
| /* Set up our host descriptor pool + data pool */ |
| if (IS_PCI(adev)) { |
| if (OK != acxpci_s_create_hostdesc_queues(adev)) |
| goto fail; |
| } |
| |
| memset(&memconf, 0, sizeof(memconf)); |
| /* the number of STAs (STA contexts) to support |
| ** NB: was set to 1 and everything seemed to work nevertheless... */ |
| memconf.no_of_stations = cpu_to_le16(VEC_SIZE(adev->sta_list)); |
| /* specify the memory block size. Default is 256 */ |
| memconf.memory_block_size = cpu_to_le16(adev->memblocksize); |
| /* let's use 50%/50% for tx/rx (specify percentage, units of 5%) */ |
| memconf.tx_rx_memory_block_allocation = ACX111_PERCENT(50); |
| /* set the count of our queues |
| ** NB: struct acx111_ie_memoryconfig shall be modified |
| ** if we ever will switch to more than one rx and/or tx queue */ |
| memconf.count_rx_queues = 1; |
| memconf.count_tx_queues = 1; |
| /* 0 == Busmaster Indirect Memory Organization, which is what we want |
| * (using linked host descs with their allocated mem). |
| * 2 == Generic Bus Slave */ |
| /* done by memset: memconf.options = 0; */ |
| /* let's use 25% for fragmentations and 75% for frame transfers |
| * (specified in units of 5%) */ |
| memconf.fragmentation = ACX111_PERCENT(75); |
| /* Rx descriptor queue config */ |
| memconf.rx_queue1_count_descs = RX_CNT; |
| memconf.rx_queue1_type = 7; /* must be set to 7 */ |
| /* done by memset: memconf.rx_queue1_prio = 0; low prio */ |
| if (IS_PCI(adev)) { |
| memconf.rx_queue1_host_rx_start = cpu2acx(adev->rxhostdesc_startphy); |
| } |
| /* Tx descriptor queue config */ |
| memconf.tx_queue1_count_descs = TX_CNT; |
| /* done by memset: memconf.tx_queue1_attributes = 0; lowest priority */ |
| |
| /* NB1: this looks wrong: (memconf,ACX1xx_IE_QUEUE_CONFIG), |
| ** (queueconf,ACX1xx_IE_MEMORY_CONFIG_OPTIONS) look swapped, eh? |
| ** But it is actually correct wrt IE numbers. |
| ** NB2: sizeof(memconf) == 28 == 0x1c but configure(ACX1xx_IE_QUEUE_CONFIG) |
| ** writes 0x20 bytes (because same IE for acx100 uses struct acx100_ie_queueconfig |
| ** which is 4 bytes larger. what a mess. TODO: clean it up) */ |
| if (OK != acx_s_configure(adev, &memconf, ACX1xx_IE_QUEUE_CONFIG)) { |
| goto fail; |
| } |
| |
| acx_s_interrogate(adev, &queueconf, ACX1xx_IE_MEMORY_CONFIG_OPTIONS); |
| |
| tx_queue_start = le32_to_cpu(queueconf.tx1_queue_address); |
| rx_queue_start = le32_to_cpu(queueconf.rx1_queue_address); |
| |
| log(L_INIT, "dump queue head (from card):\n" |
| "len: %u\n" |
| "tx_memory_block_address: %X\n" |
| "rx_memory_block_address: %X\n" |
| "tx1_queue address: %X\n" |
| "rx1_queue address: %X\n", |
| le16_to_cpu(queueconf.len), |
| le32_to_cpu(queueconf.tx_memory_block_address), |
| le32_to_cpu(queueconf.rx_memory_block_address), |
| tx_queue_start, |
| rx_queue_start); |
| |
| if (IS_PCI(adev)) |
| acxpci_create_desc_queues(adev, tx_queue_start, rx_queue_start); |
| |
| FN_EXIT1(OK); |
| return OK; |
| fail: |
| if (IS_PCI(adev)) |
| acxpci_free_desc_queues(adev); |
| |
| FN_EXIT1(NOT_OK); |
| return NOT_OK; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx111_s_create_dma_regions); |
| |
| /*********************************************************************** |
| */ |
| static void |
| acx_s_initialize_rx_config(acx_device_t *adev) |
| { |
| struct { |
| u16 id; |
| u16 len; |
| u16 rx_cfg1; |
| u16 rx_cfg2; |
| } ACX_PACKED cfg; |
| |
| switch (adev->mode) { |
| case ACX_MODE_OFF: |
| adev->rx_config_1 = (u16) (0 |
| /* | RX_CFG1_INCLUDE_RXBUF_HDR */ |
| /* | RX_CFG1_FILTER_SSID */ |
| /* | RX_CFG1_FILTER_BCAST */ |
| /* | RX_CFG1_RCV_MC_ADDR1 */ |
| /* | RX_CFG1_RCV_MC_ADDR0 */ |
| /* | RX_CFG1_FILTER_ALL_MULTI */ |
| /* | RX_CFG1_FILTER_BSSID */ |
| /* | RX_CFG1_FILTER_MAC */ |
| /* | RX_CFG1_RCV_PROMISCUOUS */ |
| /* | RX_CFG1_INCLUDE_FCS */ |
| /* | RX_CFG1_INCLUDE_PHY_HDR */ |
| ); |
| adev->rx_config_2 = (u16) (0 |
| /*| RX_CFG2_RCV_ASSOC_REQ */ |
| /*| RX_CFG2_RCV_AUTH_FRAMES */ |
| /*| RX_CFG2_RCV_BEACON_FRAMES */ |
| /*| RX_CFG2_RCV_CONTENTION_FREE */ |
| /*| RX_CFG2_RCV_CTRL_FRAMES */ |
| /*| RX_CFG2_RCV_DATA_FRAMES */ |
| /*| RX_CFG2_RCV_BROKEN_FRAMES */ |
| /*| RX_CFG2_RCV_MGMT_FRAMES */ |
| /*| RX_CFG2_RCV_PROBE_REQ */ |
| /*| RX_CFG2_RCV_PROBE_RESP */ |
| /*| RX_CFG2_RCV_ACK_FRAMES */ |
| /*| RX_CFG2_RCV_OTHER */ |
| ); |
| break; |
| case ACX_MODE_MONITOR: |
| adev->rx_config_1 = (u16) (0 |
| /* | RX_CFG1_INCLUDE_RXBUF_HDR */ |
| /* | RX_CFG1_FILTER_SSID */ |
| /* | RX_CFG1_FILTER_BCAST */ |
| /* | RX_CFG1_RCV_MC_ADDR1 */ |
| /* | RX_CFG1_RCV_MC_ADDR0 */ |
| /* | RX_CFG1_FILTER_ALL_MULTI */ |
| /* | RX_CFG1_FILTER_BSSID */ |
| /* | RX_CFG1_FILTER_MAC */ |
| | RX_CFG1_RCV_PROMISCUOUS |
| /* | RX_CFG1_INCLUDE_FCS */ |
| /* | RX_CFG1_INCLUDE_PHY_HDR */ |
| ); |
| adev->rx_config_2 = (u16) (0 |
| | RX_CFG2_RCV_ASSOC_REQ |
| | RX_CFG2_RCV_AUTH_FRAMES |
| | RX_CFG2_RCV_BEACON_FRAMES |
| | RX_CFG2_RCV_CONTENTION_FREE |
| | RX_CFG2_RCV_CTRL_FRAMES |
| | RX_CFG2_RCV_DATA_FRAMES |
| | RX_CFG2_RCV_BROKEN_FRAMES |
| | RX_CFG2_RCV_MGMT_FRAMES |
| | RX_CFG2_RCV_PROBE_REQ |
| | RX_CFG2_RCV_PROBE_RESP |
| | RX_CFG2_RCV_ACK_FRAMES |
| | RX_CFG2_RCV_OTHER |
| ); |
| break; |
| default: |
| adev->rx_config_1 = (u16) (0 |
| /* | RX_CFG1_INCLUDE_RXBUF_HDR */ |
| /* | RX_CFG1_FILTER_SSID */ |
| /* | RX_CFG1_FILTER_BCAST */ |
| /* | RX_CFG1_RCV_MC_ADDR1 */ |
| /* | RX_CFG1_RCV_MC_ADDR0 */ |
| /* | RX_CFG1_FILTER_ALL_MULTI */ |
| /* | RX_CFG1_FILTER_BSSID */ |
| | RX_CFG1_FILTER_MAC |
| /* | RX_CFG1_RCV_PROMISCUOUS */ |
| /* | RX_CFG1_INCLUDE_FCS */ |
| /* | RX_CFG1_INCLUDE_PHY_HDR */ |
| ); |
| adev->rx_config_2 = (u16) (0 |
| | RX_CFG2_RCV_ASSOC_REQ |
| | RX_CFG2_RCV_AUTH_FRAMES |
| | RX_CFG2_RCV_BEACON_FRAMES |
| | RX_CFG2_RCV_CONTENTION_FREE |
| | RX_CFG2_RCV_CTRL_FRAMES |
| | RX_CFG2_RCV_DATA_FRAMES |
| /*| RX_CFG2_RCV_BROKEN_FRAMES */ |
| | RX_CFG2_RCV_MGMT_FRAMES |
| | RX_CFG2_RCV_PROBE_REQ |
| | RX_CFG2_RCV_PROBE_RESP |
| /*| RX_CFG2_RCV_ACK_FRAMES */ |
| | RX_CFG2_RCV_OTHER |
| ); |
| break; |
| } |
| adev->rx_config_1 |= RX_CFG1_INCLUDE_RXBUF_HDR; |
| |
| if ((adev->rx_config_1 & RX_CFG1_INCLUDE_PHY_HDR) |
| || (adev->firmware_numver >= 0x02000000)) |
| adev->phy_header_len = IS_ACX111(adev) ? 8 : 4; |
| else |
| adev->phy_header_len = 0; |
| |
| log(L_INIT, "setting RXconfig to %04X:%04X\n", |
| adev->rx_config_1, adev->rx_config_2); |
| cfg.rx_cfg1 = cpu_to_le16(adev->rx_config_1); |
| cfg.rx_cfg2 = cpu_to_le16(adev->rx_config_2); |
| acx_s_configure(adev, &cfg, ACX1xx_IE_RXCONFIG); |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_s_set_defaults |
| */ |
| void |
| acx_s_set_defaults(acx_device_t *adev) |
| { |
| unsigned long flags; |
| |
| FN_ENTER; |
| |
| /* do it before getting settings, prevent bogus channel 0 warning */ |
| adev->channel = 1; |
| |
| /* query some settings from the card. |
| * NOTE: for some settings, e.g. CCA and ED (ACX100!), an initial |
| * query is REQUIRED, otherwise the card won't work correctly! */ |
| adev->get_mask = GETSET_ANTENNA|GETSET_SENSITIVITY|GETSET_STATION_ID|GETSET_REG_DOMAIN; |
| /* Only ACX100 supports ED and CCA */ |
| if (IS_ACX100(adev)) |
| adev->get_mask |= GETSET_CCA|GETSET_ED_THRESH; |
| |
| acx_s_update_card_settings(adev); |
| |
| acx_lock(adev, flags); |
| |
| /* set our global interrupt mask */ |
| if (IS_PCI(adev)) |
| acxpci_set_interrupt_mask(adev); |
| |
| adev->led_power = 1; /* LED is active on startup */ |
| adev->brange_max_quality = 60; /* LED blink max quality is 60 */ |
| adev->brange_time_last_state_change = jiffies; |
| |
| /* copy the MAC address we just got from the card |
| * into our MAC address used during current 802.11 session */ |
| MAC_COPY(adev->dev_addr, adev->ndev->dev_addr); |
| MAC_BCAST(adev->ap); |
| |
| adev->essid_len = |
| snprintf(adev->essid, sizeof(adev->essid), "STA%02X%02X%02X", |
| adev->dev_addr[3], adev->dev_addr[4], adev->dev_addr[5]); |
| adev->essid_active = 1; |
| |
| /* we have a nick field to waste, so why not abuse it |
| * to announce the driver version? ;-) */ |
| strncpy(adev->nick, "acx " ACX_RELEASE, IW_ESSID_MAX_SIZE); |
| |
| if (IS_PCI(adev)) { /* FIXME: this should be made to apply to USB, too! */ |
| /* first regulatory domain entry in EEPROM == default reg. domain */ |
| adev->reg_dom_id = adev->cfgopt_domains.list[0]; |
| } |
| |
| /* 0xffff would be better, but then we won't get a "scan complete" |
| * interrupt, so our current infrastructure will fail: */ |
| adev->scan_count = 1; |
| adev->scan_mode = ACX_SCAN_OPT_ACTIVE; |
| adev->scan_duration = 100; |
| adev->scan_probe_delay = 200; |
| /* reported to break scanning: adev->scan_probe_delay = adev->cfgopt_probe_delay; */ |
| adev->scan_rate = ACX_SCAN_RATE_1; |
| |
| adev->mode = ACX_MODE_2_STA; |
| adev->ieee->sec.auth_mode = WLAN_AUTH_OPEN; |
| adev->listen_interval = 100; |
| adev->beacon_interval = DEFAULT_BEACON_INTERVAL; |
| adev->dtim_interval = DEFAULT_DTIM_INTERVAL; |
| |
| adev->msdu_lifetime = DEFAULT_MSDU_LIFETIME; |
| |
| adev->rts_threshold = DEFAULT_RTS_THRESHOLD; |
| adev->frag_threshold = 2346; |
| |
| /* use standard default values for retry limits */ |
| adev->short_retry = 7; /* max. retries for (short) non-RTS packets */ |
| adev->long_retry = 4; /* max. retries for long (RTS) packets */ |
| |
| adev->preamble_mode = 2; /* auto */ |
| adev->fallback_threshold = 3; |
| adev->stepup_threshold = 10; |
| adev->rate_bcast = RATE111_1; |
| adev->rate_bcast100 = RATE100_1; |
| adev->rate_basic = RATE111_1 | RATE111_2; |
| adev->rate_auto = 1; |
| if (IS_ACX111(adev)) { |
| adev->rate_oper = RATE111_ALL; |
| } else { |
| adev->rate_oper = RATE111_ACX100_COMPAT; |
| } |
| |
| /* Supported Rates element - the rates here are given in units of |
| * 500 kbit/s, plus 0x80 added. See 802.11-1999.pdf item 7.3.2.2 */ |
| acx_l_update_ratevector(adev); |
| |
| /* set some more defaults */ |
| if (IS_ACX111(adev)) { |
| /* 30mW (15dBm) is default, at least in my acx111 card: */ |
| adev->tx_level_dbm = 15; |
| } else { |
| /* don't use max. level, since it might be dangerous |
| * (e.g. WRT54G people experience |
| * excessive Tx power damage!) */ |
| adev->tx_level_dbm = 18; |
| } |
| /* adev->tx_level_auto = 1; */ |
| if (IS_ACX111(adev)) { |
| /* start with sensitivity level 1 out of 3: */ |
| adev->sensitivity = 1; |
| } |
| |
| /* #define ENABLE_POWER_SAVE */ |
| #ifdef ENABLE_POWER_SAVE |
| adev->ps_wakeup_cfg = PS_CFG_ENABLE | PS_CFG_WAKEUP_ALL_BEAC; |
| adev->ps_listen_interval = 1; |
| adev->ps_options = PS_OPT_ENA_ENHANCED_PS | PS_OPT_TX_PSPOLL | PS_OPT_STILL_RCV_BCASTS; |
| adev->ps_hangover_period = 30; |
| adev->ps_enhanced_transition_time = 0; |
| #else |
| adev->ps_wakeup_cfg = 0; |
| adev->ps_listen_interval = 0; |
| adev->ps_options = 0; |
| adev->ps_hangover_period = 0; |
| adev->ps_enhanced_transition_time = 0; |
| #endif |
| |
| /* These settings will be set in fw on ifup */ |
| adev->set_mask = 0 |
| | GETSET_RETRY |
| | SET_MSDU_LIFETIME |
| /* configure card to do rate fallback when in auto rate mode */ |
| | SET_RATE_FALLBACK |
| | SET_RXCONFIG |
| | GETSET_TXPOWER |
| /* better re-init the antenna value we got above */ |
| | GETSET_ANTENNA |
| #if POWER_SAVE_80211 |
| | GETSET_POWER_80211 |
| #endif |
| ; |
| |
| acx_unlock(adev, flags); |
| acx_lock_unhold(); /* hold time 844814 CPU ticks @2GHz */ |
| |
| acx_s_initialize_rx_config(adev); |
| |
| FN_EXIT0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_s_set_defaults); |
| |
| /*********************************************************************** |
| ** FIXME: this should be solved in a general way for all radio types |
| ** by decoding the radio firmware module, |
| ** since it probably has some standard structure describing how to |
| ** set the power level of the radio module which it controls. |
| ** Or maybe not, since the radio module probably has a function interface |
| ** instead which then manages Tx level programming :-\ |
| */ |
| static int |
| acx111_s_set_tx_level(acx_device_t *adev, u8 level_dbm) |
| { |
| struct acx111_ie_tx_level tx_level; |
| |
| /* my acx111 card has two power levels in its configoptions (== EEPROM): |
| * 1 (30mW) [15dBm] |
| * 2 (10mW) [10dBm] |
| * For now, just assume all other acx111 cards have the same. |
| * FIXME: Ideally we would query it here, but we first need a |
| * standard way to query individual configoptions easily. |
| * Well, now we have proper cfgopt txpower variables, but this still |
| * hasn't been done yet, since it also requires dBm <-> mW conversion here... */ |
| if (level_dbm <= 12) { |
| tx_level.level = 2; /* 10 dBm */ |
| adev->tx_level_dbm = 10; |
| } else { |
| tx_level.level = 1; /* 15 dBm */ |
| adev->tx_level_dbm = 15; |
| } |
| if (level_dbm != adev->tx_level_dbm) |
| log(L_INIT, "acx111 firmware has specific " |
| "power levels only: adjusted %d dBm to %d dBm!\n", |
| level_dbm, adev->tx_level_dbm); |
| |
| return acx_s_configure(adev, &tx_level, ACX1xx_IE_DOT11_TX_POWER_LEVEL); |
| } |
| |
| static int |
| acx_s_set_tx_level(acx_device_t *adev, u8 level_dbm) |
| { |
| if (IS_ACX111(adev)) { |
| return acx111_s_set_tx_level(adev, level_dbm); |
| } |
| if (IS_PCI(adev)) { |
| return acx100pci_s_set_tx_level(adev, level_dbm); |
| } |
| return OK; |
| } |
| |
| |
| /*********************************************************************** |
| */ |
| //SM: have no dev specific code, should be moved to sm layer |
| /* Filter out unrelated packets, call ieee80211_rx[_mgt] */ |
| static int |
| _TODO_ieee80211_rx_any(struct ieee80211_device *ieee, |
| struct sk_buff *skb, struct ieee80211_rx_stats *stats) |
| { |
| struct ieee80211_hdr_4addr *hdr; |
| int is_packet_for_us; |
| u16 fc; |
| |
| if (ieee->iw_mode == IW_MODE_MONITOR) |
| return ieee80211_rx(ieee, skb, stats) ? 0 : -EINVAL; |
| |
| hdr = (struct ieee80211_hdr_4addr *)skb->data; |
| fc = le16_to_cpu(hdr->frame_ctl); |
| |
| if ((fc & IEEE80211_FCTL_VERS) != 0) |
| return -EINVAL; |
| |
| switch (fc & IEEE80211_FCTL_FTYPE) { |
| case IEEE80211_FTYPE_MGMT: |
| ieee80211_rx_mgt(ieee, hdr, stats); |
| return 0; |
| case IEEE80211_FTYPE_DATA: |
| break; |
| case IEEE80211_FTYPE_CTL: |
| return 0; |
| default: |
| return -EINVAL; |
| } |
| |
| is_packet_for_us = 0; |
| switch (ieee->iw_mode) { |
| case IW_MODE_ADHOC: |
| /* our BSS and not from/to DS */ |
| if (memcmp(hdr->addr3, ieee->bssid, ETH_ALEN) == 0) |
| if ((fc & (IEEE80211_FCTL_TODS+IEEE80211_FCTL_FROMDS)) == 0) { |
| /* promisc: get all */ |
| if (ieee->dev->flags & IFF_PROMISC) |
| is_packet_for_us = 1; |
| /* to us */ |
| else if (memcmp(hdr->addr1, ieee->dev->dev_addr, ETH_ALEN) == 0) |
| is_packet_for_us = 1; |
| /* mcast */ |
| else if (is_multicast_ether_addr(hdr->addr1)) |
| is_packet_for_us = 1; |
| } |
| break; |
| case IW_MODE_INFRA: |
| /* our BSS (== from our AP) and from DS */ |
| if (memcmp(hdr->addr2, ieee->bssid, ETH_ALEN) == 0) |
| if ((fc & (IEEE80211_FCTL_TODS+IEEE80211_FCTL_FROMDS)) == IEEE80211_FCTL_FROMDS) { |
| /* promisc: get all */ |
| if (ieee->dev->flags & IFF_PROMISC) |
| is_packet_for_us = 1; |
| /* to us */ |
| else if (memcmp(hdr->addr1, ieee->dev->dev_addr, ETH_ALEN) == 0) |
| is_packet_for_us = 1; |
| /* mcast */ |
| else if (is_multicast_ether_addr(hdr->addr1)) { |
| /* not our own packet bcasted from AP */ |
| if (memcmp(hdr->addr3, ieee->dev->dev_addr, ETH_ALEN)) |
| is_packet_for_us = 1; |
| } |
| } |
| break; |
| default: |
| /* ? */ |
| break; |
| } |
| |
| if (is_packet_for_us) |
| return (ieee80211_rx(ieee, skb, stats) ? 0 : -EINVAL); |
| return 0; |
| } |
| |
| //SM |
| void |
| acx_l_softmac_process_rxbuf(acx_device_t *adev, rxbuffer_t *rxbuf) |
| { |
| struct ieee80211_rx_stats stats; |
| struct ieee80211_hdr_3addr *hdr; |
| struct sk_buff *skb; |
| int skb_len; |
| u16 fc; |
| |
| FN_ENTER; |
| |
| skb_len = RXBUF_BYTES_RCVD(adev, rxbuf); |
| hdr = acx_get_wlan_hdr(adev, rxbuf); |
| fc = le16_to_cpu(hdr->frame_ctl); |
| |
| if ( ((WF_FC_FSTYPE & fc) != WF_FSTYPE_BEACON) |
| || (acx_debug & L_XFER_BEACON) |
| ) { |
| log(L_XFER|L_DATA, "rx: %s " |
| "time:%u len:%u signal:%u SNR:%u macstat:%02X " |
| "phystat:%02X phyrate:%u status:%u\n", |
| acx_get_packet_type_string(fc), |
| le32_to_cpu(rxbuf->time), |
| skb_len, |
| acx_signal_to_winlevel(rxbuf->phy_level), |
| acx_signal_to_winlevel(rxbuf->phy_snr), |
| rxbuf->mac_status, |
| rxbuf->phy_stat_baseband, |
| rxbuf->phy_plcp_signal, |
| adev->status); |
| } |
| |
| if (unlikely(acx_debug & L_DATA)) { |
| printk("rx: 802.11 buf[%u]: ", skb_len); |
| acx_dump_bytes(hdr, skb_len); |
| } |
| |
| /* FIXME: should check for Rx errors (rxbuf->mac_status? |
| * discard broken packets - but NOT for monitor!) |
| * and update Rx packet statistics here */ |
| |
| /* we are in big luck: the acx100 doesn't modify any of the fields */ |
| /* in the 802.11 frame. just pass this packet into the PF_PACKET */ |
| /* subsystem. yeah. */ |
| |
| /* sanity check */ |
| if (unlikely(skb_len > WLAN_A4FR_MAXLEN_WEP)) { |
| printk("%s: oversized frame of %d bytes dropped\n", |
| adev->ndev->name, skb_len); |
| goto end; |
| } |
| |
| /* allocate skb */ |
| skb = dev_alloc_skb(skb_len); |
| if (unlikely(!skb)) { |
| printk("%s: no memory for skb (%u bytes)\n", |
| adev->ndev->name, skb_len); |
| goto end; |
| } |
| |
| skb_put(skb, skb_len); |
| memcpy(skb->data, hdr, skb_len); |
| |
| memset(&stats, 0, sizeof(stats)); |
| stats.mac_time = le16_to_cpu(rxbuf->time); |
| //stats.rssi = |
| stats.signal = rxbuf->phy_snr; |
| stats.noise = rxbuf->phy_level; |
| stats.rate = rxbuf->phy_plcp_signal / 5; |
| stats.received_channel = adev->channel; |
| //stats.control = |
| stats.mask = 0 |
| | IEEE80211_STATMASK_SIGNAL |
| | IEEE80211_STATMASK_NOISE |
| | IEEE80211_STATMASK_RATE |
| //| IEEE80211_STATMASK_RSSI |
| ; |
| stats.freq = IEEE80211_24GHZ_BAND; |
| stats.len = skb->len; |
| |
| _TODO_ieee80211_rx_any(adev->ieee, skb, &stats); |
| end: |
| FN_EXIT0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_l_softmac_process_rxbuf); |
| |
| |
| /*********************************************************************** |
| ** acx_l_handle_txrate_auto |
| ** |
| ** Theory of operation: |
| ** client->rate_cap is a bitmask of rates client is capable of. |
| ** client->rate_cfg is a bitmask of allowed (configured) rates. |
| ** It is set as a result of iwconfig rate N [auto] |
| ** or iwpriv set_rates "N,N,N N,N,N" commands. |
| ** It can be fixed (e.g. 0x0080 == 18Mbit only), |
| ** auto (0x00ff == 18Mbit or any lower value), |
| ** and code handles any bitmask (0x1081 == try 54Mbit,18Mbit,1Mbit _only_). |
| ** |
| ** client->rate_cur is a value for rate111 field in tx descriptor. |
| ** It is always set to txrate_cfg sans zero or more most significant |
| ** bits. This routine handles selection of new rate_cur value depending on |
| ** outcome of last tx event. |
| ** |
| ** client->rate_100 is a precalculated rate value for acx100 |
| ** (we can do without it, but will need to calculate it on each tx). |
| ** |
| ** You cannot configure mixed usage of 5.5 and/or 11Mbit rate |
| ** with PBCC and CCK modulation. Either both at CCK or both at PBCC. |
| ** In theory you can implement it, but so far it is considered not worth doing. |
| ** |
| ** 22Mbit, of course, is PBCC always. */ |
| |
| /* maps acx100 tx descr rate field to acx111 one */ |
| static u16 |
| rate100to111(u8 r) |
| { |
| switch (r) { |
| case RATE100_1: return RATE111_1; |
| case RATE100_2: return RATE111_2; |
| case RATE100_5: |
| case (RATE100_5 | RATE100_PBCC511): return RATE111_5; |
| case RATE100_11: |
| case (RATE100_11 | RATE100_PBCC511): return RATE111_11; |
| case RATE100_22: return RATE111_22; |
| default: |
| printk("acx: unexpected acx100 txrate: %u! " |
| "Please report\n", r); |
| return RATE111_1; |
| } |
| } |
| |
| |
| void |
| acx_l_handle_txrate_auto(acx_device_t *adev, struct client *txc, |
| u16 cur, u8 rate100, u16 rate111, |
| u8 error, int pkts_to_ignore) |
| { |
| u16 sent_rate; |
| int slower_rate_was_used; |
| |
| /* vda: hmm. current code will do this: |
| ** 1. send packets at 11 Mbit, stepup++ |
| ** 2. will try to send at 22Mbit. hardware will see no ACK, |
| ** retries at 11Mbit, success. code notes that used rate |
| ** is lower. stepup = 0, fallback++ |
| ** 3. repeat step 2 fallback_count times. Fall back to |
| ** 11Mbit. go to step 1. |
| ** If stepup_count is large (say, 16) and fallback_count |
| ** is small (3), this wouldn't be too bad wrt throughput */ |
| |
| if (unlikely(!cur)) { |
| printk("acx: BUG! ratemask is empty\n"); |
| return; /* or else we may lock up the box */ |
| } |
| |
| /* do some preparations, i.e. calculate the one rate that was |
| * used to send this packet */ |
| if (IS_ACX111(adev)) { |
| sent_rate = 1 << highest_bit(rate111 & RATE111_ALL); |
| } else { |
| sent_rate = rate100to111(rate100); |
| } |
| /* sent_rate has only one bit set now, corresponding to tx rate |
| * which was used by hardware to tx this particular packet */ |
| |
| /* now do the actual auto rate management */ |
| log(L_XFER, "tx: %sclient=%p/"MACSTR" used=%04X cur=%04X cfg=%04X " |
| "__=%u/%u ^^=%u/%u\n", |
| (txc->ignore_count > 0) ? "[IGN] " : "", |
| txc, MAC(txc->address), sent_rate, cur, txc->rate_cfg, |
| txc->fallback_count, adev->fallback_threshold, |
| txc->stepup_count, adev->stepup_threshold |
| ); |
| |
| /* we need to ignore old packets already in the tx queue since |
| * they use older rate bytes configured before our last rate change, |
| * otherwise our mechanism will get confused by interpreting old data. |
| * Do it after logging above */ |
| if (txc->ignore_count) { |
| txc->ignore_count--; |
| return; |
| } |
| |
| /* true only if the only nonzero bit in sent_rate is |
| ** less significant than highest nonzero bit in cur */ |
| slower_rate_was_used = ( cur > ((sent_rate<<1)-1) ); |
| |
| if (slower_rate_was_used || error) { |
| txc->stepup_count = 0; |
| if (++txc->fallback_count <= adev->fallback_threshold) |
| return; |
| txc->fallback_count = 0; |
| |
| /* clear highest 1 bit in cur */ |
| sent_rate = RATE111_54; |
| while (!(cur & sent_rate)) sent_rate >>= 1; |
| CLEAR_BIT(cur, sent_rate); |
| if (!cur) /* we can't disable all rates! */ |
| cur = sent_rate; |
| log(L_XFER, "tx: falling back to ratemask %04X\n", cur); |
| |
| } else { /* there was neither lower rate nor error */ |
| txc->fallback_count = 0; |
| if (++txc->stepup_count <= adev->stepup_threshold) |
| return; |
| txc->stepup_count = 0; |
| |
| /* Sanitize. Sort of not needed, but I dont trust hw that much... |
| ** what if it can report bogus tx rates sometimes? */ |
| while (!(cur & sent_rate)) sent_rate >>= 1; |
| |
| /* try to find a higher sent_rate that isn't yet in our |
| * current set, but is an allowed cfg */ |
| while (1) { |
| sent_rate <<= 1; |
| if (sent_rate > txc->rate_cfg) |
| /* no higher rates allowed by config */ |
| return; |
| if (!(cur & sent_rate) && (txc->rate_cfg & sent_rate)) |
| /* found */ |
| break; |
| /* not found, try higher one */ |
| } |
| SET_BIT(cur, sent_rate); |
| log(L_XFER, "tx: stepping up to ratemask %04X\n", cur); |
| } |
| |
| txc->rate_cur = cur; |
| txc->ignore_count = pkts_to_ignore; |
| /* calculate acx100 style rate byte if needed */ |
| if (IS_ACX100(adev)) { |
| txc->rate_100 = acx_bitpos2rate100[highest_bit(cur)]; |
| } |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_l_handle_txrate_auto); |
| |
| |
| //SM |
| int |
| acx_i_ieee80211_start_xmit(struct ieee80211_txb *txb, |
| struct net_device *ndev, |
| int pri) |
| { |
| acx_device_t *adev = ndev2adev(ndev); |
| int i; |
| int rc = -ENODEV; |
| unsigned long flags; |
| |
| if (unlikely(!txb)) { /* indicate success */ |
| rc = OK; |
| goto end_no_unlock; |
| } |
| if (unlikely(!adev)) { |
| goto end_no_unlock; |
| } |
| |
| acx_lock(adev, flags); |
| |
| for (i = 0; i < txb->nr_frags; i++) { |
| tx_t *tx; |
| void *txbuf; |
| struct sk_buff *skb = txb->fragments[i]; |
| tx = acx_l_alloc_tx(adev); |
| if (unlikely(!tx)) { |
| printk_ratelimited("%s: start_xmit: txdesc ring is full, " |
| "dropping tx\n", ndev->name); |
| rc = -ENOMEM; |
| goto end; |
| } |
| txbuf = acx_l_get_txbuf(adev, tx); |
| if (unlikely(!txbuf)) { |
| /* Card was removed */ |
| rc = -ENOMEM; |
| acx_l_dealloc_tx(adev, tx); |
| goto end; |
| } |
| memcpy(txbuf, skb->data, skb->len); |
| acx_l_tx_data(adev, tx, skb->len); |
| //ndev->trans_start = jiffies; |
| //adev->stats.tx_packets++; |
| //adev->stats.tx_bytes += skb->len; |
| } |
| rc = 0; |
| |
| end: |
| acx_unlock(adev, flags); |
| |
| end_no_unlock: |
| if (!rc && txb) |
| ieee80211_txb_free(txb); |
| |
| FN_EXIT1(rc); |
| return rc; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_i_ieee80211_start_xmit); |
| |
| /*********************************************************************** |
| ** acx_l_update_ratevector |
| ** |
| ** Updates adev->rate_supported[_len] according to rate_{basic,oper} |
| */ |
| const u8 |
| acx_bitpos2ratebyte[] = { |
| DOT11RATEBYTE_1, |
| DOT11RATEBYTE_2, |
| DOT11RATEBYTE_5_5, |
| DOT11RATEBYTE_6_G, |
| DOT11RATEBYTE_9_G, |
| DOT11RATEBYTE_11, |
| DOT11RATEBYTE_12_G, |
| DOT11RATEBYTE_18_G, |
| DOT11RATEBYTE_22, |
| DOT11RATEBYTE_24_G, |
| DOT11RATEBYTE_36_G, |
| DOT11RATEBYTE_48_G, |
| DOT11RATEBYTE_54_G, |
| }; |
| |
| void |
| acx_l_update_ratevector(acx_device_t *adev) |
| { |
| u16 bcfg = adev->rate_basic; |
| u16 ocfg = adev->rate_oper; |
| u8 *supp = adev->rate_supported; |
| const u8 *dot11 = acx_bitpos2ratebyte; |
| |
| FN_ENTER; |
| |
| while (ocfg) { |
| if (ocfg & 1) { |
| *supp = *dot11; |
| if (bcfg & 1) { |
| *supp |= 0x80; |
| } |
| supp++; |
| } |
| dot11++; |
| ocfg >>= 1; |
| bcfg >>= 1; |
| } |
| adev->rate_supported_len = supp - adev->rate_supported; |
| if (acx_debug & L_ASSOC) { |
| printk("new ratevector: "); |
| acx_dump_bytes(adev->rate_supported, adev->rate_supported_len); |
| } |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_l_sta_list_init |
| */ |
| static void |
| acx_l_sta_list_init(acx_device_t *adev) |
| { |
| FN_ENTER; |
| memset(adev->sta_hash_tab, 0, sizeof(adev->sta_hash_tab)); |
| memset(adev->sta_list, 0, sizeof(adev->sta_list)); |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_set_status |
| ** |
| ** This function is called in many atomic regions, must not sleep |
| ** |
| ** This function does not need locking UNLESS you call it |
| ** as acx_set_status(ACX_STATUS_4_ASSOCIATED), bacause this can |
| ** wake queue. This can race with stop_queue elsewhere. |
| ** See acx_stop_queue comment. */ |
| void |
| acx_set_status(acx_device_t *adev, u16 new_status) |
| { |
| #define QUEUE_OPEN_AFTER_ASSOC 1 /* this really seems to be needed now */ |
| u16 old_status = adev->status; |
| |
| FN_ENTER; |
| |
| log(L_ASSOC, "%s(%d):%s\n", |
| __func__, new_status, acx_get_status_name(new_status)); |
| |
| /* wireless_send_event never sleeps */ |
| if (ACX_STATUS_4_ASSOCIATED == new_status) { |
| union iwreq_data wrqu; |
| |
| wrqu.data.length = 0; |
| wrqu.data.flags = 0; |
| wireless_send_event(adev->ndev, SIOCGIWSCAN, &wrqu, NULL); |
| |
| wrqu.data.length = 0; |
| wrqu.data.flags = 0; |
| MAC_COPY(wrqu.ap_addr.sa_data, adev->bssid); |
| wrqu.ap_addr.sa_family = ARPHRD_ETHER; |
| wireless_send_event(adev->ndev, SIOCGIWAP, &wrqu, NULL); |
| } else { |
| union iwreq_data wrqu; |
| |
| /* send event with empty BSSID to indicate we're not associated */ |
| MAC_ZERO(wrqu.ap_addr.sa_data); |
| wrqu.ap_addr.sa_family = ARPHRD_ETHER; |
| wireless_send_event(adev->ndev, SIOCGIWAP, &wrqu, NULL); |
| } |
| |
| adev->status = new_status; |
| |
| switch (new_status) { |
| case ACX_STATUS_1_SCANNING: |
| adev->scan_retries = 0; |
| /* 1.0 s initial scan time */ |
| acx_set_timer(adev, 1000000); |
| break; |
| case ACX_STATUS_2_WAIT_AUTH: |
| case ACX_STATUS_3_AUTHENTICATED: |
| adev->auth_or_assoc_retries = 0; |
| acx_set_timer(adev, 1500000); /* 1.5 s */ |
| break; |
| } |
| |
| #if QUEUE_OPEN_AFTER_ASSOC |
| if (new_status == ACX_STATUS_4_ASSOCIATED) { |
| if (old_status < ACX_STATUS_4_ASSOCIATED) { |
| /* ah, we're newly associated now, |
| * so let's indicate carrier */ |
| acx_carrier_on(adev->ndev, "after association"); |
| acx_wake_queue(adev->ndev, "after association"); |
| } |
| } else { |
| /* not associated any more, so let's kill carrier */ |
| if (old_status >= ACX_STATUS_4_ASSOCIATED) { |
| acx_carrier_off(adev->ndev, "after losing association"); |
| acx_stop_queue(adev->ndev, "after losing association"); |
| } |
| } |
| #endif |
| FN_EXIT0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_set_status); |
| |
| /*********************************************************************** |
| ** acx_i_timer |
| ** |
| ** Fires up periodically. Used to kick scan/auth/assoc if something goes wrong |
| */ |
| void |
| acx_i_timer(unsigned long address) |
| { |
| unsigned long flags; |
| acx_device_t *adev = (acx_device_t*)address; |
| |
| FN_ENTER; |
| |
| acx_lock(adev, flags); |
| |
| log(L_DEBUG|L_ASSOC, "%s: adev->status=%d (%s)\n", |
| __func__, adev->status, acx_get_status_name(adev->status)); |
| |
| switch (adev->status) { |
| case ACX_STATUS_1_SCANNING: |
| /* was set to 0 by set_status() */ |
| if (++adev->scan_retries < 7) { |
| acx_set_timer(adev, 1000000); |
| /* used to interrogate for scan status. |
| ** We rely on SCAN_COMPLETE IRQ instead */ |
| log(L_ASSOC, "continuing scan (%d sec)\n", |
| adev->scan_retries); |
| } else { |
| log(L_ASSOC, "stopping scan\n"); |
| /* send stop_scan cmd when we leave the interrupt context, |
| * and make a decision what to do next (COMPLETE_SCAN) */ |
| acx_schedule_task(adev, |
| ACX_AFTER_IRQ_CMD_STOP_SCAN + ACX_AFTER_IRQ_COMPLETE_SCAN); |
| } |
| break; |
| case ACX_STATUS_2_WAIT_AUTH: |
| /* was set to 0 by set_status() */ |
| if (++adev->auth_or_assoc_retries < 10) { |
| log(L_ASSOC, "resend authen1 request (attempt %d)\n", |
| adev->auth_or_assoc_retries + 1); |
| //sm? acx_l_transmit_authen1(adev); |
| } else { |
| /* time exceeded: fall back to scanning mode */ |
| log(L_ASSOC, |
| "authen1 request reply timeout, giving up\n"); |
| /* we are a STA, need to find AP anyhow */ |
| acx_set_status(adev, ACX_STATUS_1_SCANNING); |
| acx_schedule_task(adev, ACX_AFTER_IRQ_RESTART_SCAN); |
| } |
| /* used to be 1500000, but some other driver uses 2.5s */ |
| acx_set_timer(adev, 2500000); |
| break; |
| case ACX_STATUS_3_AUTHENTICATED: |
| /* was set to 0 by set_status() */ |
| if (++adev->auth_or_assoc_retries < 10) { |
| log(L_ASSOC, "resend assoc request (attempt %d)\n", |
| adev->auth_or_assoc_retries + 1); |
| //sm? acx_l_transmit_assoc_req(adev); |
| } else { |
| /* time exceeded: give up */ |
| log(L_ASSOC, |
| "association request reply timeout, giving up\n"); |
| /* we are a STA, need to find AP anyhow */ |
| acx_set_status(adev, ACX_STATUS_1_SCANNING); |
| acx_schedule_task(adev, ACX_AFTER_IRQ_RESTART_SCAN); |
| } |
| acx_set_timer(adev, 2500000); /* see above */ |
| break; |
| case ACX_STATUS_4_ASSOCIATED: |
| default: |
| break; |
| } |
| |
| acx_unlock(adev, flags); |
| |
| FN_EXIT0; |
| } |
| |
| EXPORT_SYMBOL_GPL(acx_i_timer); |
| |
| /*********************************************************************** |
| ** acx_set_timer |
| ** |
| ** Sets the 802.11 state management timer's timeout. |
| */ |
| void |
| acx_set_timer(acx_device_t *adev, int timeout_us) |
| { |
| FN_ENTER; |
| |
| log(L_DEBUG|L_IRQ, "%s(%u ms)\n", __func__, timeout_us/1000); |
| if (!(adev->dev_state_mask & ACX_STATE_IFACE_UP)) { |
| printk("attempt to set the timer " |
| "when the card interface is not up!\n"); |
| goto end; |
| } |
| |
| /* first check if the timer was already initialized, THEN modify it */ |
| if (adev->mgmt_timer.function) { |
| mod_timer(&adev->mgmt_timer, |
| jiffies + (timeout_us * HZ / 1000000)); |
| } |
| end: |
| FN_EXIT0; |
| } |
| |
| |
| /*********************************************************************** |
| ** acx_s_complete_scan |
| ** |
| ** Called either from after_interrupt_task() if: |
| ** 1) there was Scan_Complete IRQ, or |
| ** 2) scanning expired in timer() |
| ** We need to decide which ESS or IBSS to join. |
| ** Iterates thru adev->sta_list: |
| ** if adev->ap is not bcast, will join only specified |
| ** ESS or IBSS with this bssid |
| ** checks peers' caps for ESS/IBSS bit |
| ** checks peers' SSID, allows exact match or hidden SSID |
| ** If station to join is chosen: |
| ** points adev->ap_client to the chosen struct client |
| ** sets adev->essid_for_assoc for future assoc attempt |
| ** Auth/assoc is not yet performed |
| ** Returns OK if there is no need to restart scan |
| */ |
| int |
| acx_s_complete_scan(acx_device_t * |