diff options
author | James Ketrenos <jketreno@linux.intel.com> | 2005-03-23 17:32:29 -0600 |
---|---|---|
committer | Jeff Garzik <jgarzik@pobox.com> | 2005-05-27 21:57:53 -0400 |
commit | 2c86c275015c880e810830304a3a4ab94803b38b (patch) | |
tree | 219bf554765cd7bacf1e120290359dfa8370c0f7 | |
parent | 0a989b24fd59e8867274246587b46f5595fa0baa (diff) |
Add ipw2100 wireless driver.
-rw-r--r-- | Documentation/networking/README.ipw2100 | 246 | ||||
-rw-r--r-- | drivers/net/wireless/Kconfig | 53 | ||||
-rw-r--r-- | drivers/net/wireless/Makefile | 2 | ||||
-rw-r--r-- | drivers/net/wireless/ipw2100.c | 8649 | ||||
-rw-r--r-- | drivers/net/wireless/ipw2100.h | 1278 |
5 files changed, 10228 insertions, 0 deletions
diff --git a/Documentation/networking/README.ipw2100 b/Documentation/networking/README.ipw2100 new file mode 100644 index 00000000000..2046948b020 --- /dev/null +++ b/Documentation/networking/README.ipw2100 @@ -0,0 +1,246 @@ + +=========================== +Intel(R) PRO/Wireless 2100 Network Connection Driver for Linux +README.ipw2100 + +March 14, 2005 + +=========================== +Index +--------------------------- +0. Introduction +1. Release 1.1.0 Current Features +2. Command Line Parameters +3. Sysfs Helper Files +4. Radio Kill Switch +5. Dynamic Firmware +6. Power Management +7. Support +8. License + + +=========================== +0. Introduction +------------ ----- ----- ---- --- -- - + +This document provides a brief overview of the features supported by the +IPW2100 driver project. The main project website, where the latest +development version of the driver can be found, is: + + http://ipw2100.sourceforge.net + +There you can find the not only the latest releases, but also information about +potential fixes and patches, as well as links to the development mailing list +for the driver project. + + +=========================== +1. Release 1.1.0 Current Supported Features +--------------------------- +- Managed (BSS) and Ad-Hoc (IBSS) +- WEP (shared key and open) +- Wireless Tools support +- 802.1x (tested with XSupplicant 1.0.1) + +Enabled (but not supported) features: +- Monitor/RFMon mode +- WPA/WPA2 + +The distinction between officially supported and enabled is a reflection +on the amount of validation and interoperability testing that has been +performed on a given feature. + + +=========================== +2. Command Line Parameters +--------------------------- + +If the driver is built as a module, the following optional parameters are used +by entering them on the command line with the modprobe command using this +syntax: + + modprobe ipw2100 [<option>=<VAL1><,VAL2>...] + +For example, to disable the radio on driver loading, enter: + + modprobe ipw2100 disable=1 + +The ipw2100 driver supports the following module parameters: + +Name Value Example: +debug 0x0-0xffffffff debug=1024 +mode 0,1,2 mode=1 /* AdHoc */ +channel int channel=3 /* Only valid in AdHoc or Monitor */ +associate boolean associate=0 /* Do NOT auto associate */ +disable boolean disable=1 /* Do not power the HW */ + + +=========================== +3. Sysfs Helper Files +--------------------------- + +There are several ways to control the behavior of the driver. Many of the +general capabilities are exposed through the Wireless Tools (iwconfig). There +are a few capabilities that are exposed through entries in the Linux Sysfs. + + +----- Driver Level ------ +For the driver level files, look in /sys/bus/pci/drivers/ipw2100/ + + debug_level + + This controls the same global as the 'debug' module parameter. For + information on the various debugging levels available, run the 'dvals' + script found in the driver source directory. + + NOTE: 'debug_level' is only enabled if CONFIG_IPW2100_DEBUG is turn + on. + +----- Device Level ------ +For the device level files look in + + /sys/bus/pci/drivers/ipw2100/{PCI-ID}/ + +For example: + /sys/bus/pci/drivers/ipw2100/0000:02:01.0 + +For the device level files, see /sys/bus/pci/drivers/ipw2100: + + rf_kill + read - + 0 = RF kill not enabled (radio on) + 1 = SW based RF kill active (radio off) + 2 = HW based RF kill active (radio off) + 3 = Both HW and SW RF kill active (radio off) + write - + 0 = If SW based RF kill active, turn the radio back on + 1 = If radio is on, activate SW based RF kill + + NOTE: If you enable the SW based RF kill and then toggle the HW + based RF kill from ON -> OFF -> ON, the radio will NOT come back on + + +=========================== +4. Radio Kill Switch +--------------------------- +Most laptops provide the ability for the user to physically disable the radio. +Some vendors have implemented this as a physical switch that requires no +software to turn the radio off and on. On other laptops, however, the switch +is controlled through a button being pressed and a software driver then making +calls to turn the radio off and on. This is referred to as a "software based +RF kill switch" + +See the Sysfs helper file 'rf_kill' for determining the state of the RF switch +on your system. + + +=========================== +5. Dynamic Firmware +--------------------------- +As the firmware is licensed under a restricted use license, it can not be +included within the kernel sources. To enable the IPW2100 you will need a +firmware image to load into the wireless NIC's processors. + +You can obtain these images from <http://ipw2100.sf.net/firmware.php>. + +See INSTALL for instructions on installing the firmware. + + +=========================== +6. Power Management +--------------------------- +The IPW2100 supports the configuration of the Power Save Protocol +through a private wireless extension interface. The IPW2100 supports +the following different modes: + + off No power management. Radio is always on. + on Automatic power management + 1-5 Different levels of power management. The higher the + number the greater the power savings, but with an impact to + packet latencies. + +Power management works by powering down the radio after a certain +interval of time has passed where no packets are passed through the +radio. Once powered down, the radio remains in that state for a given +period of time. For higher power savings, the interval between last +packet processed to sleep is shorter and the sleep period is longer. + +When the radio is asleep, the access point sending data to the station +must buffer packets at the AP until the station wakes up and requests +any buffered packets. If you have an AP that does not correctly support +the PSP protocol you may experience packet loss or very poor performance +while power management is enabled. If this is the case, you will need +to try and find a firmware update for your AP, or disable power +management (via `iwconfig eth1 power off`) + +To configure the power level on the IPW2100 you use a combination of +iwconfig and iwpriv. iwconfig is used to turn power management on, off, +and set it to auto. + + iwconfig eth1 power off Disables radio power down + iwconfig eth1 power on Enables radio power management to + last set level (defaults to AUTO) + iwpriv eth1 set_power 0 Sets power level to AUTO and enables + power management if not previously + enabled. + iwpriv eth1 set_power 1-5 Set the power level as specified, + enabling power management if not + previously enabled. + +You can view the current power level setting via: + + iwpriv eth1 get_power + +It will return the current period or timeout that is configured as a string +in the form of xxxx/yyyy (z) where xxxx is the timeout interval (amount of +time after packet processing), yyyy is the period to sleep (amount of time to +wait before powering the radio and querying the access point for buffered +packets), and z is the 'power level'. If power management is turned off the +xxxx/yyyy will be replaced with 'off' -- the level reported will be the active +level if `iwconfig eth1 power on` is invoked. + + +=========================== +7. Support +--------------------------- + +For general development information and support, +go to: + + http://ipw2100.sf.net/ + +The ipw2100 1.1.0 driver and firmware can be downloaded from: + + http://support.intel.com + +For installation support on the ipw2100 1.1.0 driver on Linux kernels +2.6.8 or greater, email support is available from: + + http://supportmail.intel.com + +=========================== +8. License +--------------------------- + + 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 the GNU General Public License (version 2) 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. + + License Contact Information: + James P. Ketrenos <ipw2100-admin@linux.intel.com> + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index 0aaa12c0c09..7cd0aee18f5 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -137,6 +137,59 @@ config PCMCIA_RAYCS comment "Wireless 802.11b ISA/PCI cards support" depends on NET_RADIO && (ISA || PCI || PPC_PMAC || PCMCIA) +config IPW2100 + tristate "Intel PRO/Wireless 2100 Network Connection" + depends on NET_RADIO && PCI && IEEE80211 + select FW_LOADER + ---help--- + A driver for the Intel PRO/Wireless 2100 Network + Connection 802.11b wireless network adapter. + + See <file:Documentation/networking/README.ipw2100> for information on + the capabilities currently enabled in this driver and for tips + for debugging issues and problems. + + In order to use this driver, you will need a firmware image for it. + You can obtain the firmware from + <http://ipw2100.sf.net/>. Once you have the firmware image, you + will need to place it in /etc/firmware. + + You will also very likely need the Wireless Tools in order to + configure your card: + + <http://www.hpl.hp.com/personal/Jean_Tourrilhes/Linux/Tools.html>. + + If you want to compile the driver as a module ( = code which can be + inserted in and remvoed from the running kernel whenever you want), + say M here and read <file:Documentation/modules.txt>. The module + will be called ipw2100.ko. + +config IPW2100_PROMISC + bool "Enable promiscuous mode" + depends on IPW2100 + ---help--- + Enables promiscuous/monitor mode support for the ipw2100 driver. + With this feature compiled into the driver, you can switch to + promiscuous mode via the Wireless Tool's Monitor mode. While in this + mode, no packets can be sent. + +config IPW_DEBUG + bool "Enable full debugging output in IPW2100 module." + depends on IPW2100 + ---help--- + This option will enable debug tracing output for the IPW2100. + + This will result in the kernel module being ~60k larger. You can + control which debug output is sent to the kernel log by setting the + value in + + /sys/bus/pci/drivers/ipw2100/debug_level + + This entry will only exist if this option is enabled. + + If you are not trying to debug or develop the IPW2100 driver, you + most likely want to say N here. + config AIRO tristate "Cisco/Aironet 34X/35X/4500/4800 ISA and PCI cards" depends on NET_RADIO && ISA && (PCI || BROKEN) diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 2b87841322c..2426885c7a5 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -2,6 +2,8 @@ # Makefile for the Linux Wireless network device drivers. # +obj-$(CONFIG_IPW2100) += ipw2100.o + obj-$(CONFIG_STRIP) += strip.o obj-$(CONFIG_ARLAN) += arlan.o diff --git a/drivers/net/wireless/ipw2100.c b/drivers/net/wireless/ipw2100.c new file mode 100644 index 00000000000..d296d464946 --- /dev/null +++ b/drivers/net/wireless/ipw2100.c @@ -0,0 +1,8649 @@ +/****************************************************************************** + + 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 Operation + +Tx - Commands and Data + +Firmware 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 being +sent 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 points +to the _next_ packet to be written and is advanced when after the TBD has been +filled. + +The firmware pulls from the TBD queue at the READ index. The READ index points +to the currently being read entry, and is advanced once the firmware is +done with a packet. + +When data is sent to the firmware, the first TBD is used to indicate to the +firmware if a Command or Data is being sent. If it is Command, all of the +command information is contained within the physical address referred to by the +TBD. If it is Data, the first TBD indicates the type of data packet, number +of 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 transmit +2) 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 has +7) 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_list + +The above steps are the same for commands, only the msg_free_list/msg_pend_list +are 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 X__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 X__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 logic + +All external entry functions are locked with the priv->action_lock to ensure +that 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/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.0" + +#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-2004 Intel Corporation" + + +/* Debugging stuff */ +#ifdef CONFIG_IPW_DEBUG +#define CONFIG_IPW2100_RX_DEBUG /* Reception debugging */ +#endif + +MODULE_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_PM +static 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])"); + +u32 ipw2100_debug_level = IPW_DL_NONE; + +#ifdef CONFIG_IPW_DEBUG +static 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 X__ipw2100_tx_send_commands(struct ipw2100_priv *priv); +static void X__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 inline void read_register(struct net_device *dev, u32 reg, u32 *val) +{ + *val = readl((void *)(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 *)(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 *)(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 *)(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 *)(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 *)(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); + aligned_len = len & (~0x3); + for (i = 0; i < aligned_len; i += 4, buf += 4, aligned_addr += 4) + read_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++) + read_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA + + i, buf); +} + +static inline int ipw2100_hw_is_adapter_in_system(struct net_device *dev) +{ + return (dev->base_addr && + (readl((void *)(dev->base_addr + IPW_REG_DOA_DEBUG_AREA_START)) + == IPW_DATA_DOA_DEBUG_VALUE)); +} + +int ipw2100_get_ordinal(struct ipw2100_priv *priv, u32 ord, + void *val, u32 *len) +{ + struct ipw2100_ordinals *ordinals = &priv->ordinals; + u32 addr; + u32 field_info; + u16 field_len; + u16 field_count; + u32 total_length; + + if (ordinals->table1_addr == 0) { + IPW_DEBUG_WARNING(DRV_NAME ": attempt to use fw ordinals " + "before they have been loaded.\n"); + return -EINVAL; + } + + if (IS_ORDINAL_TABLE_ONE(ordinals, ord)) { + if (*len < IPW_ORD_TAB_1_ENTRY_SIZE) { + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + + IPW_DEBUG_WARNING(DRV_NAME + ": ordinal buffer length too small, need %d\n", + IPW_ORD_TAB_1_ENTRY_SIZE); + + return -EINVAL; + } + + read_nic_dword(priv->net_dev, ordinals->table1_addr + (ord << 2), + &addr); + read_nic_dword(priv->net_dev, addr, val); + + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + + return 0; + } + + if (IS_ORDINAL_TABLE_TWO(ordinals, ord)) { + + ord -= IPW_START_ORD_TAB_2; + + /* get the address of statistic */ + read_nic_dword(priv->net_dev, ordinals->table2_addr + (ord << 3), + &addr); + + /* get the second DW of statistics ; + * two 16-bit words - first is length, second is count */ + read_nic_dword(priv->net_dev, + ordinals->table2_addr + (ord << 3) + sizeof(u32), + &field_info); + + /* get each entry length */ + field_len = *((u16 *)&field_info); + + /* get number of entries */ + field_count = *(((u16 *)&field_info) + 1); + + /* abort if no enought memory */ + total_length = field_len * field_count; + if (total_length > *len) { + *len = total_length; + return -EINVAL; + } + + *len = total_length; + if (!total_length) + return 0; + + /* read the ordinal data from the SRAM */ + read_nic_memory(priv->net_dev, addr, total_length, val); + + return 0; + } + + IPW_DEBUG_WARNING(DRV_NAME ": ordinal %d neither in table 1 nor " + "in table 2\n", ord); + + return -EINVAL; +} + +static int ipw2100_set_ordinal(struct ipw2100_priv *priv, u32 ord, u32 *val, + u32 *len) +{ + struct ipw2100_ordinals *ordinals = &priv->ordinals; + u32 addr; + + if (IS_ORDINAL_TABLE_ONE(ordinals, ord)) { + if (*len != IPW_ORD_TAB_1_ENTRY_SIZE) { + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + IPW_DEBUG_INFO("wrong size\n"); + return -EINVAL; + } + + read_nic_dword(priv->net_dev, ordinals->table1_addr + (ord << 2), + &addr); + + write_nic_dword(priv->net_dev, addr, *val); + + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + + return 0; + } + + IPW_DEBUG_INFO("wrong table\n"); + if (IS_ORDINAL_TABLE_TWO(ordinals, ord)) + return -EINVAL; + + return -EINVAL; +} + +static char *snprint_line(char *buf, size_t count, + const u8 *data, u32 len, u32 ofs) +{ + int out, i, j, l; + char c; + + out = snprintf(buf, count, "%08X", ofs); + + for (l = 0, i = 0; i < 2; i++) { + out += snprintf(buf + out, count - out, " "); + for (j = 0; j < 8 && l < len; j++, l++) + out += snprintf(buf + out, count - out, "%02X ", + data[(i * 8 + j)]); + for (; j < 8; j++) + out += snprintf(buf + out, count - out, " "); + } + + out += snprintf(buf + out, count - out, " "); + for (l = 0, i = 0; i < 2; i++) { + out += snprintf(buf + out, count - out, " "); + for (j = 0; j < 8 && l < len; j++, l++) { + c = data[(i * 8 + j)]; + if (!isascii(c) || !isprint(c)) + c = '.'; + + out += snprintf(buf + out, count - out, "%c", c); + } + + for (; j < 8; j++) + out += snprintf(buf + out, count - out, " "); + } + + return buf; +} + +static void printk_buf(int level, const u8 *data, u32 len) +{ + char line[81]; + u32 ofs = 0; + if (!(ipw2100_debug_level & level)) + return; + + while (len) { + printk(KERN_DEBUG "%s\n", + snprint_line(line, sizeof(line), &data[ofs], + min(len, 16U), ofs)); + ofs += 16; + len -= min(len, 16U); + } +} + + + +#define MAX_RESET_BACKOFF 10 + +static inline void schedule_reset(struct ipw2100_priv *priv) +{ + unsigned long now = get_seconds(); + + /* If we haven't received a reset request within the backoff period, + * then we can reset the backoff interval so this reset occurs + * immediately */ + if (priv->reset_backoff && + (now - priv->last_reset > priv->reset_backoff)) + priv->reset_backoff = 0; + + priv->last_reset = get_seconds(); + + if (!(priv->status & STATUS_RESET_PENDING)) { + IPW_DEBUG_INFO("%s: Scheduling firmware restart (%ds).\n", + priv->net_dev->name, priv->reset_backoff); + netif_carrier_off(priv->net_dev); + netif_stop_queue(priv->net_dev); + priv->status |= STATUS_RESET_PENDING; + if (priv->reset_backoff) + queue_delayed_work(priv->workqueue, &priv->reset_work, + priv->reset_backoff * HZ); + else + queue_work(priv->workqueue, &priv->reset_work); + + if (priv->reset_backoff < MAX_RESET_BACKOFF) + priv->reset_backoff++; + + wake_up_interruptible(&priv->wait_command_queue); + } else + IPW_DEBUG_INFO("%s: Firmware restart already in progress.\n", + priv->net_dev->name); + +} + +#define HOST_COMPLETE_TIMEOUT (2 * HZ) +static int ipw2100_hw_send_command(struct ipw2100_priv *priv, + struct host_command * cmd) +{ + struct list_head *element; + struct ipw2100_tx_packet *packet; + unsigned long flags; + int err = 0; + + IPW_DEBUG_HC("Sending %s command (#%d), %d bytes\n", + command_types[cmd->host_command], cmd->host_command, + cmd->host_command_length); + printk_buf(IPW_DL_HC, (u8*)cmd->host_command_parameters, + cmd->host_command_length); + + spin_lock_irqsave(&priv->low_lock, flags); + + if (priv->fatal_error) { + IPW_DEBUG_INFO("Attempt to send command while hardware in fatal error condition.\n"); + err = -EIO; + goto fail_unlock; + } + + if (!(priv->status & STATUS_RUNNING)) { + IPW_DEBUG_INFO("Attempt to send command while hardware is not running.\n"); + err = -EIO; + goto fail_unlock; + } + + if (priv->status & STATUS_CMD_ACTIVE) { + IPW_DEBUG_INFO("Attempt to send command while another command is pending.\n"); + err = -EBUSY; + goto fail_unlock; + } + + if (list_empty(&priv->msg_free_list)) { + IPW_DEBUG_INFO("no available msg buffers\n"); + goto fail_unlock; + } + + priv->status |= STATUS_CMD_ACTIVE; + priv->messages_sent++; + + element = priv->msg_free_list.next; + + packet = list_entry(element, struct ipw2100_tx_packet, list); + packet->jiffy_start = jiffies; + + /* initialize the firmware command packet */ + packet->info.c_struct.cmd->host_command_reg = cmd->host_command; + packet->info.c_struct.cmd->host_command_reg1 = cmd->host_command1; |