o wl3501: new wireless driver for Planet WL 3501 802.11 PCMCIA card

After a long while, and with wireless becoming a hot topic, at least
for me, I get back to work on this driver, Aristeu, Niemeyer, Marcelino,
this will finally be integrated! Whee! :-)
parent e6d3a689
...@@ -296,6 +296,13 @@ config PCMCIA_ATMEL ...@@ -296,6 +296,13 @@ config PCMCIA_ATMEL
firmware package can be downloaded from firmware package can be downloaded from
http://www.thekelleys.org.uk/atmel/atmel_firmware.tar.gz http://www.thekelleys.org.uk/atmel/atmel_firmware.tar.gz
config PCMCIA_WL3501
tristate "Planet WL3501 PCMCIA cards"
depends on NET_RADIO && EXPERIMENTAL && PCMCIA
---help---
A driver for WL3501 PCMCIA 802.11 wireless cards made by Planet.
This driver supports standard Linux wireless extensions.
# yes, this works even when no drivers are selected # yes, this works even when no drivers are selected
config NET_WIRELESS config NET_WIRELESS
bool bool
......
...@@ -23,4 +23,4 @@ obj-$(CONFIG_AIRO_CS) += airo_cs.o airo.o ...@@ -23,4 +23,4 @@ obj-$(CONFIG_AIRO_CS) += airo_cs.o airo.o
# 16-bit wireless PCMCIA client drivers # 16-bit wireless PCMCIA client drivers
obj-$(CONFIG_PCMCIA_RAYCS) += ray_cs.o obj-$(CONFIG_PCMCIA_RAYCS) += ray_cs.o
obj-$(CONFIG_PCMCIA_ATMEL) += atmel_cs.o atmel.o obj-$(CONFIG_PCMCIA_ATMEL) += atmel_cs.o atmel.o
obj-$(CONFIG_PCMCIA_WL3501) += wl3501_cs.o
#ifndef __WL3501_H__
#define __WL3501_H__
#define WL3501_SLOW_DOWN_IO __asm__ __volatile__("outb %al,$0x80")
/* define for WLA 2.0 */
#define WL3501_BLKSZ 256
/*
* ID for input Signals of DRIVER block
* bit[7-5] is block ID: 000
* bit[4-0] is signal ID
*/
#define WL3501_Alarm 0x00
#define WL3501_MdConfirm 0x01
#define WL3501_MdIndicate 0x02
#define WL3501_AssocConfirm 0x03
#define WL3501_AssocIndicate 0x04
#define WL3501_AutheConfirm 0x05
#define WL3501_AutheIndicate 0x06
#define WL3501_DeautheConfirm 0x07
#define WL3501_DeautheIndicate 0x08
#define WL3501_DisassocConfirm 0x09
#define WL3501_DisassocIndicate 0x0A
#define WL3501_GetConfirm 0x0B
#define WL3501_JoinConfirm 0x0C
#define WL3501_PowermgtConfirm 0x0D
#define WL3501_ReassocConfirm 0x0E
#define WL3501_ReassocIndicate 0x0F
#define WL3501_ScanConfirm 0x10
#define WL3501_SetConfirm 0x11
#define WL3501_StartConfirm 0x12
#define WL3501_ResyncConfirm 0x13
#define WL3501_SiteConfirm 0x14
#define WL3501_SaveConfirm 0x15
#define WL3501_RFtestConfirm 0x16
/*
* ID for input Signals of MLME block
* bit[7-5] is block ID: 010
* bit[4-0] is signal ID
*/
#define WL3501_AssocRequest 0x20
#define WL3501_AutheRequest 0x21
#define WL3501_DeautheRequest 0x22
#define WL3501_DisassocRequest 0x23
#define WL3501_GetRequest 0x24
#define WL3501_JoinRequest 0x25
#define WL3501_PowermgtRequest 0x26
#define WL3501_ReassocRequest 0x27
#define WL3501_ScanRequest 0x28
#define WL3501_SetRequest 0x29
#define WL3501_StartRequest 0x2A
#define WL3501_MdRequest 0x2B
#define WL3501_ResyncRequest 0x2C
#define WL3501_SiteRequest 0x2D
#define WL3501_SaveRequest 0x2E
#define WL3501_RFtestRequest 0x2F
#define WL3501_MmConfirm 0x60
#define WL3501_MmIndicate 0x61
#define WL3501_Infrastructure 0
#define WL3501_Independent 1
#define WL3501_Any_bss 2
#define WL3501_ActiveScan 0
#define WL3501_PassiveScan 1
#define WL3501_TxResult_Success 0
#define WL3501_TxResult_NoBss 1
#define WL3501_TxResult_retryLimit 2
#define WL3501_Open_System 0
#define WL3501_Share_Key 1
#define EtherII 0
#define Ether802_3e 1
#define Ether802_3f 2
#define WL3501_STATUS_SUCCESS 0
#define WL3501_STATUS_INVALID 1
#define WL3501_STATUS_TIMEOUT 2
#define WL3501_STATUS_REFUSED 3
#define WL3501_STATUS_MANYREQ 4
#define WL3501_STATUS_ALREADYBSS 5
#define WL3501_FREQ_DOMAIN_FCC 0x10 /* Channel 1 to 11 */
#define WL3501_FREQ_DOMAIN_IC 0x20 /* Channel 1 to 11 */
#define WL3501_FREQ_DOMAIN_ETSI 0x30 /* Channel 1 to 13 */
#define WL3501_FREQ_DOMAIN_SPAIN 0x31 /* Channel 10 to 11 */
#define WL3501_FREQ_DOMAIN_FRANCE 0x32 /* Channel 10 to 13 */
#define WL3501_FREQ_DOMAIN_MKK 0x40 /* Channel 14 */
struct wl3501_tx_hdr {
u16 tx_cnt;
unsigned char sync[16];
u16 sfd;
unsigned char signal;
unsigned char service;
u16 len;
u16 crc16;
u16 frame_ctrl;
u16 duration_id;
unsigned char addr1[6];
unsigned char addr2[6];
unsigned char addr3[6];
u16 seq_ctrl;
unsigned char addr4[6];
};
struct wl3501_rx_hdr {
u16 rx_next_blk;
u16 rc_next_frame_blk;
unsigned char rx_blk_ctrl;
unsigned char rx_next_frame;
unsigned char rx_next_frame1;
unsigned char rssi;
unsigned char time[8];
unsigned char signal;
unsigned char service;
u16 len;
u16 crc16;
u16 frame_ctrl;
u16 duration;
unsigned char addr1[6];
unsigned char addr2[6];
unsigned char addr3[6];
u16 seq;
unsigned char addr4[6];
};
struct wl3501_start_req {
u16 next_blk;
unsigned char sig_id;
unsigned char bss_type;
u16 beacon_period;
u16 dtim_period;
u16 probe_delay;
u16 cap_info;
unsigned char ssid[34];
unsigned char bss_basic_rate_set[10];
unsigned char operational_rate_set[10];
unsigned char cf_pset[8];
unsigned char phy_pset[3];
unsigned char ibss_pset[4];
};
struct wl3501_assoc_req {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 timeout;
u16 cap_info;
u16 listen_interval;
unsigned char mac_addr[6];
};
struct wl3501_assoc_conf {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 status;
};
struct wl3501_assoc_ind {
u16 next_blk;
unsigned char sig_id;
unsigned char mac_addr[6];
};
struct wl3501_auth_req {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 type;
u16 timeout;
unsigned char mac_addr[6];
};
struct wl3501_auth_conf {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 Type;
u16 status;
unsigned char mac_addr[6];
};
struct wl3501_get_conf {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 mib_status;
u16 mib_attrib;
unsigned char mib_value[100];
};
struct wl3501_join_req {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
unsigned char operational_rate_set[10];
u16 reserved2;
u16 timeout;
u16 probe_delay;
unsigned char timestamp[8];
unsigned char local_time[8];
u16 beacon_period;
u16 dtim_period;
u16 cap_info;
unsigned char bss_type;
unsigned char bssid[6];
unsigned char ssid[34];
unsigned char phy_pset[3];
unsigned char cf_pset[8];
unsigned char ibss_pset[4];
unsigned char bss_basic_rate_set[10];
};
struct wl3501_join_conf {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 status;
};
struct wl3501_scan_req {
u16 next_blk;
unsigned char sig_id;
unsigned char bss_type;
u16 probe_delay;
u16 min_chan_time;
u16 max_chan_time;
unsigned char chan_list[14];
unsigned char bssid[6];
unsigned char ssid[34];
unsigned char scan_type;
};
struct wl3501_scan_conf {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 status;
unsigned char timestamp[8];
unsigned char localtime[8];
u16 beacon_period;
u16 dtim_period;
u16 cap_info;
unsigned char bss_type;
unsigned char bssid[6];
unsigned char ssid[34];
unsigned char phy_pset[3];
unsigned char cf_pset[8];
unsigned char ibss_pset[4];
unsigned char bss_basic_rate_set[10];
unsigned char rssi;
};
struct wl3501_start_conf {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 status;
};
struct wl3501_md_req {
u16 next_blk;
unsigned char sig_id;
unsigned char routing;
u16 data;
u16 size;
unsigned char pri;
unsigned char service_class;
unsigned char daddr[6];
unsigned char saddr[6];
};
struct wl3501_md_ind {
u16 next_blk;
unsigned char sig_id;
unsigned char routing;
u16 data;
u16 size;
unsigned char reception;
unsigned char pri;
unsigned char service_class;
unsigned char daddr[6];
unsigned char saddr[6];
};
struct wl3501_md_conf {
u16 next_blk;
unsigned char sig_id;
unsigned char reserved;
u16 data;
unsigned char status;
unsigned char pri;
unsigned char service_class;
};
struct wl3501_resync_req {
u16 next_blk;
unsigned char sig_id;
};
/* For rough constant delay */
#define WL3501_NOPLOOP(n) { int x = 0; while (x++ < n) WL3501_SLOW_DOWN_IO; }
/* Ethernet MAC addr, BSS_ID, or ESS_ID */
/* With this, we may simply write "x=y;" instead of "memcpy(x, y, 6);" */
/* It's more efficiency with compiler's optimization and more clearly */
struct wl3501_mac_addr {
u8 b0;
u8 b1;
u8 b2;
u8 b3;
u8 b4;
u8 b5;
} __attribute__ ((packed));
/* Definitions for supporting clone adapters. */
/* System Interface Registers (SIR space) */
#define WL3501_NIC_GCR ((u8)0x00) /* SIR0 - General Conf Register */
#define WL3501_NIC_BSS ((u8)0x01) /* SIR1 - Bank Switching Select Reg */
#define WL3501_NIC_LMAL ((u8)0x02) /* SIR2 - Local Mem addr Reg [7:0] */
#define WL3501_NIC_LMAH ((u8)0x03) /* SIR3 - Local Mem addr Reg [14:8] */
#define WL3501_NIC_IODPA ((u8)0x04) /* SIR4 - I/O Data Port A */
#define WL3501_NIC_IODPB ((u8)0x05) /* SIR5 - I/O Data Port B */
#define WL3501_NIC_IODPC ((u8)0x06) /* SIR6 - I/O Data Port C */
#define WL3501_NIC_IODPD ((u8)0x07) /* SIR7 - I/O Data Port D */
/* Bits in GCR */
#define WL3501_GCR_SWRESET ((u8)0x80)
#define WL3501_GCR_CORESET ((u8)0x40)
#define WL3501_GCR_DISPWDN ((u8)0x20)
#define WL3501_GCR_ECWAIT ((u8)0x10)
#define WL3501_GCR_ECINT ((u8)0x08)
#define WL3501_GCR_INT2EC ((u8)0x04)
#define WL3501_GCR_ENECINT ((u8)0x02)
#define WL3501_GCR_DAM ((u8)0x01)
/* Bits in BSS (Bank Switching Select Register) */
#define WL3501_BSS_FPAGE0 ((u8)0x20) /* Flash memory page0 */
#define WL3501_BSS_FPAGE1 ((u8)0x28)
#define WL3501_BSS_FPAGE2 ((u8)0x30)
#define WL3501_BSS_FPAGE3 ((u8)0x38)
#define WL3501_BSS_SPAGE0 ((u8)0x00) /* SRAM page0 */
#define WL3501_BSS_SPAGE1 ((u8)0x08)
#define WL3501_BSS_SPAGE2 ((u8)0x10)
#define WL3501_BSS_SPAGE3 ((u8)0x18)
/* Define Driver Interface */
/* Refer IEEE 802.11 */
/* Tx packet header, include PLCP and MPDU */
/* Tx PLCP Header */
struct wl3501_80211_tx_plcp_hdr {
u8 sync[16];
u16 sfd;
u8 signal;
u8 service;
u16 len;
u16 crc16;
} __attribute__ ((packed));
/*
* Data Frame MAC Header (IEEE 802.11)
* FIXME: try to use ieee_802_11_header (see linux/802_11.h)
*/
struct wl3501_80211_data_mac_hdr {
u16 frame_ctrl;
u16 duration_id;
struct wl3501_mac_addr addr1;
struct wl3501_mac_addr addr2;
struct wl3501_mac_addr addr3;
u16 seq_ctrl;
struct wl3501_mac_addr addr4;
} __attribute__ ((packed));
struct wl3501_80211_tx_hdr {
struct wl3501_80211_tx_plcp_hdr pclp_hdr;
struct wl3501_80211_data_mac_hdr mac_hdr;
} __attribute__ ((packed));
/*
Reserve the beginning Tx space for descriptor use.
TxBlockOffset --> *----*----*----*----* \
(TxFreeDesc) | 0 | 1 | 2 | 3 | \
| 4 | 5 | 6 | 7 | |
| 8 | 9 | 10 | 11 | TX_DESC * 20
| 12 | 13 | 14 | 15 | |
| 16 | 17 | 18 | 19 | /
TxBufferBegin --> *----*----*----*----* /
(TxBufferHead) | |
(TxBufferTail) | |
| Send Buffer |
| |
| |
*-------------------*
TxBufferEnd -------------------------/
*/
struct wl3501_card {
int base_addr;
struct wl3501_mac_addr mac_addr;
u16 tx_buffer_size;
u16 tx_buffer_head;
u16 tx_buffer_tail;
u16 tx_buffer_cnt;
u16 esbq_req_start;
u16 esbq_req_end;
u16 esbq_req_head;
u16 esbq_req_tail;
u16 esbq_confirm_start;
u16 esbq_confirm_end;
u16 esbq_confirm;
struct wl3501_mac_addr bssid;
u8 llc_type;
u8 net_type;
u8 essid[34];
u8 keep_essid[34];
u8 ether_type;
u8 chan;
u8 def_chan;
u16 start_seg;
u16 bss_cnt;
u16 join_sta_bss;
u8 cap_info;
u8 adhoc_times;
int card_start;
struct wl3501_scan_conf bss_set[20];
u8 driver_state;
u8 freq_domain;
u8 version[2];
struct net_device_stats stats;
struct iw_statistics wstats;
dev_node_t node;
};
/*
* wl3501_ioctl_blk is put into ifreq.ifr_data which is a union (16 bytes)
* sizeof(wl3501_ioctl_blk) must be less than 16 bytes.
*/
struct wl3501_ioctl_blk {
u16 cmd; /* Command to run */
u16 len; /* Length of the data buffer */
unsigned char *data; /* Pointer to the data buffer */
};
struct wl3501_ioctl_parm {
u8 def_chan;
u8 chan;
u8 net_type;
u8 essid[34];
u8 keep_essid[34];
u8 version[2];
u8 freq_domain;
};
#define WL3501_IOCTL_GET_PARAMETER 0 /* Get parameter */
#define WL3501_IOCTL_SET_PARAMETER 1 /* Get parameter */
#define WL3501_IOCTL_WRITE_FLASH 2 /* Write firmware into Flash ROM */
#define WL3501_IOCTL_SET_RESET 3 /* Reset the firmware */
#endif /* __WL3501_H__ */
/*
* WL3501 Wireless LAN PCMCIA Card Driver for Linux
* Written originally for Linux 2.0.30 by Fox Chen, mhchen@golf.ccl.itri.org.tw
* Ported to 2.2, 2.4 & 2.5 by Arnaldo Carvalho de Melo <acme@conectiva.com.br>
* Wireless extensions in 2.4 by Gustavo Niemeyer <niemeyer@conectiva.com>
*
* References:
*
* 1. WL24xx packet drivers (tooasm.asm)
* 2. Access Point Firmware Interface Specification for IEEE 802.11 SUTRO
* 3. IEEE 802.11
* 4. Linux network driver (/usr/src/linux/drivers/net)
* 5. ISA card driver - wl24.c
* 6. Linux PCMCIA skeleton driver - skeleton.c
* 7. Linux PCMCIA 3c589 network driver - 3c589_cs.c
*
* Tested with WL2400 firmware 1.2, Linux 2.0.30, and pcmcia-cs-2.9.12
* 1. Performance: about 165 Kbytes/sec in TCP/IP with Ad-Hoc mode.
* rsh 192.168.1.3 "dd if=/dev/zero bs=1k count=1000" > /dev/null
* (Specification 2M bits/sec. is about 250 Kbytes/sec., but we must deduct
* ETHER/IP/UDP/TCP header, and acknowledgement overhead)
*
* Tested with Planet AP in 2.4.17, 184 KiB/s in UDP in Infrastructure mode,
* 173 KiB/s in TCP.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/ioport.h>
#include <linux/fcntl.h>
#include <linux/wireless.h>
#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/ds.h>
#include "wl3501.h"
/*
* All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If you do not
* define PCMCIA_DEBUG at all, all the debug code will be left out. If you
* compile with PCMCIA_DEBUG=0, the debug code will be present but disabled --
* but it can then be enabled for specific modules at load time with a
* 'pc_debug=#' option to insmod.
*/
//#define PCMCIA_DEBUG 100
#ifdef PCMCIA_DEBUG
static int pc_debug = PCMCIA_DEBUG;
MODULE_PARM(pc_debug, "i");
#define dprintk(n, format, args...) \
{ if (pc_debug > (n)) \
printk(KERN_INFO "%s: " format "\n", __FUNCTION__, ##args); }
#else
#define dprintk(n, format, args...)
#endif
static u8 wl3501_fpage[] = { WL3501_BSS_FPAGE0, WL3501_BSS_FPAGE1,
WL3501_BSS_FPAGE2, WL3501_BSS_FPAGE3 };
#define wl3501_outb(a, b) { outb(a, b); WL3501_SLOW_DOWN_IO; }
#define wl3501_outb_p(a, b) { outb_p(a, b); WL3501_SLOW_DOWN_IO; }
#define wl3501_outsb(a, b, c) { outsb(a, b, c); WL3501_SLOW_DOWN_IO; }
/* Parameters that can be set with 'insmod' */
/* Bit map of interrupts to choose from */
/* This means pick from 15, 14, 12, 11, 10, 9, 7, 5, 4, and 3 */
static unsigned long wl3501_irq_mask = 0xdeb8;
static int wl3501_irq_list[4] = { -1 };
MODULE_PARM(wl3501_irq_mask, "i");
MODULE_PARM(wl3501_irq_list, "1-4i");
MODULE_AUTHOR("Fox Chen <mhchen@golf.ccl.itri.org.tw>, "
"Arnaldo Carvalho de Melo <acme@conectiva.com.br>,"
"Gustavo Niemeyer <niemeyer@conectiva.com.br>");
MODULE_DESCRIPTION("Planet wl3501 wireless driver");
MODULE_LICENSE("GPL");
/*
* The event() function is this driver's Card Services event handler. It will
* be called by Card Services when an appropriate card status event is
* received. The config() and release() entry points are used to configure or
* release a socket, in response to card insertion and ejection events. They
* are invoked from the wl24 event handler.
*/
static void wl3501_config(dev_link_t *link);
static void wl3501_release(unsigned long arg);
static int wl3501_event(event_t event, int pri, event_callback_args_t *args);
/*
* The dev_info variable is the "key" that is used to match up this
* device driver with appropriate cards, through the card configuration
* database.
*/
static dev_info_t wl3501_dev_info = "wl3501_cs";
/*
* A linked list of "instances" of the wl24 device. Each actual PCMCIA card
* corresponds to one device instance, and is described by one dev_link_t
* structure (defined in ds.h).
*
* You may not want to use a linked list for this -- for example, the memory
* card driver uses an array of dev_link_t pointers, where minor device numbers
* are used to derive the corresponding array index.
*/
static dev_link_t *wl3501_dev_list;
static __inline__ void wl3501_switch_page(struct wl3501_card *this, u8 page)
{
wl3501_outb(page, this->base_addr + WL3501_NIC_BSS);
}
/*
* Hold SUTRO. (i.e. make SUTRO stop)
* Return: 1 if SUTRO is originally running
*/
static int wl3501_hold_sutro(struct wl3501_card *this)
{
u8 old = inb(this->base_addr + WL3501_NIC_GCR);
u8 new = (old & ~(WL3501_GCR_ECINT | WL3501_GCR_INT2EC)) |
WL3501_GCR_ECWAIT;
wl3501_outb(new, this->base_addr + WL3501_NIC_GCR);
return !(old & WL3501_GCR_ECWAIT);
}
/*
* UnHold SUTRO. (i.e. make SUTRO running)
* Return: 1 if SUTRO is originally running
*/
static int wl3501_unhold_sutro(struct wl3501_card *this)
{
u8 old = inb(this->base_addr + WL3501_NIC_GCR);
u8 new = old & (~(WL3501_GCR_ECINT | WL3501_GCR_INT2EC |
WL3501_GCR_ECWAIT));
wl3501_outb(new, this->base_addr + WL3501_NIC_GCR);
return !(old & WL3501_GCR_ECWAIT);
}
/*
* Get Ethernet MAC addresss.
*
* WARNING: We switch to FPAGE0 and switc back again.
* Making sure there is no other WL function beening called by ISR.
*/
static int wl3501_get_flash_mac_addr(struct wl3501_card *this)
{
int base_addr = this->base_addr;
/* get MAC addr */
wl3501_outb(WL3501_BSS_FPAGE3, base_addr + WL3501_NIC_BSS); /* BSS */
wl3501_outb(0x00, base_addr + WL3501_NIC_LMAL); /* LMAL */
wl3501_outb(0x40, base_addr + WL3501_NIC_LMAH); /* LMAH */
/* wait for reading EEPROM */
WL3501_NOPLOOP(100);
this->mac_addr.b0 = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
this->mac_addr.b1 = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
this->mac_addr.b2 = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
this->mac_addr.b3 = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
this->mac_addr.b4 = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
this->mac_addr.b5 = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
this->freq_domain = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
wl3501_outb(WL3501_BSS_FPAGE0, base_addr + WL3501_NIC_BSS);
wl3501_outb(0x04, base_addr + WL3501_NIC_LMAL);
wl3501_outb(0x40, base_addr + WL3501_NIC_LMAH);
WL3501_NOPLOOP(100);
this->version[0] = inb(base_addr + WL3501_NIC_IODPA);
WL3501_NOPLOOP(100);
this->version[1] = inb(base_addr + WL3501_NIC_IODPA);
/* switch to SRAM Page 0 (for safety) */
wl3501_switch_page(this, WL3501_BSS_SPAGE0);
/* The MAC addr should be 00:60:... */
return this->mac_addr.b0 == 0x00 && this->mac_addr.b1 == 0x60;
}
static void wl3501_flash_outb(struct wl3501_card *this, u16 page, u16 addr,
u8 data)
{
/* switch to Flash RAM Page page */
wl3501_outb(wl3501_fpage[page], this->base_addr + WL3501_NIC_BSS);
/* set LMAL and LMAH */
wl3501_outb(addr & 0xff, this->base_addr + WL3501_NIC_LMAL);
wl3501_outb(addr >> 8, this->base_addr + WL3501_NIC_LMAH);
/* out data to Port A */
wl3501_outb(data, this->base_addr + WL3501_NIC_IODPA);
}
static u8 wl3501_flash_inb(struct wl3501_card *this, u16 page, u16 addr)
{
/* switch to Flash RAM Page page */
wl3501_outb(wl3501_fpage[page], this->base_addr + WL3501_NIC_BSS);
/* set LMAL and LMAH */
wl3501_outb(addr & 0xff, this->base_addr + WL3501_NIC_LMAL);
wl3501_outb(addr >> 8, this->base_addr + WL3501_NIC_LMAH);
/* out data to Port A */
return inb(this->base_addr + WL3501_NIC_IODPA);
}
/*
* When calling this function, must hold SUTRO first.
*/
static u16 wl3501_get_flash_id(struct wl3501_card *this)
{
u8 byte0, byte1;
u16 id;
/* Autoselect command */
wl3501_flash_outb(this, 0, 0x5555, 0xaa);
wl3501_flash_outb(this, 0, 0x2aaa, 0x55);
wl3501_flash_outb(this, 0, 0x5555, 0x90);
WL3501_NOPLOOP(10000);
byte0 = wl3501_flash_inb(this, 0, 0x0);
byte1 = wl3501_flash_inb(this, 0, 0x1);
id = (byte0 << 8) | byte1;
printk(KERN_INFO "Flash ROM ID = 0x%x\n", id);
return id;
}
/*
* Polling if Erase/Programming command is completed
* Note: IF a == b THEN XOR(a,b) = 0
*
* When calling this function, must hold SUTRO first.
*/
static int wl3501_flash_write_ok(struct wl3501_card *this)
{
u8 byte0, byte1;
/* Check 'Toggle Bit' (DQ6) to see if completed */
do {
byte0 = wl3501_flash_inb(this, 0, 0x0);
byte1 = wl3501_flash_inb(this, 0, 0x0);
/* Test if exceeded Time Limits (DQ5) */
if (byte1 & 0x20) {
/* Must test DQ6 again before return 0 */
byte0 = wl3501_flash_inb(this, 0, 0x0);
byte1 = wl3501_flash_inb(this, 0, 0x0);
return !((byte0 ^ byte1) & 0x40);
}
} while ((byte0 ^ byte1) & 0x40);
return 1;
}
/*
* When calling this function, must hold SUTRO first.
*/
static int wl3501_flash_erase_sector(struct wl3501_card *this, u16 sector)
{
u16 page = sector / 2;
u16 addr = (sector & 1) ? 0x4000 : 0x0;
/* Sector Erase command (6 commands must within 100 uS) */
wl3501_flash_outb(this, 0, 0x5555, 0xaa);
wl3501_flash_outb(this, 0, 0x2aaa, 0x55);
wl3501_flash_outb(this, 0, 0x5555, 0x80);
wl3501_flash_outb(this, 0, 0x5555, 0xaa);
wl3501_flash_outb(this, 0, 0x2aaa, 0x55);
wl3501_flash_outb(this, page, addr, 0x30);
return wl3501_flash_write_ok(this);
}
/*
* When calling this function, must hold SUTRO first.
*/
int wl3501_flash_writeb(struct wl3501_card *this, u16 page, u16 addr,
u8 data)
{
/* Autoselect command */
wl3501_flash_outb(this, 0, 0x5555, 0xaa);
wl3501_flash_outb(this, 0, 0x2aaa, 0x55);
wl3501_flash_outb(this, 0, 0x5555, 0xa0);
wl3501_flash_outb(this, page, addr, data);
return wl3501_flash_write_ok(this);
}
/**
* wl3501_write_flash - Write mibExtra into flash ROM
* @this - card
* @bf - buffer to write
* @len = buffer length
*
* In fact, only first 29 bytes are used. (not all of extra MIB)
* To prevent alter other data will be used by future versions, we preserve
* 256 bytes.
*/
static int wl3501_write_flash(struct wl3501_card *this, unsigned char *bf,
int len)
{
int i;
u32 bf_addr = *(u32 *)bf;
int running = wl3501_hold_sutro(this);
u16 flash_id = wl3501_get_flash_id(this);
bf += 4;
len -= 4;
if (flash_id == 0x0120) {
/* It's AMD AM29F010, must be erase before programming */
/* Erase 1st 16 Kbytes within Page 0 */
if (!(bf_addr & 0x3fff)) {
if (!wl3501_flash_erase_sector(this, bf_addr >> 14)) {
printk(KERN_WARNING
"wl3501_flash_erase_sector(0) failed\n");
if (running)
wl3501_unhold_sutro(this);
return 0;
}
}
} else if (flash_id != 0xf51d) {
/* It's not AT29C010A */
printk(KERN_WARNING "Flash ROM type (0x%x) is unknown!\n",
flash_id);
if (running)
wl3501_unhold_sutro(this);
return 0;
}
/* Programming flash ROM byte by byte */
for (i = 0; i < len; i++) {
if (!wl3501_flash_writeb(this, bf_addr >> 15, i, *bf)) {
printk(KERN_WARNING
"wl3501_flash_writeb(buf[%d]) failed!\n", i);
if (running)
wl3501_unhold_sutro(this);
return 0;
}
bf++;
}
#if 0
/* Reset command */
wl3501_outb(this, 0, 0x5555, 0xaa);
wl3501_outb(this, 0, 0x2aaa, 0x55);
wl3501_outb(this, 0, 0x5555, 0xf0);
WL3501_NOPLOOP(10000);
if (running)
wl3501_unhold_sutro(this);
#endif
return 1;
}
/**
* wl3501_set_to_wla - Move 'size' bytes from PC to card
* @dest: Card addressing space
* @src: PC addressing space
* @size: Bytes to move
*
* Move 'size' bytes from PC to card. (Shouldn't be interrupted)
*/
void wl3501_set_to_wla(struct wl3501_card *this, u16 dest, void *src, int size)
{
/* switch to SRAM Page 0 */
wl3501_switch_page(this, (dest & 0x8000) ? WL3501_BSS_SPAGE1 :
WL3501_BSS_SPAGE0);
/* wl3501_outb(((dest>> 11) & 0x18), this->base_addr +
WL3501_NIC_BSS); */
/* set LMAL and LMAH */
wl3501_outb(dest & 0xff, this->base_addr + WL3501_NIC_LMAL);
wl3501_outb(((dest >> 8) & 0x7f), this->base_addr + WL3501_NIC_LMAH);
/* rep out to Port A */
wl3501_outsb(this->base_addr + WL3501_NIC_IODPA, src, size);
}
/**
* wl3501_get_from_wla - Move 'size' bytes from card to PC
* @src: Card addressing space
* @dest: PC addressing space
* @size: Bytes to move
*
* Move 'size' bytes from card to PC. (Shouldn't be interrupted)
*/
void wl3501_get_from_wla(struct wl3501_card *this, u16 src, void *dest,
int size)
{
/* switch to SRAM Page 0 */
wl3501_switch_page(this, (src & 0x8000) ? WL3501_BSS_SPAGE1 :
WL3501_BSS_SPAGE0);
/* wl3501_outb(((src>> 11) & 0x18), this->base_addr +
WL3501_NIC_BSS); */
/* set LMAL and LMAH */
wl3501_outb(src & 0xff, this->base_addr + WL3501_NIC_LMAL);
wl3501_outb((src >> 8) & 0x7f, this->base_addr + WL3501_NIC_LMAH);
/* rep get from Port A */
insb(this->base_addr + WL3501_NIC_IODPA, dest, size);
}
/*
* Get/Allocate a free Tx Data Buffer
*
* *--------------*-----------------*----------------------------------*
* | PLCP | MAC Header | DST SRC Data ... |
* | (24 bytes) | (30 bytes) | (6) (6) (Ethernet Row Data) |
* *--------------*-----------------*----------------------------------*
* \ \- IEEE 802.11 -/ \------------ uDataLen -----------/
* \-struct wl3501_80211_tx_hdr--/ \-------- Ethernet Frame -------/
*
* Return = Postion in Card
*/
static u16 wl3501_get_tx_buffer(struct wl3501_card *this, u16 len)
{
u16 next, blk_cnt = 0, zero = 0;
u16 full_len = sizeof(struct wl3501_80211_tx_hdr) + len;
u16 ret = 0;
if (full_len > this->tx_buffer_cnt * 254)
goto out;
ret = this->tx_buffer_head;
while (full_len) {
if (full_len < 254)
full_len = 0;
else
full_len -= 254;
wl3501_get_from_wla(this, this->tx_buffer_head, &next,
sizeof(next));
if (!full_len)
wl3501_set_to_wla(this, this->tx_buffer_head, &zero,
sizeof(zero));
this->tx_buffer_head = next;
blk_cnt++;
/* if buffer is not enough */
if (!next && full_len) {
this->tx_buffer_head = ret;
ret = 0;
goto out;
}
}
this->tx_buffer_cnt -= blk_cnt;
out:
return ret;
}
/*
* Free an allocated Tx Buffer. ptr must be correct position.
*/
static void wl3501_free_tx_buffer(struct wl3501_card *this, u16 ptr)
{
/* check if all space is not free */
if (!this->tx_buffer_head)
this->tx_buffer_head = ptr;
else
wl3501_set_to_wla(this, this->tx_buffer_tail,
&ptr, sizeof(ptr));
while (ptr) {
u16 next;
this->tx_buffer_cnt++;
wl3501_get_from_wla(this, ptr, &next, sizeof(next));
this->tx_buffer_tail = ptr;
ptr = next;
}
}
static int wl3501_esbq_req_test(struct wl3501_card *this)
{
u8 tmp;
wl3501_get_from_wla(this, this->esbq_req_head + 3, &tmp, sizeof(tmp));
return tmp & 0x80;
}
static void wl3501_esbq_req(struct wl3501_card *this, u16 * ptr)
{
u16 tmp = 0;
wl3501_set_to_wla(this, this->esbq_req_head, ptr, 2);
wl3501_set_to_wla(this, this->esbq_req_head + 2, &tmp, sizeof(tmp));
this->esbq_req_head += 4;
if (this->esbq_req_head >= this->esbq_req_end)
this->esbq_req_head = this->esbq_req_start;
}
/**
* wl3501_send_pkt - Send a packet.
* @this - card
*
* Send a packet.
*
* data = Ethernet raw frame. (e.g. data[0] - data[5] is Dest MAC Addr,
* data[6] - data[11] is Src MAC Addr)
* Ref: IEEE 802.11
*/
static int wl3501_send_pkt(struct wl3501_card *this, u8 * data, u16 len)
{
u16 bf, sig, next, tmplen, pktlen, tmp;
struct wl3501_md_req signal;
u8 *pdata = (char *)data;
if (wl3501_esbq_req_test(this)) {
sig = wl3501_get_tx_buffer(this, sizeof(signal));
if (!sig) /* No free buffer available */
return 0;
bf = wl3501_get_tx_buffer(this, len + 26 + 24);
if (!bf) {
/* No free buffer available */
wl3501_free_tx_buffer(this, sig);
return 0;
}
memcpy((char *)&(signal.daddr[0]), pdata, 12);
pktlen = len - 12;
pdata += 12;
signal.next_blk = 0;
signal.sig_id = WL3501_MdRequest;
signal.data = bf;
if (this->llc_type == 1) {
if (((*pdata) * 256 + (*(pdata + 1))) > 1500) {
signal.size = pktlen + 24 + 4 + 6;
tmp = 0xaaaa;
wl3501_set_to_wla(this, bf + 2 +
sizeof(struct wl3501_tx_hdr) -
6, &tmp, sizeof(tmp));
tmp = 0x03;
wl3501_set_to_wla(this, bf + 2 +
sizeof(struct wl3501_tx_hdr) -
4, &tmp, sizeof(tmp));
tmp = 0x0;
wl3501_set_to_wla(this, bf + 2 +
sizeof(struct wl3501_tx_hdr) -
2, &tmp, sizeof(tmp));
if (pktlen >
(254 - sizeof(struct wl3501_tx_hdr))) {
tmplen =
254 - sizeof(struct wl3501_tx_hdr);
pktlen -= tmplen;
} else {
tmplen = pktlen;
pktlen = 0;
}
wl3501_set_to_wla(this, bf + 2 +
sizeof(struct wl3501_tx_hdr),
pdata, tmplen);
pdata += tmplen;
wl3501_get_from_wla(this, bf, &next,
sizeof(next));
bf = next;
} else {
signal.size = pktlen + 24 + 4 - 2;
pdata += 2;
pktlen -= 2;
if (pktlen >
(254 - sizeof(struct wl3501_tx_hdr) + 6)) {
tmplen = 254 -
sizeof(struct wl3501_tx_hdr) + 6;
pktlen -= tmplen;
} else {
tmplen = pktlen;
pktlen = 0;
}
wl3501_set_to_wla(this, bf + 2 +
sizeof(struct wl3501_tx_hdr) -
6, pdata, tmplen);
pdata += tmplen;
wl3501_get_from_wla(this, bf, &next,
sizeof(next));
bf = next;
}
} else {
pktlen += 12;
signal.size = pktlen + 24 + 4;
pdata -= 12;
if (pktlen > (254 - sizeof(struct wl3501_tx_hdr) + 6)) {
tmplen = 254 - sizeof(struct wl3501_tx_hdr) + 6;
pktlen -= tmplen;
} else {
tmplen = pktlen;
pktlen = 0;
}
wl3501_set_to_wla(this, bf + 2 +
sizeof(struct wl3501_tx_hdr) - 6,
pdata, tmplen);
pdata += tmplen;
wl3501_get_from_wla(this, bf, &next, sizeof(next));
bf = next;
}
while (pktlen > 0) {
if (pktlen > 254) {
tmplen = 254;
pktlen -= 254;
} else {
tmplen = pktlen;
pktlen = 0;
}
wl3501_set_to_wla(this, bf + 2, pdata, tmplen);
pdata += tmplen;
wl3501_get_from_wla(this, bf, &next, sizeof(next));
bf = next;
}
wl3501_set_to_wla(this, sig, &signal, sizeof(signal));
wl3501_esbq_req(this, &sig);
}
return 1;
}
static int wl3501_mgmt_resync(struct wl3501_card *this)
{
struct wl3501_resync_req signal;
u16 ptr;
signal.next_blk = 0;
signal.sig_id = WL3501_ResyncRequest;
if (wl3501_esbq_req_test(this)) {
ptr = wl3501_get_tx_buffer(this, sizeof(signal));
if (ptr) {
wl3501_set_to_wla(this, ptr, &signal, sizeof(signal));
wl3501_esbq_req(this, &ptr);
return 1;
}
}
return 0;
}
static int wl3501_mgmt_scan(struct wl3501_card *this, u16 chan_time)
{
struct wl3501_scan_req signal;
u16 ptr;
signal.next_blk = 0;
signal.sig_id = WL3501_ScanRequest;
signal.ssid[0] = 0;
signal.ssid[1] = 0;
signal.scan_type = WL3501_ActiveScan;
signal.probe_delay = 0x10;
signal.min_chan_time = chan_time;
signal.max_chan_time = chan_time;
if (this->net_type == WL3501_Infrastructure)
signal.bss_type = WL3501_Infrastructure;
else
signal.bss_type = WL3501_Independent;
this->bss_cnt = this->join_sta_bss = 0;
if (wl3501_esbq_req_test(this)) {
ptr = wl3501_get_tx_buffer(this, sizeof(signal));
if (ptr) {
wl3501_set_to_wla(this, ptr, &signal, sizeof(signal));
wl3501_esbq_req(this, &ptr);
return 1;
}
}
return 0;
}
static int wl3501_mgmt_join(struct wl3501_card *this, u16 stas)
{
struct wl3501_join_req signal;
u16 ptr;
signal.next_blk = 0;
signal.sig_id = WL3501_JoinRequest;
signal.timeout = 10;
memcpy((char *)&(signal.beacon_period),
(char *)&(this->bss_set[stas].beacon_period), 72);
this->cap_info = signal.cap_info;
this->chan = signal.phy_pset[2];
if (wl3501_esbq_req_test(this)) {
ptr = wl3501_get_tx_buffer(this, sizeof(signal));
if (ptr) {
wl3501_set_to_wla(this, ptr, &signal, sizeof(signal));
wl3501_esbq_req(this, &ptr);
return 1;
}
}
return 0;
}
static int wl3501_mgmt_start(struct wl3501_card *this)
{
struct wl3501_start_req signal;
u16 ptr;
signal.next_blk = 0;
signal.sig_id = WL3501_StartRequest;
memcpy((char *)signal.ssid, (char *)this->essid, 34);
memcpy((char *)this->keep_essid, (char *)this->essid, 34);
signal.bss_type = WL3501_Independent;
signal.beacon_period = 400;
signal.dtim_period = 1;
signal.phy_pset[0] = 3;
signal.phy_pset[1] = 1;
signal.phy_pset[2] = this->chan;
signal.cap_info = 0x02;
signal.bss_basic_rate_set[0] = 0x01;
signal.bss_basic_rate_set[1] = 0x02;
signal.bss_basic_rate_set[2] = 0x82;
signal.bss_basic_rate_set[3] = 0x84;
signal.operational_rate_set[0] = 0x01;
signal.operational_rate_set[1] = 0x02;
signal.operational_rate_set[2] = 0x82;
signal.operational_rate_set[3] = 0x84;
signal.ibss_pset[0] = 6;
signal.ibss_pset[1] = 2;
signal.ibss_pset[2] = 10;
signal.ibss_pset[3] = 0;
if (wl3501_esbq_req_test(this)) {
ptr = wl3501_get_tx_buffer(this, sizeof(signal));
if (ptr) {
wl3501_set_to_wla(this, ptr, &signal, sizeof(signal));
wl3501_esbq_req(this, &ptr);
return 1;
}
}
return 0;
}
#ifdef WL3501_STUPID_LOOP
#define wl3501_cmdloop(__cmd) \
({ int i; \
for (i = 0; i < 3000; i++) \
if (__cmd) \
break; \
i != 3000; \
})
#else
#define wl3501_cmdloop(__cmd) ({ __cmd; 1; })
#endif
static void wl3501_mgmt_scan_confirm(struct wl3501_card *this, u16 addr)
{
u16 i, j;
int matchflag = 0;
struct wl3501_scan_conf signal;
wl3501_get_from_wla(this, addr, &signal, sizeof(signal));
if (signal.status == WL3501_STATUS_SUCCESS) {
if (this->driver_state == WL3501_ScanRequest) {
for (i = 0; i < this->bss_cnt; i++) {
if (!memcmp((char *)this->bss_set[i].bssid,
(char *)signal.bssid, 6))
break;
}
if ((i == this->bss_cnt) && i < 20) {
memcpy((char *)
&(this->bss_set[i].beacon_period),
(char *)&(signal.beacon_period), 73);
this->bss_cnt++;
}
} else if (((this->net_type == WL3501_Infrastructure) &&
(signal.cap_info & 0x01)) ||
((this->net_type == WL3501_Independent) &&
(signal.cap_info & 0x02)) ||
this->net_type == 2) {
if (this->essid[1] == 0)
matchflag = 1;
else if (this->essid[1] == 3 &&
!strncmp((char *)&this->essid[2], "ANY", 3))
matchflag = 1;
else if (this->essid[1] != signal.ssid[1])
matchflag = 0;
else if (memcmp((char *)&(this->essid[2]),
(char *)&(signal.ssid[2]),
this->essid[1]))
matchflag = 0;
else
matchflag = 1;
if (matchflag) {
for (i = 0; i < this->bss_cnt; i++) {
if (memcmp
((char *)this->bss_set[i].bssid,
(char *)signal.bssid, 6) == 0) {
matchflag = 0;
break;
}
}
}
if (matchflag && (i < 20)) {
memcpy((char *)
&(this->bss_set[i].beacon_period),
(char *)&(signal.beacon_period), 73);
this->bss_cnt++;
}
}
} else if ((signal.status == WL3501_STATUS_TIMEOUT) &&
(this->driver_state != WL3501_ScanRequest)) {
this->join_sta_bss = 0;
for (j = this->join_sta_bss; j < this->bss_cnt; j++)
if (wl3501_cmdloop(wl3501_mgmt_join(this, j)))
break;
this->join_sta_bss = j;
if (this->join_sta_bss == this->bss_cnt) {
if (this->net_type == WL3501_Infrastructure)
wl3501_cmdloop(wl3501_mgmt_scan(this, 100));
else {
this->adhoc_times++;
if (this->adhoc_times > 8)
wl3501_cmdloop(wl3501_mgmt_start(this));
else
wl3501_cmdloop(wl3501_mgmt_scan(this, 100));
}
}
}
}
/**
* wl3501_block_interrupt - Mask interrupt from SUTRO
* @this - card
*
* Mask interrupt from SUTRO. (i.e. SUTRO cannot interrupt the HOST)
* Return: 1 if interrupt is originally enabled
*/
static int wl3501_block_interrupt(struct wl3501_card *this)
{
u8 old = inb(this->base_addr + WL3501_NIC_GCR);
u8 new = old & (~(WL3501_GCR_ECINT | WL3501_GCR_INT2EC |
WL3501_GCR_ENECINT));
wl3501_outb(new, this->base_addr + WL3501_NIC_GCR);
return old & WL3501_GCR_ENECINT;
}
/**
* wl3501_unblock_interrupt - Enable interrupt from SUTRO
* @this - card
*
* Enable interrupt from SUTRO. (i.e. SUTRO can interrupt the HOST)
* Return: 1 if interrupt is originally enabled
*/
static int wl3501_unblock_interrupt(struct wl3501_card *this)
{
u8 old = inb(this->base_addr + WL3501_NIC_GCR);
u8 new = (old & ~(WL3501_GCR_ECINT | WL3501_GCR_INT2EC)) |
WL3501_GCR_ENECINT;
wl3501_outb(new, this->base_addr + WL3501_NIC_GCR);
return old & WL3501_GCR_ENECINT;
}
/**
* wl3501_receive - Receive data from Receive Queue.
*
* Receive data from Receive Queue.
*
* @this: card
* @bf: address of host
* @size: size of buffer.
*/
static u16 wl3501_receive(struct wl3501_card *this, u8 *bf, u16 size)
{
u16 next_addr, next_addr1;
u8 *data = bf + 12;
size -= 12;
wl3501_get_from_wla(this, this->start_seg + 2,
&next_addr, sizeof(next_addr));
if (this->llc_type == 1) {
if (this->ether_type == EtherII) {
if (size >
WL3501_BLKSZ - sizeof(struct wl3501_rx_hdr)) {
wl3501_get_from_wla(this, this->start_seg +
sizeof(struct
wl3501_rx_hdr), data,
WL3501_BLKSZ -
sizeof(struct
wl3501_rx_hdr));
size -=
WL3501_BLKSZ - sizeof(struct wl3501_rx_hdr);
data +=
WL3501_BLKSZ - sizeof(struct wl3501_rx_hdr);
} else {
wl3501_get_from_wla(this, this->start_seg +
sizeof(struct
wl3501_rx_hdr), data,
size);
size = 0;
}
} else {
size -= 2;
*data = (size >> 8) & 0xff;
*(data + 1) = size & 0xff;
data += 2;
if (size >
WL3501_BLKSZ - sizeof(struct wl3501_rx_hdr) + 6) {
wl3501_get_from_wla(this, this->start_seg +
sizeof(struct wl3501_rx_hdr)
- 6, data,
WL3501_BLKSZ -
sizeof(struct wl3501_rx_hdr)
+ 6);
size =
size - (WL3501_BLKSZ -
sizeof(struct wl3501_rx_hdr)) + 6;
data +=
WL3501_BLKSZ -
sizeof(struct wl3501_rx_hdr) + 6;
} else {
wl3501_get_from_wla(this, this->start_seg +
sizeof(struct wl3501_rx_hdr)
- 6, data, size);
size = 0;
}
}
} else {
if (size > WL3501_BLKSZ - sizeof(struct wl3501_rx_hdr) - 6) {
wl3501_get_from_wla(this, this->start_seg +
sizeof(struct wl3501_rx_hdr) + 6,
data, WL3501_BLKSZ -
sizeof(struct wl3501_rx_hdr) - 6);
size = size - (WL3501_BLKSZ -
sizeof(struct wl3501_rx_hdr)) - 6;
data +=
(WL3501_BLKSZ - sizeof(struct wl3501_rx_hdr) - 6);
} else {
wl3501_get_from_wla(this, this->start_seg +
sizeof(struct wl3501_rx_hdr) + 6,
data, size);
size = 0;
}
}
while (size > 0) {
if (size > WL3501_BLKSZ - 5) {
wl3501_get_from_wla(this, next_addr + 5, data,
WL3501_BLKSZ - 5);
size -= (WL3501_BLKSZ - 5);
data -= (WL3501_BLKSZ - 5);
wl3501_get_from_wla(this, next_addr + 2, &next_addr1,
sizeof(next_addr1));
next_addr = next_addr1;
} else {
wl3501_get_from_wla(this, next_addr + 5, data, size);
size = 0;
}
}
return 0;
}
static void wl3501_esbq_req_free(struct wl3501_card *this)
{
u8 tmp;
u16 addr;
if (this->esbq_req_head == this->esbq_req_tail)
goto out;
wl3501_get_from_wla(this, this->esbq_req_tail + 3, &tmp, sizeof(tmp));
if (!(tmp & 0x80))
goto out;
wl3501_get_from_wla(this, this->esbq_req_tail, &addr, sizeof(addr));
wl3501_free_tx_buffer(this, addr);
this->esbq_req_tail += 4;
if (this->esbq_req_tail >= this->esbq_req_end)
this->esbq_req_tail = this->esbq_req_start;
out:
return;
}
static int wl3501_esbq_confirm(struct wl3501_card *this)
{
u8 tmp;
wl3501_get_from_wla(this, this->esbq_confirm + 3, &tmp, sizeof(tmp));
return tmp & 0x80;
}
static void wl3501_esbq_confirm_done(struct wl3501_card *this)
{
u8 tmp = 0;
wl3501_set_to_wla(this, this->esbq_confirm + 3, &tmp, sizeof(tmp));
this->esbq_confirm += 4;
if (this->esbq_confirm >= this->esbq_confirm_end)
this->esbq_confirm = this->esbq_confirm_start;
}
static int wl3501_mgmt_auth(struct wl3501_card *this)
{
struct wl3501_auth_req signal;
u16 ptr;
signal.next_blk = 0;
signal.sig_id = WL3501_AutheRequest;
signal.type = WL3501_Open_System;
signal.timeout = 1000;
memcpy((char *)&(signal.mac_addr), (char *)&(this->bssid), 6);
if (wl3501_esbq_req_test(this)) {
ptr = wl3501_get_tx_buffer(this, sizeof(signal));
if (ptr) {
wl3501_set_to_wla(this, ptr, &signal, sizeof(signal));
wl3501_esbq_req(this, &ptr);
return 1;
}
}
return 0;
}
static int wl3501_mgmt_association(struct wl3501_card *this)
{
struct wl3501_assoc_req signal;
u16 ptr;
signal.next_blk = 0;
signal.sig_id = WL3501_AssocRequest;
signal.timeout = 1000;
signal.listen_interval = 5;
signal.cap_info = this->cap_info;
memcpy((char *)&(signal.mac_addr), (char *)&(this->bssid), 6);
if (wl3501_esbq_req_test(this)) {
ptr = wl3501_get_tx_buffer(this, sizeof(signal));
if (ptr) {
wl3501_set_to_wla(this, ptr, &signal, sizeof(signal));
wl3501_esbq_req(this, &ptr);
return 1;
}
}
return 0;
}
static void wl3501_mgmt_join_confirm(struct wl3501_card *this, u16 addr)
{
u16 i, j;
struct wl3501_join_conf signal;
wl3501_get_from_wla(this, addr, &signal, sizeof(signal));
if (signal.status == WL3501_STATUS_SUCCESS) {
if (this->net_type == WL3501_Infrastructure) {
if (this->join_sta_bss < this->bss_cnt) {
i = this->join_sta_bss;
memcpy((char *)&(this->bssid),
(char *)&(this->bss_set[i].bssid), 6);
this->chan = this->bss_set[i].phy_pset[2];
memcpy((char *)this->keep_essid,
(char *)this->bss_set[i].ssid, 34);
wl3501_cmdloop(wl3501_mgmt_auth(this));
}
} else {
this->card_start = 1;
i = this->join_sta_bss;
memcpy((char *)&(this->bssid),
(char *)&(this->bss_set[i].bssid), 6);
this->chan = this->bss_set[i].phy_pset[2];
memcpy((char *)this->keep_essid,
(char *)this->bss_set[i].ssid, 34);
printk(KERN_INFO "Wireless LAN On-Line Join:"
" %2X %2X %2X %2X %2X %2X\n", this->bssid.b0,
this->bssid.b1, this->bssid.b2, this->bssid.b3,
this->bssid.b4, this->bssid.b5);
}
} else {
this->join_sta_bss++;
for (j = this->join_sta_bss; j < this->bss_cnt; j++)
if (wl3501_cmdloop(wl3501_mgmt_join(this, j)))
break;
this->join_sta_bss = j;
if (this->join_sta_bss == this->bss_cnt) {
if (this->net_type == WL3501_Infrastructure)
wl3501_cmdloop(wl3501_mgmt_scan(this, 100));
else {
this->adhoc_times++;
if (this->adhoc_times > 8)
wl3501_cmdloop(wl3501_mgmt_start(this));
else
wl3501_cmdloop(wl3501_mgmt_scan(this, 100));
}
}
}
}
static void wl3501_rx_interrupt(struct net_device *dev)
{
int size;
int morepkts;
u16 addr, tmp, pkt_len;
u8 sig_id;
struct wl3501_md_conf md_conf;
struct wl3501_md_ind md_ind;
struct wl3501_get_conf get_conf;
struct wl3501_start_conf start_conf;
struct wl3501_auth_conf auth_conf;
struct wl3501_assoc_conf assoc_conf;
struct sk_buff *skb;
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
loop:
morepkts = 0;
if (!wl3501_esbq_confirm(this))
goto free;
wl3501_get_from_wla(this, this->esbq_confirm, &addr, sizeof(addr));
wl3501_get_from_wla(this, addr + 2, &sig_id, sizeof(sig_id));
switch (sig_id) {
case WL3501_DeautheIndicate:
case WL3501_DisassocIndicate:
case WL3501_Alarm:
if (this->net_type == WL3501_Infrastructure) {
printk(KERN_INFO "Wireless LAN Off-Line\n");
netif_stop_queue(dev);
wl3501_cmdloop(wl3501_mgmt_resync(this));
}
break;
case WL3501_MdConfirm:
wl3501_get_from_wla(this, addr, &md_conf, sizeof(md_conf));
wl3501_free_tx_buffer(this, md_conf.data);
netif_wake_queue(dev);
break;
case WL3501_MdIndicate:
wl3501_get_from_wla(this, addr, &md_ind, sizeof(md_ind));
this->start_seg = md_ind.data;
if (this->llc_type == 1) {
wl3501_get_from_wla(this, md_ind.data +
sizeof(struct wl3501_rx_hdr) - 6,
&tmp, sizeof(tmp));
if (tmp == 0xaaaa) {
pkt_len = md_ind.size + 12 - 24 - 4 - 6;
this->ether_type = EtherII;
} else if (tmp == 0xe0e0) {
pkt_len = md_ind.size + 12 - 24 - 4 + 2;
this->ether_type = Ether802_3e;
} else {
pkt_len = md_ind.size + 12 - 24 - 4 + 2;
this->ether_type = Ether802_3f;
}
} else
pkt_len = md_ind.size - 24 - 4;
size = pkt_len;
skb = dev_alloc_skb(size + 5);
if (!skb) {
printk(KERN_WARNING
"%s: Cannot allocate a sk_buff "
"of size %d.\n", dev->name, size);
/* must drop this packet to ensure
interrupt will come again */
this->stats.rx_dropped++;
break;
}
skb->dev = dev;
skb_reserve(skb, 2); /* IP headers on 16 bytes
boundaries */
skb_put(skb, size); /* Make room */
memcpy((char *)skb->data, (char *)&(md_ind.daddr), 12);
wl3501_receive(this, skb->data, size);
skb->protocol = eth_type_trans(skb, dev);
dev->last_rx = jiffies;
this->stats.rx_packets++;
this->stats.rx_bytes += skb->len;
netif_rx(skb);
break;
case WL3501_GetConfirm:
wl3501_get_from_wla(this, addr, &get_conf, sizeof(get_conf));
#if 0
if (!get_conf.mib_status) {
switch (get_conf.mib_attrib) {
case wl3501_mib_MacAddress:
break;
case wl3501_mib_CurrentRegDomain:
break;
}
}
#endif
break;
case WL3501_StartConfirm:
wl3501_get_from_wla(this, addr, &start_conf,
sizeof(start_conf));
this->card_start = start_conf.status == WL3501_STATUS_SUCCESS;
if (this->card_start)
netif_wake_queue(dev);
else
netif_stop_queue(dev);
break;
case WL3501_ScanConfirm:
wl3501_mgmt_scan_confirm(this, addr);
break;
case WL3501_JoinConfirm:
wl3501_mgmt_join_confirm(this, addr);
if (this->card_start)
netif_wake_queue(dev);
else
netif_stop_queue(dev);
break;
case WL3501_AssocConfirm:
wl3501_get_from_wla(this, addr, &assoc_conf,
sizeof(assoc_conf));
if (assoc_conf.status == WL3501_STATUS_SUCCESS) {
printk(KERN_INFO "Wireless LAN On-Line Join:"
" %2X %2X %2X %2X %2X %2X\n",
this->bssid.b0, this->bssid.b1,
this->bssid.b2, this->bssid.b3,
this->bssid.b4, this->bssid.b5);
this->card_start = 1;
} else
this->card_start = 0;
if (this->card_start)
netif_wake_queue(dev);
else
netif_stop_queue(dev);
break;
case WL3501_AutheConfirm:
wl3501_get_from_wla(this, addr, &auth_conf, sizeof(auth_conf));
if (auth_conf.status == WL3501_STATUS_SUCCESS)
wl3501_cmdloop(wl3501_mgmt_association(this));
else
wl3501_cmdloop(wl3501_mgmt_resync(this));
break;
case WL3501_ResyncConfirm:
wl3501_mgmt_resync(this); /* FIXME: should be resync_confirm */
break;
}
wl3501_esbq_confirm_done(this);
morepkts = 1;
/* free request if necessary */
free:
wl3501_esbq_req_free(this);
if (morepkts)
goto loop;
}
static __inline__ void wl3501_ack_interrupt(struct wl3501_card *this)
{
wl3501_outb(WL3501_GCR_ECINT, this->base_addr + WL3501_NIC_GCR);
}
/**
* wl3501_interrupt - Hardware interrupt from card.
* @irq - Interrupt number
* @dev_id - net_device
* @regs - registers
*
* We must acknowledge the interrupt as soon as possible, and block the
* interrupt from the same card immediately to prevent re-entry.
*
* Before accessing the Control_Status_Block, we must lock SUTRO first.
* On the other hand, to prevent SUTRO from malfunctioning, we must
* unlock the SUTRO as soon as possible.
*/
static void wl3501_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct net_device *dev = (struct net_device *)dev_id;
struct wl3501_card *this;
if (!dev) {
printk(KERN_ERR "%s: irq %d for unknown device.\n",
__FUNCTION__, irq);
return;
}
this = (struct wl3501_card *)dev->priv;
wl3501_ack_interrupt(this);
wl3501_block_interrupt(this);
wl3501_rx_interrupt(dev);
/* Clean Zone End */
wl3501_unblock_interrupt(this);
}
static int wl3501_reset_board(struct wl3501_card *this)
{
u8 tmp;
u32 i;
/* Coreset */
wl3501_outb_p(WL3501_GCR_CORESET, this->base_addr + WL3501_NIC_GCR);
wl3501_outb_p(0, this->base_addr + WL3501_NIC_GCR);
wl3501_outb_p(WL3501_GCR_CORESET, this->base_addr + WL3501_NIC_GCR);
/* Reset SRAM 0x480 to zero */
tmp = 0;
wl3501_set_to_wla(this, 0x480, &tmp, sizeof(tmp));
/* Start up */
wl3501_outb_p(0, this->base_addr + WL3501_NIC_GCR);
WL3501_NOPLOOP(1024 * 50);
wl3501_unblock_interrupt(this); /* acme: was commented */
/* Polling Self_Test_Status */
for (i = 0; i < 10000; i++) {
wl3501_get_from_wla(this, 0x480, &tmp, sizeof(tmp));
if (tmp == 'W') {
/* firmware complete all test successfully */
tmp = 'A';
wl3501_set_to_wla(this, 0x480, &tmp, sizeof(tmp));
return 1;
}
WL3501_NOPLOOP(10);
}
return 0;
}
static int wl3501_init_firmware(struct wl3501_card *this)
{
u16 ptr, next;
if (!wl3501_reset_board(this)) {
printk(KERN_WARNING "wlapi.c: wl3501_reset_board failed!\n");
return 1;
}
/* Switch to SRAM Page 0 */
wl3501_switch_page(this, WL3501_BSS_SPAGE0);
/* Read parameter from card */
wl3501_get_from_wla(this, 0x482, &this->esbq_req_start, 2);
wl3501_get_from_wla(this, 0x486, &this->esbq_req_end, 2);
wl3501_get_from_wla(this, 0x488, &this->esbq_confirm_start, 2);
wl3501_get_from_wla(this, 0x48c, &this->esbq_confirm_end, 2);
wl3501_get_from_wla(this, 0x48e, &this->tx_buffer_head, 2);
wl3501_get_from_wla(this, 0x492, &this->tx_buffer_size, 2);
this->esbq_req_tail = this->esbq_req_head = this->esbq_req_start;
this->esbq_req_end += this->esbq_req_start;
this->esbq_confirm = this->esbq_confirm_start;
this->esbq_confirm_end += this->esbq_confirm_start;
/* Initial Tx Buffer */
this->tx_buffer_cnt = 1;
ptr = this->tx_buffer_head;
next = ptr + WL3501_BLKSZ;
while ((next - this->tx_buffer_head) < this->tx_buffer_size) {
this->tx_buffer_cnt++;
wl3501_set_to_wla(this, ptr, &next, sizeof(next));
ptr = next;
next = ptr + WL3501_BLKSZ;
}
next = 0;
wl3501_set_to_wla(this, ptr, &next, sizeof(next));
this->tx_buffer_tail = ptr;
return 1;
}
static int wl3501_close(struct net_device *dev)
{
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
dev_link_t *link;
int rc = -ENODEV;
/* Check if the device is in wl3501_dev_list */
for (link = wl3501_dev_list; link; link = link->next)
if (link->priv == dev)
break;
if (!link)
goto out;
link->open--;
/* Stop wl3501_hard_start_xmit() from now on */
netif_stop_queue(dev);
this->card_start = 0;
wl3501_ack_interrupt(this);
/* Mask interrupts from the SUTRO */
wl3501_block_interrupt(this);
if (link->state & DEV_STALE_CONFIG) {
link->release.expires = jiffies + HZ * 20;
link->state |= DEV_RELEASE_PENDING;
add_timer(&link->release);
}
rc = 0;
printk(KERN_INFO "%s : WL24xx closed\n", dev->name);
out:
return rc;
}
/**
* wl3501_reset - Reset the SUTRO.
* @dev - network device
*
* It is almost the same as wl3501_open(). In fact, we may just wl3501_close()
* and wl3501_open() again, but I wouldn't like to free_irq() when the driver
* is running. It seems to be dangerous.
*/
static int wl3501_reset(struct net_device *dev)
{
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
/* Stop processing interrupt from the card */
wl3501_block_interrupt(this);
/* Initial WL24 firmware */
printk(KERN_INFO "%s: Initialize WL24xx firmware...\n", dev->name);
if (!wl3501_init_firmware(this)) {
printk(KERN_WARNING "%s: Can't initialize Firmware!\n",
dev->name);
/* Free IRQ, and mark IRQ as unused */
free_irq(dev->irq, dev);
return -ENODEV;
}
/* Initial device variables */
this->card_start = 0;
/* queue has to be started only when the Card is Started */
netif_stop_queue(dev);
this->adhoc_times = 0;
wl3501_ack_interrupt(this);
/* Enable interrupt from card */
wl3501_unblock_interrupt(this);
wl3501_cmdloop(wl3501_mgmt_scan(this, 100));
printk(KERN_INFO "%s: device reset\n", dev->name);
return 0;
}
static void wl3501_tx_timeout(struct net_device *dev)
{
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
struct net_device_stats *stats = &this->stats;
int rc;
// printk(KERN_WARNING "%s: Tx timeout! Resetting card.\n", dev->name);
stats->tx_errors++;
rc = wl3501_reset(dev);
if (rc)
printk(KERN_ERR "%s: Error %d resetting card on Tx timeout!\n",
dev->name, rc);
else {
dev->trans_start = jiffies;
netif_wake_queue(dev);
}
}
/*
* Return : 0 - OK
* 1 - Could not transmit (dev_queue_xmit will queue it)
* and try to sent it later
*/
static int wl3501_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
int enabled, send;
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
if (!netif_running(dev) || !this->card_start) {
// printk(KERN_ERR "%s: Tx on stopped device!\n", dev->name);
return 1;
}
if (netif_queue_stopped(dev)) {
printk(KERN_ERR "%s: Tx while transmitter busy!\n", dev->name);
return 1;
}
/* Avoid re-entry. Block a timer-based transmit from overlapping. */
netif_stop_queue(dev);
/* Good! This packet owns the transmitter, block interrupt immediately.
* wl3501_interrupt() has no chance to start the queue if it gets
* ISR_Tx. */
/* Mask interrupts from the SUTRO!
* We must mask interrupt from the same card, to prevent interrupt
* routine from accessing data structure and I/O port while
* wl3501_send_pkt is running. It's very important! */
enabled = wl3501_block_interrupt(this);
/* Record transmitt start time */
dev->trans_start = jiffies;
/* Send the packet with default speed */
send = wl3501_send_pkt(this, skb->data, skb->len);
/* Turn SUTRO interrupt back on only if it is originally enabled */
if (enabled)
wl3501_unblock_interrupt(this);
/* If sent successfully, start queue. Otherwise, buffer is enqueued
* and will be restarted again when the SUTRO interrupts us and
* returns ISR_Tx */
if (send) {
dev_kfree_skb(skb);
netif_start_queue(dev); /* Let others own the transmitter */
return 0;
}
return 1; /* Try next time */
}
static int wl3501_open(struct net_device *dev)
{
int rc = -ENODEV;
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
dev_link_t *link;
/* Check if the device is in wl3501_dev_list */
for (link = wl3501_dev_list; link; link = link->next)
if (link->priv == dev)
break;
if (!DEV_OK(link))
goto out;
netif_device_attach(dev);
link->open++;
/* Initial WL24 firmware */
printk(KERN_INFO "%s: Initialize WL24xx firmware...\n", dev->name);
if (!wl3501_init_firmware(this)) {
printk(KERN_WARNING "%s: Can't initialize firmware!\n",
dev->name);
goto out;
}
/* Initial device variables */
netif_start_queue(dev);
this->card_start = 0;
this->adhoc_times = 0;
/* Acknowledge Interrupt, for cleaning last state */
wl3501_ack_interrupt(this);
/* Enable interrupt from card after all */
wl3501_unblock_interrupt(this);
wl3501_cmdloop(wl3501_mgmt_scan(this, 100));
rc = 0;
printk(KERN_INFO "%s: WL24xx opened\n", dev->name);
out:
return rc;
}
/**
* wl3501_init - "initialize" board
* @dev - network device
*
* We never need to do anything when a wl3501 device is "initialized" by the net
* software, because we only register already-found cards.
*/
static int wl3501_init(struct net_device *dev)
{
dprintk(0, "called");
return 0;
}
struct net_device_stats *wl3501_get_stats(struct net_device *dev)
{
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
return &this->stats;
}
struct iw_statistics *wl3501_get_wireless_stats(struct net_device *dev)
{
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
struct iw_statistics *wstats = &this->wstats;
wstats->status = this->card_start;
wstats->qual.qual = 0;
wstats->qual.level = 0;
wstats->qual.noise = 0;
wstats->discard.nwid = 0;
wstats->discard.code = 0;
wstats->discard.misc = 0;
return wstats;
}
/**
* wl3501_set_multicast_list - Set or clear the multicast filter
* @dev - network device
*
* Set or clear the multicast filter for this card.
*
* CAUTION: To prevent interrupted by WL_Interrupt() and timer-based
* wl3501_hard_start_xmit() from other interrupts, this should be run
* single-threaded. This function is expected to be a rare operation, and it's
* simpler to just use cli() to disable ALL interrupts.
*/
static void wl3501_set_multicast_list(struct net_device *dev)
{
#if 0
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
unsigned long flags;
u8 filter = RMR_UNICAST | RMR_BROADCAST; /* Normal mode */
if (dev->flags & IFF_PROMISC) /* Promiscuous mode */
filter |= RMR_PROMISCUOUS | RMR_ALL_MULTICAST;
else if (dev->mc_count || (dev->flags & IFF_ALLMULTI))
/* Allow multicast */
filter |= RMR_ALL_MULTICAST;
save_flags(flags);
cli();
/* Must not be interrupted */
wl3501_set_mib_value(this, TYPE_EXTRA_MIB, IDX_RECEIVEMODE,
&filter, sizeof(filter));
restore_flags(flags);
#endif
}
/**
* wl3501_ioctl - Perform IOCTL call functions
* @dev - network device
* @ifreq - request
* @cmd - command
*
* Perform IOCTL call functions here. Some are privileged operations and the
* effective uid is checked in those cases.
*
* This part is optional. Needed only if you want to run wlu (unix version).
*
* CAUTION: To prevent interrupted by wl3501_interrupt() and timer-based
* wl3501_hard_start_xmit() from other interrupts, this should be run
* single-threaded. This function is expected to be a rare operation, and it's
* simpler to just use cli() to disable ALL interrupts.
*/
static int wl3501_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
int rc = 0;
struct wl3501_card *this = (struct wl3501_card *)dev->priv;
struct wl3501_ioctl_parm parm;
struct wl3501_ioctl_blk *blk = (struct wl3501_ioctl_blk *)
&rq->ifr_data;
unsigned char bf[1028];
unsigned long flags;
/* Keep flags first, these are inline functions */
save_flags(flags);
cli();
switch (blk->cmd) {
case WL3501_IOCTL_SET_RESET: /* Reset drv - needed after set */
if (!capable(CAP_NET_ADMIN)) {
rc = -EPERM;
break;
}
rc = wl3501_reset(dev);
break;
case WL3501_IOCTL_WRITE_FLASH: /* Write firmware into Flash */
if (!capable(CAP_NET_ADMIN)) {
rc = -EPERM;
break;
}
if (copy_from_user(bf, blk->data, blk->len)) {
rc = -EFAULT;
break;
}
rc = wl3501_write_flash(this, bf, blk->len) ? 0 : -EIO;
break;
case WL3501_IOCTL_GET_PARAMETER:
/* Get parameter */
parm.def_chan = this->def_chan;
parm.chan = this->chan;
parm.net_type = this->net_type;
parm.version[0] = this->version[0];
parm.version[1] = this->version[1];
parm.freq_domain = this->freq_domain;
memcpy((char *)&(parm.keep_essid[0]),
(char *)&(this->keep_essid[0]), 34);
memcpy((char *)&(parm.essid[0]), (char *)&(this->essid[0]), 34);
blk->len = sizeof(parm);
if (copy_to_user(blk->data, &parm, blk->len)) {
rc = -EFAULT;
break;
}
break;
case WL3501_IOCTL_SET_PARAMETER:{
/* Set parameter */
if (!capable(CAP_NET_ADMIN)) {
rc = -EPERM;
break;
}
if (copy_from_user(&parm, blk->data, sizeof(parm))) {
rc = -EFAULT;
break;
}
this->def_chan = parm.def_chan;
this->net_type = parm.net_type;
memcpy((char *)&(this->essid[0]),
(char *)&(parm.essid[0]), 34);
rc = wl3501_reset(dev);
break;
}
default:
rc = -EOPNOTSUPP;
}
restore_flags(flags);
return rc;
}
/**
* wl3501_detach - deletes a driver "instance"
* @link - FILL_IN
*
* This deletes a driver "instance". The device is de-registered with Card
* Services. If it has been released, all local data structures are freed.
* Otherwise, the structures will be freed when the device is released.
*/
static void wl3501_detach(dev_link_t *link)
{
dev_link_t **linkp;
/* Locate device structure */
for (linkp = &wl3501_dev_list; *linkp; linkp = &(*linkp)->next)
if (*linkp == link)
break;
if (!*linkp)
goto out;
/* If the device is currently configured and active, we won't actually
* delete it yet. Instead, it is marked so that when the release()
* function is called, that will trigger a proper detach(). */
if (link->state & DEV_CONFIG) {
#ifdef PCMCIA_DEBUG
printk(KERN_DEBUG "wl3501_cs: detach postponed, '%s' "
"still locked\n", link->dev->dev_name);
#endif
link->state |= DEV_STALE_LINK;
goto out;
}
/* Break the link with Card Services */
if (link->handle)
CardServices(DeregisterClient, link->handle);
/* Unlink device structure, free pieces */
*linkp = link->next;
if (link->priv)
kfree(link->priv);
kfree(link);
out:
return;
}
/**
* wl3501_flush_stale_links - Remove zombie instances
*
* Remove zombie instances (card removed, detach pending)
*/
static void wl3501_flush_stale_links(void)
{
dev_link_t *link, *next;
for (link = wl3501_dev_list; link; link = next) {
next = link->next;
if (link->state & DEV_STALE_LINK)
wl3501_detach(link);
}
}
/**
* wl3501_attach - creates an "instance" of the driver
*
* Creates an "instance" of the driver, allocating local data structures for
* one device. The device is registered with Card Services.
*
* The dev_link structure is initialized, but we don't actually configure the
* card at this point -- we wait until we receive a card insertion event.
*/
static dev_link_t *wl3501_attach(void)
{
client_reg_t client_reg;
dev_link_t *link;
struct net_device *dev;
int ret, i;
wl3501_flush_stale_links();
/* Initialize the dev_link_t structure */
link = kmalloc(sizeof(*link), GFP_KERNEL);
if (!link)
goto out;
memset(link, 0, sizeof(struct dev_link_t));
link->release.function = wl3501_release;
link->release.data = (unsigned long)link;
/* The io structure describes IO port mapping */
link->io.NumPorts1 = 16;
link->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
link->io.IOAddrLines = 5;
/* Interrupt setup */
link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT;
link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID;
link->irq.IRQInfo2 = wl3501_irq_mask;
if (wl3501_irq_list[0] == -1)
link->irq.IRQInfo2 = wl3501_irq_mask;
else
for (i = 0; i < 4; i++)
link->irq.IRQInfo2 |= 1 << wl3501_irq_list[i];
link->irq.Handler = wl3501_interrupt;
/* General socket configuration */
link->conf.Attributes = CONF_ENABLE_IRQ;
link->conf.Vcc = 50;
link->conf.IntType = INT_MEMORY_AND_IO;
link->conf.ConfigIndex = 1;
link->conf.Present = PRESENT_OPTION;
/* Allocate space for private device-specific data */
dev = kmalloc(sizeof(*dev) + sizeof(struct wl3501_card), GFP_KERNEL);
if (!dev)
goto out_link;
memset(dev, 0, sizeof(*dev) + sizeof(struct wl3501_card));
ether_setup(dev);
dev->priv = dev + 1;
dev->init = wl3501_init;
dev->open = wl3501_open;
dev->stop = wl3501_close;
dev->hard_start_xmit = wl3501_hard_start_xmit;
dev->tx_timeout = wl3501_tx_timeout;
dev->watchdog_timeo = HZ;
dev->get_stats = wl3501_get_stats;
dev->get_wireless_stats = wl3501_get_wireless_stats;
dev->set_multicast_list = wl3501_set_multicast_list;
dev->do_ioctl = wl3501_ioctl;
netif_stop_queue(dev);
link->priv = link->irq.Instance = dev;
/* Register with Card Services */
link->next = wl3501_dev_list;
wl3501_dev_list = link;
client_reg.dev_info = &wl3501_dev_info;
client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE;
client_reg.EventMask = CS_EVENT_CARD_INSERTION |
CS_EVENT_RESET_PHYSICAL |
CS_EVENT_CARD_RESET | CS_EVENT_CARD_REMOVAL |
CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
client_reg.event_handler = wl3501_event;
client_reg.Version = 0x0210;
client_reg.event_callback_args.client_data = link;
ret = CardServices(RegisterClient, &link->handle, &client_reg);
if (ret) {
cs_error(link->handle, RegisterClient, ret);
wl3501_detach(link);
link = NULL;
}
out:
return link;
out_link:
kfree(link);
link = NULL;
goto out;
}
#define CS_CHECK(fn, args...) \
while ((last_ret = CardServices(last_fn = (fn), args)) != 0) goto cs_failed
/**
* wl3501_config - configure the PCMCIA socket and make eth device available
* @link - FILL_IN
*
* wl3501_config() is scheduled to run after a CARD_INSERTION event is
* received, to configure the PCMCIA socket, and to make the ethernet device
* available to the system.
*/
static void wl3501_config(dev_link_t *link)
{
tuple_t tuple;
cisparse_t parse;
client_handle_t handle = link->handle;
struct net_device *dev = link->priv;
int i = 0, j, last_fn, last_ret;
unsigned char buf[64];
struct wl3501_card *this;
/* This reads the card's CONFIG tuple to find its config registers. */
tuple.Attributes = 0;
tuple.DesiredTuple = CISTPL_CONFIG;
CS_CHECK(GetFirstTuple, handle, &tuple);
tuple.TupleData = buf;
tuple.TupleDataMax = 64;
tuple.TupleOffset = 0;
CS_CHECK(GetTupleData, handle, &tuple);
CS_CHECK(ParseTuple, handle, &tuple, &parse);
link->conf.ConfigBase = parse.config.base;
link->conf.Present = parse.config.rmask[0];
/* Configure card */
link->state |= DEV_CONFIG;
/* Try allocating IO ports. This tries a few fixed addresses. If you
* want, you can also read the card's config table to pick addresses --
* see the serial driver for an example. */
for (j = 0x280; j < 0x400; j += 0x20) {
/* The '^0x300' is so that we probe 0x300-0x3ff first, then
* 0x200-0x2ff, and so on, because this seems safer */
link->io.BasePort1 = j;
link->io.BasePort2 = link->io.BasePort1 + 0x10;
i = CardServices(RequestIO, link->handle, &link->io);
if (i == CS_SUCCESS)
break;
}
if (i != CS_SUCCESS) {
cs_error(link->handle, RequestIO, i);
goto failed;
}
/* Now allocate an interrupt line. Note that this does not actually
* assign a handler to the interrupt. */
CS_CHECK(RequestIRQ, link->handle, &link->irq);
/* This actually configures the PCMCIA socket -- setting up the I/O
* windows and the interrupt mapping. */
CS_CHECK(RequestConfiguration, link->handle, &link->conf);
dev->irq = link->irq.AssignedIRQ;
dev->base_addr = link->io.BasePort1;
netif_start_queue(dev);
if (register_netdev(dev)) {
printk(KERN_NOTICE "wl3501_cs: register_netdev() failed\n");
goto failed;
}
SET_MODULE_OWNER(dev);
/* At this point, the dev_node_t structure(s) should be initialized and
* arranged in a linked list at link->dev. */
link->dev = &((struct wl3501_card *)dev->priv)->node;
link->state &= ~DEV_CONFIG_PENDING;
this = (struct wl3501_card *)dev->priv;
this->base_addr = dev->base_addr;
if (!wl3501_get_flash_mac_addr(this)) {
printk(KERN_WARNING "%s: Cant read MAC addr in flash ROM?\n",
dev->name);
goto failed;
}
strcpy(this->node.dev_name, dev->name);
/* print probe information */
printk(KERN_INFO "%s: wl3501 @ 0x%3.3x, IRQ %d, MAC addr in flash ROM:",
dev->name, this->base_addr, (int)dev->irq);
for (i = 0; i < 6; i++) {
dev->dev_addr[i] = ((char *)&this->mac_addr)[i];
printk("%c%02x", i ? ':' : ' ', dev->dev_addr[i]);
}
printk("\n");
/* initialize card parameter - add by jss */
this->net_type = WL3501_Infrastructure;
this->llc_type = 1;
this->def_chan = 1;
this->bss_cnt = 0;
this->join_sta_bss = 0;
this->adhoc_times = 0;
this->driver_state = 0;
this->card_start = 0;
this->essid[0] = 0;
this->essid[1] = 3;
this->essid[2] = 'A';
this->essid[3] = 'N';
this->essid[4] = 'Y';
switch (this->freq_domain) {
case 0x31:
case 0x32:
this->def_chan = 10;
break;
case 0x40:
this->def_chan = 14;
break;
case 0x10:
case 0x20:
case 0x30:
default:
this->def_chan = 1;
break;
}
goto out;
cs_failed:
cs_error(link->handle, last_fn, last_ret);
failed:
wl3501_release((unsigned long)link);
out:
return;
}
/**
* wl3501_release - unregister the net, release PCMCIA configuration
* @arg - FILL_IN
*
* After a card is removed, wl3501_release() will unregister the net device,
* and release the PCMCIA configuration. If the device is still open, this
* will be postponed until it is closed.
*/
static void wl3501_release(unsigned long arg)
{
dev_link_t *link = (dev_link_t *) arg;
struct net_device *dev = link->priv;
/* If the device is currently in use, we won't release until it is
* actually closed. */
if (link->open) {
dprintk(1, "release postponed, '%s' still open",
link->dev->dev_name);
link->state |= DEV_STALE_CONFIG;
goto out;
}
/* Unlink the device chain */
if (link->dev) {
unregister_netdev(dev);
link->dev = NULL;
}
/* Don't bother checking to see if these succeed or not */
CardServices(ReleaseConfiguration, link->handle);
CardServices(ReleaseIO, link->handle, &link->io);
CardServices(ReleaseIRQ, link->handle, &link->irq);
link->state &= ~DEV_CONFIG;
if (link->state & DEV_STALE_LINK)
wl3501_detach(link);
out:
return;
}
/**
* wl3501_event - The card status event handler
* @event - event
* @pri - priority
* @args - arguments for this event
*
* The card status event handler. Mostly, this schedules other stuff to run
* after an event is received. A CARD_REMOVAL event also sets some flags to
* discourage the net drivers from trying to talk to the card any more.
*
* When a CARD_REMOVAL event is received, we immediately set a flag to block
* future accesses to this device. All the functions that actually access the
* device should check this flag to make sure the card is still present.
*/
static int wl3501_event(event_t event, int pri, event_callback_args_t *args)
{
dev_link_t *link = args->client_data;
struct net_device *dev = link->priv;
switch (event) {
case CS_EVENT_CARD_REMOVAL:
link->state &= ~DEV_PRESENT;
if (link->state & DEV_CONFIG) {
netif_stop_queue(dev);
link->release.expires = jiffies + HZ * 20;
add_timer(&link->release);
}
break;
case CS_EVENT_CARD_INSERTION:
link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
wl3501_config(link);
break;
case CS_EVENT_PM_SUSPEND:
link->state |= DEV_SUSPEND;
/* Fall through... */
case CS_EVENT_RESET_PHYSICAL:
if (link->state & DEV_CONFIG) {
if (link->open) {
netif_stop_queue(dev);
}
CardServices(ReleaseConfiguration, link->handle);
}
break;
case CS_EVENT_PM_RESUME:
link->state &= ~DEV_SUSPEND;
/* Fall through... */
case CS_EVENT_CARD_RESET:
if (link->state & DEV_CONFIG) {
CardServices(RequestConfiguration, link->handle,
&link->conf);
if (link->open)
wl3501_reset(dev);
}
break;
}
return 0;
}
static struct pcmcia_driver wl3501_driver = {
.owner = THIS_MODULE,
.drv = {
.name = "wl3501_cs",
},
.attach = wl3501_attach,
.detach = wl3501_detach,
};
static int __init wl3501_init_module(void)
{
servinfo_t serv;
dprintk(0, ": loading");
CardServices(GetCardServicesInfo, &serv);
if (serv.Revision != CS_RELEASE_CODE) {
printk(KERN_NOTICE
"wl3501_cs: Card Services release does not match!\n"
"Compiled with 0x%x, but current is 0x%lx\n",
CS_RELEASE_CODE, (unsigned long)serv.Revision);
/* return -1; */
}
pcmcia_register_driver(&wl3501_driver);
return 0;
}
static void __exit wl3501_exit_module(void)
{
dprintk(0, ": unloading");
pcmcia_unregister_driver(&wl3501_driver);
while (wl3501_dev_list) {
if (wl3501_dev_list->state & DEV_CONFIG)
wl3501_release((unsigned long)wl3501_dev_list);
wl3501_detach(wl3501_dev_list);
}
}
module_init(wl3501_init_module);
module_exit(wl3501_exit_module);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment