cops.c

来自「linux 内核源代码」· C语言 代码 · 共 1,039 行 · 第 1/2 页

C
1,039
字号
/*      cops.c: LocalTalk driver for Linux. * *	Authors: *      - Jay Schulist <jschlst@samba.org> * *	With more than a little help from; *	- Alan Cox <Alan.Cox@linux.org>  * *      Derived from: *      - skeleton.c: A network driver outline for linux. *        Written 1993-94 by Donald Becker. *	- ltpc.c: A driver for the LocalTalk PC card. *	  Written by Bradford W. Johnson. * *      Copyright 1993 United States Government as represented by the *      Director, National Security Agency. * *      This software may be used and distributed according to the terms *      of the GNU General Public License, incorporated herein by reference. * *	Changes: *	19970608	Alan Cox	Allowed dual card type support *					Can set board type in insmod *					Hooks for cops_setup routine *					(not yet implemented). *	19971101	Jay Schulist	Fixes for multiple lt* devices. *	19980607	Steven Hirsch	Fixed the badly broken support *					for Tangent type cards. Only *                                      tested on Daystar LT200. Some *                                      cleanup of formatting and program *                                      logic.  Added emacs 'local-vars' *                                      setup for Jay's brace style. *	20000211	Alan Cox	Cleaned up for softnet */static const char *version ="cops.c:v0.04 6/7/98 Jay Schulist <jschlst@samba.org>\n";/* *  Sources: *      COPS Localtalk SDK. This provides almost all of the information *      needed. *//* * insmod/modprobe configurable stuff. *	- IO Port, choose one your card supports or 0 if you dare. *	- IRQ, also choose one your card supports or nothing and let *	  the driver figure it out. */#include <linux/module.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/fcntl.h>#include <linux/interrupt.h>#include <linux/ptrace.h>#include <linux/ioport.h>#include <linux/in.h>#include <linux/slab.h>#include <linux/string.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include <linux/if_arp.h>#include <linux/if_ltalk.h>#include <linux/delay.h>	/* For udelay() */#include <linux/atalk.h>#include <linux/spinlock.h>#include <linux/bitops.h>#include <asm/system.h>#include <asm/io.h>#include <asm/dma.h>#include "cops.h"		/* Our Stuff */#include "cops_ltdrv.h"		/* Firmware code for Tangent type cards. */#include "cops_ffdrv.h"		/* Firmware code for Dayna type cards. *//* *      The name of the card. Is used for messages and in the requests for *      io regions, irqs and dma channels */static const char *cardname = "cops";#ifdef CONFIG_COPS_DAYNAstatic int board_type = DAYNA;	/* Module exported */#elsestatic int board_type = TANGENT;#endifstatic int io = 0x240;		/* Default IO for Dayna */static int irq = 5;		/* Default IRQ *//* *	COPS Autoprobe information. *	Right now if port address is right but IRQ is not 5 this will *      return a 5 no matter what since we will still get a status response. *      Need one more additional check to narrow down after we have gotten *      the ioaddr. But since only other possible IRQs is 3 and 4 so no real *	hurry on this. I *STRONGLY* recommend using IRQ 5 for your card with *	this driver. *  *	This driver has 2 modes and they are: Dayna mode and Tangent mode. *	Each mode corresponds with the type of card. It has been found *	that there are 2 main types of cards and all other cards are *	the same and just have different names or only have minor differences *	such as more IO ports. As this driver is tested it will *	become more clear on exactly what cards are supported. The driver *	defaults to using Dayna mode. To change the drivers mode, simply *	select Dayna or Tangent mode when configuring the kernel. * *      This driver should support: *      TANGENT driver mode: *              Tangent ATB-II, Novell NL-1000, Daystar Digital LT-200, *		COPS LT-1 *      DAYNA driver mode: *              Dayna DL2000/DaynaTalk PC (Half Length), COPS LT-95,  *		Farallon PhoneNET PC III, Farallon PhoneNET PC II *	Other cards possibly supported mode unkown though: *		Dayna DL2000 (Full length), COPS LT/M (Micro-Channel) * *	Cards NOT supported by this driver but supported by the ltpc.c *	driver written by Bradford W. Johnson <johns393@maroon.tc.umn.edu> *		Farallon PhoneNET PC *		Original Apple LocalTalk PC card *  *      N.B. * *      The Daystar Digital LT200 boards do not support interrupt-driven *      IO.  You must specify 'irq=0xff' as a module parameter to invoke *      polled mode.  I also believe that the port probing logic is quite *      dangerous at best and certainly hopeless for a polled card.  Best to  *      specify both. - Steve H. * *//* * Zero terminated list of IO ports to probe. */static unsigned int ports[] = { 	0x240, 0x340, 0x200, 0x210, 0x220, 0x230, 0x260, 	0x2A0, 0x300, 0x310, 0x320, 0x330, 0x350, 0x360,	0};/* * Zero terminated list of IRQ ports to probe. */static int cops_irqlist[] = {	5, 4, 3, 0 };static struct timer_list cops_timer;/* use 0 for production, 1 for verification, 2 for debug, 3 for verbose debug */#ifndef COPS_DEBUG#define COPS_DEBUG 1 #endifstatic unsigned int cops_debug = COPS_DEBUG;/* The number of low I/O ports used by the card. */#define COPS_IO_EXTENT       8/* Information that needs to be kept for each board. */struct cops_local{        struct net_device_stats stats;        int board;			/* Holds what board type is. */	int nodeid;			/* Set to 1 once have nodeid. */        unsigned char node_acquire;	/* Node ID when acquired. */        struct atalk_addr node_addr;	/* Full node address */	spinlock_t lock;		/* RX/TX lock */};/* Index to functions, as function prototypes. */static int  cops_probe1 (struct net_device *dev, int ioaddr);static int  cops_irq (int ioaddr, int board);static int  cops_open (struct net_device *dev);static int  cops_jumpstart (struct net_device *dev);static void cops_reset (struct net_device *dev, int sleep);static void cops_load (struct net_device *dev);static int  cops_nodeid (struct net_device *dev, int nodeid);static irqreturn_t cops_interrupt (int irq, void *dev_id);static void cops_poll (unsigned long ltdev);static void cops_timeout(struct net_device *dev);static void cops_rx (struct net_device *dev);static int  cops_send_packet (struct sk_buff *skb, struct net_device *dev);static void set_multicast_list (struct net_device *dev);static int  cops_ioctl (struct net_device *dev, struct ifreq *rq, int cmd);static int  cops_close (struct net_device *dev);static struct net_device_stats *cops_get_stats (struct net_device *dev);static void cleanup_card(struct net_device *dev){	if (dev->irq)		free_irq(dev->irq, dev);	release_region(dev->base_addr, COPS_IO_EXTENT);}/* *      Check for a network adaptor of this type, and return '0' iff one exists. *      If dev->base_addr == 0, probe all likely locations. *      If dev->base_addr in [1..0x1ff], always return failure. *        otherwise go with what we pass in. */struct net_device * __init cops_probe(int unit){	struct net_device *dev;	unsigned *port;	int base_addr;	int err = 0;	dev = alloc_ltalkdev(sizeof(struct cops_local));	if (!dev)		return ERR_PTR(-ENOMEM);	if (unit >= 0) {		sprintf(dev->name, "lt%d", unit);		netdev_boot_setup_check(dev);		irq = dev->irq;		base_addr = dev->base_addr;	} else {		base_addr = dev->base_addr = io;	}	if (base_addr > 0x1ff) {    /* Check a single specified location. */		err = cops_probe1(dev, base_addr);	} else if (base_addr != 0) { /* Don't probe at all. */		err = -ENXIO;	} else {		/* FIXME  Does this really work for cards which generate irq?		 * It's definitely N.G. for polled Tangent. sh		 * Dayna cards don't autoprobe well at all, but if your card is		 * at IRQ 5 & IO 0x240 we find it every time. ;) JS		 */		for (port = ports; *port && cops_probe1(dev, *port) < 0; port++)			;		if (!*port)			err = -ENODEV;	}	if (err)		goto out;	err = register_netdev(dev);	if (err)		goto out1;	return dev;out1:	cleanup_card(dev);out:	free_netdev(dev);	return ERR_PTR(err);}/* *      This is the real probe routine. Linux has a history of friendly device *      probes on the ISA bus. A good device probes avoids doing writes, and *      verifies that the correct device exists and functions. */static int __init cops_probe1(struct net_device *dev, int ioaddr){        struct cops_local *lp;	static unsigned version_printed;	int board = board_type;	int retval;	        if(cops_debug && version_printed++ == 0)		printk("%s", version);	/* Grab the region so no one else tries to probe our ioports. */	if (!request_region(ioaddr, COPS_IO_EXTENT, dev->name))		return -EBUSY;        /*         * Since this board has jumpered interrupts, allocate the interrupt         * vector now. There is no point in waiting since no other device         * can use the interrupt, and this marks the irq as busy. Jumpered         * interrupts are typically not reported by the boards, and we must         * used AutoIRQ to find them.	 */	dev->irq = irq;	switch (dev->irq)	{		case 0:			/* COPS AutoIRQ routine */			dev->irq = cops_irq(ioaddr, board);			if (dev->irq)				break;			/* No IRQ found on this port, fallthrough */		case 1:			retval = -EINVAL;			goto err_out;		/* Fixup for users that don't know that IRQ 2 is really		 * IRQ 9, or don't know which one to set.		 */		case 2:			dev->irq = 9;			break;		/* Polled operation requested. Although irq of zero passed as		 * a parameter tells the init routines to probe, we'll		 * overload it to denote polled operation at runtime.		 */		case 0xff:			dev->irq = 0;			break;		default:			break;	}	/* Reserve any actual interrupt. */	if (dev->irq) {		retval = request_irq(dev->irq, &cops_interrupt, 0, dev->name, dev);		if (retval)			goto err_out;	}	dev->base_addr = ioaddr;        lp = netdev_priv(dev);        spin_lock_init(&lp->lock);	/* Copy local board variable to lp struct. */	lp->board               = board;	dev->hard_start_xmit    = cops_send_packet;	dev->tx_timeout		= cops_timeout;	dev->watchdog_timeo	= HZ * 2;        dev->get_stats          = cops_get_stats;	dev->open               = cops_open;        dev->stop               = cops_close;        dev->do_ioctl           = cops_ioctl;	dev->set_multicast_list = set_multicast_list;        dev->mc_list            = NULL;	/* Tell the user where the card is and what mode we're in. */	if(board==DAYNA)		printk("%s: %s at %#3x, using IRQ %d, in Dayna mode.\n", 			dev->name, cardname, ioaddr, dev->irq);	if(board==TANGENT) {		if(dev->irq)			printk("%s: %s at %#3x, IRQ %d, in Tangent mode\n", 				dev->name, cardname, ioaddr, dev->irq);		else			printk("%s: %s at %#3x, using polled IO, in Tangent mode.\n", 				dev->name, cardname, ioaddr);	}        return 0;err_out:	release_region(ioaddr, COPS_IO_EXTENT);	return retval;}static int __init cops_irq (int ioaddr, int board){       /*         * This does not use the IRQ to determine where the IRQ is. We just         * assume that when we get a correct status response that it's the IRQ.         * This really just verifies the IO port but since we only have access         * to such a small number of IRQs (5, 4, 3) this is not bad.         * This will probably not work for more than one card.         */        int irqaddr=0;        int i, x, status;        if(board==DAYNA)        {                outb(0, ioaddr+DAYNA_RESET);                inb(ioaddr+DAYNA_RESET);                mdelay(333);        }        if(board==TANGENT)        {                inb(ioaddr);                outb(0, ioaddr);                outb(0, ioaddr+TANG_RESET);        }        for(i=0; cops_irqlist[i] !=0; i++)        {                irqaddr = cops_irqlist[i];                for(x = 0xFFFF; x>0; x --)    /* wait for response */                {                        if(board==DAYNA)                        {                                status = (inb(ioaddr+DAYNA_CARD_STATUS)&3);                                if(status == 1)                                        return irqaddr;                        }                        if(board==TANGENT)                        {                                if((inb(ioaddr+TANG_CARD_STATUS)& TANG_TX_READY) !=0)                                        return irqaddr;                        }                }        }        return 0;       /* no IRQ found */}/* * Open/initialize the board. This is called (in the current kernel) * sometime after booting when the 'ifconfig' program is run. */static int cops_open(struct net_device *dev){    struct cops_local *lp = netdev_priv(dev);	if(dev->irq==0)	{		/*		 * I don't know if the Dayna-style boards support polled 		 * operation.  For now, only allow it for Tangent.		 */		if(lp->board==TANGENT)	/* Poll 20 times per second */		{		    init_timer(&cops_timer);		    cops_timer.function = cops_poll;		    cops_timer.data 	= (unsigned long)dev;		    cops_timer.expires 	= jiffies + HZ/20;		    add_timer(&cops_timer);		} 		else 		{			printk(KERN_WARNING "%s: No irq line set\n", dev->name);			return -EAGAIN;		}	}	cops_jumpstart(dev);	/* Start the card up. */	netif_start_queue(dev);        return 0;}/* *	This allows for a dynamic start/restart of the entire card. */static int cops_jumpstart(struct net_device *dev){	struct cops_local *lp = netdev_priv(dev);	/*         *      Once the card has the firmware loaded and has acquired         *      the nodeid, if it is reset it will lose it all.         */        cops_reset(dev,1);	/* Need to reset card before load firmware. */        cops_load(dev);		/* Load the firmware. */	/*	 *	If atalkd already gave us a nodeid we will use that	 *	one again, else we wait for atalkd to give us a nodeid	 *	in cops_ioctl. This may cause a problem if someone steals	 *	our nodeid while we are resetting.	 */		if(lp->nodeid == 1)		cops_nodeid(dev,lp->node_acquire);	return 0;}static void tangent_wait_reset(int ioaddr){	int timeout=0;	while(timeout++ < 5 && (inb(ioaddr+TANG_CARD_STATUS)&TANG_TX_READY)==0)		mdelay(1);   /* Wait 1 second */}/* *      Reset the LocalTalk board. */static void cops_reset(struct net_device *dev, int sleep){        struct cops_local *lp = netdev_priv(dev);        int ioaddr=dev->base_addr;        if(lp->board==TANGENT)        {                inb(ioaddr);		/* Clear request latch. */                outb(0,ioaddr);		/* Clear the TANG_TX_READY flop. */                outb(0, ioaddr+TANG_RESET);	/* Reset the adapter. */		tangent_wait_reset(ioaddr);                outb(0, ioaddr+TANG_CLEAR_INT);        }        if(lp->board==DAYNA)        {                outb(0, ioaddr+DAYNA_RESET);	/* Assert the reset port */                inb(ioaddr+DAYNA_RESET);	/* Clear the reset */                if(sleep)                {                        long snap=jiffies;			/* Let card finish initializing, about 1/3 second */	                while(jiffies-snap<HZ/3)                                schedule();                }                else                        mdelay(333);        }	netif_wake_queue(dev);	return;}static void cops_load (struct net_device *dev){        struct ifreq ifr;        struct ltfirmware *ltf= (struct ltfirmware *)&ifr.ifr_ifru;        struct cops_local *lp = netdev_priv(dev);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?