Skip to content
Snippets Groups Projects
ray_cs.c 88.9 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
/*=============================================================================
 *
 * A  PCMCIA client driver for the Raylink wireless LAN card.
 * The starting point for this module was the skeleton.c in the
 * PCMCIA 2.9.12 package written by David Hinds, dahinds@users.sourceforge.net
 *
 *
 * Copyright (c) 1998  Corey Thomas (corey@world.std.com)
 *
 * This driver is free software; you can redistribute it and/or modify
 * it under the terms of version 2 only of the GNU General Public License as
Linus Torvalds's avatar
Linus Torvalds committed
 * published by the Free Software Foundation.
 *
 * It is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 *
 * Changes:
 * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/08/2000
 * - reorganize kmallocs in ray_attach, checking all for failure
 *   and releasing the previous allocations if one fails
 *
 * Daniele Bellucci <bellucda@tiscali.it> - 07/10/2003
 * - Audit copy_to_user in ioctl(SIOCGIWESSID)
Linus Torvalds's avatar
Linus Torvalds committed
=============================================================================*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/ptrace.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/ioport.h>
#include <linux/skbuff.h>
#include <linux/ethtool.h>
Al Viro's avatar
Al Viro committed
#include <linux/ieee80211.h>
Linus Torvalds's avatar
Linus Torvalds committed

#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/ds.h>
#include <pcmcia/mem_op.h>

#include <linux/wireless.h>
#include <net/iw_handler.h>
Linus Torvalds's avatar
Linus Torvalds committed

#include <asm/io.h>
#include <asm/system.h>
#include <asm/byteorder.h>
#include <asm/uaccess.h>

/* Warning : these stuff will slow down the driver... */
#define WIRELESS_SPY		/* Enable spying addresses */
/* Definitions we need for spy */
typedef struct iw_statistics iw_stats;
typedef u_char mac_addr[ETH_ALEN];	/* Hardware address */
Linus Torvalds's avatar
Linus Torvalds committed

#include "rayctl.h"
#include "ray_cs.h"


/** Prototypes based on PCMCIA skeleton driver *******************************/
static int ray_config(struct pcmcia_device *link);
static void ray_release(struct pcmcia_device *link);
static void ray_detach(struct pcmcia_device *p_dev);
Linus Torvalds's avatar
Linus Torvalds committed

/***** Prototypes indicated by device structure ******************************/
static int ray_dev_close(struct net_device *dev);
static int ray_dev_config(struct net_device *dev, struct ifmap *map);
static struct net_device_stats *ray_get_stats(struct net_device *dev);
static int ray_dev_init(struct net_device *dev);

static const struct ethtool_ops netdev_ethtool_ops;
Linus Torvalds's avatar
Linus Torvalds committed

static int ray_open(struct net_device *dev);
static netdev_tx_t ray_dev_start_xmit(struct sk_buff *skb,
					    struct net_device *dev);
Linus Torvalds's avatar
Linus Torvalds committed
static void set_multicast_list(struct net_device *dev);
static void ray_update_multi_list(struct net_device *dev, int all);
static int translate_frame(ray_dev_t *local, struct tx_msg __iomem *ptx,
			   unsigned char *data, int len);
static void ray_build_header(ray_dev_t *local, struct tx_msg __iomem *ptx,
			     UCHAR msg_type, unsigned char *data);
Linus Torvalds's avatar
Linus Torvalds committed
static void untranslate(ray_dev_t *local, struct sk_buff *skb, int len);
static iw_stats *ray_get_wireless_stats(struct net_device *dev);
static const struct iw_handler_def ray_handler_def;
Linus Torvalds's avatar
Linus Torvalds committed

/***** Prototypes for raylink functions **************************************/
static int asc_to_int(char a);
static void authenticate(ray_dev_t *local);
static int build_auth_frame(ray_dev_t *local, UCHAR *dest, int auth_type);
static void authenticate_timeout(u_long);
static int get_free_ccs(ray_dev_t *local);
static int get_free_tx_ccs(ray_dev_t *local);
static void init_startup_params(ray_dev_t *local);
static int parse_addr(char *in_str, UCHAR *out);
static int ray_hw_xmit(unsigned char *data, int len, struct net_device *dev, UCHAR type);
Linus Torvalds's avatar
Linus Torvalds committed
static int ray_init(struct net_device *dev);
static int interrupt_ecf(ray_dev_t *local, int ccs);
static void ray_reset(struct net_device *dev);
static void ray_update_parm(struct net_device *dev, UCHAR objid, UCHAR *value, int len);
static void verify_dl_startup(u_long);

/* Prototypes for interrpt time functions **********************************/
static irqreturn_t ray_interrupt(int reg, void *dev_id);
Linus Torvalds's avatar
Linus Torvalds committed
static void clear_interrupt(ray_dev_t *local);
static void rx_deauthenticate(ray_dev_t *local, struct rcs __iomem *prcs,
			      unsigned int pkt_addr, int rx_len);
Linus Torvalds's avatar
Linus Torvalds committed
static int copy_from_rx_buff(ray_dev_t *local, UCHAR *dest, int pkt_addr, int len);
static void ray_rx(struct net_device *dev, ray_dev_t *local, struct rcs __iomem *prcs);
static void release_frag_chain(ray_dev_t *local, struct rcs __iomem *prcs);
static void rx_authenticate(ray_dev_t *local, struct rcs __iomem *prcs,
			    unsigned int pkt_addr, int rx_len);
static void rx_data(struct net_device *dev, struct rcs __iomem *prcs,
		    unsigned int pkt_addr, int rx_len);
Linus Torvalds's avatar
Linus Torvalds committed
static void associate(ray_dev_t *local);

/* Card command functions */
static int dl_startup_params(struct net_device *dev);
static void join_net(u_long local);
static void start_net(u_long local);
/* void start_net(ray_dev_t *local); */

/*===========================================================================*/
/* Parameters that can be set with 'insmod' */

/* ADHOC=0, Infrastructure=1 */
static int net_type = ADHOC;

/* Hop dwell time in Kus (1024 us units defined by 802.11) */
static int hop_dwell = 128;

/* Beacon period in Kus */
static int beacon_period = 256;

/* power save mode (0 = off, 1 = save power) */
static int psm;

/* String for network's Extended Service Set ID. 32 Characters max */
static char *essid;

/* Default to encapsulation unless translation requested */
static int translate = 1;

static int country = USA;

static int sniffer;

static int bc;

/* 48 bit physical card address if overriding card's real physical
 * address is required.  Since IEEE 802.11 addresses are 48 bits
 * like ethernet, an int can't be used, so a string is used. To
 * allow use of addresses starting with a decimal digit, the first
 * character must be a letter and will be ignored. This letter is
 * followed by up to 12 hex digits which are the address.  If less
 * than 12 digits are used, the address will be left filled with 0's.
 * Note that bit 0 of the first byte is the broadcast bit, and evil
 * things will happen if it is not 0 in a card address.
 */
static char *phy_addr = NULL;


/* A struct pcmcia_device structure has fields for most things that are needed
Linus Torvalds's avatar
Linus Torvalds committed
   to keep track of a socket, but there will usually be some device
   specific information that also needs to be kept track of.  The
   'priv' pointer in a struct pcmcia_device structure can be used to point to
Linus Torvalds's avatar
Linus Torvalds committed
   a device-specific private data structure, like this.
*/
static unsigned int ray_mem_speed = 500;

/* WARNING: THIS DRIVER IS NOT CAPABLE OF HANDLING MULTIPLE DEVICES! */
static struct pcmcia_device *this_device = NULL;

Linus Torvalds's avatar
Linus Torvalds committed
MODULE_AUTHOR("Corey Thomas <corey@world.std.com>");
MODULE_DESCRIPTION("Raylink/WebGear wireless LAN driver");
MODULE_LICENSE("GPL");

module_param(net_type, int, 0);
module_param(hop_dwell, int, 0);
module_param(beacon_period, int, 0);
module_param(psm, int, 0);
module_param(essid, charp, 0);
module_param(translate, int, 0);
module_param(country, int, 0);
module_param(sniffer, int, 0);
module_param(bc, int, 0);
module_param(phy_addr, charp, 0);
module_param(ray_mem_speed, int, 0);

static UCHAR b5_default_startup_parms[] = {
	0, 0,			/* Adhoc station */
	'L', 'I', 'N', 'U', 'X', 0, 0, 0,	/* 32 char ESSID */
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	1, 0,			/* Active scan, CA Mode */
	0, 0, 0, 0, 0, 0,	/* No default MAC addr  */
	0x7f, 0xff,		/* Frag threshold */
	0x00, 0x80,		/* Hop time 128 Kus */
	0x01, 0x00,		/* Beacon period 256 Kus */
	0x01, 0x07, 0xa3,	/* DTIM, retries, ack timeout */
	0x1d, 0x82, 0x4e,	/* SIFS, DIFS, PIFS */
	0x7f, 0xff,		/* RTS threshold */
	0x04, 0xe2, 0x38, 0xA4,	/* scan_dwell, max_scan_dwell */
	0x05,			/* assoc resp timeout thresh */
	0x08, 0x02, 0x08,	/* adhoc, infra, super cycle max */
	0,			/* Promiscuous mode */
	0x0c, 0x0bd,		/* Unique word */
	0x32,			/* Slot time */
	0xff, 0xff,		/* roam-low snr, low snr count */
	0x05, 0xff,		/* Infra, adhoc missed bcn thresh */
	0x01, 0x0b, 0x4f,	/* USA, hop pattern, hop pat length */
Linus Torvalds's avatar
Linus Torvalds committed
/* b4 - b5 differences start here */
	0x00, 0x3f,		/* CW max */
	0x00, 0x0f,		/* CW min */
	0x04, 0x08,		/* Noise gain, limit offset */
	0x28, 0x28,		/* det rssi, med busy offsets */
	7,			/* det sync thresh */
	0, 2, 2,		/* test mode, min, max */
	0,			/* allow broadcast SSID probe resp */
	0, 0,			/* privacy must start, can join */
	2, 0, 0, 0, 0, 0, 0, 0	/* basic rate set */
Linus Torvalds's avatar
Linus Torvalds committed
};

static UCHAR b4_default_startup_parms[] = {
	0, 0,			/* Adhoc station */
	'L', 'I', 'N', 'U', 'X', 0, 0, 0,	/* 32 char ESSID */
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	1, 0,			/* Active scan, CA Mode */
	0, 0, 0, 0, 0, 0,	/* No default MAC addr  */
	0x7f, 0xff,		/* Frag threshold */
	0x02, 0x00,		/* Hop time */
	0x00, 0x01,		/* Beacon period */
	0x01, 0x07, 0xa3,	/* DTIM, retries, ack timeout */
	0x1d, 0x82, 0xce,	/* SIFS, DIFS, PIFS */
	0x7f, 0xff,		/* RTS threshold */
	0xfb, 0x1e, 0xc7, 0x5c,	/* scan_dwell, max_scan_dwell */
	0x05,			/* assoc resp timeout thresh */
	0x04, 0x02, 0x4,	/* adhoc, infra, super cycle max */
	0,			/* Promiscuous mode */
	0x0c, 0x0bd,		/* Unique word */
	0x4e,			/* Slot time (TBD seems wrong) */
	0xff, 0xff,		/* roam-low snr, low snr count */
	0x05, 0xff,		/* Infra, adhoc missed bcn thresh */
	0x01, 0x0b, 0x4e,	/* USA, hop pattern, hop pat length */
Linus Torvalds's avatar
Linus Torvalds committed
/* b4 - b5 differences start here */
	0x3f, 0x0f,		/* CW max, min */
	0x04, 0x08,		/* Noise gain, limit offset */
	0x28, 0x28,		/* det rssi, med busy offsets */
	7,			/* det sync thresh */
	0, 2, 2			/* test mode, min, max */
Linus Torvalds's avatar
Linus Torvalds committed
};
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
static unsigned char eth2_llc[] = { 0xaa, 0xaa, 3, 0, 0, 0 };
Linus Torvalds's avatar
Linus Torvalds committed

static char hop_pattern_length[] = { 1,
	USA_HOP_MOD, EUROPE_HOP_MOD,
	JAPAN_HOP_MOD, KOREA_HOP_MOD,
	SPAIN_HOP_MOD, FRANCE_HOP_MOD,
	ISRAEL_HOP_MOD, AUSTRALIA_HOP_MOD,
	JAPAN_TEST_HOP_MOD
Linus Torvalds's avatar
Linus Torvalds committed
};

static char rcsid[] =
    "Raylink/WebGear wireless LAN - Corey <Thomas corey@world.std.com>";
Linus Torvalds's avatar
Linus Torvalds committed

static const struct net_device_ops ray_netdev_ops = {
	.ndo_init 		= ray_dev_init,
	.ndo_open 		= ray_open,
	.ndo_stop 		= ray_dev_close,
	.ndo_start_xmit		= ray_dev_start_xmit,
	.ndo_set_config		= ray_dev_config,
	.ndo_get_stats		= ray_get_stats,
	.ndo_set_multicast_list = set_multicast_list,
	.ndo_change_mtu		= eth_change_mtu,
	.ndo_set_mac_address 	= eth_mac_addr,
	.ndo_validate_addr	= eth_validate_addr,
};

Linus Torvalds's avatar
Linus Torvalds committed
/*=============================================================================
    ray_attach() 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 int ray_probe(struct pcmcia_device *p_dev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	ray_dev_t *local;
	struct net_device *dev;

	dev_dbg(&p_dev->dev, "ray_attach()\n");

	/* Allocate space for private device-specific data */
	dev = alloc_etherdev(sizeof(ray_dev_t));
	if (!dev)
		goto fail_alloc_dev;

	local = netdev_priv(dev);
	local->finder = p_dev;

	/* The io structure describes IO port mapping. None used here */
	p_dev->io.NumPorts1 = 0;
	p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
	p_dev->io.IOAddrLines = 5;

	/* Interrupt setup. For PCMCIA, driver takes what's given */
	p_dev->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING;
	p_dev->irq.Handler = &ray_interrupt;

	/* General socket configuration */
	p_dev->conf.Attributes = CONF_ENABLE_IRQ;
	p_dev->conf.IntType = INT_MEMORY_AND_IO;
	p_dev->conf.ConfigIndex = 1;

	p_dev->priv = dev;

	local->finder = p_dev;
	local->card_status = CARD_INSERTED;
	local->authentication_state = UNAUTHENTICATED;
	local->num_multi = 0;
	dev_dbg(&p_dev->dev, "ray_attach p_dev = %p,  dev = %p,  local = %p, intr = %p\n",
	      p_dev, dev, local, &ray_interrupt);

	/* Raylink entries in the device structure */
	dev->netdev_ops = &ray_netdev_ops;
	SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops);
	dev->wireless_handlers = &ray_handler_def;
#ifdef WIRELESS_SPY
	local->wireless_data.spy_data = &local->spy_data;
	dev->wireless_data = &local->wireless_data;
#endif /* WIRELESS_SPY */
	dev_dbg(&p_dev->dev, "ray_cs ray_attach calling ether_setup.)\n");
	netif_stop_queue(dev);
Linus Torvalds's avatar
Linus Torvalds committed

	init_timer(&local->timer);
Linus Torvalds's avatar
Linus Torvalds committed

	this_device = p_dev;
	return ray_config(p_dev);
Linus Torvalds's avatar
Linus Torvalds committed

fail_alloc_dev:
	return -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
} /* ray_attach */
Linus Torvalds's avatar
Linus Torvalds committed
/*=============================================================================
    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 ray_detach(struct pcmcia_device *link)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct net_device *dev;
	ray_dev_t *local;
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed

	this_device = NULL;
	dev = link->priv;
	ray_release(link);
	local = netdev_priv(dev);
	del_timer(&local->timer);
Linus Torvalds's avatar
Linus Torvalds committed

	if (link->priv) {
		if (link->dev_node)
			unregister_netdev(dev);
		free_netdev(dev);
	}
	dev_dbg(&link->dev, "ray_cs ray_detach ending\n");
Linus Torvalds's avatar
Linus Torvalds committed
} /* ray_detach */
Linus Torvalds's avatar
Linus Torvalds committed
/*=============================================================================
    ray_config() is run after a CARD_INSERTION event
    is received, to configure the PCMCIA socket, and to make the
    ethernet device available to the system.
=============================================================================*/
#define MAX_TUPLE_SIZE 128
static int ray_config(struct pcmcia_device *link)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int i;
	win_req_t req;
	memreq_t mem;
	struct net_device *dev = (struct net_device *)link->priv;
	ray_dev_t *local = netdev_priv(dev);


	/* Determine card type and firmware version */
	printk(KERN_INFO "ray_cs Detected: %s%s%s%s\n",
	       link->prod_id[0] ? link->prod_id[0] : " ",
	       link->prod_id[1] ? link->prod_id[1] : " ",
	       link->prod_id[2] ? link->prod_id[2] : " ",
	       link->prod_id[3] ? link->prod_id[3] : " ");

	/* Now allocate an interrupt line.  Note that this does not
	   actually assign a handler to the interrupt.
	 */
	ret = pcmcia_request_irq(link, &link->irq);
	if (ret)
		goto failed;
	dev->irq = link->irq.AssignedIRQ;

	/* This actually configures the PCMCIA socket -- setting up
	   the I/O windows and the interrupt mapping.
	 */
	ret = pcmcia_request_configuration(link, &link->conf);
	if (ret)
		goto failed;
Linus Torvalds's avatar
Linus Torvalds committed

/*** Set up 32k window for shared memory (transmit and control) ************/
	req.Attributes =
	    WIN_DATA_WIDTH_8 | WIN_MEMORY_TYPE_CM | WIN_ENABLE | WIN_USE_WAIT;
	req.Base = 0;
	req.Size = 0x8000;
	req.AccessSpeed = ray_mem_speed;
	ret = pcmcia_request_window(link, &req, &link->win);
	mem.CardOffset = 0x0000;
	mem.Page = 0;
	ret = pcmcia_map_mem_page(link, link->win, &mem);
	local->sram = ioremap(req.Base, req.Size);
Linus Torvalds's avatar
Linus Torvalds committed

/*** Set up 16k window for shared memory (receive buffer) ***************/
	req.Attributes =
	    WIN_DATA_WIDTH_8 | WIN_MEMORY_TYPE_CM | WIN_ENABLE | WIN_USE_WAIT;
	req.Base = 0;
	req.Size = 0x4000;
	req.AccessSpeed = ray_mem_speed;
	ret = pcmcia_request_window(link, &req, &local->rmem_handle);
	mem.CardOffset = 0x8000;
	mem.Page = 0;
	ret = pcmcia_map_mem_page(link, local->rmem_handle, &mem);
	local->rmem = ioremap(req.Base, req.Size);
Linus Torvalds's avatar
Linus Torvalds committed

/*** Set up window for attribute memory ***********************************/
	req.Attributes =
	    WIN_DATA_WIDTH_8 | WIN_MEMORY_TYPE_AM | WIN_ENABLE | WIN_USE_WAIT;
	req.Base = 0;
	req.Size = 0x1000;
	req.AccessSpeed = ray_mem_speed;
	ret = pcmcia_request_window(link, &req, &local->amem_handle);
	mem.CardOffset = 0x0000;
	mem.Page = 0;
	ret = pcmcia_map_mem_page(link, local->amem_handle, &mem);
	local->amem = ioremap(req.Base, req.Size);

	dev_dbg(&link->dev, "ray_config sram=%p\n", local->sram);
	dev_dbg(&link->dev, "ray_config rmem=%p\n", local->rmem);
	dev_dbg(&link->dev, "ray_config amem=%p\n", local->amem);
	if (ray_init(dev) < 0) {
		ray_release(link);
		return -ENODEV;
	}

	SET_NETDEV_DEV(dev, &link->dev);
	i = register_netdev(dev);
	if (i != 0) {
		printk("ray_config register_netdev() failed\n");
		ray_release(link);
		return i;
	}

	strcpy(local->node.dev_name, dev->name);
	link->dev_node = &local->node;

	printk(KERN_INFO "%s: RayLink, irq %d, hw_addr %pM\n",
	       dev->name, dev->irq, dev->dev_addr);

	return 0;
Linus Torvalds's avatar
Linus Torvalds committed

	ray_release(link);
	return -ENODEV;
Linus Torvalds's avatar
Linus Torvalds committed
} /* ray_config */

static inline struct ccs __iomem *ccs_base(ray_dev_t *dev)
{
	return dev->sram + CCS_BASE;
}

static inline struct rcs __iomem *rcs_base(ray_dev_t *dev)
{
	/*
	 * This looks nonsensical, since there is a separate
	 * RCS_BASE. But the difference between a "struct rcs"
	 * and a "struct ccs" ends up being in the _index_ off
	 * the base, so the base pointer is the same for both
	 * ccs/rcs.
	 */
	return dev->sram + CCS_BASE;
}

/*===========================================================================*/
static int ray_init(struct net_device *dev)
{
	int i;
	UCHAR *p;
	struct ccs __iomem *pccs;
	ray_dev_t *local = netdev_priv(dev);
	struct pcmcia_device *link = local->finder;
	dev_dbg(&link->dev, "ray_init(0x%p)\n", dev);
	if (!(pcmcia_dev_present(link))) {
		dev_dbg(&link->dev, "ray_init - device not present\n");
		return -1;
	}

	local->net_type = net_type;
	local->sta_type = TYPE_STA;

	/* Copy the startup results to local memory */
	memcpy_fromio(&local->startup_res, local->sram + ECF_TO_HOST_BASE,
		      sizeof(struct startup_res_6));

	/* Check Power up test status and get mac address from card */
	if (local->startup_res.startup_word != 0x80) {
		printk(KERN_INFO "ray_init ERROR card status = %2x\n",
		       local->startup_res.startup_word);
		local->card_status = CARD_INIT_ERROR;
		return -1;
	}

	local->fw_ver = local->startup_res.firmware_version[0];
	local->fw_bld = local->startup_res.firmware_version[1];
	local->fw_var = local->startup_res.firmware_version[2];
	dev_dbg(&link->dev, "ray_init firmware version %d.%d\n", local->fw_ver,
	      local->fw_bld);

	local->tib_length = 0x20;
	if ((local->fw_ver == 5) && (local->fw_bld >= 30))
		local->tib_length = local->startup_res.tib_length;
	dev_dbg(&link->dev, "ray_init tib_length = 0x%02x\n", local->tib_length);
	/* Initialize CCS's to buffer free state */
	pccs = ccs_base(local);
	for (i = 0; i < NUMBER_OF_CCS; i++) {
		writeb(CCS_BUFFER_FREE, &(pccs++)->buffer_status);
	}
	init_startup_params(local);

	/* copy mac address to startup parameters */
	if (parse_addr(phy_addr, local->sparm.b4.a_mac_addr)) {
		p = local->sparm.b4.a_mac_addr;
	} else {
		memcpy(&local->sparm.b4.a_mac_addr,
		       &local->startup_res.station_addr, ADDRLEN);
		p = local->sparm.b4.a_mac_addr;
	}

	clear_interrupt(local);	/* Clear any interrupt from the card */
	local->card_status = CARD_AWAITING_PARAM;
	dev_dbg(&link->dev, "ray_init ending\n");
Linus Torvalds's avatar
Linus Torvalds committed
} /* ray_init */
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
/* Download startup parameters to the card and command it to read them       */
static int dl_startup_params(struct net_device *dev)
{
	int ccsindex;
	ray_dev_t *local = netdev_priv(dev);
	struct ccs __iomem *pccs;
	struct pcmcia_device *link = local->finder;

	dev_dbg(&link->dev, "dl_startup_params entered\n");
	if (!(pcmcia_dev_present(link))) {
		dev_dbg(&link->dev, "ray_cs dl_startup_params - device not present\n");
		return -1;
	}

	/* Copy parameters to host to ECF area */
	if (local->fw_ver == 0x55)
		memcpy_toio(local->sram + HOST_TO_ECF_BASE, &local->sparm.b4,
			    sizeof(struct b4_startup_params));
	else
		memcpy_toio(local->sram + HOST_TO_ECF_BASE, &local->sparm.b5,
			    sizeof(struct b5_startup_params));

	/* Fill in the CCS fields for the ECF */
	if ((ccsindex = get_free_ccs(local)) < 0)
		return -1;
	local->dl_param_ccs = ccsindex;
	pccs = ccs_base(local) + ccsindex;
	writeb(CCS_DOWNLOAD_STARTUP_PARAMS, &pccs->cmd);
	dev_dbg(&link->dev, "dl_startup_params start ccsindex = %d\n",
	      local->dl_param_ccs);
	/* Interrupt the firmware to process the command */
	if (interrupt_ecf(local, ccsindex)) {
		printk(KERN_INFO "ray dl_startup_params failed - "
		       "ECF not ready for intr\n");
		local->card_status = CARD_DL_PARAM_ERROR;
		writeb(CCS_BUFFER_FREE, &(pccs++)->buffer_status);
		return -2;
	}
	local->card_status = CARD_DL_PARAM;
	/* Start kernel timer to wait for dl startup to complete. */
	local->timer.expires = jiffies + HZ / 2;
	local->timer.data = (long)local;
	local->timer.function = &verify_dl_startup;
	add_timer(&local->timer);
	      "ray_cs dl_startup_params started timer for verify_dl_startup\n");
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
} /* dl_startup_params */
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
static void init_startup_params(ray_dev_t *local)
{
	int i;

	if (country > JAPAN_TEST)
		country = USA;
	else if (country < USA)
		country = USA;
	/* structure for hop time and beacon period is defined here using
	 * New 802.11D6.1 format.  Card firmware is still using old format
	 * until version 6.
	 *    Before                    After
	 *    a_hop_time ms byte        a_hop_time ms byte
	 *    a_hop_time 2s byte        a_hop_time ls byte
	 *    a_hop_time ls byte        a_beacon_period ms byte
	 *    a_beacon_period           a_beacon_period ls byte
	 *
	 *    a_hop_time = uS           a_hop_time = KuS
	 *    a_beacon_period = hops    a_beacon_period = KuS
	 *//* 64ms = 010000 */
	if (local->fw_ver == 0x55) {
		memcpy((UCHAR *) &local->sparm.b4, b4_default_startup_parms,
		       sizeof(struct b4_startup_params));
		/* Translate sane kus input values to old build 4/5 format */
		/* i = hop time in uS truncated to 3 bytes */
		i = (hop_dwell * 1024) & 0xffffff;
		local->sparm.b4.a_hop_time[0] = (i >> 16) & 0xff;
		local->sparm.b4.a_hop_time[1] = (i >> 8) & 0xff;
		local->sparm.b4.a_beacon_period[0] = 0;
		local->sparm.b4.a_beacon_period[1] =
		    ((beacon_period / hop_dwell) - 1) & 0xff;
		local->sparm.b4.a_curr_country_code = country;
		local->sparm.b4.a_hop_pattern_length =
		    hop_pattern_length[(int)country] - 1;
		if (bc) {
			local->sparm.b4.a_ack_timeout = 0x50;
			local->sparm.b4.a_sifs = 0x3f;
		}
	} else { /* Version 5 uses real kus values */
		memcpy((UCHAR *) &local->sparm.b5, b5_default_startup_parms,
		       sizeof(struct b5_startup_params));

		local->sparm.b5.a_hop_time[0] = (hop_dwell >> 8) & 0xff;
		local->sparm.b5.a_hop_time[1] = hop_dwell & 0xff;
		local->sparm.b5.a_beacon_period[0] =
		    (beacon_period >> 8) & 0xff;
		local->sparm.b5.a_beacon_period[1] = beacon_period & 0xff;
		if (psm)
			local->sparm.b5.a_power_mgt_state = 1;
		local->sparm.b5.a_curr_country_code = country;
		local->sparm.b5.a_hop_pattern_length =
		    hop_pattern_length[(int)country];
	}

	local->sparm.b4.a_network_type = net_type & 0x01;
	local->sparm.b4.a_acting_as_ap_status = TYPE_STA;

	if (essid != NULL)
		strncpy(local->sparm.b4.a_current_ess_id, essid, ESSID_SIZE);
} /* init_startup_params */

Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
static void verify_dl_startup(u_long data)
{
	ray_dev_t *local = (ray_dev_t *) data;
	struct ccs __iomem *pccs = ccs_base(local) + local->dl_param_ccs;
	UCHAR status;
	struct pcmcia_device *link = local->finder;
Linus Torvalds's avatar
Linus Torvalds committed

	if (!(pcmcia_dev_present(link))) {
		dev_dbg(&link->dev, "ray_cs verify_dl_startup - device not present\n");
		int i;
		printk(KERN_DEBUG
		       "verify_dl_startup parameters sent via ccs %d:\n",
		       local->dl_param_ccs);
		for (i = 0; i < sizeof(struct b5_startup_params); i++) {
			printk(" %2x",
			       (unsigned int)readb(local->sram +
						   HOST_TO_ECF_BASE + i));
		}
		printk("\n");
	}
Linus Torvalds's avatar
Linus Torvalds committed
#endif

	status = readb(&pccs->buffer_status);
	if (status != CCS_BUFFER_FREE) {
		printk(KERN_INFO
		       "Download startup params failed.  Status = %d\n",
		       status);
		local->card_status = CARD_DL_PARAM_ERROR;
		return;
	}
	if (local->sparm.b4.a_network_type == ADHOC)
		start_net((u_long) local);
	else
		join_net((u_long) local);

	return;
Linus Torvalds's avatar
Linus Torvalds committed
} /* end verify_dl_startup */
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
/* Command card to start a network */
static void start_net(u_long data)
{
	ray_dev_t *local = (ray_dev_t *) data;
	struct ccs __iomem *pccs;
	int ccsindex;
	struct pcmcia_device *link = local->finder;
	if (!(pcmcia_dev_present(link))) {
		dev_dbg(&link->dev, "ray_cs start_net - device not present\n");
		return;
	}
	/* Fill in the CCS fields for the ECF */
	if ((ccsindex = get_free_ccs(local)) < 0)
		return;
	pccs = ccs_base(local) + ccsindex;
	writeb(CCS_START_NETWORK, &pccs->cmd);
	writeb(0, &pccs->var.start_network.update_param);
	/* Interrupt the firmware to process the command */
	if (interrupt_ecf(local, ccsindex)) {
		dev_dbg(&link->dev, "ray start net failed - card not ready for intr\n");
		writeb(CCS_BUFFER_FREE, &(pccs++)->buffer_status);
		return;
	}
	local->card_status = CARD_DOING_ACQ;
	return;
Linus Torvalds's avatar
Linus Torvalds committed
} /* end start_net */
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
/* Command card to join a network */
static void join_net(u_long data)
{
	ray_dev_t *local = (ray_dev_t *) data;

	struct ccs __iomem *pccs;
	int ccsindex;
	struct pcmcia_device *link = local->finder;

	if (!(pcmcia_dev_present(link))) {
		dev_dbg(&link->dev, "ray_cs join_net - device not present\n");
		return;
	}
	/* Fill in the CCS fields for the ECF */
	if ((ccsindex = get_free_ccs(local)) < 0)
		return;
	pccs = ccs_base(local) + ccsindex;
	writeb(CCS_JOIN_NETWORK, &pccs->cmd);
	writeb(0, &pccs->var.join_network.update_param);
	writeb(0, &pccs->var.join_network.net_initiated);
	/* Interrupt the firmware to process the command */
	if (interrupt_ecf(local, ccsindex)) {
		dev_dbg(&link->dev, "ray join net failed - card not ready for intr\n");
		writeb(CCS_BUFFER_FREE, &(pccs++)->buffer_status);
		return;
	}
	local->card_status = CARD_DOING_ACQ;
	return;
Linus Torvalds's avatar
Linus Torvalds committed
}
Linus Torvalds's avatar
Linus Torvalds committed
/*============================================================================
    After a card is removed, ray_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 ray_release(struct pcmcia_device *link)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct net_device *dev = link->priv;
	ray_dev_t *local = netdev_priv(dev);
	int i;

	dev_dbg(&link->dev, "ray_release\n");

	del_timer(&local->timer);

	iounmap(local->sram);
	iounmap(local->rmem);
	iounmap(local->amem);
	/* Do bother checking to see if these succeed or not */
	i = pcmcia_release_window(link, local->amem_handle);
		dev_dbg(&link->dev, "ReleaseWindow(local->amem) ret = %x\n", i);
	i = pcmcia_release_window(link, local->rmem_handle);
		dev_dbg(&link->dev, "ReleaseWindow(local->rmem) ret = %x\n", i);
	pcmcia_disable_device(link);

	dev_dbg(&link->dev, "ray_release ending\n");
static int ray_suspend(struct pcmcia_device *link)
{
	struct net_device *dev = link->priv;

		netif_device_detach(dev);
static int ray_resume(struct pcmcia_device *link)
{
	struct net_device *dev = link->priv;

		ray_reset(dev);
		netif_device_attach(dev);
	}
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
static int ray_dev_init(struct net_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
{
#ifdef RAY_IMMEDIATE_INIT
	int i;
#endif /* RAY_IMMEDIATE_INIT */
	ray_dev_t *local = netdev_priv(dev);
	struct pcmcia_device *link = local->finder;

	dev_dbg(&link->dev, "ray_dev_init(dev=%p)\n", dev);
	if (!(pcmcia_dev_present(link))) {
		dev_dbg(&link->dev, "ray_dev_init - device not present\n");
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef RAY_IMMEDIATE_INIT
	/* Download startup parameters */
	if ((i = dl_startup_params(dev)) < 0) {
		printk(KERN_INFO "ray_dev_init dl_startup_params failed - "
		       "returns 0x%x\n", i);
		return -1;
	}
#else /* RAY_IMMEDIATE_INIT */
	/* Postpone the card init so that we can still configure the card,
	 * for example using the Wireless Extensions. The init will happen
	 * in ray_open() - Jean II */
	      "ray_dev_init: postponing card init to ray_open() ; Status = %d\n",
	      local->card_status);
#endif /* RAY_IMMEDIATE_INIT */

	/* copy mac and broadcast addresses to linux device */
	memcpy(dev->dev_addr, &local->sparm.b4.a_mac_addr, ADDRLEN);
	memset(dev->broadcast, 0xff, ETH_ALEN);

	dev_dbg(&link->dev, "ray_dev_init ending\n");
Linus Torvalds's avatar
Linus Torvalds committed
}
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
static int ray_dev_config(struct net_device *dev, struct ifmap *map)
{
	ray_dev_t *local = netdev_priv(dev);
	struct pcmcia_device *link = local->finder;
	/* Dummy routine to satisfy device structure */
	dev_dbg(&link->dev, "ray_dev_config(dev=%p,ifmap=%p)\n", dev, map);
	if (!(pcmcia_dev_present(link))) {
		dev_dbg(&link->dev, "ray_dev_config - device not present\n");
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
}
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
static netdev_tx_t ray_dev_start_xmit(struct sk_buff *skb,
					    struct net_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	ray_dev_t *local = netdev_priv(dev);
	struct pcmcia_device *link = local->finder;
	short length = skb->len;

	if (!pcmcia_dev_present(link)) {
		dev_dbg(&link->dev, "ray_dev_start_xmit - device not present\n");
		dev_kfree_skb(skb);
		return NETDEV_TX_OK;
	dev_dbg(&link->dev, "ray_dev_start_xmit(skb=%p, dev=%p)\n", skb, dev);
	if (local->authentication_state == NEED_TO_AUTH) {
		dev_dbg(&link->dev, "ray_cs Sending authentication request.\n");
		if (!build_auth_frame(local, local->auth_id, OPEN_AUTH_REQUEST)) {
			local->authentication_state = AUTHENTICATED;
			netif_stop_queue(dev);
		}
	}

	if (length < ETH_ZLEN) {
		if (skb_padto(skb, ETH_ZLEN))
		length = ETH_ZLEN;
	}
	switch (ray_hw_xmit(skb->data, length, dev, DATA_TYPE)) {
	case XMIT_NO_CCS:
	case XMIT_NEED_AUTH:
		netif_stop_queue(dev);
	case XMIT_NO_INTR:
	case XMIT_MSG_BAD:
	case XMIT_OK:
	default:
		dev->trans_start = jiffies;
		dev_kfree_skb(skb);
	}
Linus Torvalds's avatar
Linus Torvalds committed
} /* ray_dev_start_xmit */
Linus Torvalds's avatar
Linus Torvalds committed
/*===========================================================================*/
static int ray_hw_xmit(unsigned char *data, int len, struct net_device *dev,
		       UCHAR msg_type)
{
	ray_dev_t *local = netdev_priv(dev);
	struct ccs __iomem *pccs;
	int ccsindex;
	int offset;
	struct tx_msg __iomem *ptx;	/* Address of xmit buffer in PC space */
	short int addr;		/* Address of xmit buffer in card space */

	pr_debug("ray_hw_xmit(data=%p, len=%d, dev=%p)\n", data, len, dev);
	if (len + TX_HEADER_LENGTH > TX_BUF_SIZE) {
		printk(KERN_INFO "ray_hw_xmit packet too large: %d bytes\n",
		       len);
		return XMIT_MSG_BAD;
	}
Linus Torvalds's avatar
Linus Torvalds committed
	switch (ccsindex = get_free_tx_ccs(local)) {
	case ECCSBUSY:
		pr_debug("ray_hw_xmit tx_ccs table busy\n");
Linus Torvalds's avatar
Linus Torvalds committed
	case ECCSFULL:
		pr_debug("ray_hw_xmit No free tx ccs\n");
Linus Torvalds's avatar
Linus Torvalds committed
	case ECARDGONE:
		netif_stop_queue(dev);
		return XMIT_NO_CCS;
Linus Torvalds's avatar
Linus Torvalds committed
	default:
		break;
	}
	addr = TX_BUF_BASE + (ccsindex << 11);

	if (msg_type == DATA_TYPE) {
		local->stats.tx_bytes += len;
		local->stats.tx_packets++;
	}

	ptx = local->sram + addr;

	ray_build_header(local, ptx, msg_type, data);
	if (translate) {
		offset = translate_frame(local, ptx, data, len);
	} else { /* Encapsulate frame */
		/* TBD TIB length will move address of ptx->var */
		memcpy_toio(&ptx->var, data, len);
		offset = 0;
	}

	/* fill in the CCS */
	pccs = ccs_base(local) + ccsindex;
	len += TX_HEADER_LENGTH + offset;