📄 ipw2100.c
字号:
/****************************************************************************** Copyright(c) 2003 - 2005 Intel Corporation. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. The full GNU General Public License is included in this distribution in the file called LICENSE. Contact Information: James P. Ketrenos <ipw2100-admin@linux.intel.com> Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 Portions of this file are based on the sample_* files provided by Wireless Extensions 0.26 package and copyright (c) 1997-2003 Jean Tourrilhes <jt@hpl.hp.com> Portions of this file are based on the Host AP project, Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen <jkmaline@cc.hut.fi> Copyright (c) 2002-2003, Jouni Malinen <jkmaline@cc.hut.fi> Portions of ipw2100_mod_firmware_load, ipw2100_do_mod_firmware_load, and ipw2100_fw_load are loosely based on drivers/sound/sound_firmware.c available in the 2.4.25 kernel sources, and are copyright (c) Alan Cox******************************************************************************//* Initial driver on which this is based was developed by Janusz Gorycki, Maciej Urbaniak, and Maciej Sosnowski. Promiscuous mode support added by Jacek Wysoczynski and Maciej Urbaniak.Theory of OperationTx - Commands and DataFirmware and host share a circular queue of Transmit Buffer Descriptors (TBDs)Each TBD contains a pointer to the physical (dma_addr_t) address of data beingsent to the firmware as well as the length of the data.The host writes to the TBD queue at the WRITE index. The WRITE index pointsto the _next_ packet to be written and is advanced when after the TBD has beenfilled.The firmware pulls from the TBD queue at the READ index. The READ index pointsto the currently being read entry, and is advanced once the firmware isdone with a packet.When data is sent to the firmware, the first TBD is used to indicate to thefirmware if a Command or Data is being sent. If it is Command, all of thecommand information is contained within the physical address referred to by theTBD. If it is Data, the first TBD indicates the type of data packet, numberof fragments, etc. The next TBD then referrs to the actual packet location.The Tx flow cycle is as follows:1) ipw2100_tx() is called by kernel with SKB to transmit2) Packet is move from the tx_free_list and appended to the transmit pending list (tx_pend_list)3) work is scheduled to move pending packets into the shared circular queue.4) when placing packet in the circular queue, the incoming SKB is DMA mapped to a physical address. That address is entered into a TBD. Two TBDs are filled out. The first indicating a data packet, the second referring to the actual payload data.5) the packet is removed from tx_pend_list and placed on the end of the firmware pending list (fw_pend_list)6) firmware is notified that the WRITE index has7) Once the firmware has processed the TBD, INTA is triggered.8) For each Tx interrupt received from the firmware, the READ index is checked to see which TBDs are done being processed.9) For each TBD that has been processed, the ISR pulls the oldest packet from the fw_pend_list.10)The packet structure contained in the fw_pend_list is then used to unmap the DMA address and to free the SKB originally passed to the driver from the kernel.11)The packet structure is placed onto the tx_free_listThe above steps are the same for commands, only the msg_free_list/msg_pend_listare used instead of tx_free_list/tx_pend_list...Critical Sections / Locking :There are two locks utilized. The first is the low level lock (priv->low_lock)that protects the following:- Access to the Tx/Rx queue lists via priv->low_lock. The lists are as follows: tx_free_list : Holds pre-allocated Tx buffers. TAIL modified in __ipw2100_tx_process() HEAD modified in ipw2100_tx() tx_pend_list : Holds used Tx buffers waiting to go into the TBD ring TAIL modified ipw2100_tx() HEAD modified by ipw2100_tx_send_data() msg_free_list : Holds pre-allocated Msg (Command) buffers TAIL modified in __ipw2100_tx_process() HEAD modified in ipw2100_hw_send_command() msg_pend_list : Holds used Msg buffers waiting to go into the TBD ring TAIL modified in ipw2100_hw_send_command() HEAD modified in ipw2100_tx_send_commands() The flow of data on the TX side is as follows: MSG_FREE_LIST + COMMAND => MSG_PEND_LIST => TBD => MSG_FREE_LIST TX_FREE_LIST + DATA => TX_PEND_LIST => TBD => TX_FREE_LIST The methods that work on the TBD ring are protected via priv->low_lock.- The internal data state of the device itself- Access to the firmware read/write indexes for the BD queues and associated logicAll external entry functions are locked with the priv->action_lock to ensurethat only one external action is invoked at a time.*/#include <linux/compiler.h>#include <linux/config.h>#include <linux/errno.h>#include <linux/if_arp.h>#include <linux/in6.h>#include <linux/in.h>#include <linux/ip.h>#include <linux/kernel.h>#include <linux/kmod.h>#include <linux/module.h>#include <linux/netdevice.h>#include <linux/ethtool.h>#include <linux/pci.h>#include <linux/dma-mapping.h>#include <linux/proc_fs.h>#include <linux/skbuff.h>#include <asm/uaccess.h>#include <asm/io.h>#define __KERNEL_SYSCALLS__#include <linux/fs.h>#include <linux/mm.h>#include <linux/slab.h>#include <linux/unistd.h>#include <linux/stringify.h>#include <linux/tcp.h>#include <linux/types.h>#include <linux/version.h>#include <linux/time.h>#include <linux/firmware.h>#include <linux/acpi.h>#include <linux/ctype.h>#include "ipw2100.h"#define IPW2100_VERSION "1.1.3"#define DRV_NAME "ipw2100"#define DRV_VERSION IPW2100_VERSION#define DRV_DESCRIPTION "Intel(R) PRO/Wireless 2100 Network Driver"#define DRV_COPYRIGHT "Copyright(c) 2003-2005 Intel Corporation"/* Debugging stuff */#ifdef CONFIG_IPW_DEBUG#define CONFIG_IPW2100_RX_DEBUG /* Reception debugging */#endifMODULE_DESCRIPTION(DRV_DESCRIPTION);MODULE_VERSION(DRV_VERSION);MODULE_AUTHOR(DRV_COPYRIGHT);MODULE_LICENSE("GPL");static int debug = 0;static int mode = 0;static int channel = 0;static int associate = 1;static int disable = 0;#ifdef CONFIG_PMstatic struct ipw2100_fw ipw2100_firmware;#endif#include <linux/moduleparam.h>module_param(debug, int, 0444);module_param(mode, int, 0444);module_param(channel, int, 0444);module_param(associate, int, 0444);module_param(disable, int, 0444);MODULE_PARM_DESC(debug, "debug level");MODULE_PARM_DESC(mode, "network mode (0=BSS,1=IBSS,2=Monitor)");MODULE_PARM_DESC(channel, "channel");MODULE_PARM_DESC(associate, "auto associate when scanning (default on)");MODULE_PARM_DESC(disable, "manually disable the radio (default 0 [radio on])");static u32 ipw2100_debug_level = IPW_DL_NONE;#ifdef CONFIG_IPW_DEBUG#define IPW_DEBUG(level, message...) \do { \ if (ipw2100_debug_level & (level)) { \ printk(KERN_DEBUG "ipw2100: %c %s ", \ in_interrupt() ? 'I' : 'U', __FUNCTION__); \ printk(message); \ } \} while (0)#else#define IPW_DEBUG(level, message...) do {} while (0)#endif /* CONFIG_IPW_DEBUG */#ifdef CONFIG_IPW_DEBUGstatic const char *command_types[] = { "undefined", "unused", /* HOST_ATTENTION */ "HOST_COMPLETE", "unused", /* SLEEP */ "unused", /* HOST_POWER_DOWN */ "unused", "SYSTEM_CONFIG", "unused", /* SET_IMR */ "SSID", "MANDATORY_BSSID", "AUTHENTICATION_TYPE", "ADAPTER_ADDRESS", "PORT_TYPE", "INTERNATIONAL_MODE", "CHANNEL", "RTS_THRESHOLD", "FRAG_THRESHOLD", "POWER_MODE", "TX_RATES", "BASIC_TX_RATES", "WEP_KEY_INFO", "unused", "unused", "unused", "unused", "WEP_KEY_INDEX", "WEP_FLAGS", "ADD_MULTICAST", "CLEAR_ALL_MULTICAST", "BEACON_INTERVAL", "ATIM_WINDOW", "CLEAR_STATISTICS", "undefined", "undefined", "undefined", "undefined", "TX_POWER_INDEX", "undefined", "undefined", "undefined", "undefined", "undefined", "undefined", "BROADCAST_SCAN", "CARD_DISABLE", "PREFERRED_BSSID", "SET_SCAN_OPTIONS", "SCAN_DWELL_TIME", "SWEEP_TABLE", "AP_OR_STATION_TABLE", "GROUP_ORDINALS", "SHORT_RETRY_LIMIT", "LONG_RETRY_LIMIT", "unused", /* SAVE_CALIBRATION */ "unused", /* RESTORE_CALIBRATION */ "undefined", "undefined", "undefined", "HOST_PRE_POWER_DOWN", "unused", /* HOST_INTERRUPT_COALESCING */ "undefined", "CARD_DISABLE_PHY_OFF", "MSDU_TX_RATES" "undefined", "undefined", "SET_STATION_STAT_BITS", "CLEAR_STATIONS_STAT_BITS", "LEAP_ROGUE_MODE", "SET_SECURITY_INFORMATION", "DISASSOCIATION_BSSID", "SET_WPA_ASS_IE"};#endif/* Pre-decl until we get the code solid and then we can clean it up */static void ipw2100_tx_send_commands(struct ipw2100_priv *priv);static void ipw2100_tx_send_data(struct ipw2100_priv *priv);static int ipw2100_adapter_setup(struct ipw2100_priv *priv);static void ipw2100_queues_initialize(struct ipw2100_priv *priv);static void ipw2100_queues_free(struct ipw2100_priv *priv);static int ipw2100_queues_allocate(struct ipw2100_priv *priv);static int ipw2100_fw_download(struct ipw2100_priv *priv, struct ipw2100_fw *fw);static int ipw2100_get_firmware(struct ipw2100_priv *priv, struct ipw2100_fw *fw);static int ipw2100_get_fwversion(struct ipw2100_priv *priv, char *buf, size_t max);static int ipw2100_get_ucodeversion(struct ipw2100_priv *priv, char *buf, size_t max);static void ipw2100_release_firmware(struct ipw2100_priv *priv, struct ipw2100_fw *fw);static int ipw2100_ucode_download(struct ipw2100_priv *priv, struct ipw2100_fw *fw);static void ipw2100_wx_event_work(struct ipw2100_priv *priv);static struct iw_statistics *ipw2100_wx_wireless_stats(struct net_device *dev);static struct iw_handler_def ipw2100_wx_handler_def;static inline void read_register(struct net_device *dev, u32 reg, u32 * val){ *val = readl((void __iomem *)(dev->base_addr + reg)); IPW_DEBUG_IO("r: 0x%08X => 0x%08X\n", reg, *val);}static inline void write_register(struct net_device *dev, u32 reg, u32 val){ writel(val, (void __iomem *)(dev->base_addr + reg)); IPW_DEBUG_IO("w: 0x%08X <= 0x%08X\n", reg, val);}static inline void read_register_word(struct net_device *dev, u32 reg, u16 * val){ *val = readw((void __iomem *)(dev->base_addr + reg)); IPW_DEBUG_IO("r: 0x%08X => %04X\n", reg, *val);}static inline void read_register_byte(struct net_device *dev, u32 reg, u8 * val){ *val = readb((void __iomem *)(dev->base_addr + reg)); IPW_DEBUG_IO("r: 0x%08X => %02X\n", reg, *val);}static inline void write_register_word(struct net_device *dev, u32 reg, u16 val){ writew(val, (void __iomem *)(dev->base_addr + reg)); IPW_DEBUG_IO("w: 0x%08X <= %04X\n", reg, val);}static inline void write_register_byte(struct net_device *dev, u32 reg, u8 val){ writeb(val, (void __iomem *)(dev->base_addr + reg)); IPW_DEBUG_IO("w: 0x%08X =< %02X\n", reg, val);}static inline void read_nic_dword(struct net_device *dev, u32 addr, u32 * val){ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, addr & IPW_REG_INDIRECT_ADDR_MASK); read_register(dev, IPW_REG_INDIRECT_ACCESS_DATA, val);}static inline void write_nic_dword(struct net_device *dev, u32 addr, u32 val){ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, addr & IPW_REG_INDIRECT_ADDR_MASK); write_register(dev, IPW_REG_INDIRECT_ACCESS_DATA, val);}static inline void read_nic_word(struct net_device *dev, u32 addr, u16 * val){ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, addr & IPW_REG_INDIRECT_ADDR_MASK); read_register_word(dev, IPW_REG_INDIRECT_ACCESS_DATA, val);}static inline void write_nic_word(struct net_device *dev, u32 addr, u16 val){ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, addr & IPW_REG_INDIRECT_ADDR_MASK); write_register_word(dev, IPW_REG_INDIRECT_ACCESS_DATA, val);}static inline void read_nic_byte(struct net_device *dev, u32 addr, u8 * val){ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, addr & IPW_REG_INDIRECT_ADDR_MASK); read_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA, val);}static inline void write_nic_byte(struct net_device *dev, u32 addr, u8 val){ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, addr & IPW_REG_INDIRECT_ADDR_MASK); write_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA, val);}static inline void write_nic_auto_inc_address(struct net_device *dev, u32 addr){ write_register(dev, IPW_REG_AUTOINCREMENT_ADDRESS, addr & IPW_REG_INDIRECT_ADDR_MASK);}static inline void write_nic_dword_auto_inc(struct net_device *dev, u32 val){ write_register(dev, IPW_REG_AUTOINCREMENT_DATA, val);}static inline void write_nic_memory(struct net_device *dev, u32 addr, u32 len, const u8 * buf){ u32 aligned_addr; u32 aligned_len; u32 dif_len; u32 i; /* read first nibble byte by byte */ aligned_addr = addr & (~0x3); dif_len = addr - aligned_addr; if (dif_len) { /* Start reading at aligned_addr + dif_len */ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, aligned_addr); for (i = dif_len; i < 4; i++, buf++) write_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA + i, *buf); len -= dif_len; aligned_addr += 4; } /* read DWs through autoincrement registers */ write_register(dev, IPW_REG_AUTOINCREMENT_ADDRESS, aligned_addr); aligned_len = len & (~0x3); for (i = 0; i < aligned_len; i += 4, buf += 4, aligned_addr += 4) write_register(dev, IPW_REG_AUTOINCREMENT_DATA, *(u32 *) buf); /* copy the last nibble */ dif_len = len - aligned_len; write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, aligned_addr); for (i = 0; i < dif_len; i++, buf++) write_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA + i, *buf);}static inline void read_nic_memory(struct net_device *dev, u32 addr, u32 len, u8 * buf){ u32 aligned_addr; u32 aligned_len; u32 dif_len; u32 i; /* read first nibble byte by byte */ aligned_addr = addr & (~0x3); dif_len = addr - aligned_addr; if (dif_len) { /* Start reading at aligned_addr + dif_len */ write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, aligned_addr); for (i = dif_len; i < 4; i++, buf++) read_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA + i, buf); len -= dif_len; aligned_addr += 4; } /* read DWs through autoincrement registers */ write_register(dev, IPW_REG_AUTOINCREMENT_ADDRESS, aligned_addr);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -