/*
 * WL24xx Wireless LAN Card Driver for Linux
 *
 * Design Issues:
 *
 *   The driver had been carefully designed with the following approachs to 
 * provide high performance and low overload in parallel tasking environment.
 *
 *   1. Prevent using cli() and sti() functions to slove critical section
 *      problem as possible.
 *   2. Not doing busy waiting.
 *   3. Only block the corresponded card's interrupt instead of sti() in ISR and
 *      related routines.
 *   4. Lock the SUTRO (O/S within card) as short as possible if necessary.
 *   5. Fully utilize Rx/Tx queueing buffer in card. (Rx/Tx is about 12K/12K)
 *   6. Avoid additional memcpy between O/S and card.
 *
 * Specification:
 *
 *   1. Fully function driver, multicast and promiscuous filter supported.
 *   2. Ad-hoc, infrastructure, and roaming algorithm supported.
 *   3. Error recovery function supported.
 *   4. Support both Plug'n'Play and Non-Plug'n'Play machines.
 *
 * 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)
 *
 * Program Flows:
 *
 *   Thread 1: WL_Probe() -> WL_Open()
 *   Thread 2: WL_StartXmit() -> WL_SendPkt() -> dev_kfree_skb()
 *   Thread 3: WL_Interrupt() -> WL_RxInterrupt() -> WL_ReceiveLook() -> 
 *             WL_Receive() -> WL_ReceiveDown() -> netif_rx()
 *   Thread 4: WL_SetMulticastList() -> WL_SetMibValue()
 *   Thread 5: WL_GetStats()
 *   Thread 6: WL_Close()
 *
 * Additional Information:
 *
 *   1. To prevent forward declaration, I put primitive functions ahead.
 *   2. All veriables are named with type, with form <type><name> e.g.:
 *      cTmp -> Type is 'c', and name is 'Tmp'
 *
 *      Type List: 
 *          c   - unsigned char (BYTE)
 *          u   - unsigned int  (WORD)
 *          b   - boolean int   (BOOL)
 *          ul  - unsigned long (DWORD)
 *          p   - pointer       (DWORD)
 *          psz - pointer to string ended with zero
 *          cpsz- const psz
 *          q   - queue
 *      Others are defined by Windows convension.
 *
 * Tested with WL2420 firmware 1.2, Linux 2.0.0, 2.0.30
 *   1. Performance: about 170 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)
 *
 * now works on 2.4.18 Tobias Hintze <th@delta.boerde.de>
 */

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/ptrace.h>
#include <linux/errno.h>
#include <linux/in.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <asm/bitops.h>
#include <asm/io.h>

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>

#include "wlapi.h"
#include "wlpnp.h"

/*
 * Uncomment this line if you would like to provide wlu support.
 * Wlu (Unix version) use ioctl() to control WL24xx cards.
 * We set the WL_Ioctl() callback when WL_Probe() running.
 */
#include "wlioctl.h"

#ifdef  WL24_DEBUG
#  define TRACE_WL(format, args...) printk(format , ## args);
#else
#  define TRACE_WL(format, args...)
#endif

/*
 * Definition for printing version information
 */
#ifdef WLAPI_DEBUG
#  define   WLAPI_DEBUG_STR         "WLAPI_DEBUG "
#else
#  define   WLAPI_DEBUG_STR         ""
#endif

#ifdef WL_SLOWDOWN_IO
#  define   WL_SLOWDOWN_IO_STR      "WL_SLOWDOWN_IO "
#else
#  define   WL_SLOWDOWN_IO_STR      ""
#endif

#ifdef WL24_DEBUG
#  define   WL24_DEBUG_STR          "ZWL_DEBUG "
#else
#  define   WL24_DEBUG_STR          ""
#endif

const char g_cpszVersion[] = "wl24.c: v1.20m1 AT29C010A supported " \
    WLAPI_DEBUG_STR WL_SLOWDOWN_IO_STR WL24_DEBUG_STR __DATE__;

int WL_RSSI = -1;

const char *WL_OP_MODE_TABLE[] = {
    "Access Point",
    "Infrastructure",
    "Ad-hoc",
    "Inter AP",
    NULL
};

void WL_RxInterrupt(struct net_device *dev, BYTE uStatus2)
{
    int         nSize;
    BOOL        bMorePkts;
    WL_Adaptor *pAdaptor = (WL_Adaptor *) dev->priv;
    
    if (uStatus2 & ISR2_RxMgmt) {
        TRACE_WL("%s: Rx >>>>> Roaming\n", dev->name);
        WL_Roaming(pAdaptor);
    }
    
    /* Check for data queue */
    bMorePkts = TRUE;
    while (bMorePkts && (nSize = WL_ReceiveDataLook(pAdaptor)) != -1) {
        struct  sk_buff *skb;
        int     nRSSI;
        MADDR   *pAddr;
        
        TRACE_WL("%s: Rx >>>>> Data\n", dev->name);
        
        skb = dev_alloc_skb(nSize + 5);
        if (skb == NULL) {
            printk("%s: Cannot allocate a sk_buff of size %d.\n", dev->name, nSize);
            pAdaptor->stat.rx_dropped++;

            /* must drop this packet to ensure interrupt will come again */
            bMorePkts = WL_ReceiveDataDone(pAdaptor);
            continue;
        }

        skb->dev = dev;
        skb_reserve(skb, 2);    /* IP headers on 16 bytes boundaries */
        skb_put(skb, nSize);    /* Make room */
        
        nRSSI = WL_ReceiveData(pAdaptor, 0, skb->data, nSize);
        bMorePkts = WL_ReceiveDataDone(pAdaptor);

        /* Forwarding packets for other infrastructure sites if I'm an AP */
        if (pAdaptor->mibExtra.CCR == CCR_AP) {
            MADDR *p = (MADDR *) skb->data;
            /* If broadcast, multicast, or WL card, forward the packet */
            if ((p->b0 & 0x01) || (p->b0==0x00 && p->b1==0x60 && p->b2==0xB3 && 
                !MA_ISEQUAL(*p, pAdaptor->MacAddress))) {
                /* Drop the packet if send failed */
                if (!WL_SendPkt(pAdaptor, skb->data, nSize, 0))
                    pAdaptor->stat.tx_dropped++;
            }
        }
        
        /* Update statistic first */
        pAdaptor->stat.rx_packets++;
        pAdaptor->stat.rx_frame_errors = nRSSI;  /* just for testing */
        
        /* Comes from monitored site ? */
        pAddr = (MADDR *) &skb->data[6];
        if (MA_ISEQUAL(*pAddr, pAdaptor->Monitor.MAddr)) {
            pAdaptor->Monitor.RSSI = nRSSI;
            pAdaptor->Monitor.TimeStamp = jiffies;
        }
        
        /* Notify the upper protocol layers that there is another packet */
        /* to handle. netif_rx() always succeeds. see dev.c for more.    */
        skb->protocol = eth_type_trans(skb, dev);
        WL_RSSI = nRSSI;
        netif_rx(skb);
        WL_RSSI = -1;
    }
}

/* 
 * Hardware interrupt from card.
 *
 * 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.
 */

void WL_Interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    struct net_device *dev = (struct net_device *) dev_id;
    WL_Adaptor *pAdaptor;
    CSB_BLOCK   csb;
    BYTE        cTmp;
    BYTE        cStatus;

    TRACE_WL("WL: interrupt begin...\n");
            
	/* th: this was a if (dev == NULL || dev->start ==0) {...} before */
    if (dev == NULL || (!netif_running(dev))) {
        printk("WL_Interrupt(): irq %d for unknown net_device.\n", irq);
        return;
    }
    
    pAdaptor = (WL_Adaptor *) dev->priv;

    /* Ack Interrupt */
    outb(GCR_ECINT, pAdaptor->nBaseAddr + NIC_GCR);

    WL_BlockInterrupt(pAdaptor);
    
    /* Clean Zone Begin */
    WL_LockSutro(pAdaptor);
    
    WL_GetFromWla(pAdaptor, Control_Status_Block, &csb, sizeof(csb));
    
    cTmp = 0;
    WL_SetToWla(pAdaptor, CSB_Interrupt_Status, &cTmp, sizeof(cTmp));
    WL_SetToWla(pAdaptor, CSB_Interrupt_Status2, &cTmp, sizeof(cTmp));

    WL_UnlockSutro(pAdaptor);

    /* 0x80 = Stop driver, and 0x40 = Restart driver are reserved */
    cStatus = csb.Interrupt_Status & 0x3F;
    
    if (cStatus & ISR_Tx) {
        TRACE_WL("%s: Tx <<<<<\n", dev->name);
        WL_PurgeTxQueue(pAdaptor, &pAdaptor->qData);
        pAdaptor->stat.tx_packets++;
		
		/* th: replaced dev->tbusy = 0; */
		netif_wake_queue(dev);

        /* Cause net_bh() to run after this interrupt handler */
        /* The function which do handle slow IRQ parts is do_bottom_half() */
        /* which runs at normal kernel priority, that means all interrupt  */
        /* are enabled. (see kernel/softirq.c) */
    }
    
    if (cStatus & ISR_Rx) {
        TRACE_WL("%s: Rx >>>>>\n", dev->name);
        WL_RxInterrupt(dev, csb.Interrupt_Status2);
    }
    
    /* Clean Zone End */
    WL_UnBlockInterrupt(pAdaptor);
    
    return;
}

/*
 * Reset the SUTRO. It is almost the same as WL_Open()
 *
 * In fact, we may just WL_Close() and WL_Open() again, but I wouldn't like to
 * free_irq() when the driver is running. It seems to be dangerous.
 */ 
int WL_Reset(struct net_device *dev)
{
    BYTE        cFilter;
    WL_Adaptor *pAdaptor    = (WL_Adaptor *) dev->priv;
    
    /* Stop processing interrupt from the card */
    WL_BlockInterrupt(pAdaptor);

    /* Initial WL24 firmware */
    printk("%s: Initialize WL24xx firmware...\n", dev->name);
    if (!WL_InitFirmware(pAdaptor)) {
        printk("%s: Cannot Initial Firmware!!\n", dev->name);
        /* Free IRQ, and mark IRQ as unused */
        free_irq(dev->irq, dev);
        /* irq2dev_map[dev->irq] = 0; */
        return -ENODEV;
    }

    /* Set up receiver filter to accept UNICAST and BROADCAST packets */
    cFilter = RMR_UNICAST | RMR_BROADCAST;
    WL_SetMibValue(pAdaptor, TYPE_EXTRA_MIB, IDX_RECEIVEMODE, &cFilter, sizeof(cFilter));

	/* th: was dev->start = 1 before */
    netif_start_queue(dev);

    /* Acknowledge Interrupt, for cleaning last state */
    outb(GCR_ECINT, pAdaptor->nBaseAddr + NIC_GCR);
    
    /* Enable interrupt from card */
    WL_UnBlockInterrupt(pAdaptor);
    
    printk("%s: device reset\n", dev->name);
    return 0;
}

/* 
 * Return : 0 - OK
 *          1 - Could not transmit (dev_queue_xmit will queue it)
 *              and try to sent it later
 */
int WL_StartXmit(struct sk_buff *skb, struct net_device *dev)
{
    BOOL        bEnabled, bSend;
    WL_Adaptor *pAdaptor    = (WL_Adaptor *) dev->priv;

	if (skb->len <= 0)
		return 0;

	if (netif_queue_stopped(dev)) {
		/* if Transmitter more than 400ms busy -> timeout */
		int tickssofar = jiffies - dev->trans_start;
		if (tickssofar < 40)
			return 1;

		if (!netif_running(dev)) {
			printk("%s: xmit on stopped card\n", dev->name);
			return 1;
		}

		printk("%s: Tx timeout, reset the card\n", dev->name);
		pAdaptor->stat.tx_errors++;
		pAdaptor->stat.tx_dropped++;

		dev->trans_start = jiffies;
		WL_Reset(dev);
	}
	
	if (test_and_set_bit(0, &dev->state)) { /* LINK_STATE_XOFF  */
		TRACE_WL("%s: Xmit access conflict.\n", dev->name);
		return 1;   /* Wait for next time */
	}

    bEnabled = WL_BlockInterrupt(pAdaptor);
    
    /* Record transmitt start time */
    dev->trans_start = jiffies;

    /* Send the packet with default speed */
    bSend = WL_SendPkt(pAdaptor, skb->data, skb->len, 0);
    
    /* Turn SUTRO interrupt back on only if it is originally enabled */
    if (bEnabled)
        WL_UnBlockInterrupt(pAdaptor);
    
    /* If sent successfully, set tbusy to be 0. Otherwise, buffer is all used */
    /* tbusy will be 0 again when the SUTRO interrupts us and returns ISR_Tx */
    if (bSend) {
		pAdaptor->stat.tx_bytes += skb->len;
		netif_start_queue(dev);
        dev_kfree_skb(skb);
        return 0;
    } else {
		wl_debug(2, "Error in transmition\n");
		pAdaptor->stat.tx_errors++;
	}
    
    return 1;   /* Try next time */
}

struct net_device_stats *WL_GetStats(struct net_device *dev)
{
    WL_Adaptor *pAdaptor    = (WL_Adaptor *) dev->priv;
    return &pAdaptor->stat;
}

int WL_Close(struct net_device *dev)
{
    WL_Adaptor *pAdaptor    = (WL_Adaptor *) dev->priv;

	if (!pAdaptor) {
		wl_debug(1, "private struct is null\n");
		return 1;
	}

	netif_stop_queue(dev);

    /* Ack Interrupt */
    outb(GCR_ECINT, pAdaptor->nBaseAddr + NIC_GCR);

    /* Mask interrupts from the SUTRO */
    WL_BlockInterrupt(pAdaptor);
    
    /* Free IRQ, and mark IRQ as unused */
    free_irq(dev->irq, dev);
    /* irq2dev_map[dev->irq] = 0; */
    
    printk("%s : device closed\n", dev->name);
    return 0;
}

int WL_Open(struct net_device *dev)
{
    BYTE        cFilter, cCCR;
    WL_Adaptor *pAdaptor    = (WL_Adaptor *) dev->priv;
    
    /* Reserve IRQ */
    if (request_irq(dev->irq, WL_Interrupt, 0, "WL24xx", dev)) {
        /* failed */
        /* irq2dev_map[dev->irq] = NULL; */
        return -EAGAIN;
    }

    /* Initial WL24 firmware */
    printk("%s: Initialize WL24xx firmware...\n", dev->name);
    if (!WL_InitFirmware(pAdaptor)) {
        printk("%s: Cannot Initial Firmware!!\n", dev->name);
        /* Free IRQ, and mark IRQ as unused */
        free_irq(dev->irq, dev);
        /* irq2dev_map[dev->irq] = 0; */
        return -ENODEV;
    }

    /* Print card setup information */
    cCCR = pAdaptor->mibExtra.CCR;

    printk("%s: Channel %d, Speed %d Mb/s, %s Mode", dev->name, 
        (int) pAdaptor->mibExtra.Channel, (int) pAdaptor->mibExtra.Speed,
        cCCR <= CCR_IAP ? WL_OP_MODE_TABLE[cCCR] : "UNKNOWN");

    if (cCCR != CCR_ADHOC) {    /* should be better within one printk :-) */
        printk(", ESS ID = %c%c%c%c%c%c", 
            pAdaptor->mibExtra.ESS_ID.b0, pAdaptor->mibExtra.ESS_ID.b1,
            pAdaptor->mibExtra.ESS_ID.b2, pAdaptor->mibExtra.ESS_ID.b3,
            pAdaptor->mibExtra.ESS_ID.b4, pAdaptor->mibExtra.ESS_ID.b5);
    }
    printk("\n");

    /* Set up receiver filter to accept UNICAST and BROADCAST packets */
    cFilter = RMR_UNICAST | RMR_BROADCAST;
    WL_SetMibValue(pAdaptor, TYPE_EXTRA_MIB, IDX_RECEIVEMODE, &cFilter, sizeof(cFilter));

	netif_start_queue(dev);
    
    /* Acknowledge Interrupt, for cleaning last state */
    outb(GCR_ECINT, pAdaptor->nBaseAddr + NIC_GCR);
    
    /* Enable interrupt from card after all */
    WL_UnBlockInterrupt(pAdaptor);

    printk("%s: device opened\n", dev->name);
    return 0;
}

/*
 * Set or clear the multicast filter for this adaptor.
 *
 * CAUTION: To prevent interrupted by WL_Interrupt() and timer-based 
 * WL_StartXmit() 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.
 */
void WL_SetMulticastList(struct net_device *dev)
{
    WL_Adaptor *pAdaptor    = (WL_Adaptor *) dev->priv;
    unsigned long ulFlags;
    BYTE cFilter;
    
    if (dev->flags & IFF_PROMISC) {
        /* Promiscuous mode */
        cFilter = RMR_UNICAST | RMR_PROMISCUOUS | RMR_ALL_MULTICAST | RMR_BROADCAST;
    }
    else if (dev->mc_count || (dev->flags & IFF_ALLMULTI)) {
        /* Allow multicast */
        cFilter = RMR_UNICAST | RMR_ALL_MULTICAST | RMR_BROADCAST;
    }
    else {
        /* Normal mode */
        cFilter = RMR_UNICAST | RMR_BROADCAST;
    }
    
    /* Keep flags first, these are inline functions */
    save_flags(ulFlags);
    cli();

    /* Must not be interrupted */
    WL_SetMibValue(pAdaptor, TYPE_EXTRA_MIB, IDX_RECEIVEMODE, &cFilter, sizeof(cFilter));

    /* Restore CLI and others */
    restore_flags(ulFlags);
}

/*
 * Try to probe WL24xx cards. Return 0 if success, or errno if failed.
 *
 * If base_addr or irq is zero, then we try to read value set by PnP BIOS.
 * Otherwise, set supplied value into non-initialized card.
 *
 */
int WL_Probe(struct net_device *dev)
{
    static int  nCardIndex = 1;     /* static, for all WL24 eth* devices */
    
    WL_Adaptor  adaptor;
    int         i;

    /* Check if byte alignment is correct or not. FOR DEBUGGING ONLY */
    if (sizeof(TX_HEADER) != 54 || sizeof(BEACON_PKT) != 55) {
        printk("wl24.c: compiler alignment option must be 1 byte.\n");
        return -ENODEV;
    }
    
    if (dev->base_addr == 0 || dev->irq == 0) {
        printk("%s: Trying to probe WL24xx PnP card...\n", dev->name);
        if (WL_PnpProbe(nCardIndex, &dev->base_addr, (BYTE *)&dev->irq)) {
            nCardIndex++;
            printk("%s: WL24xx PnP card probe at 0x%3.3x, IRQ %d\n", 
                    dev->name, (int) dev->base_addr, (int) dev->irq);
        }
        else {
            printk("%s: Cannot probe PnP card, you must specify base address and irq.\n", dev->name);
            return -ENODEV;
        }
    }
    else {
        /* Try to find non-PNPed WL24xx PnP cards that are installed */
        /* in a PC which does not support PnP BIOS. */
        /* If found, set the card's IRQ and I/O up  */
        WL_PnpSetNonInitCard(dev->base_addr, dev->irq);
    }    
    
    memset(&adaptor, 0, sizeof(adaptor));
    adaptor.nBaseAddr = dev->base_addr;
    
    if (!WL_GetFlashMacAddress(&adaptor)) {
        printk("%s: Cannot read MAC address in flash ROM ??\n", dev->name);
        return -ENODEV;
    }

    /* print probe information */
    printk("%s: WL24xx at 0x%3.3x, IRQ %d, MAC address in flash ROM:", 
        dev->name, adaptor.nBaseAddr, (int) dev->irq);
    for (i = 0; i < 6; i++) {
        dev->dev_addr[i] = ((BYTE *) &adaptor.MacAddress)[i];
        printk("%c%02x", i ? ':' : ' ', dev->dev_addr[i]);
    }
    printk("\n");

	netif_stop_queue(dev);
	

    /* Store the WL_Adaptor sturcture into private memory */
    dev->priv = kmalloc(sizeof(WL_Adaptor), GFP_KERNEL);
    if (dev->priv == NULL)
        return -ENOMEM;
        
    memcpy(dev->priv, &adaptor, sizeof(adaptor));
    
    /* Reserved I/O address and IRQ */        
    dev->base_addr  = adaptor.nBaseAddr;
	if (check_region(dev->base_addr, WL_IO_REGION)) {
		wl_debug(0, "%s: base_addr 0x%lx busy\n", dev->name, dev->base_addr);
		kfree(dev->priv);
		dev->priv = NULL;
		return -EBUSY;
	}

    request_region(dev->base_addr, WL_IO_REGION, "WL24xx");

    /* Set up Callback functions */
    dev->open               = &WL_Open;
    dev->hard_start_xmit    = &WL_StartXmit;
    dev->stop               = &WL_Close;
    dev->get_stats          = &WL_GetStats;
    dev->set_multicast_list = &WL_SetMulticastList;
#ifdef __WL_IOCTL_H__
    dev->do_ioctl           = &WL_Ioctl;
#endif

    ether_setup(dev);

    return 0;
}
