Copyright(c) 2003 - 2006 Intel Corporation. All rights reserved.
802.11 status code portion of this file from ethereal-0.10.6: Copyright 2000, Axis Communications AB Ethereal - Network traffic analyzer By Gerald Combs <gerald@ethereal.com> Copyright 1998 Gerald Combs
Contact Information: Intel Linux Wireless <ilw@linux.intel.com> Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
/* Ugly macro to convert literal channel numbers into their mhz equivalents * There are certianly some conditions that will break this (like feeding it '30')
* but they shouldn't arise since nothing talks on channel 30. */ #define ieee80211chan2mhz(x) \
(((x) <= 14) ? \
(((x) == 14) ? 2484 : ((x) * 5) + 2407) : \
((x) + 1000) * 5)
/* Read the first dword (or portion) byte by byte */ if (unlikely(dif_len)) {
_ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); /* Start reading at aligned_addr + dif_len */ for (i = dif_len; ((i < 4) && (num > 0)); i++, num--)
*buf++ = _ipw_read8(priv, IPW_INDIRECT_DATA + i);
aligned_addr += 4;
}
/* Read all of the middle dwords as dwords, with auto-increment */
_ipw_write32(priv, IPW_AUTOINC_ADDR, aligned_addr); for (; num >= 4; buf += 4, aligned_addr += 4, num -= 4)
*(u32 *) buf = _ipw_read32(priv, IPW_AUTOINC_DATA);
/* Read the last dword (or portion) byte by byte */ if (unlikely(num)) {
_ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); for (i = 0; num > 0; i++, num--)
*buf++ = ipw_read8(priv, IPW_INDIRECT_DATA + i);
}
}
/* General purpose, no alignment requirement, iterative (multi-byte) write, */ /* for area above 1st 4K of SRAM/reg space */ staticvoid _ipw_write_indirect(struct ipw_priv *priv, u32 addr, u8 * buf, int num)
{
u32 aligned_addr = addr & IPW_INDIRECT_ADDR_MASK; /* dword align */
u32 dif_len = addr - aligned_addr;
u32 i;
/* Write the first dword (or portion) byte by byte */ if (unlikely(dif_len)) {
_ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); /* Start writing at aligned_addr + dif_len */ for (i = dif_len; ((i < 4) && (num > 0)); i++, num--, buf++)
_ipw_write8(priv, IPW_INDIRECT_DATA + i, *buf);
aligned_addr += 4;
}
/* Write all of the middle dwords as dwords, with auto-increment */
_ipw_write32(priv, IPW_AUTOINC_ADDR, aligned_addr); for (; num >= 4; buf += 4, aligned_addr += 4, num -= 4)
_ipw_write32(priv, IPW_AUTOINC_DATA, *(u32 *) buf);
/* Write the last dword (or portion) byte by byte */ if (unlikely(num)) {
_ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); for (i = 0; num > 0; i++, num--, buf++)
_ipw_write8(priv, IPW_INDIRECT_DATA + i, *buf);
}
}
/* General purpose, no alignment requirement, iterative (multi-byte) write, */ /* for 1st 4K of SRAM/regs space */ staticvoid ipw_write_direct(struct ipw_priv *priv, u32 addr, void *buf, int num)
{
memcpy_toio((priv->hw_base + addr), buf, num);
}
/* Set bit(s) in low 4K of SRAM/regs */ staticinlinevoid ipw_set_bit(struct ipw_priv *priv, u32 reg, u32 mask)
{
ipw_write32(priv, reg, ipw_read32(priv, reg) | mask);
}
staticchar *ipw_error_desc(u32 val)
{ switch (val) { case IPW_FW_ERROR_OK: return"ERROR_OK"; case IPW_FW_ERROR_FAIL: return"ERROR_FAIL"; case IPW_FW_ERROR_MEMORY_UNDERFLOW: return"MEMORY_UNDERFLOW"; case IPW_FW_ERROR_MEMORY_OVERFLOW: return"MEMORY_OVERFLOW"; case IPW_FW_ERROR_BAD_PARAM: return"BAD_PARAM"; case IPW_FW_ERROR_BAD_CHECKSUM: return"BAD_CHECKSUM"; case IPW_FW_ERROR_NMI_INTERRUPT: return"NMI_INTERRUPT"; case IPW_FW_ERROR_BAD_DATABASE: return"BAD_DATABASE"; case IPW_FW_ERROR_ALLOC_FAIL: return"ALLOC_FAIL"; case IPW_FW_ERROR_DMA_UNDERRUN: return"DMA_UNDERRUN"; case IPW_FW_ERROR_DMA_STATUS: return"DMA_STATUS"; case IPW_FW_ERROR_DINO_ERROR: return"DINO_ERROR"; case IPW_FW_ERROR_EEPROM_ERROR: return"EEPROM_ERROR"; case IPW_FW_ERROR_SYSASSERT: return"SYSASSERT"; case IPW_FW_ERROR_FATAL_ERROR: return"FATAL_ERROR"; default: return"UNKNOWN_ERROR";
}
}
/* verify device ordinal tables have been initialized */ if (!priv->table0_addr || !priv->table1_addr || !priv->table2_addr) {
IPW_DEBUG_ORD("Access ordinals before initialization\n"); return -EINVAL;
}
switch (IPW_ORD_TABLE_ID_MASK & ord) { case IPW_ORD_TABLE_0_MASK: /* * TABLE 0: Direct access to a table of 32 bit values * * This is a very simple table with the data directly * read from the table
*/
/* remove the table id from the ordinal */
ord &= IPW_ORD_TABLE_VALUE_MASK;
/* boundary check */ if (ord > priv->table0_len) {
IPW_DEBUG_ORD("ordinal value (%i) longer then " "max (%i)\n", ord, priv->table0_len); return -EINVAL;
}
/* verify we have enough room to store the value */ if (*len < sizeof(u32)) {
IPW_DEBUG_ORD("ordinal buffer length too small, " "need %zd\n", sizeof(u32)); return -EINVAL;
}
case IPW_ORD_TABLE_1_MASK: /* * TABLE 1: Indirect access to a table of 32 bit values * * This is a fairly large table of u32 values each * representing starting addr for the data (which is * also a u32)
*/
/* remove the table id from the ordinal */
ord &= IPW_ORD_TABLE_VALUE_MASK;
/* boundary check */ if (ord > priv->table1_len) {
IPW_DEBUG_ORD("ordinal value too long\n"); return -EINVAL;
}
/* verify we have enough room to store the value */ if (*len < sizeof(u32)) {
IPW_DEBUG_ORD("ordinal buffer length too small, " "need %zd\n", sizeof(u32)); return -EINVAL;
}
case IPW_ORD_TABLE_2_MASK: /* * TABLE 2: Indirect access to a table of variable sized values * * This table consist of six values, each containing * - dword containing the starting offset of the data * - dword containing the lengh in the first 16bits * and the count in the second 16bits
*/
/* remove the table id from the ordinal */
ord &= IPW_ORD_TABLE_VALUE_MASK;
/* boundary check */ if (ord > priv->table2_len) {
IPW_DEBUG_ORD("ordinal value too long\n"); return -EINVAL;
}
/* get the address of statistic */
addr = ipw_read_reg32(priv, priv->table2_addr + (ord << 3));
/* get the second DW of statistics ;
* two 16-bit words - first is length, second is count */
field_info =
ipw_read_reg32(priv,
priv->table2_addr + (ord << 3) + sizeof(u32));
/* get each entry length */
field_len = *((u16 *) & field_info);
/* get number of entries */
field_count = *(((u16 *) & field_info) + 1);
/* abort if not enough memory */
total_len = field_len * field_count; if (total_len > *len) {
*len = total_len; return -EINVAL;
}
/* * LED behavior: * - On radio ON, turn on any LEDs that require to be on during start * - On initialization, start unassociated blink * - On association, disable unassociated blink * - On disassociation, start unassociated blink * - On radio OFF, turn off any LEDs started during radio on *
*/ #define LD_TIME_LINK_ON msecs_to_jiffies(300) #define LD_TIME_LINK_OFF msecs_to_jiffies(2700) #define LD_TIME_ACT_ON msecs_to_jiffies(250)
/* If configured to not use LEDs, or nic_type is 1,
* then we don't toggle a LINK led */ if (priv->config & CFG_NO_LED || priv->nic_type == EEPROM_NIC_TYPE_1) return;
spin_lock_irqsave(&priv->lock, flags);
if (!(priv->status & STATUS_RF_KILL_MASK) &&
!(priv->status & STATUS_LED_LINK_ON)) {
IPW_DEBUG_LED("Link LED On\n");
led = ipw_read_reg32(priv, IPW_EVENT_REG);
led |= priv->led_association_on;
/* If we aren't associated, schedule turning the LED off */ if (!(priv->status & STATUS_ASSOCIATED))
schedule_delayed_work(&priv->led_link_off,
LD_TIME_LINK_ON);
}
/* If configured not to use LEDs, or nic type is 1,
* then we don't goggle the LINK led. */ if (priv->config & CFG_NO_LED || priv->nic_type == EEPROM_NIC_TYPE_1) return;
spin_lock_irqsave(&priv->lock, flags);
if (priv->status & STATUS_LED_LINK_ON) {
led = ipw_read_reg32(priv, IPW_EVENT_REG);
led &= priv->led_association_off;
led = ipw_register_toggle(led);
/* If we aren't associated and the radio is on, schedule
* turning the LED on (blink while unassociated) */ if (!(priv->status & STATUS_RF_KILL_MASK) &&
!(priv->status & STATUS_ASSOCIATED))
schedule_delayed_work(&priv->led_link_on,
LD_TIME_LINK_OFF);
cancel_delayed_work(&priv->led_act_off);
schedule_delayed_work(&priv->led_act_off, LD_TIME_ACT_ON);
} else { /* Reschedule LED off for full time period */
cancel_delayed_work(&priv->led_act_off);
schedule_delayed_work(&priv->led_act_off, LD_TIME_ACT_ON);
}
}
/* Only nic type 1 supports mode LEDs */ if (priv->config & CFG_NO_LED ||
priv->nic_type != EEPROM_NIC_TYPE_1 || !priv->assoc_network) return;
spin_lock_irqsave(&priv->lock, flags);
led = ipw_read_reg32(priv, IPW_EVENT_REG); if (priv->assoc_network->mode == IEEE_A) {
led |= priv->led_ofdm_on;
led &= priv->led_association_off;
IPW_DEBUG_LED("Mode LED On: 802.11a\n");
} elseif (priv->assoc_network->mode == IEEE_G) {
led |= priv->led_ofdm_on;
led |= priv->led_association_on;
IPW_DEBUG_LED("Mode LED On: 802.11g\n");
} else {
led &= priv->led_ofdm_off;
led |= priv->led_association_on;
IPW_DEBUG_LED("Mode LED On: 802.11b\n");
}
/* Set the default PINs for the OFDM leds */
priv->led_ofdm_on = IPW_OFDM_LED;
priv->led_ofdm_off = ~(IPW_OFDM_LED);
switch (priv->nic_type) { case EEPROM_NIC_TYPE_1: /* In this NIC type, the LEDs are reversed.... */
priv->led_activity_on = IPW_ASSOCIATED_LED;
priv->led_activity_off = ~(IPW_ASSOCIATED_LED);
priv->led_association_on = IPW_ACTIVITY_LED;
priv->led_association_off = ~(IPW_ACTIVITY_LED);
if (!(priv->config & CFG_NO_LED))
ipw_led_band_on(priv);
/* And we don't blink link LEDs for this nic, so
* just return here */ return;
case EEPROM_NIC_TYPE_3: case EEPROM_NIC_TYPE_2: case EEPROM_NIC_TYPE_4: case EEPROM_NIC_TYPE_0: break;
default:
IPW_DEBUG_INFO("Unknown NIC type from EEPROM: %d\n",
priv->nic_type);
priv->nic_type = EEPROM_NIC_TYPE_0; break;
}
if (!(priv->config & CFG_NO_LED)) { if (priv->status & STATUS_ASSOCIATED)
ipw_led_link_on(priv); else
ipw_led_link_off(priv);
}
}
/* * The following adds a new attribute to the sysfs representation * of this device driver (i.e. a new file in /sys/bus/pci/drivers/ipw/) * used for controlling the debug level. * * See the level definitions in ipw for details.
*/ static ssize_t debug_level_show(struct device_driver *d, char *buf)
{ return sprintf(buf, "0x%08X\n", ipw_debug_level);
}
IPW_DEBUG_RF_KILL("Manual SW RF Kill set to: RADIO %s\n",
disable_radio ? "OFF" : "ON");
if (disable_radio) {
priv->status |= STATUS_RF_KILL_SW;
cancel_delayed_work(&priv->request_scan);
cancel_delayed_work(&priv->request_direct_scan);
cancel_delayed_work(&priv->request_passive_scan);
cancel_delayed_work(&priv->scan_event);
schedule_work(&priv->down);
} else {
priv->status &= ~STATUS_RF_KILL_SW; if (rf_kill_active(priv)) {
IPW_DEBUG_RF_KILL("Can not turn radio back on - " "disabled by HW switch\n"); /* Make sure the RF_KILL check timer is running */
cancel_delayed_work(&priv->rf_kill);
schedule_delayed_work(&priv->rf_kill,
round_jiffies_relative(2 * HZ));
} else
schedule_work(&priv->up);
}
/* list of space separated channels to scan, optionally ending with 0 */ while ((channel = simple_strtol(p, NULL, 0))) { if (pos == MAX_SPEED_SCAN - 1) {
priv->speed_scan[pos] = 0; break;
}
if (libipw_is_valid_channel(priv->ieee, channel))
priv->speed_scan[pos++] = channel; else
IPW_WARNING("Skipping invalid channel request: %d\n",
channel);
p = strchr(p, ' '); if (!p) break; while (*p == ' ' || *p == '\t')
p++;
}
/* XXX: If hardware encryption is for WPA/WPA2,
* we have to notify the supplicant. */ if (priv->ieee->sec.encrypt) {
priv->status &= ~STATUS_ASSOCIATED;
notify_wx_assoc_event(priv);
}
/* Keep the restart process from trying to send host
* commands by clearing the INIT status bit */
priv->status &= ~STATUS_INIT;
/* Cancel currently queued command. */
priv->status &= ~STATUS_HCMD_ACTIVE;
wake_up_interruptible(&priv->wait_command_queue);
now = jiffies;
end = now + HOST_COMPLETE_TIMEOUT;
again:
rc = wait_event_interruptible_timeout(priv->wait_command_queue,
!(priv->
status & STATUS_HCMD_ACTIVE),
end - now); if (rc < 0) {
now = jiffies; if (time_before(now, end)) goto again;
rc = 0;
}
if (!priv) {
IPW_ERROR("Invalid args\n"); return -1;
}
/* If on battery, set to 3, if AC set to CAM, else user
* level */ switch (mode) { case IPW_POWER_BATTERY:
param = cpu_to_le32(IPW_POWER_INDEX_3); break; case IPW_POWER_AC:
param = cpu_to_le32(IPW_POWER_MODE_CAM); break; default:
param = cpu_to_le32(mode); break;
}
/* * The IPW device contains a Microwire compatible EEPROM that stores * various data like the MAC address. Usually the firmware has exclusive * access to the eeprom, but during device initialization (before the * device driver has sent the HostComplete command to the firmware) the * device driver has read access to the EEPROM by way of indirect addressing * through a couple of memory mapped registers. * * The following is a simplified implementation for pulling data out of the * eeprom, along with some helper functions to find information in * the per device private data's copy of the eeprom. * * NOTE: To better understand how these functions work (i.e what is a chip * select and why do have to keep driving the eeprom clock?), read * just about any data sheet for a Microwire compatible EEPROM.
*/
/* write a 32 bit value into the indirect accessor register */ staticinlinevoid eeprom_write_reg(struct ipw_priv *p, u32 data)
{
ipw_write_reg32(p, FW_MEM_REG_EEPROM_ACCESS, data);
/* the eeprom requires some time to complete the operation */
udelay(p->eeprom_delay);
}
/* push a single bit down to the eeprom */ staticinlinevoid eeprom_write_bit(struct ipw_priv *p, u8 bit)
{ int d = (bit ? EEPROM_BIT_DI : 0);
eeprom_write_reg(p, EEPROM_BIT_CS | d);
eeprom_write_reg(p, EEPROM_BIT_CS | d | EEPROM_BIT_SK);
}
/* push an opcode followed by an address down to the eeprom */ staticvoid eeprom_op(struct ipw_priv *priv, u8 op, u8 addr)
{ int i;
eeprom_cs(priv);
eeprom_write_bit(priv, 1);
eeprom_write_bit(priv, op & 2);
eeprom_write_bit(priv, op & 1); for (i = 7; i >= 0; i--) {
eeprom_write_bit(priv, addr & (1 << i));
}
}
/* pull 16 bits off the eeprom, one bit at a time */ static u16 eeprom_read_u16(struct ipw_priv *priv, u8 addr)
{ int i;
u16 r = 0;
/* Send dummy bit */
eeprom_write_reg(priv, EEPROM_BIT_CS);
/* Read the byte off the eeprom one bit at a time */ for (i = 0; i < 16; i++) {
u32 data = 0;
eeprom_write_reg(priv, EEPROM_BIT_CS | EEPROM_BIT_SK);
eeprom_write_reg(priv, EEPROM_BIT_CS);
data = ipw_read_reg32(priv, FW_MEM_REG_EEPROM_ACCESS);
r = (r << 1) | ((data & EEPROM_BIT_DO) ? 1 : 0);
}
/* Send another dummy bit */
eeprom_write_reg(priv, 0);
eeprom_disable_cs(priv);
return r;
}
/* helper function for pulling the mac address out of the private */ /* data's copy of the eeprom data */ staticvoid eeprom_parse_mac(struct ipw_priv *priv, u8 * mac)
{
memcpy(mac, &priv->eeprom[EEPROM_MAC_ADDRESS], ETH_ALEN);
}
/* read entire contents of eeprom into private buffer */ for (i = 0; i < 128; i++)
eeprom[i] = cpu_to_le16(eeprom_read_u16(priv, (u8) i));
IPW_DEBUG_TRACE("<<\n");
}
/* * Either the device driver (i.e. the host) or the firmware can * load eeprom data into the designated region in SRAM. If neither * happens then the FW will shutdown with a fatal error. * * In order to signal the FW to load the EEPROM, the EEPROM_LOAD_DISABLE * bit needs region of shared SRAM needs to be non-zero.
*/ staticvoid ipw_eeprom_init_sram(struct ipw_priv *priv)
{ int i;
IPW_DEBUG_TRACE(">>\n");
/* If the data looks correct, then copy it to our private copy. Otherwise let the firmware know to perform the operation on its own.
*/ if (priv->eeprom[EEPROM_VERSION] != 0) {
IPW_DEBUG_INFO("Writing EEPROM data into SRAM\n");
/* write the eeprom data to sram */ for (i = 0; i < IPW_EEPROM_IMAGE_SIZE; i++)
ipw_write8(priv, IPW_EEPROM_DATA + i, priv->eeprom[i]);
/* Do not load eeprom data on fatal error or suspend */
ipw_write32(priv, IPW_EEPROM_LOAD_DISABLE, 0);
} else {
IPW_DEBUG_INFO("Enabling FW initialization of SRAM\n");
/* Load eeprom data on fatal error or suspend */
ipw_write32(priv, IPW_EEPROM_LOAD_DISABLE, 1);
}
staticint ipw_fw_dma_enable(struct ipw_priv *priv)
{ /* start dma engine but no transfers yet */
IPW_DEBUG_FW(">> :\n");
/* Start the dma */
ipw_fw_dma_reset_command_blocks(priv);
/* Write CB base address */
ipw_write_reg32(priv, IPW_DMA_I_CB_BASE, IPW_SHARED_SRAM_DMA_CONTROL);
IPW_DEBUG_FW("<< :\n"); return 0;
}
staticvoid ipw_fw_dma_abort(struct ipw_priv *priv)
{
u32 control = 0;
IPW_DEBUG_FW(">> :\n");
/* set the Stop and Abort bit */
control = DMA_CONTROL_SMALL_CB_CONST_VALUE | DMA_CB_STOP_AND_ABORT;
ipw_write_reg32(priv, IPW_DMA_I_DMA_CONTROL, control);
priv->sram_desc.last_cb_index = 0;
/* Read the DMA Controlor register */
register_value = ipw_read_reg32(priv, IPW_DMA_I_DMA_CONTROL);
IPW_DEBUG_FW_INFO("IPW_DMA_I_DMA_CONTROL is 0x%x\n", register_value);
/* Print the CB values */
cb_fields_address = address;
register_value = ipw_read_reg32(priv, cb_fields_address);
IPW_DEBUG_FW_INFO("Current CB Control Field is 0x%x\n", register_value);
cb_fields_address += sizeof(u32);
register_value = ipw_read_reg32(priv, cb_fields_address);
IPW_DEBUG_FW_INFO("Current CB Source Field is 0x%x\n", register_value);
cb_fields_address += sizeof(u32);
register_value = ipw_read_reg32(priv, cb_fields_address);
IPW_DEBUG_FW_INFO("Current CB Destination Field is 0x%x\n",
register_value);
cb_fields_address += sizeof(u32);
register_value = ipw_read_reg32(priv, cb_fields_address);
IPW_DEBUG_FW_INFO("Current CB Status Field is 0x%x\n", register_value);
for (i = 0; i < nr; i++) {
size = min_t(u32, len - i * CB_MAX_LENGTH, CB_MAX_LENGTH);
ret = ipw_fw_dma_add_command_block(priv, src_address[i],
dest_address +
i * CB_MAX_LENGTH, size,
0, 0); if (ret) {
IPW_DEBUG_FW_INFO(": Failed\n"); return -1;
} else
IPW_DEBUG_FW_INFO(": Added new cb\n");
}
/* timeout in msec, attempted in 10-msec quanta */ staticint ipw_poll_bit(struct ipw_priv *priv, u32 addr, u32 mask, int timeout)
{ int i = 0;
do { if ((ipw_read32(priv, addr) & mask) == mask) return i;
mdelay(10);
i += 10;
} while (i < timeout);
return -ETIME;
}
/* These functions load the firmware and micro code for the operation of * the ipw hardware. It assumes the buffer has all the bits for the * image and the caller is handling the memory allocation and clean up.
*/
staticint ipw_stop_master(struct ipw_priv *priv)
{ int rc;
/* write ucode */ /* * @bug * Do NOT set indirect address register once and then * store data to indirect data register in the loop. * It seems very reasonable, but in this case DINO do not * accept ucode. It is essential to set address each time.
*/ /* load new ipw uCode */ for (i = 0; i < len / 2; i++)
ipw_write_reg16(priv, IPW_BASEBAND_CONTROL_STORE,
le16_to_cpu(image[i]));
/* this is where the igx / win driver deveates from the VAP driver. */
/* wait for alive response */ for (i = 0; i < 100; i++) { /* poll for incoming data */
cr = ipw_read_reg8(priv, IPW_BASEBAND_CONTROL_STATUS); if (cr & DINO_RXFIFO_DATA) break;
mdelay(1);
}
if (cr & DINO_RXFIFO_DATA) { /* alive_command_responce size is NOT multiple of 4 */
__le32 response_buffer[(sizeof(priv->dino_alive) + 3) / 4];
nr = (chunk_len + CB_MAX_LENGTH - 1) / CB_MAX_LENGTH; for (i = 0; i < nr; i++) {
virts[total_nr] = dma_pool_alloc(pool, GFP_KERNEL,
&phys[total_nr]); if (!virts[total_nr]) {
ret = -ENOMEM; goto out;
}
size = min_t(u32, chunk_len - i * CB_MAX_LENGTH,
CB_MAX_LENGTH);
memcpy(virts[total_nr], start, size);
start += size;
total_nr++; /* We don't support fw chunk larger than 64*8K */
BUG_ON(total_nr > CB_NUMBER_OF_ELEMENTS_SMALL);
}
/* build DMA packet and queue up for sending */ /* dma to chunk->address, the chunk->length bytes from data +
* offeset*/ /* Dma loading */
ret = ipw_fw_dma_add_buffer(priv, &phys[total_nr - nr],
nr, le32_to_cpu(chunk->address),
chunk_len); if (ret) {
IPW_DEBUG_INFO("dmaAddBuffer Failed\n"); goto out;
}
offset += chunk_len;
} while (offset < len);
/* Run the DMA and wait for the answer */
ret = ipw_fw_dma_kick(priv); if (ret) {
IPW_ERROR("dmaKick Failed\n"); goto out;
}
ret = ipw_fw_dma_wait(priv); if (ret) {
IPW_ERROR("dmaWaitSync Failed\n"); goto out;
}
out: for (i = 0; i < total_nr; i++)
dma_pool_free(pool, virts[i], phys[i]);
/* enable power management */
ipw_set_bit(priv, IPW_GP_CNTRL_RW,
IPW_GP_CNTRL_BIT_HOST_ALLOWS_STANDBY);
IPW_DEBUG_TRACE("<<\n");
}
staticint ipw_init_nic(struct ipw_priv *priv)
{ int rc;
IPW_DEBUG_TRACE(">>\n"); /* reset */ /*prvHwInitNic */ /* set "initialization complete" bit to move adapter to D0 state */
ipw_set_bit(priv, IPW_GP_CNTRL_RW, IPW_GP_CNTRL_BIT_INIT_DONE);
/* set "initialization complete" bit to move adapter to D0 state */
ipw_set_bit(priv, IPW_GP_CNTRL_RW, IPW_GP_CNTRL_BIT_INIT_DONE);
IPW_DEBUG_TRACE(">>\n"); return 0;
}
/* Call this function from process context, it will sleep in request_firmware. * Probe is an ok place to call this from.
*/ staticint ipw_reset_nic(struct ipw_priv *priv)
{ int rc = 0; unsignedlong flags;
/* ask firmware_class module to get the boot firmware off disk */
rc = request_firmware(raw, name, &priv->pci_dev->dev); if (rc < 0) {
IPW_ERROR("%s request_firmware failed: Reason %d\n", name, rc); return rc;
}
if ((*raw)->size < sizeof(*fw)) {
IPW_ERROR("%s is too small (%zd)\n", name, (*raw)->size); return -EINVAL;
}
fw = (void *)(*raw)->data;
if ((*raw)->size < sizeof(*fw) + le32_to_cpu(fw->boot_size) +
le32_to_cpu(fw->ucode_size) + le32_to_cpu(fw->fw_size)) {
IPW_ERROR("%s is too small or corrupt (%zd)\n",
name, (*raw)->size); return -EINVAL;
}
/* Fill the rx_used queue with _all_ of the Rx buffers */ for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++) { /* In the reset function, these buffers may have been allocated
* to an SKB, so we need to unmap and free potential storage */ if (rxq->pool[i].skb != NULL) {
dma_unmap_single(&priv->pci_dev->dev,
rxq->pool[i].dma_addr,
IPW_RX_BUF_SIZE, DMA_FROM_DEVICE);
dev_kfree_skb_irq(rxq->pool[i].skb);
rxq->pool[i].skb = NULL;
}
list_add_tail(&rxq->pool[i].list, &rxq->rx_used);
}
/* Set us so that we have processed and used all buffers, but have
* not restocked the Rx queue with fresh buffers */
rxq->read = rxq->write = 0;
rxq->free_count = 0;
spin_unlock_irqrestore(&rxq->lock, flags);
}
switch (priv->ieee->iw_mode) { case IW_MODE_ADHOC:
name = "ipw2200-ibss.fw"; break; #ifdef CONFIG_IPW2200_MONITOR case IW_MODE_MONITOR:
name = "ipw2200-sniffer.fw"; break; #endif case IW_MODE_INFRA:
name = "ipw2200-bss.fw"; break;
}
/* * DMA services * * Theory of operation * * A queue is a circular buffers with 'Read' and 'Write' pointers. * 2 empty entries always kept in the buffer to protect from overflow. * * For Tx queue, there are low mark and high mark limits. If, after queuing * the packet for Tx, free space become < low mark, Tx queue stopped. When * reclaiming packets (on 'tx done IRQ), if free space become > high mark, * Tx queue resumed. * * The IPW operates with six queues, one receive queue in the device's * sram, one transmit queue for sending commands to the device firmware, * and four transmit queues for data. * * The four transmit queues allow for performing quality of service (qos) * transmissions as per the 802.11 protocol. Currently Linux does not * provide a mechanism to the user for utilizing prioritized queues, so * we only utilize the first data transmit queue (queue1).
*/
/* * Driver allocates buffers of this size for Rx
*/
/* * ipw_rx_queue_space - Return number of free slots available in queue.
*/ staticint ipw_rx_queue_space(conststruct ipw_rx_queue *q)
{ int s = q->read - q->write; if (s <= 0)
s += RX_QUEUE_SIZE; /* keep some buffer to not confuse full and empty queue */
s -= 2; if (s < 0)
s = 0; return s;
}
staticinlineint ipw_tx_queue_space(conststruct clx2_queue *q)
{ int s = q->last_used - q->first_empty; if (s <= 0)
s += q->n_bd;
s -= 2; /* keep some reserve to not confuse empty and full situations */ if (s < 0)
s = 0; return s;
}
/* * Initialize common DMA queue structure * * @param q queue to init * @param count Number of BD's to allocate. Should be power of 2 * @param read_register Address for 'read' register * (not offset within BAR, full address) * @param write_register Address for 'write' register * (not offset within BAR, full address) * @param base_register Address for 'base' register * (not offset within BAR, full address) * @param size Address for 'size' register * (not offset within BAR, full address)
*/ staticvoid ipw_queue_init(struct ipw_priv *priv, struct clx2_queue *q, int count, u32 read, u32 write, u32 base, u32 size)
{
q->n_bd = count;
/* * Free one TFD, those at index [txq->q.last_used]. * Do NOT advance any indexes * * @param dev * @param txq
*/ staticvoid ipw_queue_tx_free_tfd(struct ipw_priv *priv, struct clx2_tx_queue *txq)
{ struct tfd_frame *bd = &txq->bd[txq->q.last_used]; struct pci_dev *dev = priv->pci_dev; int i;
/* classify bd */ if (bd->control_flags.message_type == TX_HOST_COMMAND_TYPE) /* nothing to cleanup after for host commands */ return;
/* sanity check */ if (le32_to_cpu(bd->u.data.num_chunks) > NUM_TFD_CHUNKS) {
IPW_ERROR("Too many chunks: %i\n",
le32_to_cpu(bd->u.data.num_chunks)); /* @todo issue fatal error, it is quite serious situation */ return;
}
/* unmap chunks if any */ for (i = 0; i < le32_to_cpu(bd->u.data.num_chunks); i++) {
dma_unmap_single(&dev->dev,
le32_to_cpu(bd->u.data.chunk_ptr[i]),
le16_to_cpu(bd->u.data.chunk_len[i]),
DMA_TO_DEVICE); if (txq->txb[txq->q.last_used]) {
libipw_txb_free(txq->txb[txq->q.last_used]);
txq->txb[txq->q.last_used] = NULL;
}
}
}
/* * Deallocate DMA queue. * * Empty queue by removing and destroying all BD's. * Free all buffers. * * @param dev * @param q
*/ staticvoid ipw_queue_tx_free(struct ipw_priv *priv, struct clx2_tx_queue *txq)
{ struct clx2_queue *q = &txq->q; struct pci_dev *dev = priv->pci_dev;
if (q->n_bd == 0) return;
/* first, empty all BD's */ for (; q->first_empty != q->last_used;
q->last_used = ipw_queue_inc_wrap(q->last_used, q->n_bd)) {
ipw_queue_tx_free_tfd(priv, txq);
}
for (i = 0; i < priv->num_stations; i++) { if (ether_addr_equal(priv->stations[i], bssid)) { /* Another node is active in network */
priv->missed_adhoc_beacons = 0; if (!(priv->config & CFG_STATIC_CHANNEL)) /* when other nodes drop out, we drop out */
priv->config &= ~CFG_ADHOC_PERSIST;
return i;
}
}
if (i == MAX_STATIONS) return IPW_INVALID_STATION;
staticconststruct ipw_status_code ipw_status_codes[] = {
{0x00, "Successful"},
{0x01, "Unspecified failure"},
{0x0A, "Cannot support all requested capabilities in the " "Capability information field"},
{0x0B, "Reassociation denied due to inability to confirm that " "association exists"},
{0x0C, "Association denied due to reason outside the scope of this " "standard"},
{0x0D, "Responding station does not support the specified authentication " "algorithm"},
{0x0E, "Received an Authentication frame with authentication sequence " "transaction sequence number out of expected sequence"},
{0x0F, "Authentication rejected because of challenge failure"},
{0x10, "Authentication rejected due to timeout waiting for next " "frame in sequence"},
{0x11, "Association denied because AP is unable to handle additional " "associated stations"},
{0x12, "Association denied due to requesting station not supporting all " "of the datarates in the BSSBasicServiceSet Parameter"},
{0x13, "Association denied due to requesting station not supporting " "short preamble operation"},
{0x14, "Association denied due to requesting station not supporting " "PBCC encoding"},
{0x15, "Association denied due to requesting station not supporting " "channel agility"},
{0x19, "Association denied due to requesting station not supporting " "short slot operation"},
{0x1A, "Association denied due to requesting station not supporting " "DSSS-OFDM operation"},
{0x28, "Invalid Information Element"},
{0x29, "Group Cipher is not valid"},
{0x2A, "Pairwise Cipher is not valid"},
{0x2B, "AKMP is not valid"},
{0x2C, "Unsupported RSN IE version"},
{0x2D, "Invalid RSN IE Capabilities"},
{0x2E, "Cipher suite is rejected per security policy"},
};
staticconstchar *ipw_get_status_code(u16 status)
{ int i; for (i = 0; i < ARRAY_SIZE(ipw_status_codes); i++) if (ipw_status_codes[i].status == (status & 0xff)) return ipw_status_codes[i].reason; return"Unknown status value.";
}
staticinlinevoid average_init(struct average *avg)
{
memset(avg, 0, sizeof(*avg));
}
/* Firmware managed, reset only when NIC is restarted, so we have to
* normalize on the current value */
ipw_get_ordinal(priv, IPW_ORD_STAT_RX_ERR_CRC,
&priv->last_rx_err, &len);
ipw_get_ordinal(priv, IPW_ORD_STAT_TX_FAILURE,
&priv->last_tx_failures, &len);
/* Driver managed, reset with each association */
priv->missed_adhoc_beacons = 0;
priv->missed_beacons = 0;
priv->tx_packets = 0;
priv->rx_packets = 0;
}
static u32 ipw_get_max_rate(struct ipw_priv *priv)
{
u32 i = 0x80000000;
u32 mask = priv->rates_mask; /* If currently associated in B mode, restrict the maximum
* rate match to B rates */ if (priv->assoc_request.ieee_mode == IPW_B_MODE)
mask &= LIBIPW_CCK_RATES_MASK;
/* TODO: Verify that the rate is supported by the current rates
* list. */
while (i && !(mask & i))
i >>= 1; switch (i) { case LIBIPW_CCK_RATE_1MB_MASK: return 1000000; case LIBIPW_CCK_RATE_2MB_MASK: return 2000000; case LIBIPW_CCK_RATE_5MB_MASK: return 5500000; case LIBIPW_OFDM_RATE_6MB_MASK: return 6000000; case LIBIPW_OFDM_RATE_9MB_MASK: return 9000000; case LIBIPW_CCK_RATE_11MB_MASK: return 11000000; case LIBIPW_OFDM_RATE_12MB_MASK: return 12000000; case LIBIPW_OFDM_RATE_18MB_MASK: return 18000000; case LIBIPW_OFDM_RATE_24MB_MASK: return 24000000; case LIBIPW_OFDM_RATE_36MB_MASK: return 36000000; case LIBIPW_OFDM_RATE_48MB_MASK: return 48000000; case LIBIPW_OFDM_RATE_54MB_MASK: return 54000000;
}
if (priv->ieee->mode == IEEE_B) return 11000000; else return 54000000;
}
static u32 ipw_get_current_rate(struct ipw_priv *priv)
{
u32 rate, len = sizeof(rate); int err;
if (!(priv->status & STATUS_ASSOCIATED)) return 0;
switch (rate) { case IPW_TX_RATE_1MB: return 1000000; case IPW_TX_RATE_2MB: return 2000000; case IPW_TX_RATE_5MB: return 5500000; case IPW_TX_RATE_6MB: return 6000000; case IPW_TX_RATE_9MB: return 9000000; case IPW_TX_RATE_11MB: return 11000000; case IPW_TX_RATE_12MB: return 12000000; case IPW_TX_RATE_18MB: return 18000000; case IPW_TX_RATE_24MB: return 24000000; case IPW_TX_RATE_36MB: return 36000000; case IPW_TX_RATE_48MB: return 48000000; case IPW_TX_RATE_54MB: return 54000000;
}
/* Missed beacon behavior: * 1st missed -> roaming_threshold, just wait, don't do any scan/roam. * roaming_threshold -> disassociate_threshold, scan and roam for better signal. * Above disassociate threshold, give up and stop scanning.
* Roaming is disabled if disassociate_threshold <= roaming_threshold */ staticvoid ipw_handle_missed_beacon(struct ipw_priv *priv, int missed_count)
{
priv->notif_missed_beacons = missed_count;
if (missed_count > priv->disassociate_threshold &&
priv->status & STATUS_ASSOCIATED) { /* If associated and we've hit the missed * beacon threshold, disassociate, turn
* off roaming, and abort any active scans */
IPW_DEBUG(IPW_DL_INFO | IPW_DL_NOTIF |
IPW_DL_STATE | IPW_DL_ASSOC, "Missed beacon: %d - disassociate\n", missed_count);
priv->status &= ~STATUS_ROAMING; if (priv->status & STATUS_SCANNING) {
IPW_DEBUG(IPW_DL_INFO | IPW_DL_NOTIF |
IPW_DL_STATE, "Aborting scan with missed beacon.\n");
schedule_work(&priv->abort_scan);
}
schedule_work(&priv->disassociate); return;
}
if (priv->status & STATUS_ROAMING) { /* If we are currently roaming, then just
* print a debug statement... */
IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE, "Missed beacon: %d - roam in progress\n",
missed_count); return;
}
if (roaming &&
(missed_count > priv->roaming_threshold &&
missed_count <= priv->disassociate_threshold)) { /* If we are not already roaming, set the ROAM * bit in the status and kick off a scan. * This can happen several times before we reach
* disassociate_threshold. */
IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE, "Missed beacon: %d - initiate " "roaming\n", missed_count); if (!(priv->status & STATUS_ROAMING)) {
priv->status |= STATUS_ROAMING; if (!(priv->status & STATUS_SCANNING))
schedule_delayed_work(&priv->request_scan, 0);
} return;
}
if (priv->status & STATUS_SCANNING &&
missed_count > IPW_MB_SCAN_CANCEL_THRESHOLD) { /* Stop scan to keep fw from getting * stuck (only if we aren't roaming -- * otherwise we'll never scan more than 2 or 3
* channels..) */
IPW_DEBUG(IPW_DL_INFO | IPW_DL_NOTIF | IPW_DL_STATE, "Aborting scan with missed beacon.\n");
schedule_work(&priv->abort_scan);
}
case HOST_NOTIFICATION_STATUS_SCAN_CHANNEL_RESULT:{ struct notif_channel_result *x =
¬if->u.channel_result;
if (size == sizeof(*x)) {
IPW_DEBUG_SCAN("Scan result for channel %d\n",
x->channel_num);
} else {
IPW_DEBUG_SCAN("Scan result of wrong size %d " "(should be %zd)\n",
size, sizeof(*x));
} break;
}
case HOST_NOTIFICATION_STATUS_SCAN_COMPLETED:{ struct notif_scan_complete *x = ¬if->u.scan_complete; if (size == sizeof(*x)) {
IPW_DEBUG_SCAN
("Scan completed: type %d, %d channels, " "%d status\n", x->scan_type,
x->num_channels, x->status);
} else {
IPW_ERROR("Scan completed of wrong size %d " "(should be %zd)\n",
size, sizeof(*x));
}
/* Do queued direct scans first */ if (priv->status & STATUS_DIRECT_SCAN_PENDING)
schedule_delayed_work(&priv->request_direct_scan, 0);
if (!(priv->status & (STATUS_ASSOCIATED |
STATUS_ASSOCIATING |
STATUS_ROAMING |
STATUS_DISASSOCIATING)))
schedule_work(&priv->associate); elseif (priv->status & STATUS_ROAMING) { if (x->status == SCAN_COMPLETED_STATUS_COMPLETE) /* If a scan completed and we are in roam mode, then * the scan that completed was the one requested as a * result of entering roam... so, schedule the
* roam work */
schedule_work(&priv->roam); else /* Don't schedule if we aborted the scan */
priv->status &= ~STATUS_ROAMING;
} elseif (priv->status & STATUS_SCAN_PENDING)
schedule_delayed_work(&priv->request_scan, 0); elseif (priv->config & CFG_BACKGROUND_SCAN
&& priv->status & STATUS_ASSOCIATED)
schedule_delayed_work(&priv->request_scan,
round_jiffies_relative(HZ));
/* Send an empty event to user space. * We don't send the received data on the event because * it would require us to do complex transcoding, and * we want to minimise the work done in the irq handler * Use a request to extract the data. * Also, we generate this even for any scan, regardless * on how the scan was initiated. User space can just * sync on periodic scan to get fresh data...
* Jean II */ if (x->status == SCAN_COMPLETED_STATUS_COMPLETE)
handle_scan_event(priv); break;
}
case HOST_NOTIFICATION_STATUS_FRAG_LENGTH:{ struct notif_frag_length *x = ¬if->u.frag_len;
if (size == sizeof(*x))
IPW_ERROR("Frag length: %d\n",
le16_to_cpu(x->frag_length)); else
IPW_ERROR("Frag length of wrong size %d " "(should be %zd)\n",
size, sizeof(*x)); break;
}
case HOST_NOTIFICATION_STATUS_LINK_DETERIORATION:{ struct notif_link_deterioration *x =
¬if->u.link_deterioration;
if (size == sizeof(*x)) {
IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE, "link deterioration: type %d, cnt %d\n",
x->silence_notification_type,
x->silence_count);
memcpy(&priv->last_link_deterioration, x, sizeof(*x));
} else {
IPW_ERROR("Link Deterioration of wrong size %d " "(should be %zd)\n",
size, sizeof(*x));
} break;
}
case HOST_NOTIFICATION_DINO_CONFIG_RESPONSE:{
IPW_ERROR("Dino config\n"); if (priv->hcmd
&& priv->hcmd->cmd != HOST_CMD_DINO_CONFIG)
IPW_ERROR("Unexpected DINO_CONFIG_RESPONSE\n");
break;
}
case HOST_NOTIFICATION_STATUS_BEACON_STATE:{ struct notif_beacon_state *x = ¬if->u.beacon_state; if (size != sizeof(*x)) {
IPW_ERROR
("Beacon state of wrong size %d (should " "be %zd)\n", size, sizeof(*x)); break;
}
if (le32_to_cpu(x->state) ==
HOST_NOTIFICATION_STATUS_BEACON_MISSING)
ipw_handle_missed_beacon(priv,
le32_to_cpu(x->
number));
break;
}
case HOST_NOTIFICATION_STATUS_TGI_TX_KEY:{ struct notif_tgi_tx_key *x = ¬if->u.tgi_tx_key; if (size == sizeof(*x)) {
IPW_ERROR("TGi Tx Key: state 0x%02x sec type " "0x%02x station %d\n",
x->key_state, x->security_type,
x->station_index); break;
}
IPW_ERROR
("TGi Tx Key of wrong size %d (should be %zd)\n",
size, sizeof(*x)); break;
}
case HOST_NOTIFICATION_CALIB_KEEP_RESULTS:{ struct notif_calibration *x = ¬if->u.calibration;
/* * Reclaim Tx queue entries no more used by NIC. * * When FW advances 'R' index, all entries between old and * new 'R' index need to be reclaimed. As result, some free space * forms. If there is enough free space (> low mark), wake Tx queue. * * @note Need to protect against garbage in 'R' index * @param priv * @param txq * @param qindex * @return Number of used entries remains in the queue
*/ staticint ipw_queue_tx_reclaim(struct ipw_priv *priv, struct clx2_tx_queue *txq, int qindex)
{
u32 hw_tail; int used; struct clx2_queue *q = &txq->q;
hw_tail = ipw_read32(priv, q->reg_r); if (hw_tail >= q->n_bd) {
IPW_ERROR
("Read index for DMA queue (%d) is out of range [0-%d)\n",
hw_tail, q->n_bd); goto done;
} for (; q->last_used != hw_tail;
q->last_used = ipw_queue_inc_wrap(q->last_used, q->n_bd)) {
ipw_queue_tx_free_tfd(priv, txq);
priv->tx_packets++;
}
done: if ((ipw_tx_queue_space(q) > q->low_mark) &&
(qindex >= 0))
netif_wake_queue(priv->net_dev);
used = q->first_empty - q->last_used; if (used < 0)
used += q->n_bd;
return used;
}
staticint ipw_queue_tx_hcmd(struct ipw_priv *priv, int hcmd, constvoid *buf, int len, int sync)
{ struct clx2_tx_queue *txq = &priv->txq_cmd; struct clx2_queue *q = &txq->q; struct tfd_frame *tfd;
if (ipw_tx_queue_space(q) < (sync ? 1 : 2)) {
IPW_ERROR("No space for Tx\n"); return -EBUSY;
}
/* * Rx theory of operation * * The host allocates 32 DMA target addresses and passes the host address * to the firmware at register IPW_RFDS_TABLE_LOWER + N * RFD_SIZE where N is * 0 to 31 * * Rx Queue Indexes * The host/firmware share two index registers for managing the Rx buffers. * * The READ index maps to the first position that the firmware may be writing * to -- the driver can read up to (but not including) this position and get * good data. * The READ index is managed by the firmware once the card is enabled. * * The WRITE index maps to the last position the driver has read from -- the * position preceding WRITE is the last slot the firmware can place a packet. * * The queue is empty (no good data) if WRITE = READ - 1, and is full if * WRITE = READ. * * During initialization the host sets up the READ queue position to the first * INDEX position, and WRITE to the last (READ - 1 wrapped) * * When the firmware places a packet in a buffer it will advance the READ index * and fire the RX interrupt. The driver can then query the READ index and * process as many packets as possible, moving the WRITE index forward as it * resets the Rx queue buffers with new memory. * * The management in the driver is as follows: * + A list of pre-allocated SKBs is stored in ipw->rxq->rx_free. When * ipw->rxq->free_count drops to or below RX_LOW_WATERMARK, work is scheduled * to replensish the ipw->rxq->rx_free. * + In ipw_rx_queue_replenish (scheduled) if 'processed' != 'read' then the * ipw->rxq is replenished and the READ INDEX is updated (updating the * 'processed' and 'read' driver indexes as well) * + A received packet is processed and handed to the kernel network stack, * detached from the ipw->rxq. The driver 'processed' index is updated. * + The Host/Firmware ipw->rxq is replenished at tasklet time from the rx_free * list. If there are no allocated buffers in ipw->rxq->rx_free, the READ * INDEX is not incremented and ipw->status(RX_STALLED) is set. If there * were enough free buffers and RX_STALLED is set it is cleared. * * * Driver sequence: * * ipw_rx_queue_alloc() Allocates rx_free * ipw_rx_queue_replenish() Replenishes rx_free list from rx_used, and calls * ipw_rx_queue_restock * ipw_rx_queue_restock() Moves available buffers from rx_free into Rx * queue, updates firmware pointers, and updates * the WRITE index. If insufficient rx_free buffers * are available, schedules ipw_rx_queue_replenish * * -- enable interrupts -- * ISR - ipw_rx() Detach ipw_rx_mem_buffers from pool up to the * READ INDEX, detaching the SKB from the pool. * Moves the packet buffer from queue to rx_used. * Calls ipw_rx_queue_restock to refill any empty * slots. * ... *
*/
/* * If there are slots in the RX queue that need to be restocked, * and we have free pre-allocated buffers, fill the ranks as much * as we can pulling from rx_free. * * This moves the 'write' index forward to catch up with 'processed', and * also updates the memory address in the firmware to reference the new * target buffer.
*/ staticvoid ipw_rx_queue_restock(struct ipw_priv *priv)
{ struct ipw_rx_queue *rxq = priv->rxq; struct list_head *element; struct ipw_rx_mem_buffer *rxb; unsignedlong flags; int write;
/* If the pre-allocated buffer pool is dropping low, schedule to
* refill it */ if (rxq->free_count <= RX_LOW_WATERMARK)
schedule_work(&priv->rx_replenish);
/* If we've added more space for the firmware to place data, tell it */ if (write != rxq->write)
ipw_write32(priv, IPW_RX_WRITE_INDEX, rxq->write);
}
/* * Move all used packet from rx_used to rx_free, allocating a new SKB for each. * Also restock the Rx queue via ipw_rx_queue_restock. * * This is called as a scheduled work item (except for during initialization)
*/ staticvoid ipw_rx_queue_replenish(void *data)
{ struct ipw_priv *priv = data; struct ipw_rx_queue *rxq = priv->rxq; struct list_head *element; struct ipw_rx_mem_buffer *rxb; unsignedlong flags;
spin_lock_irqsave(&rxq->lock, flags); while (!list_empty(&rxq->rx_used)) {
element = rxq->rx_used.next;
rxb = list_entry(element, struct ipw_rx_mem_buffer, list);
rxb->skb = alloc_skb(IPW_RX_BUF_SIZE, GFP_ATOMIC); if (!rxb->skb) {
printk(KERN_CRIT "%s: Can not allocate SKB buffers.\n",
priv->net_dev->name); /* We don't reschedule replenish work here -- we will * call the restock method and if it still needs
* more buffers it will schedule replenish */ break;
}
list_del(element);
/* Assumes that the skb field of the buffers in 'pool' is kept accurate. * If an SKB has been detached, the POOL needs to have its SKB set to NULL * This free routine walks the list of POOL entries and if SKB is set to * non NULL it is unmapped and freed
*/ staticvoid ipw_rx_queue_free(struct ipw_priv *priv, struct ipw_rx_queue *rxq)
{ int i;
if (!rxq) return;
for (i = 0; i < RX_QUEUE_SIZE + RX_FREE_BUFFERS; i++) { if (rxq->pool[i].skb != NULL) {
dma_unmap_single(&priv->pci_dev->dev,
rxq->pool[i].dma_addr,
IPW_RX_BUF_SIZE, DMA_FROM_DEVICE);
dev_kfree_skb(rxq->pool[i].skb);
}
}
/* Fill the rx_used queue with _all_ of the Rx buffers */ for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++)
list_add_tail(&rxq->pool[i].list, &rxq->rx_used);
/* Set us so that we have processed and used all buffers, but have
* not restocked the Rx queue with fresh buffers */
rxq->read = rxq->write = 0;
rxq->free_count = 0;
staticvoid ipw_copy_rates(struct ipw_supported_rates *dest, conststruct ipw_supported_rates *src)
{
u8 i; for (i = 0; i < src->num_rates; i++)
dest->supported_rates[i] = src->supported_rates[i];
dest->num_rates = src->num_rates;
}
/* TODO: Look at sniffed packets in the air to determine if the basic rate * mask should ever be used -- right now all callers to add the scan rates are
* set with the modulation = CCK, so BASIC_RATE_MASK is never set... */ staticvoid ipw_add_cck_scan_rates(struct ipw_supported_rates *rates,
u8 modulation, u32 rate_mask)
{
u8 basic_mask = (LIBIPW_OFDM_MODULATION == modulation) ?
LIBIPW_BASIC_RATE_MASK : 0;
if (rate_mask & LIBIPW_CCK_RATE_1MB_MASK)
rates->supported_rates[rates->num_rates++] =
LIBIPW_BASIC_RATE_MASK | LIBIPW_CCK_RATE_1MB;
if (rate_mask & LIBIPW_CCK_RATE_2MB_MASK)
rates->supported_rates[rates->num_rates++] =
LIBIPW_BASIC_RATE_MASK | LIBIPW_CCK_RATE_2MB;
if (rate_mask & LIBIPW_CCK_RATE_5MB_MASK)
rates->supported_rates[rates->num_rates++] = basic_mask |
LIBIPW_CCK_RATE_5MB;
if (rate_mask & LIBIPW_CCK_RATE_11MB_MASK)
rates->supported_rates[rates->num_rates++] = basic_mask |
LIBIPW_CCK_RATE_11MB;
}
/* Verify that this network's capability is compatible with the
* current mode (AdHoc or Infrastructure) */ if ((priv->ieee->iw_mode == IW_MODE_ADHOC &&
!(network->capability & WLAN_CAPABILITY_IBSS))) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded due to capability mismatch.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
if (unlikely(roaming)) { /* If we are roaming, then ensure check if this is a valid
* network to try and roam to */ if ((network->ssid_len != match->network->ssid_len) ||
memcmp(network->ssid, match->network->ssid,
network->ssid_len)) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because of non-network ESSID.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
} else { /* If an ESSID has been configured then compare the broadcast
* ESSID to ours */ if ((priv->config & CFG_STATIC_ESSID) &&
((network->ssid_len != priv->essid_len) ||
memcmp(network->ssid, priv->essid,
min(network->ssid_len, priv->essid_len)))) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because of ESSID mismatch: '%*pE'.\n",
network->ssid_len, network->ssid,
network->bssid, priv->essid_len,
priv->essid); return 0;
}
}
/* If the old network rate is better than this one, don't bother
* testing everything else. */
if (network->time_stamp[0] < match->network->time_stamp[0]) {
IPW_DEBUG_MERGE("Network '%*pE excluded because newer than current network.\n",
match->network->ssid_len, match->network->ssid); return 0;
} elseif (network->time_stamp[1] < match->network->time_stamp[1]) {
IPW_DEBUG_MERGE("Network '%*pE excluded because newer than current network.\n",
match->network->ssid_len, match->network->ssid); return 0;
}
/* Now go through and see if the requested network is valid... */ if (priv->ieee->scan_age != 0 &&
time_after(jiffies, network->last_scanned + priv->ieee->scan_age)) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because of age: %ums.\n",
network->ssid_len, network->ssid,
network->bssid,
jiffies_to_msecs(jiffies -
network->last_scanned)); return 0;
}
if ((priv->config & CFG_STATIC_CHANNEL) &&
(network->channel != priv->channel)) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because of channel mismatch: %d != %d.\n",
network->ssid_len, network->ssid,
network->bssid,
network->channel, priv->channel); return 0;
}
if (ether_addr_equal(network->bssid, priv->bssid)) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because of the same BSSID match: %pM.\n",
network->ssid_len, network->ssid,
network->bssid, priv->bssid); return 0;
}
/* Filter out any incompatible freq / mode combinations */ if (!libipw_is_valid_mode(priv->ieee, network->mode)) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because of invalid frequency/mode combination.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
/* Ensure that the rates supported by the driver are compatible with
* this AP, including verification of basic rates (mandatory) */ if (!ipw_compatible_rates(priv, network, &rates)) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because configured rate mask excludes AP mandatory rate.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
if (rates.num_rates == 0) {
IPW_DEBUG_MERGE("Network '%*pE (%pM)' excluded because of no compatible rates.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
/* TODO: Perform any further minimal comparititive tests. We do not * want to put too much policy logic here; intelligent scan selection
* should occur within a generic IEEE 802.11 user space tool. */
/* Set up 'new' AP to this network */
ipw_copy_rates(&match->rates, &rates);
match->network = network;
IPW_DEBUG_MERGE("Network '%*pE (%pM)' is a viable match.\n",
network->ssid_len, network->ssid, network->bssid);
if ((priv->status & STATUS_ASSOCIATED) &&
(priv->ieee->iw_mode == IW_MODE_ADHOC)) { /* First pass through ROAM process -- look for a better
* network */ unsignedlong flags;
/* Verify that this network's capability is compatible with the
* current mode (AdHoc or Infrastructure) */ if ((priv->ieee->iw_mode == IW_MODE_INFRA &&
!(network->capability & WLAN_CAPABILITY_ESS)) ||
(priv->ieee->iw_mode == IW_MODE_ADHOC &&
!(network->capability & WLAN_CAPABILITY_IBSS))) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded due to capability mismatch.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
if (unlikely(roaming)) { /* If we are roaming, then ensure check if this is a valid
* network to try and roam to */ if ((network->ssid_len != match->network->ssid_len) ||
memcmp(network->ssid, match->network->ssid,
network->ssid_len)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of non-network ESSID.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
} else { /* If an ESSID has been configured then compare the broadcast
* ESSID to ours */ if ((priv->config & CFG_STATIC_ESSID) &&
((network->ssid_len != priv->essid_len) ||
memcmp(network->ssid, priv->essid,
min(network->ssid_len, priv->essid_len)))) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of ESSID mismatch: '%*pE'.\n",
network->ssid_len, network->ssid,
network->bssid, priv->essid_len,
priv->essid); return 0;
}
}
/* If the old network rate is better than this one, don't bother
* testing everything else. */ if (match->network && match->network->stats.rssi > network->stats.rssi) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because '%*pE (%pM)' has a stronger signal.\n",
network->ssid_len, network->ssid,
network->bssid, match->network->ssid_len,
match->network->ssid, match->network->bssid); return 0;
}
/* If this network has already had an association attempt within the
* last 3 seconds, do not try and associate again... */ if (network->last_associate &&
time_after(network->last_associate + (HZ * 3UL), jiffies)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of storming (%ums since last assoc attempt).\n",
network->ssid_len, network->ssid,
network->bssid,
jiffies_to_msecs(jiffies -
network->last_associate)); return 0;
}
/* Now go through and see if the requested network is valid... */ if (priv->ieee->scan_age != 0 &&
time_after(jiffies, network->last_scanned + priv->ieee->scan_age)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of age: %ums.\n",
network->ssid_len, network->ssid,
network->bssid,
jiffies_to_msecs(jiffies -
network->last_scanned)); return 0;
}
if ((priv->config & CFG_STATIC_CHANNEL) &&
(network->channel != priv->channel)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of channel mismatch: %d != %d.\n",
network->ssid_len, network->ssid,
network->bssid,
network->channel, priv->channel); return 0;
}
if ((priv->config & CFG_STATIC_BSSID) &&
!ether_addr_equal(network->bssid, priv->bssid)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of BSSID mismatch: %pM.\n",
network->ssid_len, network->ssid,
network->bssid, priv->bssid); return 0;
}
/* Filter out any incompatible freq / mode combinations */ if (!libipw_is_valid_mode(priv->ieee, network->mode)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of invalid frequency/mode combination.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
/* Filter out invalid channel in current GEO */ if (!libipw_is_valid_channel(priv->ieee, network->channel)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of invalid channel in current GEO\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
/* Ensure that the rates supported by the driver are compatible with
* this AP, including verification of basic rates (mandatory) */ if (!ipw_compatible_rates(priv, network, &rates)) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because configured rate mask excludes AP mandatory rate.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
if (rates.num_rates == 0) {
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' excluded because of no compatible rates.\n",
network->ssid_len, network->ssid,
network->bssid); return 0;
}
/* TODO: Perform any further minimal comparititive tests. We do not * want to put too much policy logic here; intelligent scan selection
* should occur within a generic IEEE 802.11 user space tool. */
/* Set up 'new' AP to this network */
ipw_copy_rates(&match->rates, &rates);
match->network = network;
IPW_DEBUG_ASSOC("Network '%*pE (%pM)' is a viable match.\n",
network->ssid_len, network->ssid, network->bssid);
/* * For the purposes of scanning, we can set our wireless mode * to trigger scans across combinations of bands, but when it * comes to creating a new ad-hoc network, we have tell the FW * exactly which band to use. * * We also have the possibility of an invalid channel for the * chossen band. Attempting to create a new ad-hoc network * with an invalid channel for wireless mode will trigger a * FW fatal error. *
*/ switch (libipw_is_valid_channel(priv->ieee, priv->channel)) { case LIBIPW_52GHZ_BAND:
network->mode = IEEE_A;
i = libipw_channel_to_index(priv->ieee, priv->channel);
BUG_ON(i == -1); if (geo->a[i].flags & LIBIPW_CH_PASSIVE_ONLY) {
IPW_WARNING("Overriding invalid channel\n");
priv->channel = geo->a[0].channel;
} break;
case LIBIPW_24GHZ_BAND: if (priv->ieee->mode & IEEE_G)
network->mode = IEEE_G; else
network->mode = IEEE_B;
i = libipw_channel_to_index(priv->ieee, priv->channel);
BUG_ON(i == -1); if (geo->bg[i].flags & LIBIPW_CH_PASSIVE_ONLY) {
IPW_WARNING("Overriding invalid channel\n");
priv->channel = geo->bg[0].channel;
} break;
staticvoid ipw_send_wep_keys(struct ipw_priv *priv, int type)
{ struct ipw_wep_key key; int i;
key.cmd_id = DINO_CMD_WEP_KEY;
key.seq_num = 0;
/* Note: AES keys cannot be set for multiple times.
* Only set it at the first time. */ for (i = 0; i < 4; i++) {
key.key_index = i | type; if (!(priv->ieee->sec.flags & (1 << i))) {
key.key_size = 0; continue;
}
if (priv->ieee->freq_band & LIBIPW_24GHZ_BAND) { int start = channel_index; if (priv->config & CFG_SPEED_SCAN) { int index;
u8 channels[LIBIPW_24GHZ_CHANNELS] = { /* nop out the list */
[0] = 0
};
/* If this channel has already been * added in scan, break from loop * and this will be the first channel * in the next scan.
*/ if (channels[channel - 1] != 0) break;
staticint ipw_passive_dwell_time(struct ipw_priv *priv)
{ /* staying on passive channels longer than the DTIM interval during a * scan, while associated, causes the firmware to cancel the scan * without notification. Hence, don't stay on passive channels longer * than the beacon interval.
*/ if (priv->status & STATUS_ASSOCIATED
&& priv->assoc_network->beacon_interval > 10) return priv->assoc_network->beacon_interval - 10; else return 120;
}
staticint ipw_request_scan_helper(struct ipw_priv *priv, int type, int direct)
{ struct ipw_scan_request_ext scan; int err = 0, scan_type;
if (!(priv->status & STATUS_INIT) ||
(priv->status & STATUS_EXIT_PENDING)) return 0;
mutex_lock(&priv->mutex);
if (direct && (priv->direct_scan_ssid_len == 0)) {
IPW_DEBUG_HC("Direct scan requested but no SSID to scan for\n");
priv->status &= ~STATUS_DIRECT_SCAN_PENDING; goto done;
}
if (priv->status & STATUS_SCANNING) {
IPW_DEBUG_HC("Concurrent scan requested. Queuing.\n");
priv->status |= direct ? STATUS_DIRECT_SCAN_PENDING :
STATUS_SCAN_PENDING; goto done;
}
if (!(priv->status & STATUS_SCAN_FORCED) &&
priv->status & STATUS_SCAN_ABORTING) {
IPW_DEBUG_HC("Scan request while abort pending. Queuing.\n");
priv->status |= direct ? STATUS_DIRECT_SCAN_PENDING :
STATUS_SCAN_PENDING; goto done;
}
if (priv->status & STATUS_RF_KILL_MASK) {
IPW_DEBUG_HC("Queuing scan due to RF Kill activation\n");
priv->status |= direct ? STATUS_DIRECT_SCAN_PENDING :
STATUS_SCAN_PENDING; goto done;
}
/* Use active scan by default. */ if (priv->config & CFG_SPEED_SCAN)
scan.dwell_time[IPW_SCAN_ACTIVE_BROADCAST_SCAN] =
cpu_to_le16(30); else
scan.dwell_time[IPW_SCAN_ACTIVE_BROADCAST_SCAN] =
cpu_to_le16(20);
/* NOTE: The card will sit on this channel for this time * period. Scan aborts are timing sensitive and frequently * result in firmware restarts. As such, it is best to * set a small dwell_time here and just keep re-issuing * scans. Otherwise fast channel hopping will not actually * hop channels. *
* TODO: Move SPEED SCAN support to all modes and bands */
scan.dwell_time[IPW_SCAN_PASSIVE_FULL_DWELL_SCAN] =
cpu_to_le16(2000);
} else { #endif/* CONFIG_IPW2200_MONITOR */ /* Honor direct scans first, otherwise if we are roaming make * this a direct scan for the current network. Finally,
* ensure that every other scan is a fast channel hop scan */ if (direct) {
err = ipw_send_ssid(priv, priv->direct_scan_ssid,
priv->direct_scan_ssid_len); if (err) {
IPW_DEBUG_HC("Attempt to send SSID command " "failed\n"); goto done;
}
staticint ipw_wpa_enable(struct ipw_priv *priv, int value)
{ /* This is called when wpa_supplicant loads and closes the driver
* interface. */
priv->ieee->wpa_enabled = value; return 0;
}
staticint ipw_wpa_set_auth_algs(struct ipw_priv *priv, int value)
{ struct libipw_device *ieee = priv->ieee; struct libipw_security sec = {
.flags = SEC_AUTH_MODE,
}; int ret = 0;
switch (param->flags & IW_AUTH_INDEX) { case IW_AUTH_WPA_VERSION: break; case IW_AUTH_CIPHER_PAIRWISE:
ipw_set_hw_decrypt_unicast(priv,
wext_cipher2level(param->value)); break; case IW_AUTH_CIPHER_GROUP:
ipw_set_hw_decrypt_multicast(priv,
wext_cipher2level(param->value)); break; case IW_AUTH_KEY_MGMT: /* * ipw2200 does not use these parameters
*/ break;
case IW_AUTH_TKIP_COUNTERMEASURES:
crypt = priv->ieee->crypt_info.crypt[priv->ieee->crypt_info.tx_keyidx]; if (!crypt || !crypt->ops->set_flags || !crypt->ops->get_flags) break;
flags = crypt->ops->get_flags(crypt->priv);
if (param->value)
flags |= IEEE80211_CRYPTO_TKIP_COUNTERMEASURES; else
flags &= ~IEEE80211_CRYPTO_TKIP_COUNTERMEASURES;
crypt->ops->set_flags(flags, crypt->priv);
break;
case IW_AUTH_DROP_UNENCRYPTED:{ /* HACK: * * wpa_supplicant calls set_wpa_enabled when the driver * is loaded and unloaded, regardless of if WPA is being * used. No other calls are made which can be used to * determine if encryption will be used or not prior to * association being expected. If encryption is not being * used, drop_unencrypted is set to false, else true -- we * can use this to determine if the CAP_PRIVACY_ON bit should * be set.
*/ struct libipw_security sec = {
.flags = SEC_ENABLED,
.enabled = param->value,
};
priv->ieee->drop_unencrypted = param->value; /* We only change SEC_LEVEL for open mode. Others * are set by ipw_wpa_set_encryption.
*/ if (!param->value) {
sec.flags |= SEC_LEVEL;
sec.level = SEC_LEVEL_0;
} else {
sec.flags |= SEC_LEVEL;
sec.level = SEC_LEVEL_1;
} if (priv->ieee->set_security)
priv->ieee->set_security(priv->ieee->dev, &sec); break;
}
case IW_AUTH_80211_AUTH_ALG:
ret = ipw_wpa_set_auth_algs(priv, param->value); break;
case IW_AUTH_WPA_ENABLED:
ret = ipw_wpa_enable(priv, param->value);
ipw_disassociate(priv); break;
case IW_AUTH_RX_UNENCRYPTED_EAPOL:
ieee->ieee802_1x = param->value; break;
case IW_AUTH_PRIVACY_INVOKED:
ieee->privacy_invoked = param->value; break;
switch (param->flags & IW_AUTH_INDEX) { case IW_AUTH_WPA_VERSION: case IW_AUTH_CIPHER_PAIRWISE: case IW_AUTH_CIPHER_GROUP: case IW_AUTH_KEY_MGMT: /* * wpa_supplicant will control these internally
*/ return -EOPNOTSUPP;
case IW_AUTH_TKIP_COUNTERMEASURES:
crypt = priv->ieee->crypt_info.crypt[priv->ieee->crypt_info.tx_keyidx]; if (!crypt || !crypt->ops->get_flags) break;
switch (mlme->cmd) { case IW_MLME_DEAUTH: /* silently ignore */ break;
case IW_MLME_DISASSOC:
ipw_disassociate(priv); break;
default: return -EOPNOTSUPP;
} return 0;
}
#ifdef CONFIG_IPW2200_QOS
/* QoS */ /* * get the modulation type of the current network or * the card current mode
*/ static u8 ipw_qos_current_mode(struct ipw_priv * priv)
{
u8 mode = 0;
if (priv->status & STATUS_ASSOCIATED) { unsignedlong flags;
if ((network->qos_data.active == 1) && (active_network == 1)) {
IPW_DEBUG_QOS("QoS was disabled call qos_activate\n");
schedule_work(&priv->qos_activate);
}
network->qos_data.active = 0;
network->qos_data.supported = 0;
} if ((priv->status & STATUS_ASSOCIATED) &&
(priv->ieee->iw_mode == IW_MODE_ADHOC) && (active_network == 0)) { if (!ether_addr_equal(network->bssid, priv->bssid)) if (network->capability & WLAN_CAPABILITY_IBSS) if ((network->ssid_len ==
priv->assoc_network->ssid_len) &&
!memcmp(network->ssid,
priv->assoc_network->ssid,
network->ssid_len)) {
schedule_work(&priv->merge_networks);
}
}
return 0;
}
/* * This function set up the firmware to support QoS. It sends * IPW_CMD_QOS_PARAMETERS and IPW_CMD_WME_INFO
*/ staticint ipw_qos_activate(struct ipw_priv *priv, struct libipw_qos_data *qos_network_data)
{ int err; struct libipw_qos_parameters qos_parameters[QOS_QOS_SETS]; struct libipw_qos_parameters *active_one = NULL;
u32 size = sizeof(struct libipw_qos_parameters);
u32 burst_duration; int i;
u8 type;
/* * send IPW_CMD_WME_INFO to the firmware
*/ staticint ipw_qos_set_info_element(struct ipw_priv *priv)
{ int ret = 0; struct libipw_qos_information_element qos_info;
if (priv->qos_data.qos_enable && qos_data->supported) {
IPW_DEBUG_QOS("QoS will be enabled for this association\n");
priv->assoc_request.policy_support |= HC_QOS_SUPPORT_ASSOC; return ipw_qos_set_info_element(priv);
}
return 0;
}
/* * handling the beaconing responses. if we get different QoS setting * off the network from the associated setting, adjust the QoS * setting
*/ staticvoid ipw_qos_association_resp(struct ipw_priv *priv, struct libipw_network *network)
{ unsignedlong flags;
u32 size = sizeof(struct libipw_qos_parameters); int set_qos_param = 0;
if (set_qos_param == 1)
schedule_work(&priv->qos_activate);
}
static u32 ipw_qos_get_burst_duration(struct ipw_priv *priv)
{
u32 ret = 0;
if (!priv) return 0;
if (!(priv->ieee->modulation & LIBIPW_OFDM_MODULATION))
ret = priv->qos_data.burst_duration_CCK; else
ret = priv->qos_data.burst_duration_OFDM;
return ret;
}
/* * Initialize the setting of QoS global
*/ staticvoid ipw_qos_init(struct ipw_priv *priv, int enable, int burst_enable, u32 burst_duration_CCK,
u32 burst_duration_OFDM)
{
priv->qos_data.qos_enable = enable;
if (priv->qos_data.qos_enable) {
priv->qos_data.def_qos_parm_CCK = &def_qos_parameters_CCK;
priv->qos_data.def_qos_parm_OFDM = &def_qos_parameters_OFDM;
IPW_DEBUG_QOS("QoS is enabled\n");
} else {
priv->qos_data.def_qos_parm_CCK = &def_parameters_CCK;
priv->qos_data.def_qos_parm_OFDM = &def_parameters_OFDM;
IPW_DEBUG_QOS("QoS is not enabled\n");
}
if (priv->ieee->wpa_ie_len) {
priv->assoc_request.policy_support = cpu_to_le16(0x02); /* RSN active */
ipw_set_rsn_capa(priv, priv->ieee->wpa_ie,
priv->ieee->wpa_ie_len);
}
/* * It is valid for our ieee device to support multiple modes, but * when it comes to associating to a given network we have to choose * just one mode.
*/ if (network->mode & priv->ieee->mode & IEEE_A)
priv->assoc_request.ieee_mode = IPW_A_MODE; elseif (network->mode & priv->ieee->mode & IEEE_G)
priv->assoc_request.ieee_mode = IPW_G_MODE; elseif (network->mode & priv->ieee->mode & IEEE_B)
priv->assoc_request.ieee_mode = IPW_B_MODE;
/* Clear the short preamble if we won't be supporting it */
priv->assoc_request.capability &=
~cpu_to_le16(WLAN_CAPABILITY_SHORT_PREAMBLE);
}
/* Clear capability bits that aren't used in Ad Hoc */ if (priv->ieee->iw_mode == IW_MODE_ADHOC)
priv->assoc_request.capability &=
~cpu_to_le16(WLAN_CAPABILITY_SHORT_SLOT_TIME);
/* * If preemption is enabled, it is possible for the association * to complete before we return from ipw_send_associate. Therefore * we have to be sure and update our priviate data first.
*/
priv->channel = network->channel;
memcpy(priv->bssid, network->bssid, ETH_ALEN);
priv->status |= STATUS_ASSOCIATING;
priv->status &= ~STATUS_SECURITY_UPDATED;
/* The roaming process is as follows: * * 1. Missed beacon threshold triggers the roaming process by * setting the status ROAM bit and requesting a scan. * 2. When the scan completes, it schedules the ROAM work * 3. The ROAM work looks at all of the known networks for one that * is a better network than the currently associated. If none * found, the ROAM process is over (ROAM bit cleared) * 4. If a better network is found, a disassociation request is * sent. * 5. When the disassociation completes, the roam work is again * scheduled. The second time through, the driver is no longer * associated, and the newly selected network is sent an * association request. * 6. At this point ,the roaming process is complete and the ROAM * status bit is cleared.
*/
/* If we are no longer associated, and the roaming bit is no longer
* set, then we are not actively roaming, so just return */ if (!(priv->status & (STATUS_ASSOCIATED | STATUS_ROAMING))) return;
if (priv->status & STATUS_ASSOCIATED) { /* First pass through ROAM process -- look for a better
* network */ unsignedlong flags;
u8 rssi = priv->assoc_network->stats.rssi;
priv->assoc_network->stats.rssi = -128;
spin_lock_irqsave(&priv->ieee->lock, flags);
list_for_each_entry(network, &priv->ieee->network_list, list) { if (network != priv->assoc_network)
ipw_best_network(priv, &match, network, 1);
}
spin_unlock_irqrestore(&priv->ieee->lock, flags);
priv->assoc_network->stats.rssi = rssi;
if (match.network == priv->assoc_network) {
IPW_DEBUG_ASSOC("No better APs in this network to " "roam to.\n");
priv->status &= ~STATUS_ROAMING;
ipw_debug_config(priv); return;
}
if (priv->ieee->iw_mode == IW_MODE_MONITOR) {
IPW_DEBUG_ASSOC("Not attempting association (monitor mode)\n"); return 0;
}
if (priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING)) {
IPW_DEBUG_ASSOC("Not attempting association (already in " "progress)\n"); return 0;
}
if (priv->status & STATUS_DISASSOCIATING) {
IPW_DEBUG_ASSOC("Not attempting association (in disassociating)\n");
schedule_work(&priv->associate); return 0;
}
if (!ipw_is_init(priv) || (priv->status & STATUS_SCANNING)) {
IPW_DEBUG_ASSOC("Not attempting association (scanning or not " "initialized)\n"); return 0;
}
if (!(priv->config & CFG_ASSOCIATE) &&
!(priv->config & (CFG_STATIC_ESSID | CFG_STATIC_BSSID))) {
IPW_DEBUG_ASSOC("Not attempting association (associate=0)\n"); return 0;
}
/* Protect our use of the network_list */
spin_lock_irqsave(&priv->ieee->lock, flags);
list_for_each_entry(network, &priv->ieee->network_list, list)
ipw_best_network(priv, &match, network, 0);
network = match.network;
rates = &match.rates;
if (network == NULL &&
priv->ieee->iw_mode == IW_MODE_ADHOC &&
priv->config & CFG_ADHOC_CREATE &&
priv->config & CFG_STATIC_ESSID &&
priv->config & CFG_STATIC_CHANNEL) { /* Use oldest network if the free list is empty */ if (list_empty(&priv->ieee->network_free_list)) { struct libipw_network *oldest = NULL; struct libipw_network *target;
/* If there are no more slots, expire the oldest */
list_del(&oldest->list);
target = oldest;
IPW_DEBUG_ASSOC("Expired '%*pE' (%pM) from network list.\n",
target->ssid_len, target->ssid,
target->bssid);
list_add_tail(&target->list,
&priv->ieee->network_free_list);
}
/* We received data from the HW, so stop the watchdog */
netif_trans_update(dev);
/* We only process data packets if the
* interface is open */ if (unlikely((le16_to_cpu(pkt->u.frame.length) + IPW_RX_FRAME_SIZE) >
skb_tailroom(rxb->skb))) {
dev->stats.rx_errors++;
priv->wstats.discard.misc++;
IPW_DEBUG_DROP("Corruption detected! Oh no!\n"); return;
} elseif (unlikely(!netif_running(priv->net_dev))) {
dev->stats.rx_dropped++;
priv->wstats.discard.misc++;
IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); return;
}
/* Advance skb->data to the start of the actual payload */
skb_reserve(rxb->skb, offsetof(struct ipw_rx_packet, u.frame.data));
/* Set the size of the skb to the size of the frame */
skb_put(rxb->skb, le16_to_cpu(pkt->u.frame.length));
IPW_DEBUG_RX("Rx packet of %d bytes.\n", rxb->skb->len);
/* HW decrypt will not clear the WEP bit, MIC, PN, etc. */
hdr = (struct libipw_hdr_4addr *)rxb->skb->data; if (priv->ieee->iw_mode != IW_MODE_MONITOR &&
(is_multicast_ether_addr(hdr->addr1) ?
!priv->ieee->host_mc_decrypt : !priv->ieee->host_decrypt))
ipw_rebuild_decrypted_skb(priv, rxb->skb);
if (!libipw_rx(priv->ieee, rxb->skb, stats))
dev->stats.rx_errors++; else { /* libipw_rx succeeded, so it now owns the SKB */
rxb->skb = NULL;
__ipw_led_activity_on(priv);
}
}
/* initial pull of some data */
u16 received_channel = frame->received_channel;
u8 antennaAndPhy = frame->antennaAndPhy;
s8 antsignal = frame->rssi_dbm - IPW_RSSI_TO_DBM; /* call it signed anyhow */
u16 pktrate = frame->rate;
/* Magic struct that slots into the radiotap header -- no reason * to build this manually element by element, we can write it much
* more efficiently than we can parse it. ORDER MATTERS HERE */ struct ipw_rt_hdr *ipw_rt;
unsignedshort len = le16_to_cpu(pkt->u.frame.length);
/* We received data from the HW, so stop the watchdog */
netif_trans_update(dev);
/* We only process data packets if the
* interface is open */ if (unlikely((le16_to_cpu(pkt->u.frame.length) + IPW_RX_FRAME_SIZE) >
skb_tailroom(rxb->skb))) {
dev->stats.rx_errors++;
priv->wstats.discard.misc++;
IPW_DEBUG_DROP("Corruption detected! Oh no!\n"); return;
} elseif (unlikely(!netif_running(priv->net_dev))) {
dev->stats.rx_dropped++;
priv->wstats.discard.misc++;
IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); return;
}
/* Libpcap 0.9.3+ can handle variable length radiotap, so we'll use
* that now */ if (len > IPW_RX_BUF_SIZE - sizeof(struct ipw_rt_hdr)) { /* FIXME: Should alloc bigger skb instead */
dev->stats.rx_dropped++;
priv->wstats.discard.misc++;
IPW_DEBUG_DROP("Dropping too large packet in monitor\n"); return;
}
ipw_rt->rt_hdr.it_version = PKTHDR_RADIOTAP_VERSION;
ipw_rt->rt_hdr.it_pad = 0; /* always good to zero */
ipw_rt->rt_hdr.it_len = cpu_to_le16(sizeof(struct ipw_rt_hdr)); /* total header+data */
/* Big bitfield of all the fields we provide in radiotap */
ipw_rt->rt_hdr.it_present = cpu_to_le32(
(1 << IEEE80211_RADIOTAP_TSFT) |
(1 << IEEE80211_RADIOTAP_FLAGS) |
(1 << IEEE80211_RADIOTAP_RATE) |
(1 << IEEE80211_RADIOTAP_CHANNEL) |
(1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) |
(1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) |
(1 << IEEE80211_RADIOTAP_ANTENNA));
/* Zero the flags, we'll add to them as we go */
ipw_rt->rt_flags = 0;
ipw_rt->rt_tsf = (u64)(frame->parent_tsf[3] << 24 |
frame->parent_tsf[2] << 16 |
frame->parent_tsf[1] << 8 |
frame->parent_tsf[0]);
/* Convert signal to DBM */
ipw_rt->rt_dbmsignal = antsignal;
ipw_rt->rt_dbmnoise = (s8) le16_to_cpu(frame->noise);
/* Convert the channel data and set the flags */
ipw_rt->rt_channel = cpu_to_le16(ieee80211chan2mhz(received_channel)); if (received_channel > 14) { /* 802.11a */
ipw_rt->rt_chbitmask =
cpu_to_le16((IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ));
} elseif (antennaAndPhy & 32) { /* 802.11b */
ipw_rt->rt_chbitmask =
cpu_to_le16((IEEE80211_CHAN_CCK | IEEE80211_CHAN_2GHZ));
} else { /* 802.11g */
ipw_rt->rt_chbitmask =
cpu_to_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_2GHZ);
}
/* set the rate in multiples of 500k/s */ switch (pktrate) { case IPW_TX_RATE_1MB:
ipw_rt->rt_rate = 2; break; case IPW_TX_RATE_2MB:
ipw_rt->rt_rate = 4; break; case IPW_TX_RATE_5MB:
ipw_rt->rt_rate = 10; break; case IPW_TX_RATE_6MB:
ipw_rt->rt_rate = 12; break; case IPW_TX_RATE_9MB:
ipw_rt->rt_rate = 18; break; case IPW_TX_RATE_11MB:
ipw_rt->rt_rate = 22; break; case IPW_TX_RATE_12MB:
ipw_rt->rt_rate = 24; break; case IPW_TX_RATE_18MB:
ipw_rt->rt_rate = 36; break; case IPW_TX_RATE_24MB:
ipw_rt->rt_rate = 48; break; case IPW_TX_RATE_36MB:
ipw_rt->rt_rate = 72; break; case IPW_TX_RATE_48MB:
ipw_rt->rt_rate = 96; break; case IPW_TX_RATE_54MB:
ipw_rt->rt_rate = 108; break; default:
ipw_rt->rt_rate = 0; break;
}
/* antenna number */
ipw_rt->rt_antenna = (antennaAndPhy & 3); /* Is this right? */
/* set the preamble flag if we have it */ if ((antennaAndPhy & 64))
ipw_rt->rt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
/* Set the size of the skb to the size of the frame */
skb_put(rxb->skb, len + sizeof(struct ipw_rt_hdr));
IPW_DEBUG_RX("Rx packet of %d bytes.\n", rxb->skb->len);
if (!libipw_rx(priv->ieee, rxb->skb, stats))
dev->stats.rx_errors++; else { /* libipw_rx succeeded, so it now owns the SKB */
rxb->skb = NULL; /* no LED during capture */
}
} #endif
/* First cache any information we need before we overwrite
* the information provided in the skb from the hardware */ struct ieee80211_hdr *hdr;
u16 channel = frame->received_channel;
u8 phy_flags = frame->antennaAndPhy;
s8 signal = frame->rssi_dbm - IPW_RSSI_TO_DBM;
s8 noise = (s8) le16_to_cpu(frame->noise);
u8 rate = frame->rate; unsignedshort len = le16_to_cpu(pkt->u.frame.length); struct sk_buff *skb; int hdr_only = 0;
u16 filter = priv->prom_priv->filter;
/* If the filter is set to not include Rx frames then return */ if (filter & IPW_PROM_NO_RX) return;
/* We received data from the HW, so stop the watchdog */
netif_trans_update(dev);
/* We only process data packets if the interface is open */ if (unlikely(!netif_running(dev))) {
dev->stats.rx_dropped++;
IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); return;
}
/* Libpcap 0.9.3+ can handle variable length radiotap, so we'll use
* that now */ if (len > IPW_RX_BUF_SIZE - sizeof(struct ipw_rt_hdr)) { /* FIXME: Should alloc bigger skb instead */
dev->stats.rx_dropped++;
IPW_DEBUG_DROP("Dropping too large packet in monitor\n"); return;
}
hdr = (void *)rxb->skb->data + IPW_RX_FRAME_SIZE; if (libipw_is_management(le16_to_cpu(hdr->frame_control))) { if (filter & IPW_PROM_NO_MGMT) return; if (filter & IPW_PROM_MGMT_HEADER_ONLY)
hdr_only = 1;
} elseif (libipw_is_control(le16_to_cpu(hdr->frame_control))) { if (filter & IPW_PROM_NO_CTL) return; if (filter & IPW_PROM_CTL_HEADER_ONLY)
hdr_only = 1;
} elseif (libipw_is_data(le16_to_cpu(hdr->frame_control))) { if (filter & IPW_PROM_NO_DATA) return; if (filter & IPW_PROM_DATA_HEADER_ONLY)
hdr_only = 1;
}
/* Copy the SKB since this is for the promiscuous side */
skb = skb_copy(rxb->skb, GFP_ATOMIC); if (skb == NULL) {
IPW_ERROR("skb_clone failed for promiscuous copy.\n"); return;
}
/* copy the frame data to write after where the radiotap header goes */
ipw_rt = (void *)skb->data;
if (hdr_only)
len = libipw_get_hdrlen(le16_to_cpu(hdr->frame_control));
memcpy(ipw_rt->payload, hdr, len);
ipw_rt->rt_hdr.it_version = PKTHDR_RADIOTAP_VERSION;
ipw_rt->rt_hdr.it_pad = 0; /* always good to zero */
ipw_rt->rt_hdr.it_len = cpu_to_le16(sizeof(*ipw_rt)); /* total header+data */
/* Set the size of the skb to the size of the frame */
skb_put(skb, sizeof(*ipw_rt) + len);
/* Big bitfield of all the fields we provide in radiotap */
ipw_rt->rt_hdr.it_present = cpu_to_le32(
(1 << IEEE80211_RADIOTAP_TSFT) |
(1 << IEEE80211_RADIOTAP_FLAGS) |
(1 << IEEE80211_RADIOTAP_RATE) |
(1 << IEEE80211_RADIOTAP_CHANNEL) |
(1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) |
(1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) |
(1 << IEEE80211_RADIOTAP_ANTENNA));
/* Zero the flags, we'll add to them as we go */
ipw_rt->rt_flags = 0;
ipw_rt->rt_tsf = (u64)(frame->parent_tsf[3] << 24 |
frame->parent_tsf[2] << 16 |
frame->parent_tsf[1] << 8 |
frame->parent_tsf[0]);
/* Convert the channel data and set the flags */
ipw_rt->rt_channel = cpu_to_le16(ieee80211chan2mhz(channel)); if (channel > 14) { /* 802.11a */
ipw_rt->rt_chbitmask =
cpu_to_le16((IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ));
} elseif (phy_flags & (1 << 5)) { /* 802.11b */
ipw_rt->rt_chbitmask =
cpu_to_le16((IEEE80211_CHAN_CCK | IEEE80211_CHAN_2GHZ));
} else { /* 802.11g */
ipw_rt->rt_chbitmask =
cpu_to_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_2GHZ);
}
/* set the rate in multiples of 500k/s */ switch (rate) { case IPW_TX_RATE_1MB:
ipw_rt->rt_rate = 2; break; case IPW_TX_RATE_2MB:
ipw_rt->rt_rate = 4; break; case IPW_TX_RATE_5MB:
ipw_rt->rt_rate = 10; break; case IPW_TX_RATE_6MB:
ipw_rt->rt_rate = 12; break; case IPW_TX_RATE_9MB:
ipw_rt->rt_rate = 18; break; case IPW_TX_RATE_11MB:
ipw_rt->rt_rate = 22; break; case IPW_TX_RATE_12MB:
ipw_rt->rt_rate = 24; break; case IPW_TX_RATE_18MB:
ipw_rt->rt_rate = 36; break; case IPW_TX_RATE_24MB:
ipw_rt->rt_rate = 48; break; case IPW_TX_RATE_36MB:
ipw_rt->rt_rate = 72; break; case IPW_TX_RATE_48MB:
ipw_rt->rt_rate = 96; break; case IPW_TX_RATE_54MB:
ipw_rt->rt_rate = 108; break; default:
ipw_rt->rt_rate = 0; break;
}
/* antenna number */
ipw_rt->rt_antenna = (phy_flags & 3);
/* set the preamble flag if we have it */ if (phy_flags & (1 << 6))
ipw_rt->rt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
IPW_DEBUG_RX("Rx packet of %d bytes.\n", skb->len);
if (!libipw_rx(priv->prom_priv->ieee, skb, stats)) {
dev->stats.rx_errors++;
dev_kfree_skb_any(skb);
}
} #endif
staticint is_network_packet(struct ipw_priv *priv, struct libipw_hdr_4addr *header)
{ /* Filter incoming packets to determine if they are targeted toward
* this network, discarding packets coming from ourselves */ switch (priv->ieee->iw_mode) { case IW_MODE_ADHOC: /* Header: Dest. | Source | BSSID */ /* packets from our adapter are dropped (echo) */ if (ether_addr_equal(header->addr2, priv->net_dev->dev_addr)) return 0;
/* {broad,multi}cast packets to our BSSID go through */ if (is_multicast_ether_addr(header->addr1)) return ether_addr_equal(header->addr3, priv->bssid);
/* packets to our adapter go through */ return ether_addr_equal(header->addr1,
priv->net_dev->dev_addr);
case IW_MODE_INFRA: /* Header: Dest. | BSSID | Source */ /* packets from our adapter are dropped (echo) */ if (ether_addr_equal(header->addr3, priv->net_dev->dev_addr)) return 0;
/* {broad,multi}cast packets to our BSS go through */ if (is_multicast_ether_addr(header->addr1)) return ether_addr_equal(header->addr2, priv->bssid);
/* packets to our adapter go through */ return ether_addr_equal(header->addr1,
priv->net_dev->dev_addr);
}
drop: /* Comment this line now since we observed the card receives * duplicate packets but the FCTL_RETRY bit is not set in the * IBSS mode with fragmentation enabled.
BUG_ON(!(le16_to_cpu(header->frame_control) & IEEE80211_FCTL_RETRY)); */ return 1;
}
if (priv->ieee->iw_mode == IW_MODE_ADHOC &&
((WLAN_FC_GET_STYPE(le16_to_cpu(header->frame_ctl)) ==
IEEE80211_STYPE_PROBE_RESP) ||
(WLAN_FC_GET_STYPE(le16_to_cpu(header->frame_ctl)) ==
IEEE80211_STYPE_BEACON))) { if (ether_addr_equal(header->addr3, priv->bssid))
ipw_add_station(priv, header->addr2);
}
if (priv->config & CFG_NET_STATS) {
IPW_DEBUG_HC("sending stat packet\n");
/* Set the size of the skb to the size of the full
* ipw header and 802.11 frame */
skb_put(skb, le16_to_cpu(pkt->u.frame.length) +
IPW_RX_FRAME_SIZE);
/* Advance past the ipw packet header to the 802.11 frame */
skb_pull(skb, IPW_RX_FRAME_SIZE);
/* Push the libipw_rx_stats before the 802.11 frame */
memcpy(skb_push(skb, sizeof(*stats)), stats, sizeof(*stats));
skb->dev = priv->ieee->dev;
/* Point raw at the libipw_stats */
skb_reset_mac_header(skb);
/* * Main entry function for receiving a packet with 80211 headers. This * should be called when ever the FW has notified us that there is a new * skb in the receive queue.
*/ staticvoid ipw_rx(struct ipw_priv *priv)
{ struct ipw_rx_mem_buffer *rxb; struct ipw_rx_packet *pkt; struct libipw_hdr_4addr *header;
u32 r, i;
u8 network_packet;
u8 fill_rx = 0;
r = ipw_read32(priv, IPW_RX_READ_INDEX);
ipw_read32(priv, IPW_RX_WRITE_INDEX);
i = priv->rxq->read;
if (ipw_rx_queue_space (priv->rxq) > (RX_QUEUE_SIZE / 2))
fill_rx = 1;
while (i != r) {
rxb = priv->rxq->queue[i]; if (unlikely(rxb == NULL)) {
printk(KERN_CRIT "Queue not allocated!\n"); break;
}
priv->rxq->queue[i] = NULL;
header =
(struct libipw_hdr_4addr *)(rxb->skb->
data +
IPW_RX_FRAME_SIZE); /* TODO: Check Ad-Hoc dest/source and make sure * that we are actually parsing these packets * correctly -- we should probably use the * frame control of the packet and disregard
* the current iw_mode */
default:
IPW_DEBUG_RX("Bad Rx packet of type %d\n",
pkt->header.message_type); break;
}
/* For now we just don't re-use anything. We can tweak this * later to try and re-use notification packets and SKBs that
* fail to Rx correctly */ if (rxb->skb != NULL) {
dev_kfree_skb_any(rxb->skb);
rxb->skb = NULL;
}
/* If there are a lot of unsued frames, restock the Rx queue
* so the ucode won't assert */ if (fill_rx) {
priv->rxq->read = i;
ipw_rx_queue_replenish(priv);
}
}
/* Backtrack one entry */
priv->rxq->read = i;
ipw_rx_queue_restock(priv);
}
/* If power management is turned on, default to AC mode */
priv->power_mode = IPW_POWER_AC;
priv->tx_power = IPW_TX_POWER_DEFAULT;
return old_mode == priv->ieee->iw_mode;
}
/* * This file defines the Wireless Extension handlers. It does not * define any methods of hardware manipulation and relies on the * functions defined in ipw_main to provide the HW interaction. * * The exception to this is the use of the ipw_get_ordinal() * function used to poll the hardware vs. making unnecessary calls. *
*/
staticint ipw_set_channel(struct ipw_priv *priv, u8 channel)
{ if (channel == 0) {
IPW_DEBUG_INFO("Setting channel to ANY (0)\n");
priv->config &= ~CFG_STATIC_CHANNEL;
IPW_DEBUG_ASSOC("Attempting to associate with new " "parameters.\n");
ipw_associate(priv); return 0;
}
priv->config |= CFG_STATIC_CHANNEL;
if (priv->channel == channel) {
IPW_DEBUG_INFO("Request to set channel to current value (%d)\n",
channel); return 0;
}
IPW_DEBUG_INFO("Setting channel to %i\n", (int)channel);
priv->channel = channel;
#ifdef CONFIG_IPW2200_MONITOR if (priv->ieee->iw_mode == IW_MODE_MONITOR) { int i; if (priv->status & STATUS_SCANNING) {
IPW_DEBUG_SCAN("Scan abort triggered due to " "channel change.\n");
ipw_abort_scan(priv);
}
for (i = 1000; i && (priv->status & STATUS_SCANNING); i--)
udelay(10);
if (priv->status & STATUS_SCANNING)
IPW_DEBUG_SCAN("Still scanning...\n"); else
IPW_DEBUG_SCAN("Took %dms to abort current scan\n",
1000 - i);
return 0;
} #endif/* CONFIG_IPW2200_MONITOR */
/* Network configuration changed -- force [re]association */
IPW_DEBUG_ASSOC("[re]association triggered due to channel change.\n"); if (!ipw_disassociate(priv))
ipw_associate(priv);
return 0;
}
staticint ipw_wx_set_freq(struct net_device *dev, struct iw_request_info *info, union iwreq_data *wrqu, char *extra)
{ struct ipw_priv *priv = libipw_priv(dev); conststruct libipw_geo *geo = libipw_get_geo(priv->ieee); struct iw_freq *fwrq = &wrqu->freq; int ret = 0, i;
u8 channel, flags; int band;
if (fwrq->m == 0) {
IPW_DEBUG_WX("SET Freq/Channel -> any\n");
mutex_lock(&priv->mutex);
ret = ipw_set_channel(priv, 0);
mutex_unlock(&priv->mutex); return ret;
} /* if setting by freq convert to channel */ if (fwrq->e == 1) {
channel = libipw_freq_to_channel(priv->ieee, fwrq->m); if (channel == 0) return -EINVAL;
} else
channel = fwrq->m;
if (!(band = libipw_is_valid_channel(priv->ieee, channel))) return -EINVAL;
if (priv->ieee->iw_mode == IW_MODE_ADHOC) {
i = libipw_channel_to_index(priv->ieee, channel); if (i == -1) return -EINVAL;
/* If we are associated, trying to associate, or have a statically
* configured CHANNEL then return that; otherwise return ANY */
mutex_lock(&priv->mutex); if (priv->config & CFG_STATIC_CHANNEL ||
priv->status & (STATUS_ASSOCIATING | STATUS_ASSOCIATED)) { int i;
i = libipw_channel_to_index(priv->ieee, priv->channel);
BUG_ON(i == -1);
wrqu->freq.e = 1;
range->max_qual.qual = 100; /* TODO: Find real max RSSI and stick here */
range->max_qual.level = 0;
range->max_qual.noise = 0;
range->max_qual.updated = 7; /* Updated all three */
range->avg_qual.qual = 70; /* TODO: Find real 'good' to 'bad' threshold value for RSSI */
range->avg_qual.level = 0; /* FIXME to real average level */
range->avg_qual.noise = 0;
range->avg_qual.updated = 7; /* Updated all three */
mutex_lock(&priv->mutex);
range->num_bitrates = min(priv->rates.num_rates, (u8) IW_MAX_BITRATES);
for (i = 0; i < range->num_bitrates; i++)
range->bitrate[i] = (priv->rates.supported_rates[i] & 0x7F) *
500000;
if (wrqu->ap_addr.sa_family != ARPHRD_ETHER) return -EINVAL;
mutex_lock(&priv->mutex); if (is_broadcast_ether_addr(wrqu->ap_addr.sa_data) ||
is_zero_ether_addr(wrqu->ap_addr.sa_data)) { /* we disable mandatory BSSID association */
IPW_DEBUG_WX("Setting AP BSSID to ANY\n");
priv->config &= ~CFG_STATIC_BSSID;
IPW_DEBUG_ASSOC("Attempting to associate with new " "parameters.\n");
ipw_associate(priv);
mutex_unlock(&priv->mutex); return 0;
}
priv->config |= CFG_STATIC_BSSID; if (ether_addr_equal(priv->bssid, wrqu->ap_addr.sa_data)) {
IPW_DEBUG_WX("BSSID set to current BSSID.\n");
mutex_unlock(&priv->mutex); return 0;
}
IPW_DEBUG_WX("Setting mandatory BSSID to %pM\n",
wrqu->ap_addr.sa_data);
/* Network configuration changed -- force [re]association */
IPW_DEBUG_ASSOC("[re]association triggered due to BSSID change.\n"); if (!ipw_disassociate(priv))
ipw_associate(priv);
/* If we are associated, trying to associate, or have a statically
* configured BSSID then return that; otherwise return ANY */
mutex_lock(&priv->mutex); if (priv->config & CFG_STATIC_BSSID ||
priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING)) {
wrqu->ap_addr.sa_family = ARPHRD_ETHER;
memcpy(wrqu->ap_addr.sa_data, priv->bssid, ETH_ALEN);
} else
eth_zero_addr(wrqu->ap_addr.sa_data);
/* Network configuration changed -- force [re]association */
IPW_DEBUG_ASSOC("[re]association triggered due to ESSID change.\n"); if (!ipw_disassociate(priv))
ipw_associate(priv);
staticint ipw_wx_set_rate(struct net_device *dev, struct iw_request_info *info, union iwreq_data *wrqu, char *extra)
{ /* TODO: We should use semaphores or locks for access to priv */ struct ipw_priv *priv = libipw_priv(dev);
u32 target_rate = wrqu->bitrate.value;
u32 fixed, mask;
/* value = -1, fixed = 0 means auto only, so we should use all rates offered by AP */ /* value = X, fixed = 1 means only rate X */ /* value = X, fixed = 0 means all rates lower equal X */
if (target_rate == -1) {
fixed = 0;
mask = LIBIPW_DEFAULT_RATES_MASK; /* Now we should reassociate */ goto apply;
}
mask = 0;
fixed = wrqu->bitrate.fixed;
if (target_rate == 1000000 || !fixed)
mask |= LIBIPW_CCK_RATE_1MB_MASK; if (target_rate == 1000000) goto apply;
if (target_rate == 2000000 || !fixed)
mask |= LIBIPW_CCK_RATE_2MB_MASK; if (target_rate == 2000000) goto apply;
if (target_rate == 5500000 || !fixed)
mask |= LIBIPW_CCK_RATE_5MB_MASK; if (target_rate == 5500000) goto apply;
if (target_rate == 6000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_6MB_MASK; if (target_rate == 6000000) goto apply;
if (target_rate == 9000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_9MB_MASK; if (target_rate == 9000000) goto apply;
if (target_rate == 11000000 || !fixed)
mask |= LIBIPW_CCK_RATE_11MB_MASK; if (target_rate == 11000000) goto apply;
if (target_rate == 12000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_12MB_MASK; if (target_rate == 12000000) goto apply;
if (target_rate == 18000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_18MB_MASK; if (target_rate == 18000000) goto apply;
if (target_rate == 24000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_24MB_MASK; if (target_rate == 24000000) goto apply;
if (target_rate == 36000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_36MB_MASK; if (target_rate == 36000000) goto apply;
if (target_rate == 48000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_48MB_MASK; if (target_rate == 48000000) goto apply;
if (target_rate == 54000000 || !fixed)
mask |= LIBIPW_OFDM_RATE_54MB_MASK; if (target_rate == 54000000) goto apply;
if (priv->rates_mask == mask) {
IPW_DEBUG_WX("Mask set to current mask.\n");
mutex_unlock(&priv->mutex); return 0;
}
priv->rates_mask = mask;
/* Network configuration changed -- force [re]association */
IPW_DEBUG_ASSOC("[re]association triggered due to rates change.\n"); if (!ipw_disassociate(priv))
ipw_associate(priv);
staticint ipw_wx_set_encode(struct net_device *dev, struct iw_request_info *info, union iwreq_data *wrqu, char *key)
{ struct ipw_priv *priv = libipw_priv(dev); int ret;
u32 cap = priv->capability;
mutex_lock(&priv->mutex);
ret = libipw_wx_set_encode(priv->ieee, info, wrqu, key);
/* In IBSS mode, we need to notify the firmware to update
* the beacon info after we changed the capability. */ if (cap != priv->capability &&
priv->ieee->iw_mode == IW_MODE_ADHOC &&
priv->status & STATUS_ASSOCIATED)
ipw_disassociate(priv);
staticint ipw_wx_set_power(struct net_device *dev, struct iw_request_info *info, union iwreq_data *wrqu, char *extra)
{ struct ipw_priv *priv = libipw_priv(dev); int err;
mutex_lock(&priv->mutex); if (wrqu->power.disabled) {
priv->power_mode = IPW_POWER_LEVEL(priv->power_mode);
err = ipw_send_power_mode(priv, IPW_POWER_MODE_CAM); if (err) {
IPW_DEBUG_WX("failed setting power mode.\n");
mutex_unlock(&priv->mutex); return err;
}
IPW_DEBUG_WX("SET Power Management Mode -> off\n");
mutex_unlock(&priv->mutex); return 0;
}
switch (wrqu->power.flags & IW_POWER_MODE) { case IW_POWER_ON: /* If not specified */ case IW_POWER_MODE: /* If set all mask */ case IW_POWER_ALL_R: /* If explicitly state all */ break; default: /* Otherwise we don't support it */
IPW_DEBUG_WX("SET PM Mode: %X not supported.\n",
wrqu->power.flags);
mutex_unlock(&priv->mutex); return -EOPNOTSUPP;
}
/* If the user hasn't specified a power management mode yet, default
* to BATTERY */ if (IPW_POWER_LEVEL(priv->power_mode) == IPW_POWER_AC)
priv->power_mode = IPW_POWER_ENABLED | IPW_POWER_BATTERY; else
priv->power_mode = IPW_POWER_ENABLED | priv->power_mode;
err = ipw_send_power_mode(priv, IPW_POWER_LEVEL(priv->power_mode)); if (err) {
IPW_DEBUG_WX("failed setting power mode.\n");
mutex_unlock(&priv->mutex); return err;
}
ret = ipw_sw_reset(priv, 2); if (!ret) {
free_firmware();
ipw_adapter_restart(priv);
}
/* The SW reset bit might have been toggled on by the 'disable'
* module parameter, so take appropriate action */
ipw_radio_kill_sw(priv, priv->status & STATUS_RF_KILL_SW);
if (!(priv->status & STATUS_RF_KILL_MASK)) { /* Configuration likely changed -- force [re]association */
IPW_DEBUG_ASSOC("[re]association triggered due to sw " "reset.\n"); if (!ipw_disassociate(priv))
ipw_associate(priv);
}
/* * Get wireless statistics. * Called by /proc/net/wireless * Also called by SIOCGIWSTATS
*/ staticstruct iw_statistics *ipw_get_wireless_stats(struct net_device *dev)
{ struct ipw_priv *priv = libipw_priv(dev); struct iw_statistics *wstats;
wstats = &priv->wstats;
/* if hw is disabled, then ipw_get_ordinal() can't be called. * netdev->get_wireless_stats seems to be called before fw is * initialized. STATUS_ASSOCIATED will only be set if the hw is up * and associated; if not associcated, the values are all meaningless
* anyway, so set them all to NULL and INVALID */ if (!(priv->status & STATUS_ASSOCIATED)) {
wstats->miss.beacon = 0;
wstats->discard.retries = 0;
wstats->qual.qual = 0;
wstats->qual.level = 0;
wstats->qual.noise = 0;
wstats->qual.updated = 7;
wstats->qual.updated |= IW_QUAL_NOISE_INVALID |
IW_QUAL_QUAL_INVALID | IW_QUAL_LEVEL_INVALID; return wstats;
}
if (!(priv->status & STATUS_ASSOCIATED)) goto drop;
hdr_len = libipw_get_hdrlen(le16_to_cpu(hdr->frame_ctl)); switch (priv->ieee->iw_mode) { case IW_MODE_ADHOC:
unicast = !is_multicast_ether_addr(hdr->addr1);
id = ipw_find_station(priv, hdr->addr1); if (id == IPW_INVALID_STATION) {
id = ipw_add_station(priv, hdr->addr1); if (id == IPW_INVALID_STATION) {
IPW_WARNING("Attempt to send data to " "invalid cell: %pM\n",
hdr->addr1); goto drop;
}
} break;
case IW_MODE_INFRA: default:
unicast = !is_multicast_ether_addr(hdr->addr3);
id = 0; break;
}
if (likely(unicast))
tfd->u.data.tx_flags |= DCT_FLAG_ACK_REQD;
if (txb->encrypted && !priv->ieee->host_encrypt) { switch (priv->ieee->sec.level) { case SEC_LEVEL_3:
tfd->u.data.tfd.tfd_24.mchdr.frame_ctl |=
cpu_to_le16(IEEE80211_FCTL_PROTECTED); /* XXX: ACK flag must be set for CCMP even if it * is a multicast/broadcast packet, because CCMP * group communication encrypted by GTK is
* actually done by the AP. */ if (!unicast)
tfd->u.data.tx_flags |= DCT_FLAG_ACK_REQD;
/* Filtering of fragment chains is done against the first fragment */
hdr = (void *)txb->fragments[0]->data; if (libipw_is_management(le16_to_cpu(hdr->frame_control))) { if (filter & IPW_PROM_NO_MGMT) return; if (filter & IPW_PROM_MGMT_HEADER_ONLY)
hdr_only = 1;
} elseif (libipw_is_control(le16_to_cpu(hdr->frame_control))) { if (filter & IPW_PROM_NO_CTL) return; if (filter & IPW_PROM_CTL_HEADER_ONLY)
hdr_only = 1;
} elseif (libipw_is_data(le16_to_cpu(hdr->frame_control))) { if (filter & IPW_PROM_NO_DATA) return; if (filter & IPW_PROM_DATA_HEADER_ONLY)
hdr_only = 1;
}
rt_hdr->it_version = PKTHDR_RADIOTAP_VERSION;
rt_hdr->it_pad = 0;
rt_hdr->it_present = 0; /* after all, it's just an idea */
rt_hdr->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_CHANNEL);
/* RF Kill is now disabled, so bring the device back up */
if (!(priv->status & STATUS_RF_KILL_MASK)) {
IPW_DEBUG_RF_KILL("HW RF Kill no longer active, restarting " "device\n");
/* we can not do an adapter restart while inside an irq lock */
schedule_work(&priv->adapter_restart);
} else
IPW_DEBUG_RF_KILL("HW RF Kill deactivated. SW RF Kill still " "enabled\n");
if (!priv->ieee->host_encrypt && (sec->flags & SEC_ENCRYPT))
ipw_set_hwcrypto_keys(priv);
/* To match current functionality of ipw2100 (which works well w/ * various supplicants, we don't force a disassociate if the
* privacy capability changes ... */ #if 0 if ((priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING)) &&
(((priv->assoc_request.capability &
cpu_to_le16(WLAN_CAPABILITY_PRIVACY)) && !sec->enabled) ||
(!(priv->assoc_request.capability &
cpu_to_le16(WLAN_CAPABILITY_PRIVACY)) && sec->enabled))) {
IPW_DEBUG_ASSOC("Disassociating due to capability " "change.\n");
ipw_disassociate(priv);
} #endif
}
staticint init_supported_rates(struct ipw_priv *priv, struct ipw_supported_rates *rates)
{ /* TODO: Mask out rates based on priv->rates_mask */
staticint ipw_config(struct ipw_priv *priv)
{ /* This is only called from ipw_up, which resets/reloads the firmware so, we don't need to first disable the card before we configure
it */ if (ipw_set_tx_power(priv)) goto error;
/* initialize adapter address */ if (ipw_send_adapter_address(priv, priv->net_dev->dev_addr)) goto error;
/* set basic system config settings */
init_sys_config(&priv->sys_config);
/* Support Bluetooth if we have BT h/w on board, and user wants to.
* Does not support BT priority yet (don't abort or defer our Tx) */ if (bt_coexist) { unsignedchar bt_caps = priv->eeprom[EEPROM_SKU_CAPABILITY];
if (bt_caps & EEPROM_SKU_CAP_BT_CHANNEL_SIG)
priv->sys_config.bt_coexistence
|= CFG_BT_COEXISTENCE_SIGNAL_CHNL; if (bt_caps & EEPROM_SKU_CAP_BT_OOB)
priv->sys_config.bt_coexistence
|= CFG_BT_COEXISTENCE_OOB;
}
/* Set hardware WEP key if it is configured. */ if ((priv->capability & CAP_PRIVACY_ON) &&
(priv->ieee->sec.level == SEC_LEVEL_1) &&
!(priv->ieee->host_encrypt || priv->ieee->host_decrypt))
ipw_set_hwcrypto_keys(priv);
return 0;
error: return -EIO;
}
/* * NOTE: * * These tables have been tested in conjunction with the * Intel PRO/Wireless 2200BG and 2915ABG Network Connection Adapters. * * Altering this values, using it on other hardware, or in geographies * not intended for resale of the above mentioned Intel adapters has * not been tested. * * Remember to update the table in README.ipw2200 when changing this * table. *
*/ staticconststruct libipw_geo ipw_geos[] = {
{ /* Restricted */ "---",
.bg_channels = 11,
.bg = {{2412, 1}, {2417, 2}, {2422, 3},
{2427, 4}, {2432, 5}, {2437, 6},
{2442, 7}, {2447, 8}, {2452, 9},
{2457, 10}, {2462, 11}},
},
/* Age scan list entries found before suspend */ if (priv->suspend_time) {
libipw_networks_age(priv->ieee, priv->suspend_time);
priv->suspend_time = 0;
}
if (priv->status & STATUS_EXIT_PENDING) return -EIO;
for (i = 0; i < MAX_HW_RESTARTS; i++) { /* Load the microcode, firmware, and eeprom.
* Also start the clocks. */
rc = ipw_load(priv); if (rc) {
IPW_ERROR("Unable to load firmware: %d\n", rc); return rc;
}
ipw_init_ordinals(priv); if (!(priv->config & CFG_CUSTOM_MAC))
eeprom_parse_mac(priv, priv->mac_addr);
eth_hw_addr_set(priv->net_dev, priv->mac_addr);
ipw_set_geo(priv);
if (priv->status & STATUS_RF_KILL_SW) {
IPW_WARNING("Radio disabled by module parameter.\n"); return 0;
} elseif (rf_kill_active(priv)) {
IPW_WARNING("Radio Frequency Kill Switch is On:\n" "Kill switch must be turned off for " "wireless networking to work.\n");
schedule_delayed_work(&priv->rf_kill, 2 * HZ); return 0;
}
rc = ipw_config(priv); if (!rc) {
IPW_DEBUG_INFO("Configured device on count %i\n", i);
/* If configure to try and auto-associate, kick
* off a scan. */
schedule_delayed_work(&priv->request_scan, 0);
return 0;
}
IPW_DEBUG_INFO("Device configuration failed: 0x%08X\n", rc);
IPW_DEBUG_INFO("Failed to config device on retry %d of %d\n",
i, MAX_HW_RESTARTS);
/* We had an error bringing up the hardware, so take it
* all the way back down so we can try again */
ipw_down(priv);
}
/* tried to restart and config the device for as long as our
* patience could withstand */
IPW_ERROR("Unable to initialize device after %d attempts.\n", i);
staticvoid ipw_deinit(struct ipw_priv *priv)
{ int i;
if (priv->status & STATUS_SCANNING) {
IPW_DEBUG_INFO("Aborting scan during shutdown.\n");
ipw_abort_scan(priv);
}
if (priv->status & STATUS_ASSOCIATED) {
IPW_DEBUG_INFO("Disassociating during shutdown.\n");
ipw_disassociate(priv);
}
ipw_led_shutdown(priv);
/* Wait up to 1s for status to change to not scanning and not * associated (disassociation can take a while for a ful 802.11
* exchange */ for (i = 1000; i && (priv->status &
(STATUS_DISASSOCIATING |
STATUS_ASSOCIATED | STATUS_SCANNING)); i--)
udelay(10);
if (priv->status & (STATUS_DISASSOCIATING |
STATUS_ASSOCIATED | STATUS_SCANNING))
IPW_DEBUG_INFO("Still associated or scanning...\n"); else
IPW_DEBUG_INFO("Took %dms to de-init\n", 1000 - i);
/* Attempt to disable the card */
ipw_send_card_disable(priv, 0);
priv->net_dev = net_dev;
priv->pci_dev = pdev;
ipw_debug_level = debug;
spin_lock_init(&priv->irq_lock);
spin_lock_init(&priv->lock); for (i = 0; i < IPW_IBSS_MAC_HASH_SIZE; i++)
INIT_LIST_HEAD(&priv->ibss_mac_hash[i]);
mutex_init(&priv->mutex); if (pci_enable_device(pdev)) {
err = -ENODEV; goto out_free_libipw;
}
pci_set_master(pdev);
err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); if (!err)
err = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); if (err) {
printk(KERN_WARNING DRV_NAME ": No suitable DMA available.\n"); goto out_pci_disable_device;
}
pci_set_drvdata(pdev, priv);
err = pci_request_regions(pdev, DRV_NAME); if (err) goto out_pci_disable_device;
/* We disable the RETRY_TIMEOUT register (0x41) to keep
* PCI Tx retries from interfering with C3 CPU state */
pci_read_config_dword(pdev, 0x40, &val); if ((val & 0x0000ff00) != 0)
pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
if (priv->rxq) {
ipw_rx_queue_free(priv, priv->rxq);
priv->rxq = NULL;
}
ipw_tx_queue_free(priv);
if (priv->cmdlog) {
kfree(priv->cmdlog);
priv->cmdlog = NULL;
}
/* make sure all works are inactive */
cancel_delayed_work_sync(&priv->adhoc_check);
cancel_work_sync(&priv->associate);
cancel_work_sync(&priv->disassociate);
cancel_work_sync(&priv->system_config);
cancel_work_sync(&priv->rx_replenish);
cancel_work_sync(&priv->adapter_restart);
cancel_delayed_work_sync(&priv->rf_kill);
cancel_work_sync(&priv->up);
cancel_work_sync(&priv->down);
cancel_delayed_work_sync(&priv->request_scan);
cancel_delayed_work_sync(&priv->request_direct_scan);
cancel_delayed_work_sync(&priv->request_passive_scan);
cancel_delayed_work_sync(&priv->scan_event);
cancel_delayed_work_sync(&priv->gather_stats);
cancel_work_sync(&priv->abort_scan);
cancel_work_sync(&priv->roam);
cancel_delayed_work_sync(&priv->scan_check);
cancel_work_sync(&priv->link_up);
cancel_work_sync(&priv->link_down);
cancel_delayed_work_sync(&priv->led_link_on);
cancel_delayed_work_sync(&priv->led_link_off);
cancel_delayed_work_sync(&priv->led_act_off);
cancel_work_sync(&priv->merge_networks);
/* Free MAC hash list for ADHOC */ for (i = 0; i < IPW_IBSS_MAC_HASH_SIZE; i++) {
list_for_each_safe(p, q, &priv->ibss_mac_hash[i]) {
list_del(p);
kfree(list_entry(p, struct ipw_ibss_seq, list));
}
}
printk(KERN_INFO "%s: Coming out of suspend...\n", dev->name);
/* * Suspend/Resume resets the PCI configuration space, so we have to * re-disable the RETRY_TIMEOUT register (0x41) to keep PCI Tx retries * from interfering with C3 CPU state. pci_restore_state won't help * here since it only restores the first 64 bytes pci config header.
*/
pci_read_config_dword(pdev, 0x40, &val); if ((val & 0x0000ff00) != 0)
pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
/* Set the device back into the PRESENT state; this will also wake
* the queue of needed */
netif_device_attach(dev);
module_param(cmdlog, int, 0444);
MODULE_PARM_DESC(cmdlog, "allocate a ring buffer for logging firmware commands");
module_param(roaming, int, 0444);
MODULE_PARM_DESC(roaming, "enable roaming support (default on)");
module_param(antenna, int, 0444);
MODULE_PARM_DESC(antenna, "select antenna 1=Main, 3=Aux, default 0 [both], 2=slow_diversity (choose the one with lower background noise)");
module_exit(ipw_exit);
module_init(ipw_init);
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.343Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.