📄 wl3501_cs.c
字号:
/* * 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 used by Fox Chen while writing the original driver for 2.0.30: * * 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 Kbytes/s in UDP in Infrastructure mode, * 173 Kbytes/s in TCP. * * Tested with Planet AP in 2.5.73-bk, 216 Kbytes/s in Infrastructure mode * with a SMP machine (dual pentium 100), using pktgen, 432 pps (pkt_size = 60) */#undef REALLY_SLOW_IO /* most systems can safely undef this */#include <linux/config.h>#include <linux/delay.h>#include <linux/types.h>#include <linux/ethtool.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/in.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/fcntl.h>#include <linux/if_arp.h>#include <linux/ioport.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include <linux/slab.h>#include <linux/string.h>#include <linux/wireless.h>#include <net/iw_handler.h>#include <pcmcia/cs_types.h>#include <pcmcia/cs.h>#include <pcmcia/cistpl.h>#include <pcmcia/cisreg.h>#include <pcmcia/ds.h>#include <asm/io.h>#include <asm/uaccess.h>#include <asm/system.h>#include "wl3501.h"#ifndef __i386__#define slow_down_io()#endif/* For rough constant delay */#define WL3501_NOPLOOP(n) { int x = 0; while (x++ < n) slow_down_io(); }/* * 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 0#ifdef PCMCIA_DEBUGstatic int pc_debug = PCMCIA_DEBUG;module_param(pc_debug, int, 0);#define dprintk(n, format, args...) \ { if (pc_debug > (n)) \ printk(KERN_INFO "%s: " format "\n", __FUNCTION__ , ##args); }#else#define dprintk(n, format, args...)#endif#define wl3501_outb(a, b) { outb(a, b); slow_down_io(); }#define wl3501_outb_p(a, b) { outb_p(a, b); slow_down_io(); }#define wl3501_outsb(a, b, c) { outsb(a, b, c); slow_down_io(); }#define WL3501_RELEASE_TIMEOUT (25 * HZ)#define WL3501_MAX_ADHOC_TRIES 16#define WL3501_RESUME 0#define WL3501_SUSPEND 1/* * 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(dev_link_t *link);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";static int wl3501_chan2freq[] = { [0] = 2412, [1] = 2417, [2] = 2422, [3] = 2427, [4] = 2432, [5] = 2437, [6] = 2442, [7] = 2447, [8] = 2452, [9] = 2457, [10] = 2462, [11] = 2467, [12] = 2472, [13] = 2477,};static const struct { int reg_domain; int min, max, deflt;} iw_channel_table[] = { { .reg_domain = IW_REG_DOMAIN_FCC, .min = 1, .max = 11, .deflt = 1, }, { .reg_domain = IW_REG_DOMAIN_DOC, .min = 1, .max = 11, .deflt = 1, }, { .reg_domain = IW_REG_DOMAIN_ETSI, .min = 1, .max = 13, .deflt = 1, }, { .reg_domain = IW_REG_DOMAIN_SPAIN, .min = 10, .max = 11, .deflt = 10, }, { .reg_domain = IW_REG_DOMAIN_FRANCE, .min = 10, .max = 13, .deflt = 10, }, { .reg_domain = IW_REG_DOMAIN_MKK, .min = 14, .max = 14, .deflt = 14, }, { .reg_domain = IW_REG_DOMAIN_MKK1, .min = 1, .max = 14, .deflt = 1, }, { .reg_domain = IW_REG_DOMAIN_ISRAEL, .min = 3, .max = 9, .deflt = 9, },};/** * iw_valid_channel - validate channel in regulatory domain * @reg_comain - regulatory domain * @channel - channel to validate * * Returns 0 if invalid in the specified regulatory domain, non-zero if valid. */static int iw_valid_channel(int reg_domain, int channel){ int i, rc = 0; for (i = 0; i < ARRAY_SIZE(iw_channel_table); i++) if (reg_domain == iw_channel_table[i].reg_domain) { rc = channel >= iw_channel_table[i].min && channel <= iw_channel_table[i].max; break; } return rc;}/** * iw_default_channel - get default channel for a regulatory domain * @reg_comain - regulatory domain * * Returns the default channel for a regulatory domain */static int iw_default_channel(int reg_domain){ int i, rc = 1; for (i = 0; i < ARRAY_SIZE(iw_channel_table); i++) if (reg_domain == iw_channel_table[i].reg_domain) { rc = iw_channel_table[i].deflt; break; } return rc;}static void iw_set_mgmt_info_element(enum iw_mgmt_info_element_ids id, struct iw_mgmt_info_element *el, void *value, int len){ el->id = id; el->len = len; memcpy(el->data, value, len);}static void iw_copy_mgmt_info_element(struct iw_mgmt_info_element *to, struct iw_mgmt_info_element *from){ iw_set_mgmt_info_element(from->id, to, from->data, from->len);}/* * 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);}/* * 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[0] = inb(base_addr + WL3501_NIC_IODPA); WL3501_NOPLOOP(100); this->mac_addr[1] = inb(base_addr + WL3501_NIC_IODPA); WL3501_NOPLOOP(100); this->mac_addr[2] = inb(base_addr + WL3501_NIC_IODPA); WL3501_NOPLOOP(100); this->mac_addr[3] = inb(base_addr + WL3501_NIC_IODPA); WL3501_NOPLOOP(100); this->mac_addr[4] = inb(base_addr + WL3501_NIC_IODPA); WL3501_NOPLOOP(100); this->mac_addr[5] = inb(base_addr + WL3501_NIC_IODPA); WL3501_NOPLOOP(100); this->reg_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[0] == 0x00 && this->mac_addr[1] == 0x60;}/** * 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) */static 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); /* 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) */static 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); /* 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 -/ \-------------- len --------------/ * \-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;}static int wl3501_esbq_exec(struct wl3501_card *this, void *sig, int sig_size){ int rc = -EIO; if (wl3501_esbq_req_test(this)) { u16 ptr = wl3501_get_tx_buffer(this, sig_size); if (ptr) { wl3501_set_to_wla(this, ptr, sig, sig_size); wl3501_esbq_req(this, &ptr); rc = 0; } } return rc;}static int wl3501_get_mib_value(struct wl3501_card *this, u8 index, void *bf, int size){ struct wl3501_get_req sig = { .sig_id = WL3501_SIG_GET_REQ, .mib_attrib = index, }; unsigned long flags; int rc = -EIO; spin_lock_irqsave(&this->lock, flags); if (wl3501_esbq_req_test(this)) { u16 ptr = wl3501_get_tx_buffer(this, sizeof(sig)); if (ptr) { wl3501_set_to_wla(this, ptr, &sig, sizeof(sig)); wl3501_esbq_req(this, &ptr); this->sig_get_confirm.mib_status = 255; spin_unlock_irqrestore(&this->lock, flags); rc = wait_event_interruptible(this->wait, this->sig_get_confirm.mib_status != 255); if (!rc)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -