diff --git a/.gitignore b/.gitignore index 179424255..db1b0dcce 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ juliet/summary_table.html incremental_data/ original/ increment/ + +.goblint*/ +result/ diff --git a/index/conf/locksmith-linux.json b/index/conf/locksmith-linux.json new file mode 100644 index 000000000..2ec1af9d1 --- /dev/null +++ b/index/conf/locksmith-linux.json @@ -0,0 +1,29 @@ +{ + "pre": { + "cppflags": [ + "-DGOBLINT_NO_PTHREAD_ONCE" + ] + }, + "ana": { + "activated": [ + "expRelation", + "base", + "threadid", + "threadflag", + "threadreturn", + "escape", + "mutex", + "access", + "mallocWrapper" + ], + "thread": { + "domain": "plain" + } + }, + "dbg": { + "debug": true, + "verbose": true, + "print_dead_code": true + }, + "printstats": true +} \ No newline at end of file diff --git a/index/defs/locksmith-linux.yml b/index/defs/locksmith-linux.yml new file mode 100644 index 000000000..ab160d616 --- /dev/null +++ b/index/defs/locksmith-linux.yml @@ -0,0 +1,6 @@ +baseconf: index/conf/locksmith-linux.json +compare: false +incremental: false + +Default: +SymbLocks: --set ana.activated[+] var_eq --set ana.activated[+] symb_locks diff --git a/index/sets/locksmith-linux.yml b/index/sets/locksmith-linux.yml new file mode 100644 index 000000000..58fc4b348 --- /dev/null +++ b/index/sets/locksmith-linux.yml @@ -0,0 +1,29 @@ +plip: + info: plip + path: linux/drivers/net/plip/plip.c + param: --enable kernel ../stub.c + +slip: + info: slip + path: linux/drivers/net/slip/slip.c + param: --enable kernel ../stub.c + +eql: + info: eql + path: linux/drivers/net/eql.c + param: --enable kernel stub.c --set pre.cppflags[+] '-DKBUILD_MODNAME="eql"' + +sis900: + info: sis900 + path: linux/drivers/net/ethernet/sis/sis900.c + param: --enable kernel ../../stub.c --set pre.cppflags[+] '-DKBUILD_MODNAME="sis900"' + +hp100: + info: hp100 + path: linux/drivers/net/ethernet/hp/hp100.c + param: --enable kernel ../../stub.c --set pre.cppflags[+] '-DKBUILD_MODNAME="hp100"' + +sundance: + info: sundance + path: linux/drivers/net/ethernet/dlink/sundance.c + param: --enable kernel ../../stub.c --set pre.cppflags[+] '-DKBUILD_MODNAME="sundance"' diff --git a/linux/drivers/net/eql.c b/linux/drivers/net/eql.c new file mode 100644 index 000000000..a10ad74cc --- /dev/null +++ b/linux/drivers/net/eql.c @@ -0,0 +1,607 @@ +/* + * Equalizer Load-balancer for serial network interfaces. + * + * (c) Copyright 1995 Simon "Guru Aleph-Null" Janes + * NCM: Network and Communications Management, Inc. + * + * (c) Copyright 2002 David S. Miller (davem@redhat.com) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * The author may be reached as simon@ncm.com, or C/O + * NCM + * Attn: Simon Janes + * 6803 Whittier Ave + * McLean VA 22101 + * Phone: 1-703-847-0040 ext 103 + */ + +/* + * Sources: + * skeleton.c by Donald Becker. + * Inspirations: + * The Harried and Overworked Alan Cox + * Conspiracies: + * The Alan Cox and Mike McLagan plot to get someone else to do the code, + * which turned out to be me. + */ + +/* + * $Log: eql.c,v $ + * Revision 1.2 1996/04/11 17:51:52 guru + * Added one-line eql_remove_slave patch. + * + * Revision 1.1 1996/04/11 17:44:17 guru + * Initial revision + * + * Revision 3.13 1996/01/21 15:17:18 alan + * tx_queue_len changes. + * reformatted. + * + * Revision 3.12 1995/03/22 21:07:51 anarchy + * Added capable() checks on configuration. + * Moved header file. + * + * Revision 3.11 1995/01/19 23:14:31 guru + * slave_load = (ULONG_MAX - (ULONG_MAX / 2)) - + * (priority_Bps) + bytes_queued * 8; + * + * Revision 3.10 1995/01/19 23:07:53 guru + * back to + * slave_load = (ULONG_MAX - (ULONG_MAX / 2)) - + * (priority_Bps) + bytes_queued; + * + * Revision 3.9 1995/01/19 22:38:20 guru + * slave_load = (ULONG_MAX - (ULONG_MAX / 2)) - + * (priority_Bps) + bytes_queued * 4; + * + * Revision 3.8 1995/01/19 22:30:55 guru + * slave_load = (ULONG_MAX - (ULONG_MAX / 2)) - + * (priority_Bps) + bytes_queued * 2; + * + * Revision 3.7 1995/01/19 21:52:35 guru + * printk's trimmed out. + * + * Revision 3.6 1995/01/19 21:49:56 guru + * This is working pretty well. I gained 1 K/s in speed.. now it's just + * robustness and printk's to be diked out. + * + * Revision 3.5 1995/01/18 22:29:59 guru + * still crashes the kernel when the lock_wait thing is woken up. + * + * Revision 3.4 1995/01/18 21:59:47 guru + * Broken set-bit locking snapshot + * + * Revision 3.3 1995/01/17 22:09:18 guru + * infinite sleep in a lock somewhere.. + * + * Revision 3.2 1995/01/15 16:46:06 guru + * Log trimmed of non-pertinent 1.x branch messages + * + * Revision 3.1 1995/01/15 14:41:45 guru + * New Scheduler and timer stuff... + * + * Revision 1.15 1995/01/15 14:29:02 guru + * Will make 1.14 (now 1.15) the 3.0 branch, and the 1.12 the 2.0 branch, the one + * with the dumber scheduler + * + * Revision 1.14 1995/01/15 02:37:08 guru + * shock.. the kept-new-versions could have zonked working + * stuff.. shudder + * + * Revision 1.13 1995/01/15 02:36:31 guru + * big changes + * + * scheduler was torn out and replaced with something smarter + * + * global names not prefixed with eql_ were renamed to protect + * against namespace collisions + * + * a few more abstract interfaces were added to facilitate any + * potential change of datastructure. the driver is still using + * a linked list of slaves. going to a heap would be a bit of + * an overkill. + * + * this compiles fine with no warnings. + * + * the locking mechanism and timer stuff must be written however, + * this version will not work otherwise + * + * Sorry, I had to rewrite most of this for 2.5.x -DaveM + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +static int eql_open(struct net_device *dev); +static int eql_close(struct net_device *dev); +static int eql_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); +static netdev_tx_t eql_slave_xmit(struct sk_buff *skb, struct net_device *dev); + +#define eql_is_slave(dev) ((dev->flags & IFF_SLAVE) == IFF_SLAVE) +#define eql_is_master(dev) ((dev->flags & IFF_MASTER) == IFF_MASTER) + +static void eql_kill_one_slave(slave_queue_t *queue, slave_t *slave); + +static void eql_timer(unsigned long param) +{ + equalizer_t *eql = (equalizer_t *) param; + struct list_head *this, *tmp, *head; + + spin_lock(&eql->queue.lock); + head = &eql->queue.all_slaves; + list_for_each_safe(this, tmp, head) { + slave_t *slave = list_entry(this, slave_t, list); + + if ((slave->dev->flags & IFF_UP) == IFF_UP) { + slave->bytes_queued -= slave->priority_Bps; + if (slave->bytes_queued < 0) + slave->bytes_queued = 0; + } else { + eql_kill_one_slave(&eql->queue, slave); + } + + } + spin_unlock(&eql->queue.lock); + + eql->timer.expires = jiffies + EQL_DEFAULT_RESCHED_IVAL; + add_timer(&eql->timer); +} + +static const char version[] __initconst = + "Equalizer2002: Simon Janes (simon@ncm.com) and David S. Miller (davem@redhat.com)"; + +static const struct net_device_ops eql_netdev_ops = { + .ndo_open = eql_open, + .ndo_stop = eql_close, + .ndo_do_ioctl = eql_ioctl, + .ndo_start_xmit = eql_slave_xmit, +}; + +static void __init eql_setup(struct net_device *dev) +{ + equalizer_t *eql = netdev_priv(dev); + + init_timer(&eql->timer); + eql->timer.data = (unsigned long) eql; + eql->timer.expires = jiffies + EQL_DEFAULT_RESCHED_IVAL; + eql->timer.function = eql_timer; + + spin_lock_init(&eql->queue.lock); + INIT_LIST_HEAD(&eql->queue.all_slaves); + eql->queue.master_dev = dev; + + dev->netdev_ops = &eql_netdev_ops; + + /* + * Now we undo some of the things that eth_setup does + * that we don't like + */ + + dev->mtu = EQL_DEFAULT_MTU; /* set to 576 in if_eql.h */ + dev->flags = IFF_MASTER; + + dev->type = ARPHRD_SLIP; + dev->tx_queue_len = 5; /* Hands them off fast */ + netif_keep_dst(dev); +} + +static int eql_open(struct net_device *dev) +{ + equalizer_t *eql = netdev_priv(dev); + + /* XXX We should force this off automatically for the user. */ + netdev_info(dev, + "remember to turn off Van-Jacobson compression on your slave devices\n"); + + BUG_ON(!list_empty(&eql->queue.all_slaves)); + + eql->min_slaves = 1; + eql->max_slaves = EQL_DEFAULT_MAX_SLAVES; /* 4 usually... */ + + add_timer(&eql->timer); + + return 0; +} + +static void eql_kill_one_slave(slave_queue_t *queue, slave_t *slave) +{ + list_del(&slave->list); + queue->num_slaves--; + slave->dev->flags &= ~IFF_SLAVE; + dev_put(slave->dev); + kfree(slave); +} + +static void eql_kill_slave_queue(slave_queue_t *queue) +{ + struct list_head *head, *tmp, *this; + + spin_lock_bh(&queue->lock); + + head = &queue->all_slaves; + list_for_each_safe(this, tmp, head) { + slave_t *s = list_entry(this, slave_t, list); + + eql_kill_one_slave(queue, s); + } + + spin_unlock_bh(&queue->lock); +} + +static int eql_close(struct net_device *dev) +{ + equalizer_t *eql = netdev_priv(dev); + + /* + * The timer has to be stopped first before we start hacking away + * at the data structure it scans every so often... + */ + + del_timer_sync(&eql->timer); + + eql_kill_slave_queue(&eql->queue); + + return 0; +} + +static int eql_enslave(struct net_device *dev, slaving_request_t __user *srq); +static int eql_emancipate(struct net_device *dev, slaving_request_t __user *srq); + +static int eql_g_slave_cfg(struct net_device *dev, slave_config_t __user *sc); +static int eql_s_slave_cfg(struct net_device *dev, slave_config_t __user *sc); + +static int eql_g_master_cfg(struct net_device *dev, master_config_t __user *mc); +static int eql_s_master_cfg(struct net_device *dev, master_config_t __user *mc); + +static int eql_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + if (cmd != EQL_GETMASTRCFG && cmd != EQL_GETSLAVECFG && + !capable(CAP_NET_ADMIN)) + return -EPERM; + + switch (cmd) { + case EQL_ENSLAVE: + return eql_enslave(dev, ifr->ifr_data); + case EQL_EMANCIPATE: + return eql_emancipate(dev, ifr->ifr_data); + case EQL_GETSLAVECFG: + return eql_g_slave_cfg(dev, ifr->ifr_data); + case EQL_SETSLAVECFG: + return eql_s_slave_cfg(dev, ifr->ifr_data); + case EQL_GETMASTRCFG: + return eql_g_master_cfg(dev, ifr->ifr_data); + case EQL_SETMASTRCFG: + return eql_s_master_cfg(dev, ifr->ifr_data); + default: + return -EOPNOTSUPP; + } +} + +/* queue->lock must be held */ +static slave_t *__eql_schedule_slaves(slave_queue_t *queue) +{ + unsigned long best_load = ~0UL; + struct list_head *this, *tmp, *head; + slave_t *best_slave; + + best_slave = NULL; + + /* Make a pass to set the best slave. */ + head = &queue->all_slaves; + list_for_each_safe(this, tmp, head) { + slave_t *slave = list_entry(this, slave_t, list); + unsigned long slave_load, bytes_queued, priority_Bps; + + /* Go through the slave list once, updating best_slave + * whenever a new best_load is found. + */ + bytes_queued = slave->bytes_queued; + priority_Bps = slave->priority_Bps; + if ((slave->dev->flags & IFF_UP) == IFF_UP) { + slave_load = (~0UL - (~0UL / 2)) - + (priority_Bps) + bytes_queued * 8; + + if (slave_load < best_load) { + best_load = slave_load; + best_slave = slave; + } + } else { + /* We found a dead slave, kill it. */ + eql_kill_one_slave(queue, slave); + } + } + return best_slave; +} + +static netdev_tx_t eql_slave_xmit(struct sk_buff *skb, struct net_device *dev) +{ + equalizer_t *eql = netdev_priv(dev); + slave_t *slave; + + spin_lock(&eql->queue.lock); + + slave = __eql_schedule_slaves(&eql->queue); + if (slave) { + struct net_device *slave_dev = slave->dev; + + skb->dev = slave_dev; + skb->priority = TC_PRIO_FILLER; + slave->bytes_queued += skb->len; + dev_queue_xmit(skb); + dev->stats.tx_packets++; + } else { + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + } + + spin_unlock(&eql->queue.lock); + + return NETDEV_TX_OK; +} + +/* + * Private ioctl functions + */ + +/* queue->lock must be held */ +static slave_t *__eql_find_slave_dev(slave_queue_t *queue, struct net_device *dev) +{ + struct list_head *this, *head; + + head = &queue->all_slaves; + list_for_each(this, head) { + slave_t *slave = list_entry(this, slave_t, list); + + if (slave->dev == dev) + return slave; + } + + return NULL; +} + +static inline int eql_is_full(slave_queue_t *queue) +{ + equalizer_t *eql = netdev_priv(queue->master_dev); + + if (queue->num_slaves >= eql->max_slaves) + return 1; + return 0; +} + +/* queue->lock must be held */ +static int __eql_insert_slave(slave_queue_t *queue, slave_t *slave) +{ + if (!eql_is_full(queue)) { + slave_t *duplicate_slave = NULL; + + duplicate_slave = __eql_find_slave_dev(queue, slave->dev); + if (duplicate_slave) + eql_kill_one_slave(queue, duplicate_slave); + + dev_hold(slave->dev); + list_add(&slave->list, &queue->all_slaves); + queue->num_slaves++; + slave->dev->flags |= IFF_SLAVE; + + return 0; + } + + return -ENOSPC; +} + +static int eql_enslave(struct net_device *master_dev, slaving_request_t __user *srqp) +{ + struct net_device *slave_dev; + slaving_request_t srq; + + if (copy_from_user(&srq, srqp, sizeof (slaving_request_t))) + return -EFAULT; + + slave_dev = __dev_get_by_name(&init_net, srq.slave_name); + if (!slave_dev) + return -ENODEV; + + if ((master_dev->flags & IFF_UP) == IFF_UP) { + /* slave is not a master & not already a slave: */ + if (!eql_is_master(slave_dev) && !eql_is_slave(slave_dev)) { + slave_t *s = kmalloc(sizeof(*s), GFP_KERNEL); + equalizer_t *eql = netdev_priv(master_dev); + int ret; + + if (!s) + return -ENOMEM; + + memset(s, 0, sizeof(*s)); + s->dev = slave_dev; + s->priority = srq.priority; + s->priority_bps = srq.priority; + s->priority_Bps = srq.priority / 8; + + spin_lock_bh(&eql->queue.lock); + ret = __eql_insert_slave(&eql->queue, s); + if (ret) + kfree(s); + + spin_unlock_bh(&eql->queue.lock); + + return ret; + } + } + + return -EINVAL; +} + +static int eql_emancipate(struct net_device *master_dev, slaving_request_t __user *srqp) +{ + equalizer_t *eql = netdev_priv(master_dev); + struct net_device *slave_dev; + slaving_request_t srq; + int ret; + + if (copy_from_user(&srq, srqp, sizeof (slaving_request_t))) + return -EFAULT; + + slave_dev = __dev_get_by_name(&init_net, srq.slave_name); + if (!slave_dev) + return -ENODEV; + + ret = -EINVAL; + spin_lock_bh(&eql->queue.lock); + if (eql_is_slave(slave_dev)) { + slave_t *slave = __eql_find_slave_dev(&eql->queue, slave_dev); + if (slave) { + eql_kill_one_slave(&eql->queue, slave); + ret = 0; + } + } + spin_unlock_bh(&eql->queue.lock); + + return ret; +} + +static int eql_g_slave_cfg(struct net_device *dev, slave_config_t __user *scp) +{ + equalizer_t *eql = netdev_priv(dev); + slave_t *slave; + struct net_device *slave_dev; + slave_config_t sc; + int ret; + + if (copy_from_user(&sc, scp, sizeof (slave_config_t))) + return -EFAULT; + + slave_dev = __dev_get_by_name(&init_net, sc.slave_name); + if (!slave_dev) + return -ENODEV; + + ret = -EINVAL; + + spin_lock_bh(&eql->queue.lock); + if (eql_is_slave(slave_dev)) { + slave = __eql_find_slave_dev(&eql->queue, slave_dev); + if (slave) { + sc.priority = slave->priority; + ret = 0; + } + } + spin_unlock_bh(&eql->queue.lock); + + if (!ret && copy_to_user(scp, &sc, sizeof (slave_config_t))) + ret = -EFAULT; + + return ret; +} + +static int eql_s_slave_cfg(struct net_device *dev, slave_config_t __user *scp) +{ + slave_t *slave; + equalizer_t *eql; + struct net_device *slave_dev; + slave_config_t sc; + int ret; + + if (copy_from_user(&sc, scp, sizeof (slave_config_t))) + return -EFAULT; + + slave_dev = __dev_get_by_name(&init_net, sc.slave_name); + if (!slave_dev) + return -ENODEV; + + ret = -EINVAL; + + eql = netdev_priv(dev); + spin_lock_bh(&eql->queue.lock); + if (eql_is_slave(slave_dev)) { + slave = __eql_find_slave_dev(&eql->queue, slave_dev); + if (slave) { + slave->priority = sc.priority; + slave->priority_bps = sc.priority; + slave->priority_Bps = sc.priority / 8; + ret = 0; + } + } + spin_unlock_bh(&eql->queue.lock); + + return ret; +} + +static int eql_g_master_cfg(struct net_device *dev, master_config_t __user *mcp) +{ + equalizer_t *eql; + master_config_t mc; + + memset(&mc, 0, sizeof(master_config_t)); + + if (eql_is_master(dev)) { + eql = netdev_priv(dev); + mc.max_slaves = eql->max_slaves; + mc.min_slaves = eql->min_slaves; + if (copy_to_user(mcp, &mc, sizeof (master_config_t))) + return -EFAULT; + return 0; + } + return -EINVAL; +} + +static int eql_s_master_cfg(struct net_device *dev, master_config_t __user *mcp) +{ + equalizer_t *eql; + master_config_t mc; + + if (copy_from_user(&mc, mcp, sizeof (master_config_t))) + return -EFAULT; + + if (eql_is_master(dev)) { + eql = netdev_priv(dev); + eql->max_slaves = mc.max_slaves; + eql->min_slaves = mc.min_slaves; + return 0; + } + return -EINVAL; +} + +static struct net_device *dev_eql; + +static int __init eql_init_module(void) +{ + int err; + + pr_info("%s\n", version); + + dev_eql = alloc_netdev(sizeof(equalizer_t), "eql", NET_NAME_UNKNOWN, + eql_setup); + if (!dev_eql) + return -ENOMEM; + + err = register_netdev(dev_eql); + if (err) + free_netdev(dev_eql); + return err; +} + +static void __exit eql_cleanup_module(void) +{ + unregister_netdev(dev_eql); + free_netdev(dev_eql); +} + +module_init(eql_init_module); +module_exit(eql_cleanup_module); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/net/eql.txt b/linux/drivers/net/eql.txt new file mode 100644 index 000000000..1e18ac91b --- /dev/null +++ b/linux/drivers/net/eql.txt @@ -0,0 +1,4 @@ +KBUILD_MODNAME not defined +--set pre.cppflags[+] '-DKBUILD_MODNAME="eql"' + +eql_netdev_ops functions not called, assignment on line 190 doesn't spawn diff --git a/linux/drivers/net/ethernet/dlink/sundance.c b/linux/drivers/net/ethernet/dlink/sundance.c new file mode 100644 index 000000000..a28a2e583 --- /dev/null +++ b/linux/drivers/net/ethernet/dlink/sundance.c @@ -0,0 +1,2027 @@ +/* sundance.c: A Linux device driver for the Sundance ST201 "Alta". */ +/* + Written 1999-2000 by Donald Becker. + + This software may be used and distributed according to the terms of + the GNU General Public License (GPL), incorporated herein by reference. + Drivers based on or derived from this code fall under the GPL and must + retain the authorship, copyright and license notice. This file is not + a complete program and may only be used when the entire operating + system is licensed under the GPL. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + + Support and updates available at + http://www.scyld.com/network/sundance.html + [link no longer provides useful info -jgarzik] + Archives of the mailing list are still available at + http://www.beowulf.org/pipermail/netdrivers/ + +*/ + +#define DRV_NAME "sundance" +#define DRV_VERSION "1.2" +#define DRV_RELDATE "11-Sep-2006" + + +/* The user-configurable values. + These may be modified when a driver module is loaded.*/ +static int debug = 1; /* 1 normal messages, 0 quiet .. 7 verbose. */ +/* Maximum number of multicast addresses to filter (vs. rx-all-multicast). + Typical is a 64 element hash table based on the Ethernet CRC. */ +static const int multicast_filter_limit = 32; + +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + Setting to > 1518 effectively disables this feature. + This chip can receive into offset buffers, so the Alpha does not + need a copy-align. */ +static int rx_copybreak; +static int flowctrl=1; + +/* media[] specifies the media type the NIC operates at. + autosense Autosensing active media. + 10mbps_hd 10Mbps half duplex. + 10mbps_fd 10Mbps full duplex. + 100mbps_hd 100Mbps half duplex. + 100mbps_fd 100Mbps full duplex. + 0 Autosensing active media. + 1 10Mbps half duplex. + 2 10Mbps full duplex. + 3 100Mbps half duplex. + 4 100Mbps full duplex. +*/ +#define MAX_UNITS 8 +static char *media[MAX_UNITS]; + + +/* Operational parameters that are set at compile time. */ + +/* Keep the ring sizes a power of two for compile efficiency. + The compiler will convert '%'<2^N> into a bit mask. + Making the Tx ring too large decreases the effectiveness of channel + bonding and packet priority, and more than 128 requires modifying the + Tx error recovery. + Large receive rings merely waste memory. */ +#define TX_RING_SIZE 32 +#define TX_QUEUE_LEN (TX_RING_SIZE - 1) /* Limit ring entries actually used. */ +#define RX_RING_SIZE 64 +#define RX_BUDGET 32 +#define TX_TOTAL_SIZE TX_RING_SIZE*sizeof(struct netdev_desc) +#define RX_TOTAL_SIZE RX_RING_SIZE*sizeof(struct netdev_desc) + +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT (4*HZ) +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +/* Include files, designed to support most kernel versions 2.0.0 and later. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* Processor type for cache alignment. */ +#include +#include +#include +#include +#include +#include +#include + +/* These identify the driver base version and may not be removed. */ +static const char version[] = + KERN_INFO DRV_NAME ".c:v" DRV_VERSION " " DRV_RELDATE + " Written by Donald Becker\n"; + +MODULE_AUTHOR("Donald Becker "); +MODULE_DESCRIPTION("Sundance Alta Ethernet driver"); +MODULE_LICENSE("GPL"); + +module_param(debug, int, 0); +module_param(rx_copybreak, int, 0); +module_param_array(media, charp, NULL, 0); +module_param(flowctrl, int, 0); +MODULE_PARM_DESC(debug, "Sundance Alta debug level (0-5)"); +MODULE_PARM_DESC(rx_copybreak, "Sundance Alta copy breakpoint for copy-only-tiny-frames"); +MODULE_PARM_DESC(flowctrl, "Sundance Alta flow control [0|1]"); + +/* + Theory of Operation + +I. Board Compatibility + +This driver is designed for the Sundance Technologies "Alta" ST201 chip. + +II. Board-specific settings + +III. Driver operation + +IIIa. Ring buffers + +This driver uses two statically allocated fixed-size descriptor lists +formed into rings by a branch from the final descriptor to the beginning of +the list. The ring sizes are set at compile time by RX/TX_RING_SIZE. +Some chips explicitly use only 2^N sized rings, while others use a +'next descriptor' pointer that the driver forms into rings. + +IIIb/c. Transmit/Receive Structure + +This driver uses a zero-copy receive and transmit scheme. +The driver allocates full frame size skbuffs for the Rx ring buffers at +open() time and passes the skb->data field to the chip as receive data +buffers. When an incoming frame is less than RX_COPYBREAK bytes long, +a fresh skbuff is allocated and the frame is copied to the new skbuff. +When the incoming frame is larger, the skbuff is passed directly up the +protocol stack. Buffers consumed this way are replaced by newly allocated +skbuffs in a later phase of receives. + +The RX_COPYBREAK value is chosen to trade-off the memory wasted by +using a full-sized skbuff for small frames vs. the copying costs of larger +frames. New boards are typically used in generously configured machines +and the underfilled buffers have negligible impact compared to the benefit of +a single allocation size, so the default value of zero results in never +copying packets. When copying is done, the cost is usually mitigated by using +a combined copy/checksum routine. Copying also preloads the cache, which is +most useful with small frames. + +A subtle aspect of the operation is that the IP header at offset 14 in an +ethernet frame isn't longword aligned for further processing. +Unaligned buffers are permitted by the Sundance hardware, so +frames are received into the skbuff at an offset of "+2", 16-byte aligning +the IP header. + +IIId. Synchronization + +The driver runs as two independent, single-threaded flows of control. One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag. The other thread is the interrupt handler, which is single +threaded by the hardware and interrupt handling software. + +The send packet thread has partial control over the Tx ring and 'dev->tbusy' +flag. It sets the tbusy flag whenever it's queuing a Tx packet. If the next +queue slot is empty, it clears the tbusy flag when finished otherwise it sets +the 'lp->tx_full' flag. + +The interrupt handler has exclusive control over the Rx ring and records stats +from the Tx ring. After reaping the stats, it marks the Tx queue entry as +empty by incrementing the dirty_tx mark. Iff the 'lp->tx_full' flag is set, it +clears both the tx_full and tbusy flags. + +IV. Notes + +IVb. References + +The Sundance ST201 datasheet, preliminary version. +The Kendin KS8723 datasheet, preliminary version. +The ICplus IP100 datasheet, preliminary version. +http://www.scyld.com/expert/100mbps.html +http://www.scyld.com/expert/NWay.html + +IVc. Errata + +*/ + +/* Work-around for Kendin chip bugs. */ +#ifndef CONFIG_SUNDANCE_MMIO +#define USE_IO_OPS 1 +#endif + +static const struct pci_device_id sundance_pci_tbl[] = { + { 0x1186, 0x1002, 0x1186, 0x1002, 0, 0, 0 }, + { 0x1186, 0x1002, 0x1186, 0x1003, 0, 0, 1 }, + { 0x1186, 0x1002, 0x1186, 0x1012, 0, 0, 2 }, + { 0x1186, 0x1002, 0x1186, 0x1040, 0, 0, 3 }, + { 0x1186, 0x1002, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 4 }, + { 0x13F0, 0x0201, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 5 }, + { 0x13F0, 0x0200, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 6 }, + { } +}; +MODULE_DEVICE_TABLE(pci, sundance_pci_tbl); + +enum { + netdev_io_size = 128 +}; + +struct pci_id_info { + const char *name; +}; +static const struct pci_id_info pci_id_tbl[] = { + {"D-Link DFE-550TX FAST Ethernet Adapter"}, + {"D-Link DFE-550FX 100Mbps Fiber-optics Adapter"}, + {"D-Link DFE-580TX 4 port Server Adapter"}, + {"D-Link DFE-530TXS FAST Ethernet Adapter"}, + {"D-Link DL10050-based FAST Ethernet Adapter"}, + {"Sundance Technology Alta"}, + {"IC Plus Corporation IP100A FAST Ethernet Adapter"}, + { } /* terminate list. */ +}; + +/* This driver was written to use PCI memory space, however x86-oriented + hardware often uses I/O space accesses. */ + +/* Offsets to the device registers. + Unlike software-only systems, device drivers interact with complex hardware. + It's not useful to define symbolic names for every register bit in the + device. The name can only partially document the semantics and make + the driver longer and more difficult to read. + In general, only the important configuration values or bits changed + multiple times should be defined symbolically. +*/ +enum alta_offsets { + DMACtrl = 0x00, + TxListPtr = 0x04, + TxDMABurstThresh = 0x08, + TxDMAUrgentThresh = 0x09, + TxDMAPollPeriod = 0x0a, + RxDMAStatus = 0x0c, + RxListPtr = 0x10, + DebugCtrl0 = 0x1a, + DebugCtrl1 = 0x1c, + RxDMABurstThresh = 0x14, + RxDMAUrgentThresh = 0x15, + RxDMAPollPeriod = 0x16, + LEDCtrl = 0x1a, + ASICCtrl = 0x30, + EEData = 0x34, + EECtrl = 0x36, + FlashAddr = 0x40, + FlashData = 0x44, + WakeEvent = 0x45, + TxStatus = 0x46, + TxFrameId = 0x47, + DownCounter = 0x18, + IntrClear = 0x4a, + IntrEnable = 0x4c, + IntrStatus = 0x4e, + MACCtrl0 = 0x50, + MACCtrl1 = 0x52, + StationAddr = 0x54, + MaxFrameSize = 0x5A, + RxMode = 0x5c, + MIICtrl = 0x5e, + MulticastFilter0 = 0x60, + MulticastFilter1 = 0x64, + RxOctetsLow = 0x68, + RxOctetsHigh = 0x6a, + TxOctetsLow = 0x6c, + TxOctetsHigh = 0x6e, + TxFramesOK = 0x70, + RxFramesOK = 0x72, + StatsCarrierError = 0x74, + StatsLateColl = 0x75, + StatsMultiColl = 0x76, + StatsOneColl = 0x77, + StatsTxDefer = 0x78, + RxMissed = 0x79, + StatsTxXSDefer = 0x7a, + StatsTxAbort = 0x7b, + StatsBcastTx = 0x7c, + StatsBcastRx = 0x7d, + StatsMcastTx = 0x7e, + StatsMcastRx = 0x7f, + /* Aliased and bogus values! */ + RxStatus = 0x0c, +}; + +#define ASIC_HI_WORD(x) ((x) + 2) + +enum ASICCtrl_HiWord_bit { + GlobalReset = 0x0001, + RxReset = 0x0002, + TxReset = 0x0004, + DMAReset = 0x0008, + FIFOReset = 0x0010, + NetworkReset = 0x0020, + HostReset = 0x0040, + ResetBusy = 0x0400, +}; + +/* Bits in the interrupt status/mask registers. */ +enum intr_status_bits { + IntrSummary=0x0001, IntrPCIErr=0x0002, IntrMACCtrl=0x0008, + IntrTxDone=0x0004, IntrRxDone=0x0010, IntrRxStart=0x0020, + IntrDrvRqst=0x0040, + StatsMax=0x0080, LinkChange=0x0100, + IntrTxDMADone=0x0200, IntrRxDMADone=0x0400, +}; + +/* Bits in the RxMode register. */ +enum rx_mode_bits { + AcceptAllIPMulti=0x20, AcceptMultiHash=0x10, AcceptAll=0x08, + AcceptBroadcast=0x04, AcceptMulticast=0x02, AcceptMyPhys=0x01, +}; +/* Bits in MACCtrl. */ +enum mac_ctrl0_bits { + EnbFullDuplex=0x20, EnbRcvLargeFrame=0x40, + EnbFlowCtrl=0x100, EnbPassRxCRC=0x200, +}; +enum mac_ctrl1_bits { + StatsEnable=0x0020, StatsDisable=0x0040, StatsEnabled=0x0080, + TxEnable=0x0100, TxDisable=0x0200, TxEnabled=0x0400, + RxEnable=0x0800, RxDisable=0x1000, RxEnabled=0x2000, +}; + +/* Bits in WakeEvent register. */ +enum wake_event_bits { + WakePktEnable = 0x01, + MagicPktEnable = 0x02, + LinkEventEnable = 0x04, + WolEnable = 0x80, +}; + +/* The Rx and Tx buffer descriptors. */ +/* Note that using only 32 bit fields simplifies conversion to big-endian + architectures. */ +struct netdev_desc { + __le32 next_desc; + __le32 status; + struct desc_frag { __le32 addr, length; } frag[1]; +}; + +/* Bits in netdev_desc.status */ +enum desc_status_bits { + DescOwn=0x8000, + DescEndPacket=0x4000, + DescEndRing=0x2000, + LastFrag=0x80000000, + DescIntrOnTx=0x8000, + DescIntrOnDMADone=0x80000000, + DisableAlign = 0x00000001, +}; + +#define PRIV_ALIGN 15 /* Required alignment mask */ +/* Use __attribute__((aligned (L1_CACHE_BYTES))) to maintain alignment + within the structure. */ +#define MII_CNT 4 +struct netdev_private { + /* Descriptor rings first for alignment. */ + struct netdev_desc *rx_ring; + struct netdev_desc *tx_ring; + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + dma_addr_t tx_ring_dma; + dma_addr_t rx_ring_dma; + struct timer_list timer; /* Media monitoring timer. */ + /* ethtool extra stats */ + struct { + u64 tx_multiple_collisions; + u64 tx_single_collisions; + u64 tx_late_collisions; + u64 tx_deferred; + u64 tx_deferred_excessive; + u64 tx_aborted; + u64 tx_bcasts; + u64 rx_bcasts; + u64 tx_mcasts; + u64 rx_mcasts; + } xstats; + /* Frequently used values: keep some adjacent for cache effect. */ + spinlock_t lock; + int msg_enable; + int chip_id; + unsigned int cur_rx, dirty_rx; /* Producer/consumer ring indices */ + unsigned int rx_buf_sz; /* Based on MTU+slack. */ + struct netdev_desc *last_tx; /* Last Tx descriptor used. */ + unsigned int cur_tx, dirty_tx; + /* These values are keep track of the transceiver/media in use. */ + unsigned int flowctrl:1; + unsigned int default_port:4; /* Last dev->if_port value. */ + unsigned int an_enable:1; + unsigned int speed; + unsigned int wol_enabled:1; /* Wake on LAN enabled */ + struct tasklet_struct rx_tasklet; + struct tasklet_struct tx_tasklet; + int budget; + int cur_task; + /* Multicast and receive mode. */ + spinlock_t mcastlock; /* SMP lock multicast updates. */ + u16 mcast_filter[4]; + /* MII transceiver section. */ + struct mii_if_info mii_if; + int mii_preamble_required; + unsigned char phys[MII_CNT]; /* MII device addresses, only first one used. */ + struct pci_dev *pci_dev; + void __iomem *base; + spinlock_t statlock; +}; + +/* The station address location in the EEPROM. */ +#define EEPROM_SA_OFFSET 0x10 +#define DEFAULT_INTR (IntrRxDMADone | IntrPCIErr | \ + IntrDrvRqst | IntrTxDone | StatsMax | \ + LinkChange) + +static int change_mtu(struct net_device *dev, int new_mtu); +static int eeprom_read(void __iomem *ioaddr, int location); +static int mdio_read(struct net_device *dev, int phy_id, int location); +static void mdio_write(struct net_device *dev, int phy_id, int location, int value); +static int mdio_wait_link(struct net_device *dev, int wait); +static int netdev_open(struct net_device *dev); +static void check_duplex(struct net_device *dev); +static void netdev_timer(unsigned long data); +static void tx_timeout(struct net_device *dev); +static void init_ring(struct net_device *dev); +static netdev_tx_t start_tx(struct sk_buff *skb, struct net_device *dev); +static int reset_tx (struct net_device *dev); +static irqreturn_t intr_handler(int irq, void *dev_instance); +static void rx_poll(unsigned long data); +static void tx_poll(unsigned long data); +static void refill_rx (struct net_device *dev); +static void netdev_error(struct net_device *dev, int intr_status); +static void netdev_error(struct net_device *dev, int intr_status); +static void set_rx_mode(struct net_device *dev); +static int __set_mac_addr(struct net_device *dev); +static int sundance_set_mac_addr(struct net_device *dev, void *data); +static struct net_device_stats *get_stats(struct net_device *dev); +static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static int netdev_close(struct net_device *dev); +static const struct ethtool_ops ethtool_ops; + +static void sundance_reset(struct net_device *dev, unsigned long reset_cmd) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base + ASICCtrl; + int countdown; + + /* ST201 documentation states ASICCtrl is a 32bit register */ + iowrite32 (reset_cmd | ioread32 (ioaddr), ioaddr); + /* ST201 documentation states reset can take up to 1 ms */ + countdown = 10 + 1; + while (ioread32 (ioaddr) & (ResetBusy << 16)) { + if (--countdown == 0) { + printk(KERN_WARNING "%s : reset not completed !!\n", dev->name); + break; + } + udelay(100); + } +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void sundance_poll_controller(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + + disable_irq(np->pci_dev->irq); + intr_handler(np->pci_dev->irq, dev); + enable_irq(np->pci_dev->irq); +} +#endif + +static const struct net_device_ops netdev_ops = { + .ndo_open = netdev_open, + .ndo_stop = netdev_close, + .ndo_start_xmit = start_tx, + .ndo_get_stats = get_stats, + .ndo_set_rx_mode = set_rx_mode, + .ndo_do_ioctl = netdev_ioctl, + .ndo_tx_timeout = tx_timeout, + .ndo_change_mtu = change_mtu, + .ndo_set_mac_address = sundance_set_mac_addr, + .ndo_validate_addr = eth_validate_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = sundance_poll_controller, +#endif +}; + +static int sundance_probe1(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct net_device *dev; + struct netdev_private *np; + static int card_idx; + int chip_idx = ent->driver_data; + int irq; + int i; + void __iomem *ioaddr; + u16 mii_ctl; + void *ring_space; + dma_addr_t ring_dma; +#ifdef USE_IO_OPS + int bar = 0; +#else + int bar = 1; +#endif + int phy, phy_end, phy_idx = 0; + +/* when built into the kernel, we only print version if device is found */ +#ifndef MODULE + static int printed_version; + if (!printed_version++) + printk(version); +#endif + + if (pci_enable_device(pdev)) + return -EIO; + pci_set_master(pdev); + + irq = pdev->irq; + + dev = alloc_etherdev(sizeof(*np)); + if (!dev) + return -ENOMEM; + SET_NETDEV_DEV(dev, &pdev->dev); + + if (pci_request_regions(pdev, DRV_NAME)) + goto err_out_netdev; + + ioaddr = pci_iomap(pdev, bar, netdev_io_size); + if (!ioaddr) + goto err_out_res; + + for (i = 0; i < 3; i++) + ((__le16 *)dev->dev_addr)[i] = + cpu_to_le16(eeprom_read(ioaddr, i + EEPROM_SA_OFFSET)); + + np = netdev_priv(dev); + np->base = ioaddr; + np->pci_dev = pdev; + np->chip_id = chip_idx; + np->msg_enable = (1 << debug) - 1; + spin_lock_init(&np->lock); + spin_lock_init(&np->statlock); + tasklet_init(&np->rx_tasklet, rx_poll, (unsigned long)dev); + tasklet_init(&np->tx_tasklet, tx_poll, (unsigned long)dev); + + ring_space = dma_alloc_coherent(&pdev->dev, TX_TOTAL_SIZE, + &ring_dma, GFP_KERNEL); + if (!ring_space) + goto err_out_cleardev; + np->tx_ring = (struct netdev_desc *)ring_space; + np->tx_ring_dma = ring_dma; + + ring_space = dma_alloc_coherent(&pdev->dev, RX_TOTAL_SIZE, + &ring_dma, GFP_KERNEL); + if (!ring_space) + goto err_out_unmap_tx; + np->rx_ring = (struct netdev_desc *)ring_space; + np->rx_ring_dma = ring_dma; + + np->mii_if.dev = dev; + np->mii_if.mdio_read = mdio_read; + np->mii_if.mdio_write = mdio_write; + np->mii_if.phy_id_mask = 0x1f; + np->mii_if.reg_num_mask = 0x1f; + + /* The chip-specific entries in the device structure. */ + dev->netdev_ops = &netdev_ops; + dev->ethtool_ops = ðtool_ops; + dev->watchdog_timeo = TX_TIMEOUT; + + pci_set_drvdata(pdev, dev); + + i = register_netdev(dev); + if (i) + goto err_out_unmap_rx; + + printk(KERN_INFO "%s: %s at %p, %pM, IRQ %d.\n", + dev->name, pci_id_tbl[chip_idx].name, ioaddr, + dev->dev_addr, irq); + + np->phys[0] = 1; /* Default setting */ + np->mii_preamble_required++; + + /* + * It seems some phys doesn't deal well with address 0 being accessed + * first + */ + if (sundance_pci_tbl[np->chip_id].device == 0x0200) { + phy = 0; + phy_end = 31; + } else { + phy = 1; + phy_end = 32; /* wraps to zero, due to 'phy & 0x1f' */ + } + for (; phy <= phy_end && phy_idx < MII_CNT; phy++) { + int phyx = phy & 0x1f; + int mii_status = mdio_read(dev, phyx, MII_BMSR); + if (mii_status != 0xffff && mii_status != 0x0000) { + np->phys[phy_idx++] = phyx; + np->mii_if.advertising = mdio_read(dev, phyx, MII_ADVERTISE); + if ((mii_status & 0x0040) == 0) + np->mii_preamble_required++; + printk(KERN_INFO "%s: MII PHY found at address %d, status " + "0x%4.4x advertising %4.4x.\n", + dev->name, phyx, mii_status, np->mii_if.advertising); + } + } + np->mii_preamble_required--; + + if (phy_idx == 0) { + printk(KERN_INFO "%s: No MII transceiver found, aborting. ASIC status %x\n", + dev->name, ioread32(ioaddr + ASICCtrl)); + goto err_out_unregister; + } + + np->mii_if.phy_id = np->phys[0]; + + /* Parse override configuration */ + np->an_enable = 1; + if (card_idx < MAX_UNITS) { + if (media[card_idx] != NULL) { + np->an_enable = 0; + if (strcmp (media[card_idx], "100mbps_fd") == 0 || + strcmp (media[card_idx], "4") == 0) { + np->speed = 100; + np->mii_if.full_duplex = 1; + } else if (strcmp (media[card_idx], "100mbps_hd") == 0 || + strcmp (media[card_idx], "3") == 0) { + np->speed = 100; + np->mii_if.full_duplex = 0; + } else if (strcmp (media[card_idx], "10mbps_fd") == 0 || + strcmp (media[card_idx], "2") == 0) { + np->speed = 10; + np->mii_if.full_duplex = 1; + } else if (strcmp (media[card_idx], "10mbps_hd") == 0 || + strcmp (media[card_idx], "1") == 0) { + np->speed = 10; + np->mii_if.full_duplex = 0; + } else { + np->an_enable = 1; + } + } + if (flowctrl == 1) + np->flowctrl = 1; + } + + /* Fibre PHY? */ + if (ioread32 (ioaddr + ASICCtrl) & 0x80) { + /* Default 100Mbps Full */ + if (np->an_enable) { + np->speed = 100; + np->mii_if.full_duplex = 1; + np->an_enable = 0; + } + } + /* Reset PHY */ + mdio_write (dev, np->phys[0], MII_BMCR, BMCR_RESET); + mdelay (300); + /* If flow control enabled, we need to advertise it.*/ + if (np->flowctrl) + mdio_write (dev, np->phys[0], MII_ADVERTISE, np->mii_if.advertising | 0x0400); + mdio_write (dev, np->phys[0], MII_BMCR, BMCR_ANENABLE|BMCR_ANRESTART); + /* Force media type */ + if (!np->an_enable) { + mii_ctl = 0; + mii_ctl |= (np->speed == 100) ? BMCR_SPEED100 : 0; + mii_ctl |= (np->mii_if.full_duplex) ? BMCR_FULLDPLX : 0; + mdio_write (dev, np->phys[0], MII_BMCR, mii_ctl); + printk (KERN_INFO "Override speed=%d, %s duplex\n", + np->speed, np->mii_if.full_duplex ? "Full" : "Half"); + + } + + /* Perhaps move the reset here? */ + /* Reset the chip to erase previous misconfiguration. */ + if (netif_msg_hw(np)) + printk("ASIC Control is %x.\n", ioread32(ioaddr + ASICCtrl)); + sundance_reset(dev, 0x00ff << 16); + if (netif_msg_hw(np)) + printk("ASIC Control is now %x.\n", ioread32(ioaddr + ASICCtrl)); + + card_idx++; + return 0; + +err_out_unregister: + unregister_netdev(dev); +err_out_unmap_rx: + dma_free_coherent(&pdev->dev, RX_TOTAL_SIZE, + np->rx_ring, np->rx_ring_dma); +err_out_unmap_tx: + dma_free_coherent(&pdev->dev, TX_TOTAL_SIZE, + np->tx_ring, np->tx_ring_dma); +err_out_cleardev: + pci_iounmap(pdev, ioaddr); +err_out_res: + pci_release_regions(pdev); +err_out_netdev: + free_netdev (dev); + return -ENODEV; +} + +static int change_mtu(struct net_device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > 8191)) /* Set by RxDMAFrameLen */ + return -EINVAL; + if (netif_running(dev)) + return -EBUSY; + dev->mtu = new_mtu; + return 0; +} + +#define eeprom_delay(ee_addr) ioread32(ee_addr) +/* Read the EEPROM and MII Management Data I/O (MDIO) interfaces. */ +static int eeprom_read(void __iomem *ioaddr, int location) +{ + int boguscnt = 10000; /* Typical 1900 ticks. */ + iowrite16(0x0200 | (location & 0xff), ioaddr + EECtrl); + do { + eeprom_delay(ioaddr + EECtrl); + if (! (ioread16(ioaddr + EECtrl) & 0x8000)) { + return ioread16(ioaddr + EEData); + } + } while (--boguscnt > 0); + return 0; +} + +/* MII transceiver control section. + Read and write the MII registers using software-generated serial + MDIO protocol. See the MII specifications or DP83840A data sheet + for details. + + The maximum data clock rate is 2.5 Mhz. The minimum timing is usually + met by back-to-back 33Mhz PCI cycles. */ +#define mdio_delay() ioread8(mdio_addr) + +enum mii_reg_bits { + MDIO_ShiftClk=0x0001, MDIO_Data=0x0002, MDIO_EnbOutput=0x0004, +}; +#define MDIO_EnbIn (0) +#define MDIO_WRITE0 (MDIO_EnbOutput) +#define MDIO_WRITE1 (MDIO_Data | MDIO_EnbOutput) + +/* Generate the preamble required for initial synchronization and + a few older transceivers. */ +static void mdio_sync(void __iomem *mdio_addr) +{ + int bits = 32; + + /* Establish sync by sending at least 32 logic ones. */ + while (--bits >= 0) { + iowrite8(MDIO_WRITE1, mdio_addr); + mdio_delay(); + iowrite8(MDIO_WRITE1 | MDIO_ShiftClk, mdio_addr); + mdio_delay(); + } +} + +static int mdio_read(struct net_device *dev, int phy_id, int location) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *mdio_addr = np->base + MIICtrl; + int mii_cmd = (0xf6 << 10) | (phy_id << 5) | location; + int i, retval = 0; + + if (np->mii_preamble_required) + mdio_sync(mdio_addr); + + /* Shift the read command bits out. */ + for (i = 15; i >= 0; i--) { + int dataval = (mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0; + + iowrite8(dataval, mdio_addr); + mdio_delay(); + iowrite8(dataval | MDIO_ShiftClk, mdio_addr); + mdio_delay(); + } + /* Read the two transition, 16 data, and wire-idle bits. */ + for (i = 19; i > 0; i--) { + iowrite8(MDIO_EnbIn, mdio_addr); + mdio_delay(); + retval = (retval << 1) | ((ioread8(mdio_addr) & MDIO_Data) ? 1 : 0); + iowrite8(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr); + mdio_delay(); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(struct net_device *dev, int phy_id, int location, int value) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *mdio_addr = np->base + MIICtrl; + int mii_cmd = (0x5002 << 16) | (phy_id << 23) | (location<<18) | value; + int i; + + if (np->mii_preamble_required) + mdio_sync(mdio_addr); + + /* Shift the command bits out. */ + for (i = 31; i >= 0; i--) { + int dataval = (mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0; + + iowrite8(dataval, mdio_addr); + mdio_delay(); + iowrite8(dataval | MDIO_ShiftClk, mdio_addr); + mdio_delay(); + } + /* Clear out extra bits. */ + for (i = 2; i > 0; i--) { + iowrite8(MDIO_EnbIn, mdio_addr); + mdio_delay(); + iowrite8(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr); + mdio_delay(); + } +} + +static int mdio_wait_link(struct net_device *dev, int wait) +{ + int bmsr; + int phy_id; + struct netdev_private *np; + + np = netdev_priv(dev); + phy_id = np->phys[0]; + + do { + bmsr = mdio_read(dev, phy_id, MII_BMSR); + if (bmsr & 0x0004) + return 0; + mdelay(1); + } while (--wait > 0); + return -1; +} + +static int netdev_open(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + const int irq = np->pci_dev->irq; + unsigned long flags; + int i; + + sundance_reset(dev, 0x00ff << 16); + + i = request_irq(irq, intr_handler, IRQF_SHARED, dev->name, dev); + if (i) + return i; + + if (netif_msg_ifup(np)) + printk(KERN_DEBUG "%s: netdev_open() irq %d\n", dev->name, irq); + + init_ring(dev); + + iowrite32(np->rx_ring_dma, ioaddr + RxListPtr); + /* The Tx list pointer is written as packets are queued. */ + + /* Initialize other registers. */ + __set_mac_addr(dev); +#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE) + iowrite16(dev->mtu + 18, ioaddr + MaxFrameSize); +#else + iowrite16(dev->mtu + 14, ioaddr + MaxFrameSize); +#endif + if (dev->mtu > 2047) + iowrite32(ioread32(ioaddr + ASICCtrl) | 0x0C, ioaddr + ASICCtrl); + + /* Configure the PCI bus bursts and FIFO thresholds. */ + + if (dev->if_port == 0) + dev->if_port = np->default_port; + + spin_lock_init(&np->mcastlock); + + set_rx_mode(dev); + iowrite16(0, ioaddr + IntrEnable); + iowrite16(0, ioaddr + DownCounter); + /* Set the chip to poll every N*320nsec. */ + iowrite8(100, ioaddr + RxDMAPollPeriod); + iowrite8(127, ioaddr + TxDMAPollPeriod); + /* Fix DFE-580TX packet drop issue */ + if (np->pci_dev->revision >= 0x14) + iowrite8(0x01, ioaddr + DebugCtrl1); + netif_start_queue(dev); + + spin_lock_irqsave(&np->lock, flags); + reset_tx(dev); + spin_unlock_irqrestore(&np->lock, flags); + + iowrite16 (StatsEnable | RxEnable | TxEnable, ioaddr + MACCtrl1); + + /* Disable Wol */ + iowrite8(ioread8(ioaddr + WakeEvent) | 0x00, ioaddr + WakeEvent); + np->wol_enabled = 0; + + if (netif_msg_ifup(np)) + printk(KERN_DEBUG "%s: Done netdev_open(), status: Rx %x Tx %x " + "MAC Control %x, %4.4x %4.4x.\n", + dev->name, ioread32(ioaddr + RxStatus), ioread8(ioaddr + TxStatus), + ioread32(ioaddr + MACCtrl0), + ioread16(ioaddr + MACCtrl1), ioread16(ioaddr + MACCtrl0)); + + /* Set the timer to check for link beat. */ + init_timer(&np->timer); + np->timer.expires = jiffies + 3*HZ; + np->timer.data = (unsigned long)dev; + np->timer.function = netdev_timer; /* timer handler */ + add_timer(&np->timer); + + /* Enable interrupts by setting the interrupt mask. */ + iowrite16(DEFAULT_INTR, ioaddr + IntrEnable); + + return 0; +} + +static void check_duplex(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + int mii_lpa = mdio_read(dev, np->phys[0], MII_LPA); + int negotiated = mii_lpa & np->mii_if.advertising; + int duplex; + + /* Force media */ + if (!np->an_enable || mii_lpa == 0xffff) { + if (np->mii_if.full_duplex) + iowrite16 (ioread16 (ioaddr + MACCtrl0) | EnbFullDuplex, + ioaddr + MACCtrl0); + return; + } + + /* Autonegotiation */ + duplex = (negotiated & 0x0100) || (negotiated & 0x01C0) == 0x0040; + if (np->mii_if.full_duplex != duplex) { + np->mii_if.full_duplex = duplex; + if (netif_msg_link(np)) + printk(KERN_INFO "%s: Setting %s-duplex based on MII #%d " + "negotiated capability %4.4x.\n", dev->name, + duplex ? "full" : "half", np->phys[0], negotiated); + iowrite16(ioread16(ioaddr + MACCtrl0) | (duplex ? 0x20 : 0), ioaddr + MACCtrl0); + } +} + +static void netdev_timer(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + int next_tick = 10*HZ; + + if (netif_msg_timer(np)) { + printk(KERN_DEBUG "%s: Media selection timer tick, intr status %4.4x, " + "Tx %x Rx %x.\n", + dev->name, ioread16(ioaddr + IntrEnable), + ioread8(ioaddr + TxStatus), ioread32(ioaddr + RxStatus)); + } + check_duplex(dev); + np->timer.expires = jiffies + next_tick; + add_timer(&np->timer); +} + +static void tx_timeout(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + unsigned long flag; + + netif_stop_queue(dev); + tasklet_disable(&np->tx_tasklet); + iowrite16(0, ioaddr + IntrEnable); + printk(KERN_WARNING "%s: Transmit timed out, TxStatus %2.2x " + "TxFrameId %2.2x," + " resetting...\n", dev->name, ioread8(ioaddr + TxStatus), + ioread8(ioaddr + TxFrameId)); + + { + int i; + for (i=0; itx_ring_dma + i*sizeof(*np->tx_ring)), + le32_to_cpu(np->tx_ring[i].next_desc), + le32_to_cpu(np->tx_ring[i].status), + (le32_to_cpu(np->tx_ring[i].status) >> 2) & 0xff, + le32_to_cpu(np->tx_ring[i].frag[0].addr), + le32_to_cpu(np->tx_ring[i].frag[0].length)); + } + printk(KERN_DEBUG "TxListPtr=%08x netif_queue_stopped=%d\n", + ioread32(np->base + TxListPtr), + netif_queue_stopped(dev)); + printk(KERN_DEBUG "cur_tx=%d(%02x) dirty_tx=%d(%02x)\n", + np->cur_tx, np->cur_tx % TX_RING_SIZE, + np->dirty_tx, np->dirty_tx % TX_RING_SIZE); + printk(KERN_DEBUG "cur_rx=%d dirty_rx=%d\n", np->cur_rx, np->dirty_rx); + printk(KERN_DEBUG "cur_task=%d\n", np->cur_task); + } + spin_lock_irqsave(&np->lock, flag); + + /* Stop and restart the chip's Tx processes . */ + reset_tx(dev); + spin_unlock_irqrestore(&np->lock, flag); + + dev->if_port = 0; + + dev->trans_start = jiffies; /* prevent tx timeout */ + dev->stats.tx_errors++; + if (np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 4) { + netif_wake_queue(dev); + } + iowrite16(DEFAULT_INTR, ioaddr + IntrEnable); + tasklet_enable(&np->tx_tasklet); +} + + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void init_ring(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + int i; + + np->cur_rx = np->cur_tx = 0; + np->dirty_rx = np->dirty_tx = 0; + np->cur_task = 0; + + np->rx_buf_sz = (dev->mtu <= 1520 ? PKT_BUF_SZ : dev->mtu + 16); + + /* Initialize all Rx descriptors. */ + for (i = 0; i < RX_RING_SIZE; i++) { + np->rx_ring[i].next_desc = cpu_to_le32(np->rx_ring_dma + + ((i+1)%RX_RING_SIZE)*sizeof(*np->rx_ring)); + np->rx_ring[i].status = 0; + np->rx_ring[i].frag[0].length = 0; + np->rx_skbuff[i] = NULL; + } + + /* Fill in the Rx buffers. Handle allocation failure gracefully. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = + netdev_alloc_skb(dev, np->rx_buf_sz + 2); + np->rx_skbuff[i] = skb; + if (skb == NULL) + break; + skb_reserve(skb, 2); /* 16 byte align the IP header. */ + np->rx_ring[i].frag[0].addr = cpu_to_le32( + dma_map_single(&np->pci_dev->dev, skb->data, + np->rx_buf_sz, DMA_FROM_DEVICE)); + if (dma_mapping_error(&np->pci_dev->dev, + np->rx_ring[i].frag[0].addr)) { + dev_kfree_skb(skb); + np->rx_skbuff[i] = NULL; + break; + } + np->rx_ring[i].frag[0].length = cpu_to_le32(np->rx_buf_sz | LastFrag); + } + np->dirty_rx = (unsigned int)(i - RX_RING_SIZE); + + for (i = 0; i < TX_RING_SIZE; i++) { + np->tx_skbuff[i] = NULL; + np->tx_ring[i].status = 0; + } +} + +static void tx_poll (unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct netdev_private *np = netdev_priv(dev); + unsigned head = np->cur_task % TX_RING_SIZE; + struct netdev_desc *txdesc = + &np->tx_ring[(np->cur_tx - 1) % TX_RING_SIZE]; + + /* Chain the next pointer */ + for (; np->cur_tx - np->cur_task > 0; np->cur_task++) { + int entry = np->cur_task % TX_RING_SIZE; + txdesc = &np->tx_ring[entry]; + if (np->last_tx) { + np->last_tx->next_desc = cpu_to_le32(np->tx_ring_dma + + entry*sizeof(struct netdev_desc)); + } + np->last_tx = txdesc; + } + /* Indicate the latest descriptor of tx ring */ + txdesc->status |= cpu_to_le32(DescIntrOnTx); + + if (ioread32 (np->base + TxListPtr) == 0) + iowrite32 (np->tx_ring_dma + head * sizeof(struct netdev_desc), + np->base + TxListPtr); +} + +static netdev_tx_t +start_tx (struct sk_buff *skb, struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + struct netdev_desc *txdesc; + unsigned entry; + + /* Calculate the next Tx descriptor entry. */ + entry = np->cur_tx % TX_RING_SIZE; + np->tx_skbuff[entry] = skb; + txdesc = &np->tx_ring[entry]; + + txdesc->next_desc = 0; + txdesc->status = cpu_to_le32 ((entry << 2) | DisableAlign); + txdesc->frag[0].addr = cpu_to_le32(dma_map_single(&np->pci_dev->dev, + skb->data, skb->len, DMA_TO_DEVICE)); + if (dma_mapping_error(&np->pci_dev->dev, + txdesc->frag[0].addr)) + goto drop_frame; + txdesc->frag[0].length = cpu_to_le32 (skb->len | LastFrag); + + /* Increment cur_tx before tasklet_schedule() */ + np->cur_tx++; + mb(); + /* Schedule a tx_poll() task */ + tasklet_schedule(&np->tx_tasklet); + + /* On some architectures: explicitly flush cache lines here. */ + if (np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 1 && + !netif_queue_stopped(dev)) { + /* do nothing */ + } else { + netif_stop_queue (dev); + } + if (netif_msg_tx_queued(np)) { + printk (KERN_DEBUG + "%s: Transmit frame #%d queued in slot %d.\n", + dev->name, np->cur_tx, entry); + } + return NETDEV_TX_OK; + +drop_frame: + dev_kfree_skb_any(skb); + np->tx_skbuff[entry] = NULL; + dev->stats.tx_dropped++; + return NETDEV_TX_OK; +} + +/* Reset hardware tx and free all of tx buffers */ +static int +reset_tx (struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + struct sk_buff *skb; + int i; + + /* Reset tx logic, TxListPtr will be cleaned */ + iowrite16 (TxDisable, ioaddr + MACCtrl1); + sundance_reset(dev, (NetworkReset|FIFOReset|DMAReset|TxReset) << 16); + + /* free all tx skbuff */ + for (i = 0; i < TX_RING_SIZE; i++) { + np->tx_ring[i].next_desc = 0; + + skb = np->tx_skbuff[i]; + if (skb) { + dma_unmap_single(&np->pci_dev->dev, + le32_to_cpu(np->tx_ring[i].frag[0].addr), + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + np->tx_skbuff[i] = NULL; + dev->stats.tx_dropped++; + } + } + np->cur_tx = np->dirty_tx = 0; + np->cur_task = 0; + + np->last_tx = NULL; + iowrite8(127, ioaddr + TxDMAPollPeriod); + + iowrite16 (StatsEnable | RxEnable | TxEnable, ioaddr + MACCtrl1); + return 0; +} + +/* The interrupt handler cleans up after the Tx thread, + and schedule a Rx thread work */ +static irqreturn_t intr_handler(int irq, void *dev_instance) +{ + struct net_device *dev = (struct net_device *)dev_instance; + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + int hw_frame_id; + int tx_cnt; + int tx_status; + int handled = 0; + int i; + + + do { + int intr_status = ioread16(ioaddr + IntrStatus); + iowrite16(intr_status, ioaddr + IntrStatus); + + if (netif_msg_intr(np)) + printk(KERN_DEBUG "%s: Interrupt, status %4.4x.\n", + dev->name, intr_status); + + if (!(intr_status & DEFAULT_INTR)) + break; + + handled = 1; + + if (intr_status & (IntrRxDMADone)) { + iowrite16(DEFAULT_INTR & ~(IntrRxDone|IntrRxDMADone), + ioaddr + IntrEnable); + if (np->budget < 0) + np->budget = RX_BUDGET; + tasklet_schedule(&np->rx_tasklet); + } + if (intr_status & (IntrTxDone | IntrDrvRqst)) { + tx_status = ioread16 (ioaddr + TxStatus); + for (tx_cnt=32; tx_status & 0x80; --tx_cnt) { + if (netif_msg_tx_done(np)) + printk + ("%s: Transmit status is %2.2x.\n", + dev->name, tx_status); + if (tx_status & 0x1e) { + if (netif_msg_tx_err(np)) + printk("%s: Transmit error status %4.4x.\n", + dev->name, tx_status); + dev->stats.tx_errors++; + if (tx_status & 0x10) + dev->stats.tx_fifo_errors++; + if (tx_status & 0x08) + dev->stats.collisions++; + if (tx_status & 0x04) + dev->stats.tx_fifo_errors++; + if (tx_status & 0x02) + dev->stats.tx_window_errors++; + + /* + ** This reset has been verified on + ** DFE-580TX boards ! phdm@macqel.be. + */ + if (tx_status & 0x10) { /* TxUnderrun */ + /* Restart Tx FIFO and transmitter */ + sundance_reset(dev, (NetworkReset|FIFOReset|TxReset) << 16); + /* No need to reset the Tx pointer here */ + } + /* Restart the Tx. Need to make sure tx enabled */ + i = 10; + do { + iowrite16(ioread16(ioaddr + MACCtrl1) | TxEnable, ioaddr + MACCtrl1); + if (ioread16(ioaddr + MACCtrl1) & TxEnabled) + break; + mdelay(1); + } while (--i); + } + /* Yup, this is a documentation bug. It cost me *hours*. */ + iowrite16 (0, ioaddr + TxStatus); + if (tx_cnt < 0) { + iowrite32(5000, ioaddr + DownCounter); + break; + } + tx_status = ioread16 (ioaddr + TxStatus); + } + hw_frame_id = (tx_status >> 8) & 0xff; + } else { + hw_frame_id = ioread8(ioaddr + TxFrameId); + } + + if (np->pci_dev->revision >= 0x14) { + spin_lock(&np->lock); + for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) { + int entry = np->dirty_tx % TX_RING_SIZE; + struct sk_buff *skb; + int sw_frame_id; + sw_frame_id = (le32_to_cpu( + np->tx_ring[entry].status) >> 2) & 0xff; + if (sw_frame_id == hw_frame_id && + !(le32_to_cpu(np->tx_ring[entry].status) + & 0x00010000)) + break; + if (sw_frame_id == (hw_frame_id + 1) % + TX_RING_SIZE) + break; + skb = np->tx_skbuff[entry]; + /* Free the original skb. */ + dma_unmap_single(&np->pci_dev->dev, + le32_to_cpu(np->tx_ring[entry].frag[0].addr), + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_irq (np->tx_skbuff[entry]); + np->tx_skbuff[entry] = NULL; + np->tx_ring[entry].frag[0].addr = 0; + np->tx_ring[entry].frag[0].length = 0; + } + spin_unlock(&np->lock); + } else { + spin_lock(&np->lock); + for (; np->cur_tx - np->dirty_tx > 0; np->dirty_tx++) { + int entry = np->dirty_tx % TX_RING_SIZE; + struct sk_buff *skb; + if (!(le32_to_cpu(np->tx_ring[entry].status) + & 0x00010000)) + break; + skb = np->tx_skbuff[entry]; + /* Free the original skb. */ + dma_unmap_single(&np->pci_dev->dev, + le32_to_cpu(np->tx_ring[entry].frag[0].addr), + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_irq (np->tx_skbuff[entry]); + np->tx_skbuff[entry] = NULL; + np->tx_ring[entry].frag[0].addr = 0; + np->tx_ring[entry].frag[0].length = 0; + } + spin_unlock(&np->lock); + } + + if (netif_queue_stopped(dev) && + np->cur_tx - np->dirty_tx < TX_QUEUE_LEN - 4) { + /* The ring is no longer full, clear busy flag. */ + netif_wake_queue (dev); + } + /* Abnormal error summary/uncommon events handlers. */ + if (intr_status & (IntrPCIErr | LinkChange | StatsMax)) + netdev_error(dev, intr_status); + } while (0); + if (netif_msg_intr(np)) + printk(KERN_DEBUG "%s: exiting interrupt, status=%#4.4x.\n", + dev->name, ioread16(ioaddr + IntrStatus)); + return IRQ_RETVAL(handled); +} + +static void rx_poll(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct netdev_private *np = netdev_priv(dev); + int entry = np->cur_rx % RX_RING_SIZE; + int boguscnt = np->budget; + void __iomem *ioaddr = np->base; + int received = 0; + + /* If EOP is set on the next entry, it's a new packet. Send it up. */ + while (1) { + struct netdev_desc *desc = &(np->rx_ring[entry]); + u32 frame_status = le32_to_cpu(desc->status); + int pkt_len; + + if (--boguscnt < 0) { + goto not_done; + } + if (!(frame_status & DescOwn)) + break; + pkt_len = frame_status & 0x1fff; /* Chip omits the CRC. */ + if (netif_msg_rx_status(np)) + printk(KERN_DEBUG " netdev_rx() status was %8.8x.\n", + frame_status); + if (frame_status & 0x001f4000) { + /* There was a error. */ + if (netif_msg_rx_err(np)) + printk(KERN_DEBUG " netdev_rx() Rx error was %8.8x.\n", + frame_status); + dev->stats.rx_errors++; + if (frame_status & 0x00100000) + dev->stats.rx_length_errors++; + if (frame_status & 0x00010000) + dev->stats.rx_fifo_errors++; + if (frame_status & 0x00060000) + dev->stats.rx_frame_errors++; + if (frame_status & 0x00080000) + dev->stats.rx_crc_errors++; + if (frame_status & 0x00100000) { + printk(KERN_WARNING "%s: Oversized Ethernet frame," + " status %8.8x.\n", + dev->name, frame_status); + } + } else { + struct sk_buff *skb; +#ifndef final_version + if (netif_msg_rx_status(np)) + printk(KERN_DEBUG " netdev_rx() normal Rx pkt length %d" + ", bogus_cnt %d.\n", + pkt_len, boguscnt); +#endif + /* Check if the packet is long enough to accept without copying + to a minimally-sized skbuff. */ + if (pkt_len < rx_copybreak && + (skb = netdev_alloc_skb(dev, pkt_len + 2)) != NULL) { + skb_reserve(skb, 2); /* 16 byte align the IP header */ + dma_sync_single_for_cpu(&np->pci_dev->dev, + le32_to_cpu(desc->frag[0].addr), + np->rx_buf_sz, DMA_FROM_DEVICE); + skb_copy_to_linear_data(skb, np->rx_skbuff[entry]->data, pkt_len); + dma_sync_single_for_device(&np->pci_dev->dev, + le32_to_cpu(desc->frag[0].addr), + np->rx_buf_sz, DMA_FROM_DEVICE); + skb_put(skb, pkt_len); + } else { + dma_unmap_single(&np->pci_dev->dev, + le32_to_cpu(desc->frag[0].addr), + np->rx_buf_sz, DMA_FROM_DEVICE); + skb_put(skb = np->rx_skbuff[entry], pkt_len); + np->rx_skbuff[entry] = NULL; + } + skb->protocol = eth_type_trans(skb, dev); + /* Note: checksum -> skb->ip_summed = CHECKSUM_UNNECESSARY; */ + netif_rx(skb); + } + entry = (entry + 1) % RX_RING_SIZE; + received++; + } + np->cur_rx = entry; + refill_rx (dev); + np->budget -= received; + iowrite16(DEFAULT_INTR, ioaddr + IntrEnable); + return; + +not_done: + np->cur_rx = entry; + refill_rx (dev); + if (!received) + received = 1; + np->budget -= received; + if (np->budget <= 0) + np->budget = RX_BUDGET; + tasklet_schedule(&np->rx_tasklet); +} + +static void refill_rx (struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + int entry; + int cnt = 0; + + /* Refill the Rx ring buffers. */ + for (;(np->cur_rx - np->dirty_rx + RX_RING_SIZE) % RX_RING_SIZE > 0; + np->dirty_rx = (np->dirty_rx + 1) % RX_RING_SIZE) { + struct sk_buff *skb; + entry = np->dirty_rx % RX_RING_SIZE; + if (np->rx_skbuff[entry] == NULL) { + skb = netdev_alloc_skb(dev, np->rx_buf_sz + 2); + np->rx_skbuff[entry] = skb; + if (skb == NULL) + break; /* Better luck next round. */ + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + np->rx_ring[entry].frag[0].addr = cpu_to_le32( + dma_map_single(&np->pci_dev->dev, skb->data, + np->rx_buf_sz, DMA_FROM_DEVICE)); + if (dma_mapping_error(&np->pci_dev->dev, + np->rx_ring[entry].frag[0].addr)) { + dev_kfree_skb_irq(skb); + np->rx_skbuff[entry] = NULL; + break; + } + } + /* Perhaps we need not reset this field. */ + np->rx_ring[entry].frag[0].length = + cpu_to_le32(np->rx_buf_sz | LastFrag); + np->rx_ring[entry].status = 0; + cnt++; + } +} +static void netdev_error(struct net_device *dev, int intr_status) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + u16 mii_ctl, mii_advertise, mii_lpa; + int speed; + + if (intr_status & LinkChange) { + if (mdio_wait_link(dev, 10) == 0) { + printk(KERN_INFO "%s: Link up\n", dev->name); + if (np->an_enable) { + mii_advertise = mdio_read(dev, np->phys[0], + MII_ADVERTISE); + mii_lpa = mdio_read(dev, np->phys[0], MII_LPA); + mii_advertise &= mii_lpa; + printk(KERN_INFO "%s: Link changed: ", + dev->name); + if (mii_advertise & ADVERTISE_100FULL) { + np->speed = 100; + printk("100Mbps, full duplex\n"); + } else if (mii_advertise & ADVERTISE_100HALF) { + np->speed = 100; + printk("100Mbps, half duplex\n"); + } else if (mii_advertise & ADVERTISE_10FULL) { + np->speed = 10; + printk("10Mbps, full duplex\n"); + } else if (mii_advertise & ADVERTISE_10HALF) { + np->speed = 10; + printk("10Mbps, half duplex\n"); + } else + printk("\n"); + + } else { + mii_ctl = mdio_read(dev, np->phys[0], MII_BMCR); + speed = (mii_ctl & BMCR_SPEED100) ? 100 : 10; + np->speed = speed; + printk(KERN_INFO "%s: Link changed: %dMbps ,", + dev->name, speed); + printk("%s duplex.\n", + (mii_ctl & BMCR_FULLDPLX) ? + "full" : "half"); + } + check_duplex(dev); + if (np->flowctrl && np->mii_if.full_duplex) { + iowrite16(ioread16(ioaddr + MulticastFilter1+2) | 0x0200, + ioaddr + MulticastFilter1+2); + iowrite16(ioread16(ioaddr + MACCtrl0) | EnbFlowCtrl, + ioaddr + MACCtrl0); + } + netif_carrier_on(dev); + } else { + printk(KERN_INFO "%s: Link down\n", dev->name); + netif_carrier_off(dev); + } + } + if (intr_status & StatsMax) { + get_stats(dev); + } + if (intr_status & IntrPCIErr) { + printk(KERN_ERR "%s: Something Wicked happened! %4.4x.\n", + dev->name, intr_status); + /* We must do a global reset of DMA to continue. */ + } +} + +static struct net_device_stats *get_stats(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + unsigned long flags; + u8 late_coll, single_coll, mult_coll; + + spin_lock_irqsave(&np->statlock, flags); + /* The chip only need report frame silently dropped. */ + dev->stats.rx_missed_errors += ioread8(ioaddr + RxMissed); + dev->stats.tx_packets += ioread16(ioaddr + TxFramesOK); + dev->stats.rx_packets += ioread16(ioaddr + RxFramesOK); + dev->stats.tx_carrier_errors += ioread8(ioaddr + StatsCarrierError); + + mult_coll = ioread8(ioaddr + StatsMultiColl); + np->xstats.tx_multiple_collisions += mult_coll; + single_coll = ioread8(ioaddr + StatsOneColl); + np->xstats.tx_single_collisions += single_coll; + late_coll = ioread8(ioaddr + StatsLateColl); + np->xstats.tx_late_collisions += late_coll; + dev->stats.collisions += mult_coll + + single_coll + + late_coll; + + np->xstats.tx_deferred += ioread8(ioaddr + StatsTxDefer); + np->xstats.tx_deferred_excessive += ioread8(ioaddr + StatsTxXSDefer); + np->xstats.tx_aborted += ioread8(ioaddr + StatsTxAbort); + np->xstats.tx_bcasts += ioread8(ioaddr + StatsBcastTx); + np->xstats.rx_bcasts += ioread8(ioaddr + StatsBcastRx); + np->xstats.tx_mcasts += ioread8(ioaddr + StatsMcastTx); + np->xstats.rx_mcasts += ioread8(ioaddr + StatsMcastRx); + + dev->stats.tx_bytes += ioread16(ioaddr + TxOctetsLow); + dev->stats.tx_bytes += ioread16(ioaddr + TxOctetsHigh) << 16; + dev->stats.rx_bytes += ioread16(ioaddr + RxOctetsLow); + dev->stats.rx_bytes += ioread16(ioaddr + RxOctetsHigh) << 16; + + spin_unlock_irqrestore(&np->statlock, flags); + + return &dev->stats; +} + +static void set_rx_mode(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + u16 mc_filter[4]; /* Multicast hash filter */ + u32 rx_mode; + int i; + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + rx_mode = AcceptBroadcast | AcceptMulticast | AcceptAll | AcceptMyPhys; + } else if ((netdev_mc_count(dev) > multicast_filter_limit) || + (dev->flags & IFF_ALLMULTI)) { + /* Too many to match, or accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys; + } else if (!netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + int bit; + int index; + int crc; + memset (mc_filter, 0, sizeof (mc_filter)); + netdev_for_each_mc_addr(ha, dev) { + crc = ether_crc_le(ETH_ALEN, ha->addr); + for (index=0, bit=0; bit < 6; bit++, crc <<= 1) + if (crc & 0x80000000) index |= 1 << bit; + mc_filter[index/16] |= (1 << (index % 16)); + } + rx_mode = AcceptBroadcast | AcceptMultiHash | AcceptMyPhys; + } else { + iowrite8(AcceptBroadcast | AcceptMyPhys, ioaddr + RxMode); + return; + } + if (np->mii_if.full_duplex && np->flowctrl) + mc_filter[3] |= 0x0200; + + for (i = 0; i < 4; i++) + iowrite16(mc_filter[i], ioaddr + MulticastFilter0 + i*2); + iowrite8(rx_mode, ioaddr + RxMode); +} + +static int __set_mac_addr(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + u16 addr16; + + addr16 = (dev->dev_addr[0] | (dev->dev_addr[1] << 8)); + iowrite16(addr16, np->base + StationAddr); + addr16 = (dev->dev_addr[2] | (dev->dev_addr[3] << 8)); + iowrite16(addr16, np->base + StationAddr+2); + addr16 = (dev->dev_addr[4] | (dev->dev_addr[5] << 8)); + iowrite16(addr16, np->base + StationAddr+4); + return 0; +} + +/* Invoked with rtnl_lock held */ +static int sundance_set_mac_addr(struct net_device *dev, void *data) +{ + const struct sockaddr *addr = data; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN); + __set_mac_addr(dev); + + return 0; +} + +static const struct { + const char name[ETH_GSTRING_LEN]; +} sundance_stats[] = { + { "tx_multiple_collisions" }, + { "tx_single_collisions" }, + { "tx_late_collisions" }, + { "tx_deferred" }, + { "tx_deferred_excessive" }, + { "tx_aborted" }, + { "tx_bcasts" }, + { "rx_bcasts" }, + { "tx_mcasts" }, + { "rx_mcasts" }, +}; + +static int check_if_running(struct net_device *dev) +{ + if (!netif_running(dev)) + return -EINVAL; + return 0; +} + +static void get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + struct netdev_private *np = netdev_priv(dev); + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); + strlcpy(info->bus_info, pci_name(np->pci_dev), sizeof(info->bus_info)); +} + +static int get_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + struct netdev_private *np = netdev_priv(dev); + spin_lock_irq(&np->lock); + mii_ethtool_gset(&np->mii_if, ecmd); + spin_unlock_irq(&np->lock); + return 0; +} + +static int set_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + struct netdev_private *np = netdev_priv(dev); + int res; + spin_lock_irq(&np->lock); + res = mii_ethtool_sset(&np->mii_if, ecmd); + spin_unlock_irq(&np->lock); + return res; +} + +static int nway_reset(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + return mii_nway_restart(&np->mii_if); +} + +static u32 get_link(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + return mii_link_ok(&np->mii_if); +} + +static u32 get_msglevel(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + return np->msg_enable; +} + +static void set_msglevel(struct net_device *dev, u32 val) +{ + struct netdev_private *np = netdev_priv(dev); + np->msg_enable = val; +} + +static void get_strings(struct net_device *dev, u32 stringset, + u8 *data) +{ + if (stringset == ETH_SS_STATS) + memcpy(data, sundance_stats, sizeof(sundance_stats)); +} + +static int get_sset_count(struct net_device *dev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(sundance_stats); + default: + return -EOPNOTSUPP; + } +} + +static void get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, u64 *data) +{ + struct netdev_private *np = netdev_priv(dev); + int i = 0; + + get_stats(dev); + data[i++] = np->xstats.tx_multiple_collisions; + data[i++] = np->xstats.tx_single_collisions; + data[i++] = np->xstats.tx_late_collisions; + data[i++] = np->xstats.tx_deferred; + data[i++] = np->xstats.tx_deferred_excessive; + data[i++] = np->xstats.tx_aborted; + data[i++] = np->xstats.tx_bcasts; + data[i++] = np->xstats.rx_bcasts; + data[i++] = np->xstats.tx_mcasts; + data[i++] = np->xstats.rx_mcasts; +} + +#ifdef CONFIG_PM + +static void sundance_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + u8 wol_bits; + + wol->wolopts = 0; + + wol->supported = (WAKE_PHY | WAKE_MAGIC); + if (!np->wol_enabled) + return; + + wol_bits = ioread8(ioaddr + WakeEvent); + if (wol_bits & MagicPktEnable) + wol->wolopts |= WAKE_MAGIC; + if (wol_bits & LinkEventEnable) + wol->wolopts |= WAKE_PHY; +} + +static int sundance_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + u8 wol_bits; + + if (!device_can_wakeup(&np->pci_dev->dev)) + return -EOPNOTSUPP; + + np->wol_enabled = !!(wol->wolopts); + wol_bits = ioread8(ioaddr + WakeEvent); + wol_bits &= ~(WakePktEnable | MagicPktEnable | + LinkEventEnable | WolEnable); + + if (np->wol_enabled) { + if (wol->wolopts & WAKE_MAGIC) + wol_bits |= (MagicPktEnable | WolEnable); + if (wol->wolopts & WAKE_PHY) + wol_bits |= (LinkEventEnable | WolEnable); + } + iowrite8(wol_bits, ioaddr + WakeEvent); + + device_set_wakeup_enable(&np->pci_dev->dev, np->wol_enabled); + + return 0; +} +#else +#define sundance_get_wol NULL +#define sundance_set_wol NULL +#endif /* CONFIG_PM */ + +static const struct ethtool_ops ethtool_ops = { + .begin = check_if_running, + .get_drvinfo = get_drvinfo, + .get_settings = get_settings, + .set_settings = set_settings, + .nway_reset = nway_reset, + .get_link = get_link, + .get_wol = sundance_get_wol, + .set_wol = sundance_set_wol, + .get_msglevel = get_msglevel, + .set_msglevel = set_msglevel, + .get_strings = get_strings, + .get_sset_count = get_sset_count, + .get_ethtool_stats = get_ethtool_stats, +}; + +static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct netdev_private *np = netdev_priv(dev); + int rc; + + if (!netif_running(dev)) + return -EINVAL; + + spin_lock_irq(&np->lock); + rc = generic_mii_ioctl(&np->mii_if, if_mii(rq), cmd, NULL); + spin_unlock_irq(&np->lock); + + return rc; +} + +static int netdev_close(struct net_device *dev) +{ + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + struct sk_buff *skb; + int i; + + /* Wait and kill tasklet */ + tasklet_kill(&np->rx_tasklet); + tasklet_kill(&np->tx_tasklet); + np->cur_tx = 0; + np->dirty_tx = 0; + np->cur_task = 0; + np->last_tx = NULL; + + netif_stop_queue(dev); + + if (netif_msg_ifdown(np)) { + printk(KERN_DEBUG "%s: Shutting down ethercard, status was Tx %2.2x " + "Rx %4.4x Int %2.2x.\n", + dev->name, ioread8(ioaddr + TxStatus), + ioread32(ioaddr + RxStatus), ioread16(ioaddr + IntrStatus)); + printk(KERN_DEBUG "%s: Queue pointers were Tx %d / %d, Rx %d / %d.\n", + dev->name, np->cur_tx, np->dirty_tx, np->cur_rx, np->dirty_rx); + } + + /* Disable interrupts by clearing the interrupt mask. */ + iowrite16(0x0000, ioaddr + IntrEnable); + + /* Disable Rx and Tx DMA for safely release resource */ + iowrite32(0x500, ioaddr + DMACtrl); + + /* Stop the chip's Tx and Rx processes. */ + iowrite16(TxDisable | RxDisable | StatsDisable, ioaddr + MACCtrl1); + + for (i = 2000; i > 0; i--) { + if ((ioread32(ioaddr + DMACtrl) & 0xc000) == 0) + break; + mdelay(1); + } + + iowrite16(GlobalReset | DMAReset | FIFOReset | NetworkReset, + ioaddr + ASIC_HI_WORD(ASICCtrl)); + + for (i = 2000; i > 0; i--) { + if ((ioread16(ioaddr + ASIC_HI_WORD(ASICCtrl)) & ResetBusy) == 0) + break; + mdelay(1); + } + +#ifdef __i386__ + if (netif_msg_hw(np)) { + printk(KERN_DEBUG " Tx ring at %8.8x:\n", + (int)(np->tx_ring_dma)); + for (i = 0; i < TX_RING_SIZE; i++) + printk(KERN_DEBUG " #%d desc. %4.4x %8.8x %8.8x.\n", + i, np->tx_ring[i].status, np->tx_ring[i].frag[0].addr, + np->tx_ring[i].frag[0].length); + printk(KERN_DEBUG " Rx ring %8.8x:\n", + (int)(np->rx_ring_dma)); + for (i = 0; i < /*RX_RING_SIZE*/4 ; i++) { + printk(KERN_DEBUG " #%d desc. %4.4x %4.4x %8.8x\n", + i, np->rx_ring[i].status, np->rx_ring[i].frag[0].addr, + np->rx_ring[i].frag[0].length); + } + } +#endif /* __i386__ debugging only */ + + free_irq(np->pci_dev->irq, dev); + + del_timer_sync(&np->timer); + + /* Free all the skbuffs in the Rx queue. */ + for (i = 0; i < RX_RING_SIZE; i++) { + np->rx_ring[i].status = 0; + skb = np->rx_skbuff[i]; + if (skb) { + dma_unmap_single(&np->pci_dev->dev, + le32_to_cpu(np->rx_ring[i].frag[0].addr), + np->rx_buf_sz, DMA_FROM_DEVICE); + dev_kfree_skb(skb); + np->rx_skbuff[i] = NULL; + } + np->rx_ring[i].frag[0].addr = cpu_to_le32(0xBADF00D0); /* poison */ + } + for (i = 0; i < TX_RING_SIZE; i++) { + np->tx_ring[i].next_desc = 0; + skb = np->tx_skbuff[i]; + if (skb) { + dma_unmap_single(&np->pci_dev->dev, + le32_to_cpu(np->tx_ring[i].frag[0].addr), + skb->len, DMA_TO_DEVICE); + dev_kfree_skb(skb); + np->tx_skbuff[i] = NULL; + } + } + + return 0; +} + +static void sundance_remove1(struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + + if (dev) { + struct netdev_private *np = netdev_priv(dev); + unregister_netdev(dev); + dma_free_coherent(&pdev->dev, RX_TOTAL_SIZE, + np->rx_ring, np->rx_ring_dma); + dma_free_coherent(&pdev->dev, TX_TOTAL_SIZE, + np->tx_ring, np->tx_ring_dma); + pci_iounmap(pdev, np->base); + pci_release_regions(pdev); + free_netdev(dev); + } +} + +#ifdef CONFIG_PM + +static int sundance_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct net_device *dev = pci_get_drvdata(pci_dev); + struct netdev_private *np = netdev_priv(dev); + void __iomem *ioaddr = np->base; + + if (!netif_running(dev)) + return 0; + + netdev_close(dev); + netif_device_detach(dev); + + pci_save_state(pci_dev); + if (np->wol_enabled) { + iowrite8(AcceptBroadcast | AcceptMyPhys, ioaddr + RxMode); + iowrite16(RxEnable, ioaddr + MACCtrl1); + } + pci_enable_wake(pci_dev, pci_choose_state(pci_dev, state), + np->wol_enabled); + pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state)); + + return 0; +} + +static int sundance_resume(struct pci_dev *pci_dev) +{ + struct net_device *dev = pci_get_drvdata(pci_dev); + int err = 0; + + if (!netif_running(dev)) + return 0; + + pci_set_power_state(pci_dev, PCI_D0); + pci_restore_state(pci_dev); + pci_enable_wake(pci_dev, PCI_D0, 0); + + err = netdev_open(dev); + if (err) { + printk(KERN_ERR "%s: Can't resume interface!\n", + dev->name); + goto out; + } + + netif_device_attach(dev); + +out: + return err; +} + +#endif /* CONFIG_PM */ + +static struct pci_driver sundance_driver = { + .name = DRV_NAME, + .id_table = sundance_pci_tbl, + .probe = sundance_probe1, + .remove = sundance_remove1, +#ifdef CONFIG_PM + .suspend = sundance_suspend, + .resume = sundance_resume, +#endif /* CONFIG_PM */ +}; + +static int __init sundance_init(void) +{ +/* when a module, this is printed whether or not devices are found in probe */ +#ifdef MODULE + printk(version); +#endif + return pci_register_driver(&sundance_driver); +} + +static void __exit sundance_exit(void) +{ + pci_unregister_driver(&sundance_driver); +} + +module_init(sundance_init); +module_exit(sundance_exit); + + diff --git a/linux/drivers/net/ethernet/dlink/sundance.txt b/linux/drivers/net/ethernet/dlink/sundance.txt new file mode 100644 index 000000000..08383a11c --- /dev/null +++ b/linux/drivers/net/ethernet/dlink/sundance.txt @@ -0,0 +1,4 @@ +KBUILD_MODNAME not defined +--set pre.cppflags[+] '-DKBUILD_MODNAME="sundance"' + +netdev_ops functions not called, assignment on line 579 doesn't spawn diff --git a/linux/drivers/net/ethernet/hp/hp100.c b/linux/drivers/net/ethernet/hp/hp100.c new file mode 100644 index 000000000..ae6e30d39 --- /dev/null +++ b/linux/drivers/net/ethernet/hp/hp100.c @@ -0,0 +1,3069 @@ +/* +** hp100.c +** HP CASCADE Architecture Driver for 100VG-AnyLan Network Adapters +** +** $Id: hp100.c,v 1.58 2001/09/24 18:03:01 perex Exp perex $ +** +** Based on the HP100 driver written by Jaroslav Kysela +** Extended for new busmaster capable chipsets by +** Siegfried "Frieder" Loeffler (dg1sek) +** +** Maintained by: Jaroslav Kysela +** +** This driver has only been tested with +** -- HP J2585B 10/100 Mbit/s PCI Busmaster +** -- HP J2585A 10/100 Mbit/s PCI +** -- HP J2970A 10 Mbit/s PCI Combo 10base-T/BNC +** -- HP J2973A 10 Mbit/s PCI 10base-T +** -- HP J2573 10/100 ISA +** -- Compex ReadyLink ENET100-VG4 10/100 Mbit/s PCI / EISA +** -- Compex FreedomLine 100/VG 10/100 Mbit/s ISA / EISA / PCI +** +** but it should also work with the other CASCADE based adapters. +** +** TODO: +** - J2573 seems to hang sometimes when in shared memory mode. +** - Mode for Priority TX +** - Check PCI registers, performance might be improved? +** - To reduce interrupt load in busmaster, one could switch off +** the interrupts that are used to refill the queues whenever the +** queues are filled up to more than a certain threshold. +** - some updates for EISA version of card +** +** +** This code is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This code 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., 675 Mass Ave, Cambridge, MA 02139, USA. +** +** 1.57c -> 1.58 +** - used indent to change coding-style +** - added KTI DP-200 EISA ID +** - ioremap is also used for low (<1MB) memory (multi-architecture support) +** +** 1.57b -> 1.57c - Arnaldo Carvalho de Melo +** - release resources on failure in init_module +** +** 1.57 -> 1.57b - Jean II +** - fix spinlocks, SMP is now working ! +** +** 1.56 -> 1.57 +** - updates for new PCI interface for 2.1 kernels +** +** 1.55 -> 1.56 +** - removed printk in misc. interrupt and update statistics to allow +** monitoring of card status +** - timing changes in xmit routines, relogin to 100VG hub added when +** driver does reset +** - included fix for Compex FreedomLine PCI adapter +** +** 1.54 -> 1.55 +** - fixed bad initialization in init_module +** - added Compex FreedomLine adapter +** - some fixes in card initialization +** +** 1.53 -> 1.54 +** - added hardware multicast filter support (doesn't work) +** - little changes in hp100_sense_lan routine +** - added support for Coax and AUI (J2970) +** - fix for multiple cards and hp100_mode parameter (insmod) +** - fix for shared IRQ +** +** 1.52 -> 1.53 +** - fixed bug in multicast support +** +*/ + +#define HP100_DEFAULT_PRIORITY_TX 0 + +#undef HP100_DEBUG +#undef HP100_DEBUG_B /* Trace */ +#undef HP100_DEBUG_BM /* Debug busmaster code (PDL stuff) */ + +#undef HP100_DEBUG_TRAINING /* Debug login-to-hub procedure */ +#undef HP100_DEBUG_TX +#undef HP100_DEBUG_IRQ +#undef HP100_DEBUG_RX + +#undef HP100_MULTICAST_FILTER /* Need to be debugged... */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hp100.h" + +/* + * defines + */ + +#define HP100_BUS_ISA 0 +#define HP100_BUS_EISA 1 +#define HP100_BUS_PCI 2 + +#define HP100_REGION_SIZE 0x20 /* for ioports */ +#define HP100_SIG_LEN 8 /* same as EISA_SIG_LEN */ + +#define HP100_MAX_PACKET_SIZE (1536+4) +#define HP100_MIN_PACKET_SIZE 60 + +#ifndef HP100_DEFAULT_RX_RATIO +/* default - 75% onboard memory on the card are used for RX packets */ +#define HP100_DEFAULT_RX_RATIO 75 +#endif + +#ifndef HP100_DEFAULT_PRIORITY_TX +/* default - don't enable transmit outgoing packets as priority */ +#define HP100_DEFAULT_PRIORITY_TX 0 +#endif + +/* + * structures + */ + +struct hp100_private { + spinlock_t lock; + char id[HP100_SIG_LEN]; + u_short chip; + u_short soft_model; + u_int memory_size; + u_int virt_memory_size; + u_short rx_ratio; /* 1 - 99 */ + u_short priority_tx; /* != 0 - priority tx */ + u_short mode; /* PIO, Shared Mem or Busmaster */ + u_char bus; + struct pci_dev *pci_dev; + short mem_mapped; /* memory mapped access */ + void __iomem *mem_ptr_virt; /* virtual memory mapped area, maybe NULL */ + unsigned long mem_ptr_phys; /* physical memory mapped area */ + short lan_type; /* 10Mb/s, 100Mb/s or -1 (error) */ + int hub_status; /* was login to hub successful? */ + u_char mac1_mode; + u_char mac2_mode; + u_char hash_bytes[8]; + + /* Rings for busmaster mode: */ + hp100_ring_t *rxrhead; /* Head (oldest) index into rxring */ + hp100_ring_t *rxrtail; /* Tail (newest) index into rxring */ + hp100_ring_t *txrhead; /* Head (oldest) index into txring */ + hp100_ring_t *txrtail; /* Tail (newest) index into txring */ + + hp100_ring_t rxring[MAX_RX_PDL]; + hp100_ring_t txring[MAX_TX_PDL]; + + u_int *page_vaddr_algn; /* Aligned virtual address of allocated page */ + u_long whatever_offset; /* Offset to bus/phys/dma address */ + int rxrcommit; /* # Rx PDLs committed to adapter */ + int txrcommit; /* # Tx PDLs committed to adapter */ +}; + +/* + * variables + */ +#ifdef CONFIG_ISA +static const char *hp100_isa_tbl[] = { + "HWPF150", /* HP J2573 rev A */ + "HWP1950", /* HP J2573 */ +}; +#endif + +#ifdef CONFIG_EISA +static struct eisa_device_id hp100_eisa_tbl[] = { + { "HWPF180" }, /* HP J2577 rev A */ + { "HWP1920" }, /* HP 27248B */ + { "HWP1940" }, /* HP J2577 */ + { "HWP1990" }, /* HP J2577 */ + { "CPX0301" }, /* ReadyLink ENET100-VG4 */ + { "CPX0401" }, /* FreedomLine 100/VG */ + { "" } /* Mandatory final entry ! */ +}; +MODULE_DEVICE_TABLE(eisa, hp100_eisa_tbl); +#endif + +#ifdef CONFIG_PCI +static const struct pci_device_id hp100_pci_tbl[] = { + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_J2585A, PCI_ANY_ID, PCI_ANY_ID,}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_J2585B, PCI_ANY_ID, PCI_ANY_ID,}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_J2970A, PCI_ANY_ID, PCI_ANY_ID,}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_J2973A, PCI_ANY_ID, PCI_ANY_ID,}, + {PCI_VENDOR_ID_COMPEX, PCI_DEVICE_ID_COMPEX_ENET100VG4, PCI_ANY_ID, PCI_ANY_ID,}, + {PCI_VENDOR_ID_COMPEX2, PCI_DEVICE_ID_COMPEX2_100VG, PCI_ANY_ID, PCI_ANY_ID,}, +/* {PCI_VENDOR_ID_KTI, PCI_DEVICE_ID_KTI_DP200, PCI_ANY_ID, PCI_ANY_ID }, */ + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(pci, hp100_pci_tbl); +#endif + +static int hp100_rx_ratio = HP100_DEFAULT_RX_RATIO; +static int hp100_priority_tx = HP100_DEFAULT_PRIORITY_TX; +static int hp100_mode = 1; + +module_param(hp100_rx_ratio, int, 0); +module_param(hp100_priority_tx, int, 0); +module_param(hp100_mode, int, 0); + +/* + * prototypes + */ + +static int hp100_probe1(struct net_device *dev, int ioaddr, u_char bus, + struct pci_dev *pci_dev); + + +static int hp100_open(struct net_device *dev); +static int hp100_close(struct net_device *dev); +static netdev_tx_t hp100_start_xmit(struct sk_buff *skb, + struct net_device *dev); +static netdev_tx_t hp100_start_xmit_bm(struct sk_buff *skb, + struct net_device *dev); +static void hp100_rx(struct net_device *dev); +static struct net_device_stats *hp100_get_stats(struct net_device *dev); +static void hp100_misc_interrupt(struct net_device *dev); +static void hp100_update_stats(struct net_device *dev); +static void hp100_clear_stats(struct hp100_private *lp, int ioaddr); +static void hp100_set_multicast_list(struct net_device *dev); +static irqreturn_t hp100_interrupt(int irq, void *dev_id); +static void hp100_start_interface(struct net_device *dev); +static void hp100_stop_interface(struct net_device *dev); +static void hp100_load_eeprom(struct net_device *dev, u_short ioaddr); +static int hp100_sense_lan(struct net_device *dev); +static int hp100_login_to_vg_hub(struct net_device *dev, + u_short force_relogin); +static int hp100_down_vg_link(struct net_device *dev); +static void hp100_cascade_reset(struct net_device *dev, u_short enable); +static void hp100_BM_shutdown(struct net_device *dev); +static void hp100_mmuinit(struct net_device *dev); +static void hp100_init_pdls(struct net_device *dev); +static int hp100_init_rxpdl(struct net_device *dev, + register hp100_ring_t * ringptr, + register u_int * pdlptr); +static int hp100_init_txpdl(struct net_device *dev, + register hp100_ring_t * ringptr, + register u_int * pdlptr); +static void hp100_rxfill(struct net_device *dev); +static void hp100_hwinit(struct net_device *dev); +static void hp100_clean_txring(struct net_device *dev); +#ifdef HP100_DEBUG +static void hp100_RegisterDump(struct net_device *dev); +#endif + +/* Conversion to new PCI API : + * Convert an address in a kernel buffer to a bus/phys/dma address. + * This work *only* for memory fragments part of lp->page_vaddr, + * because it was properly DMA allocated via pci_alloc_consistent(), + * so we just need to "retrieve" the original mapping to bus/phys/dma + * address - Jean II */ +static inline dma_addr_t virt_to_whatever(struct net_device *dev, u32 * ptr) +{ + struct hp100_private *lp = netdev_priv(dev); + return ((u_long) ptr) + lp->whatever_offset; +} + +static inline u_int pdl_map_data(struct hp100_private *lp, void *data) +{ + return pci_map_single(lp->pci_dev, data, + MAX_ETHER_SIZE, PCI_DMA_FROMDEVICE); +} + +/* TODO: This function should not really be needed in a good design... */ +static void wait(void) +{ + mdelay(1); +} + +/* + * probe functions + * These functions should - if possible - avoid doing write operations + * since this could cause problems when the card is not installed. + */ + +/* + * Read board id and convert to string. + * Effectively same code as decode_eisa_sig + */ +static const char *hp100_read_id(int ioaddr) +{ + int i; + static char str[HP100_SIG_LEN]; + unsigned char sig[4], sum; + unsigned short rev; + + hp100_page(ID_MAC_ADDR); + sum = 0; + for (i = 0; i < 4; i++) { + sig[i] = hp100_inb(BOARD_ID + i); + sum += sig[i]; + } + + sum += hp100_inb(BOARD_ID + i); + if (sum != 0xff) + return NULL; /* bad checksum */ + + str[0] = ((sig[0] >> 2) & 0x1f) + ('A' - 1); + str[1] = (((sig[0] & 3) << 3) | (sig[1] >> 5)) + ('A' - 1); + str[2] = (sig[1] & 0x1f) + ('A' - 1); + rev = (sig[2] << 8) | sig[3]; + sprintf(str + 3, "%04X", rev); + + return str; +} + +#ifdef CONFIG_ISA +static __init int hp100_isa_probe1(struct net_device *dev, int ioaddr) +{ + const char *sig; + int i; + + if (!request_region(ioaddr, HP100_REGION_SIZE, "hp100")) + goto err; + + if (hp100_inw(HW_ID) != HP100_HW_ID_CASCADE) { + release_region(ioaddr, HP100_REGION_SIZE); + goto err; + } + + sig = hp100_read_id(ioaddr); + release_region(ioaddr, HP100_REGION_SIZE); + + if (sig == NULL) + goto err; + + for (i = 0; i < ARRAY_SIZE(hp100_isa_tbl); i++) { + if (!strcmp(hp100_isa_tbl[i], sig)) + break; + + } + + if (i < ARRAY_SIZE(hp100_isa_tbl)) + return hp100_probe1(dev, ioaddr, HP100_BUS_ISA, NULL); + err: + return -ENODEV; + +} +/* + * Probe for ISA board. + * EISA and PCI are handled by device infrastructure. + */ + +static int __init hp100_isa_probe(struct net_device *dev, int addr) +{ + int err = -ENODEV; + + /* Probe for a specific ISA address */ + if (addr > 0xff && addr < 0x400) + err = hp100_isa_probe1(dev, addr); + + else if (addr != 0) + err = -ENXIO; + + else { + /* Probe all ISA possible port regions */ + for (addr = 0x100; addr < 0x400; addr += 0x20) { + err = hp100_isa_probe1(dev, addr); + if (!err) + break; + } + } + return err; +} +#endif /* CONFIG_ISA */ + +#if !defined(MODULE) && defined(CONFIG_ISA) +struct net_device * __init hp100_probe(int unit) +{ + struct net_device *dev = alloc_etherdev(sizeof(struct hp100_private)); + int err; + + if (!dev) + return ERR_PTR(-ENODEV); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4200, TRACE); + printk("hp100: %s: probe\n", dev->name); +#endif + + if (unit >= 0) { + sprintf(dev->name, "eth%d", unit); + netdev_boot_setup_check(dev); + } + + err = hp100_isa_probe(dev, dev->base_addr); + if (err) + goto out; + + return dev; + out: + free_netdev(dev); + return ERR_PTR(err); +} +#endif /* !MODULE && CONFIG_ISA */ + +static const struct net_device_ops hp100_bm_netdev_ops = { + .ndo_open = hp100_open, + .ndo_stop = hp100_close, + .ndo_start_xmit = hp100_start_xmit_bm, + .ndo_get_stats = hp100_get_stats, + .ndo_set_rx_mode = hp100_set_multicast_list, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static const struct net_device_ops hp100_netdev_ops = { + .ndo_open = hp100_open, + .ndo_stop = hp100_close, + .ndo_start_xmit = hp100_start_xmit, + .ndo_get_stats = hp100_get_stats, + .ndo_set_rx_mode = hp100_set_multicast_list, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static int hp100_probe1(struct net_device *dev, int ioaddr, u_char bus, + struct pci_dev *pci_dev) +{ + int i; + int err = -ENODEV; + const char *eid; + u_int chip; + u_char uc; + u_int memory_size = 0, virt_memory_size = 0; + u_short local_mode, lsw; + short mem_mapped; + unsigned long mem_ptr_phys; + void __iomem *mem_ptr_virt; + struct hp100_private *lp; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4201, TRACE); + printk("hp100: %s: probe1\n", dev->name); +#endif + + /* memory region for programmed i/o */ + if (!request_region(ioaddr, HP100_REGION_SIZE, "hp100")) + goto out1; + + if (hp100_inw(HW_ID) != HP100_HW_ID_CASCADE) + goto out2; + + chip = hp100_inw(PAGING) & HP100_CHIPID_MASK; +#ifdef HP100_DEBUG + if (chip == HP100_CHIPID_SHASTA) + printk("hp100: %s: Shasta Chip detected. (This is a pre 802.12 chip)\n", dev->name); + else if (chip == HP100_CHIPID_RAINIER) + printk("hp100: %s: Rainier Chip detected. (This is a pre 802.12 chip)\n", dev->name); + else if (chip == HP100_CHIPID_LASSEN) + printk("hp100: %s: Lassen Chip detected.\n", dev->name); + else + printk("hp100: %s: Warning: Unknown CASCADE chip (id=0x%.4x).\n", dev->name, chip); +#endif + + dev->base_addr = ioaddr; + + eid = hp100_read_id(ioaddr); + if (eid == NULL) { /* bad checksum? */ + printk(KERN_WARNING "%s: bad ID checksum at base port 0x%x\n", + __func__, ioaddr); + goto out2; + } + + hp100_page(ID_MAC_ADDR); + for (i = uc = 0; i < 7; i++) + uc += hp100_inb(LAN_ADDR + i); + if (uc != 0xff) { + printk(KERN_WARNING + "%s: bad lan address checksum at port 0x%x)\n", + __func__, ioaddr); + err = -EIO; + goto out2; + } + + /* Make sure, that all registers are correctly updated... */ + + hp100_load_eeprom(dev, ioaddr); + wait(); + + /* + * Determine driver operation mode + * + * Use the variable "hp100_mode" upon insmod or as kernel parameter to + * force driver modes: + * hp100_mode=1 -> default, use busmaster mode if configured. + * hp100_mode=2 -> enable shared memory mode + * hp100_mode=3 -> force use of i/o mapped mode. + * hp100_mode=4 -> same as 1, but re-set the enable bit on the card. + */ + + /* + * LSW values: + * 0x2278 -> J2585B, PnP shared memory mode + * 0x2270 -> J2585B, shared memory mode, 0xdc000 + * 0xa23c -> J2585B, I/O mapped mode + * 0x2240 -> EISA COMPEX, BusMaster (Shasta Chip) + * 0x2220 -> EISA HP, I/O (Shasta Chip) + * 0x2260 -> EISA HP, BusMaster (Shasta Chip) + */ + +#if 0 + local_mode = 0x2270; + hp100_outw(0xfefe, OPTION_LSW); + hp100_outw(local_mode | HP100_SET_LB | HP100_SET_HB, OPTION_LSW); +#endif + + /* hp100_mode value maybe used in future by another card */ + local_mode = hp100_mode; + if (local_mode < 1 || local_mode > 4) + local_mode = 1; /* default */ +#ifdef HP100_DEBUG + printk("hp100: %s: original LSW = 0x%x\n", dev->name, + hp100_inw(OPTION_LSW)); +#endif + + if (local_mode == 3) { + hp100_outw(HP100_MEM_EN | HP100_RESET_LB, OPTION_LSW); + hp100_outw(HP100_IO_EN | HP100_SET_LB, OPTION_LSW); + hp100_outw(HP100_BM_WRITE | HP100_BM_READ | HP100_RESET_HB, OPTION_LSW); + printk("hp100: IO mapped mode forced.\n"); + } else if (local_mode == 2) { + hp100_outw(HP100_MEM_EN | HP100_SET_LB, OPTION_LSW); + hp100_outw(HP100_IO_EN | HP100_SET_LB, OPTION_LSW); + hp100_outw(HP100_BM_WRITE | HP100_BM_READ | HP100_RESET_HB, OPTION_LSW); + printk("hp100: Shared memory mode requested.\n"); + } else if (local_mode == 4) { + if (chip == HP100_CHIPID_LASSEN) { + hp100_outw(HP100_BM_WRITE | HP100_BM_READ | HP100_SET_HB, OPTION_LSW); + hp100_outw(HP100_IO_EN | HP100_MEM_EN | HP100_RESET_LB, OPTION_LSW); + printk("hp100: Busmaster mode requested.\n"); + } + local_mode = 1; + } + + if (local_mode == 1) { /* default behaviour */ + lsw = hp100_inw(OPTION_LSW); + + if ((lsw & HP100_IO_EN) && (~lsw & HP100_MEM_EN) && + (~lsw & (HP100_BM_WRITE | HP100_BM_READ))) { +#ifdef HP100_DEBUG + printk("hp100: %s: IO_EN bit is set on card.\n", dev->name); +#endif + local_mode = 3; + } else if (chip == HP100_CHIPID_LASSEN && + (lsw & (HP100_BM_WRITE | HP100_BM_READ)) == (HP100_BM_WRITE | HP100_BM_READ)) { + /* Conversion to new PCI API : + * I don't have the doc, but I assume that the card + * can map the full 32bit address space. + * Also, we can have EISA Busmaster cards (not tested), + * so beware !!! - Jean II */ + if((bus == HP100_BUS_PCI) && + (pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32)))) { + /* Gracefully fallback to shared memory */ + goto busmasterfail; + } + printk("hp100: Busmaster mode enabled.\n"); + hp100_outw(HP100_MEM_EN | HP100_IO_EN | HP100_RESET_LB, OPTION_LSW); + } else { + busmasterfail: +#ifdef HP100_DEBUG + printk("hp100: %s: Card not configured for BM or BM not supported with this card.\n", dev->name); + printk("hp100: %s: Trying shared memory mode.\n", dev->name); +#endif + /* In this case, try shared memory mode */ + local_mode = 2; + hp100_outw(HP100_MEM_EN | HP100_SET_LB, OPTION_LSW); + /* hp100_outw(HP100_IO_EN|HP100_RESET_LB, OPTION_LSW); */ + } + } +#ifdef HP100_DEBUG + printk("hp100: %s: new LSW = 0x%x\n", dev->name, hp100_inw(OPTION_LSW)); +#endif + + /* Check for shared memory on the card, eventually remap it */ + hp100_page(HW_MAP); + mem_mapped = ((hp100_inw(OPTION_LSW) & (HP100_MEM_EN)) != 0); + mem_ptr_phys = 0UL; + mem_ptr_virt = NULL; + memory_size = (8192 << ((hp100_inb(SRAM) >> 5) & 0x07)); + virt_memory_size = 0; + + /* For memory mapped or busmaster mode, we want the memory address */ + if (mem_mapped || (local_mode == 1)) { + mem_ptr_phys = (hp100_inw(MEM_MAP_LSW) | (hp100_inw(MEM_MAP_MSW) << 16)); + mem_ptr_phys &= ~0x1fff; /* 8k alignment */ + + if (bus == HP100_BUS_ISA && (mem_ptr_phys & ~0xfffff) != 0) { + printk("hp100: Can only use programmed i/o mode.\n"); + mem_ptr_phys = 0; + mem_mapped = 0; + local_mode = 3; /* Use programmed i/o */ + } + + /* We do not need access to shared memory in busmaster mode */ + /* However in slave mode we need to remap high (>1GB) card memory */ + if (local_mode != 1) { /* = not busmaster */ + /* We try with smaller memory sizes, if ioremap fails */ + for (virt_memory_size = memory_size; virt_memory_size > 16383; virt_memory_size >>= 1) { + if ((mem_ptr_virt = ioremap((u_long) mem_ptr_phys, virt_memory_size)) == NULL) { +#ifdef HP100_DEBUG + printk("hp100: %s: ioremap for 0x%x bytes high PCI memory at 0x%lx failed\n", dev->name, virt_memory_size, mem_ptr_phys); +#endif + } else { +#ifdef HP100_DEBUG + printk("hp100: %s: remapped 0x%x bytes high PCI memory at 0x%lx to %p.\n", dev->name, virt_memory_size, mem_ptr_phys, mem_ptr_virt); +#endif + break; + } + } + + if (mem_ptr_virt == NULL) { /* all ioremap tries failed */ + printk("hp100: Failed to ioremap the PCI card memory. Will have to use i/o mapped mode.\n"); + local_mode = 3; + virt_memory_size = 0; + } + } + } + + if (local_mode == 3) { /* io mapped forced */ + mem_mapped = 0; + mem_ptr_phys = 0; + mem_ptr_virt = NULL; + printk("hp100: Using (slow) programmed i/o mode.\n"); + } + + /* Initialise the "private" data structure for this card. */ + lp = netdev_priv(dev); + + spin_lock_init(&lp->lock); + strlcpy(lp->id, eid, HP100_SIG_LEN); + lp->chip = chip; + lp->mode = local_mode; + lp->bus = bus; + lp->pci_dev = pci_dev; + lp->priority_tx = hp100_priority_tx; + lp->rx_ratio = hp100_rx_ratio; + lp->mem_ptr_phys = mem_ptr_phys; + lp->mem_ptr_virt = mem_ptr_virt; + hp100_page(ID_MAC_ADDR); + lp->soft_model = hp100_inb(SOFT_MODEL); + lp->mac1_mode = HP100_MAC1MODE3; + lp->mac2_mode = HP100_MAC2MODE3; + memset(&lp->hash_bytes, 0x00, 8); + + dev->base_addr = ioaddr; + + lp->memory_size = memory_size; + lp->virt_memory_size = virt_memory_size; + lp->rx_ratio = hp100_rx_ratio; /* can be conf'd with insmod */ + + if (lp->mode == 1) /* busmaster */ + dev->netdev_ops = &hp100_bm_netdev_ops; + else + dev->netdev_ops = &hp100_netdev_ops; + + /* Ask the card for which IRQ line it is configured */ + if (bus == HP100_BUS_PCI) { + dev->irq = pci_dev->irq; + } else { + hp100_page(HW_MAP); + dev->irq = hp100_inb(IRQ_CHANNEL) & HP100_IRQMASK; + if (dev->irq == 2) + dev->irq = 9; + } + + if (lp->mode == 1) /* busmaster */ + dev->dma = 4; + + /* Ask the card for its MAC address and store it for later use. */ + hp100_page(ID_MAC_ADDR); + for (i = uc = 0; i < 6; i++) + dev->dev_addr[i] = hp100_inb(LAN_ADDR + i); + + /* Reset statistics (counters) */ + hp100_clear_stats(lp, ioaddr); + + /* If busmaster mode is wanted, a dma-capable memory area is needed for + * the rx and tx PDLs + * PCI cards can access the whole PC memory. Therefore GFP_DMA is not + * needed for the allocation of the memory area. + */ + + /* TODO: We do not need this with old cards, where PDLs are stored + * in the cards shared memory area. But currently, busmaster has been + * implemented/tested only with the lassen chip anyway... */ + if (lp->mode == 1) { /* busmaster */ + dma_addr_t page_baddr; + /* Get physically continuous memory for TX & RX PDLs */ + /* Conversion to new PCI API : + * Pages are always aligned and zeroed, no need to it ourself. + * Doc says should be OK for EISA bus as well - Jean II */ + lp->page_vaddr_algn = pci_alloc_consistent(lp->pci_dev, MAX_RINGSIZE, &page_baddr); + if (!lp->page_vaddr_algn) { + err = -ENOMEM; + goto out_mem_ptr; + } + lp->whatever_offset = ((u_long) page_baddr) - ((u_long) lp->page_vaddr_algn); + +#ifdef HP100_DEBUG_BM + printk("hp100: %s: Reserved DMA memory from 0x%x to 0x%x\n", dev->name, (u_int) lp->page_vaddr_algn, (u_int) lp->page_vaddr_algn + MAX_RINGSIZE); +#endif + lp->rxrcommit = lp->txrcommit = 0; + lp->rxrhead = lp->rxrtail = &(lp->rxring[0]); + lp->txrhead = lp->txrtail = &(lp->txring[0]); + } + + /* Initialise the card. */ + /* (I'm not really sure if it's a good idea to do this during probing, but + * like this it's assured that the lan connection type can be sensed + * correctly) + */ + hp100_hwinit(dev); + + /* Try to find out which kind of LAN the card is connected to. */ + lp->lan_type = hp100_sense_lan(dev); + + /* Print out a message what about what we think we have probed. */ + printk("hp100: at 0x%x, IRQ %d, ", ioaddr, dev->irq); + switch (bus) { + case HP100_BUS_EISA: + printk("EISA"); + break; + case HP100_BUS_PCI: + printk("PCI"); + break; + default: + printk("ISA"); + break; + } + printk(" bus, %dk SRAM (rx/tx %d%%).\n", lp->memory_size >> 10, lp->rx_ratio); + + if (lp->mode == 2) { /* memory mapped */ + printk("hp100: Memory area at 0x%lx-0x%lx", mem_ptr_phys, + (mem_ptr_phys + (mem_ptr_phys > 0x100000 ? (u_long) lp->memory_size : 16 * 1024)) - 1); + if (mem_ptr_virt) + printk(" (virtual base %p)", mem_ptr_virt); + printk(".\n"); + + /* Set for info when doing ifconfig */ + dev->mem_start = mem_ptr_phys; + dev->mem_end = mem_ptr_phys + lp->memory_size; + } + + printk("hp100: "); + if (lp->lan_type != HP100_LAN_ERR) + printk("Adapter is attached to "); + switch (lp->lan_type) { + case HP100_LAN_100: + printk("100Mb/s Voice Grade AnyLAN network.\n"); + break; + case HP100_LAN_10: + printk("10Mb/s network (10baseT).\n"); + break; + case HP100_LAN_COAX: + printk("10Mb/s network (coax).\n"); + break; + default: + printk("Warning! Link down.\n"); + } + + err = register_netdev(dev); + if (err) + goto out3; + + return 0; +out3: + if (local_mode == 1) + pci_free_consistent(lp->pci_dev, MAX_RINGSIZE + 0x0f, + lp->page_vaddr_algn, + virt_to_whatever(dev, lp->page_vaddr_algn)); +out_mem_ptr: + if (mem_ptr_virt) + iounmap(mem_ptr_virt); +out2: + release_region(ioaddr, HP100_REGION_SIZE); +out1: + return err; +} + +/* This procedure puts the card into a stable init state */ +static void hp100_hwinit(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4202, TRACE); + printk("hp100: %s: hwinit\n", dev->name); +#endif + + /* Initialise the card. -------------------------------------------- */ + + /* Clear all pending Ints and disable Ints */ + hp100_page(PERFORMANCE); + hp100_outw(0xfefe, IRQ_MASK); /* mask off all ints */ + hp100_outw(0xffff, IRQ_STATUS); /* clear all pending ints */ + + hp100_outw(HP100_INT_EN | HP100_RESET_LB, OPTION_LSW); + hp100_outw(HP100_TRI_INT | HP100_SET_HB, OPTION_LSW); + + if (lp->mode == 1) { + hp100_BM_shutdown(dev); /* disables BM, puts cascade in reset */ + wait(); + } else { + hp100_outw(HP100_INT_EN | HP100_RESET_LB, OPTION_LSW); + hp100_cascade_reset(dev, 1); + hp100_page(MAC_CTRL); + hp100_andb(~(HP100_RX_EN | HP100_TX_EN), MAC_CFG_1); + } + + /* Initiate EEPROM reload */ + hp100_load_eeprom(dev, 0); + + wait(); + + /* Go into reset again. */ + hp100_cascade_reset(dev, 1); + + /* Set Option Registers to a safe state */ + hp100_outw(HP100_DEBUG_EN | + HP100_RX_HDR | + HP100_EE_EN | + HP100_BM_WRITE | + HP100_BM_READ | HP100_RESET_HB | + HP100_FAKE_INT | + HP100_INT_EN | + HP100_MEM_EN | + HP100_IO_EN | HP100_RESET_LB, OPTION_LSW); + + hp100_outw(HP100_TRI_INT | + HP100_MMAP_DIS | HP100_SET_HB, OPTION_LSW); + + hp100_outb(HP100_PRIORITY_TX | + HP100_ADV_NXT_PKT | + HP100_TX_CMD | HP100_RESET_LB, OPTION_MSW); + + /* TODO: Configure MMU for Ram Test. */ + /* TODO: Ram Test. */ + + /* Re-check if adapter is still at same i/o location */ + /* (If the base i/o in eeprom has been changed but the */ + /* registers had not been changed, a reload of the eeprom */ + /* would move the adapter to the address stored in eeprom */ + + /* TODO: Code to implement. */ + + /* Until here it was code from HWdiscover procedure. */ + /* Next comes code from mmuinit procedure of SCO BM driver which is + * called from HWconfigure in the SCO driver. */ + + /* Initialise MMU, eventually switch on Busmaster Mode, initialise + * multicast filter... + */ + hp100_mmuinit(dev); + + /* We don't turn the interrupts on here - this is done by start_interface. */ + wait(); /* TODO: Do we really need this? */ + + /* Enable Hardware (e.g. unreset) */ + hp100_cascade_reset(dev, 0); + + /* ------- initialisation complete ----------- */ + + /* Finally try to log in the Hub if there may be a VG connection. */ + if ((lp->lan_type == HP100_LAN_100) || (lp->lan_type == HP100_LAN_ERR)) + hp100_login_to_vg_hub(dev, 0); /* relogin */ + +} + + +/* + * mmuinit - Reinitialise Cascade MMU and MAC settings. + * Note: Must already be in reset and leaves card in reset. + */ +static void hp100_mmuinit(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + int i; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4203, TRACE); + printk("hp100: %s: mmuinit\n", dev->name); +#endif + +#ifdef HP100_DEBUG + if (0 != (hp100_inw(OPTION_LSW) & HP100_HW_RST)) { + printk("hp100: %s: Not in reset when entering mmuinit. Fix me.\n", dev->name); + return; + } +#endif + + /* Make sure IRQs are masked off and ack'ed. */ + hp100_page(PERFORMANCE); + hp100_outw(0xfefe, IRQ_MASK); /* mask off all ints */ + hp100_outw(0xffff, IRQ_STATUS); /* ack IRQ */ + + /* + * Enable Hardware + * - Clear Debug En, Rx Hdr Pipe, EE En, I/O En, Fake Int and Intr En + * - Set Tri-State Int, Bus Master Rd/Wr, and Mem Map Disable + * - Clear Priority, Advance Pkt and Xmit Cmd + */ + + hp100_outw(HP100_DEBUG_EN | + HP100_RX_HDR | + HP100_EE_EN | HP100_RESET_HB | + HP100_IO_EN | + HP100_FAKE_INT | + HP100_INT_EN | HP100_RESET_LB, OPTION_LSW); + + hp100_outw(HP100_TRI_INT | HP100_SET_HB, OPTION_LSW); + + if (lp->mode == 1) { /* busmaster */ + hp100_outw(HP100_BM_WRITE | + HP100_BM_READ | + HP100_MMAP_DIS | HP100_SET_HB, OPTION_LSW); + } else if (lp->mode == 2) { /* memory mapped */ + hp100_outw(HP100_BM_WRITE | + HP100_BM_READ | HP100_RESET_HB, OPTION_LSW); + hp100_outw(HP100_MMAP_DIS | HP100_RESET_HB, OPTION_LSW); + hp100_outw(HP100_MEM_EN | HP100_SET_LB, OPTION_LSW); + hp100_outw(HP100_IO_EN | HP100_SET_LB, OPTION_LSW); + } else if (lp->mode == 3) { /* i/o mapped mode */ + hp100_outw(HP100_MMAP_DIS | HP100_SET_HB | + HP100_IO_EN | HP100_SET_LB, OPTION_LSW); + } + + hp100_page(HW_MAP); + hp100_outb(0, EARLYRXCFG); + hp100_outw(0, EARLYTXCFG); + + /* + * Enable Bus Master mode + */ + if (lp->mode == 1) { /* busmaster */ + /* Experimental: Set some PCI configuration bits */ + hp100_page(HW_MAP); + hp100_andb(~HP100_PDL_USE3, MODECTRL1); /* BM engine read maximum */ + hp100_andb(~HP100_TX_DUALQ, MODECTRL1); /* No Queue for Priority TX */ + + /* PCI Bus failures should result in a Misc. Interrupt */ + hp100_orb(HP100_EN_BUS_FAIL, MODECTRL2); + + hp100_outw(HP100_BM_READ | HP100_BM_WRITE | HP100_SET_HB, OPTION_LSW); + hp100_page(HW_MAP); + /* Use Burst Mode and switch on PAGE_CK */ + hp100_orb(HP100_BM_BURST_RD | HP100_BM_BURST_WR, BM); + if ((lp->chip == HP100_CHIPID_RAINIER) || (lp->chip == HP100_CHIPID_SHASTA)) + hp100_orb(HP100_BM_PAGE_CK, BM); + hp100_orb(HP100_BM_MASTER, BM); + } else { /* not busmaster */ + + hp100_page(HW_MAP); + hp100_andb(~HP100_BM_MASTER, BM); + } + + /* + * Divide card memory into regions for Rx, Tx and, if non-ETR chip, PDLs + */ + hp100_page(MMU_CFG); + if (lp->mode == 1) { /* only needed for Busmaster */ + int xmit_stop, recv_stop; + + if ((lp->chip == HP100_CHIPID_RAINIER) || + (lp->chip == HP100_CHIPID_SHASTA)) { + int pdl_stop; + + /* + * Each pdl is 508 bytes long. (63 frags * 4 bytes for address and + * 4 bytes for header). We will leave NUM_RXPDLS * 508 (rounded + * to the next higher 1k boundary) bytes for the rx-pdl's + * Note: For non-etr chips the transmit stop register must be + * programmed on a 1k boundary, i.e. bits 9:0 must be zero. + */ + pdl_stop = lp->memory_size; + xmit_stop = (pdl_stop - 508 * (MAX_RX_PDL) - 16) & ~(0x03ff); + recv_stop = (xmit_stop * (lp->rx_ratio) / 100) & ~(0x03ff); + hp100_outw((pdl_stop >> 4) - 1, PDL_MEM_STOP); +#ifdef HP100_DEBUG_BM + printk("hp100: %s: PDL_STOP = 0x%x\n", dev->name, pdl_stop); +#endif + } else { + /* ETR chip (Lassen) in busmaster mode */ + xmit_stop = (lp->memory_size) - 1; + recv_stop = ((lp->memory_size * lp->rx_ratio) / 100) & ~(0x03ff); + } + + hp100_outw(xmit_stop >> 4, TX_MEM_STOP); + hp100_outw(recv_stop >> 4, RX_MEM_STOP); +#ifdef HP100_DEBUG_BM + printk("hp100: %s: TX_STOP = 0x%x\n", dev->name, xmit_stop >> 4); + printk("hp100: %s: RX_STOP = 0x%x\n", dev->name, recv_stop >> 4); +#endif + } else { + /* Slave modes (memory mapped and programmed io) */ + hp100_outw((((lp->memory_size * lp->rx_ratio) / 100) >> 4), RX_MEM_STOP); + hp100_outw(((lp->memory_size - 1) >> 4), TX_MEM_STOP); +#ifdef HP100_DEBUG + printk("hp100: %s: TX_MEM_STOP: 0x%x\n", dev->name, hp100_inw(TX_MEM_STOP)); + printk("hp100: %s: RX_MEM_STOP: 0x%x\n", dev->name, hp100_inw(RX_MEM_STOP)); +#endif + } + + /* Write MAC address into page 1 */ + hp100_page(MAC_ADDRESS); + for (i = 0; i < 6; i++) + hp100_outb(dev->dev_addr[i], MAC_ADDR + i); + + /* Zero the multicast hash registers */ + for (i = 0; i < 8; i++) + hp100_outb(0x0, HASH_BYTE0 + i); + + /* Set up MAC defaults */ + hp100_page(MAC_CTRL); + + /* Go to LAN Page and zero all filter bits */ + /* Zero accept error, accept multicast, accept broadcast and accept */ + /* all directed packet bits */ + hp100_andb(~(HP100_RX_EN | + HP100_TX_EN | + HP100_ACC_ERRORED | + HP100_ACC_MC | + HP100_ACC_BC | HP100_ACC_PHY), MAC_CFG_1); + + hp100_outb(0x00, MAC_CFG_2); + + /* Zero the frame format bit. This works around a training bug in the */ + /* new hubs. */ + hp100_outb(0x00, VG_LAN_CFG_2); /* (use 802.3) */ + + if (lp->priority_tx) + hp100_outb(HP100_PRIORITY_TX | HP100_SET_LB, OPTION_MSW); + else + hp100_outb(HP100_PRIORITY_TX | HP100_RESET_LB, OPTION_MSW); + + hp100_outb(HP100_ADV_NXT_PKT | + HP100_TX_CMD | HP100_RESET_LB, OPTION_MSW); + + /* If busmaster, initialize the PDLs */ + if (lp->mode == 1) + hp100_init_pdls(dev); + + /* Go to performance page and initialize isr and imr registers */ + hp100_page(PERFORMANCE); + hp100_outw(0xfefe, IRQ_MASK); /* mask off all ints */ + hp100_outw(0xffff, IRQ_STATUS); /* ack IRQ */ +} + +/* + * open/close functions + */ + +static int hp100_open(struct net_device *dev) +{ + struct hp100_private *lp = netdev_priv(dev); +#ifdef HP100_DEBUG_B + int ioaddr = dev->base_addr; +#endif + +#ifdef HP100_DEBUG_B + hp100_outw(0x4204, TRACE); + printk("hp100: %s: open\n", dev->name); +#endif + + /* New: if bus is PCI or EISA, interrupts might be shared interrupts */ + if (request_irq(dev->irq, hp100_interrupt, + lp->bus == HP100_BUS_PCI || lp->bus == + HP100_BUS_EISA ? IRQF_SHARED : 0, + dev->name, dev)) { + printk("hp100: %s: unable to get IRQ %d\n", dev->name, dev->irq); + return -EAGAIN; + } + + dev->trans_start = jiffies; /* prevent tx timeout */ + netif_start_queue(dev); + + lp->lan_type = hp100_sense_lan(dev); + lp->mac1_mode = HP100_MAC1MODE3; + lp->mac2_mode = HP100_MAC2MODE3; + memset(&lp->hash_bytes, 0x00, 8); + + hp100_stop_interface(dev); + + hp100_hwinit(dev); + + hp100_start_interface(dev); /* sets mac modes, enables interrupts */ + + return 0; +} + +/* The close function is called when the interface is to be brought down */ +static int hp100_close(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4205, TRACE); + printk("hp100: %s: close\n", dev->name); +#endif + + hp100_page(PERFORMANCE); + hp100_outw(0xfefe, IRQ_MASK); /* mask off all IRQs */ + + hp100_stop_interface(dev); + + if (lp->lan_type == HP100_LAN_100) + lp->hub_status = hp100_login_to_vg_hub(dev, 0); + + netif_stop_queue(dev); + + free_irq(dev->irq, dev); + +#ifdef HP100_DEBUG + printk("hp100: %s: close LSW = 0x%x\n", dev->name, + hp100_inw(OPTION_LSW)); +#endif + + return 0; +} + + +/* + * Configure the PDL Rx rings and LAN + */ +static void hp100_init_pdls(struct net_device *dev) +{ + struct hp100_private *lp = netdev_priv(dev); + hp100_ring_t *ringptr; + u_int *pageptr; /* Warning : increment by 4 - Jean II */ + int i; + +#ifdef HP100_DEBUG_B + int ioaddr = dev->base_addr; +#endif + +#ifdef HP100_DEBUG_B + hp100_outw(0x4206, TRACE); + printk("hp100: %s: init pdls\n", dev->name); +#endif + + if (!lp->page_vaddr_algn) + printk("hp100: %s: Warning: lp->page_vaddr_algn not initialised!\n", dev->name); + else { + /* pageptr shall point into the DMA accessible memory region */ + /* we use this pointer to status the upper limit of allocated */ + /* memory in the allocated page. */ + /* note: align the pointers to the pci cache line size */ + memset(lp->page_vaddr_algn, 0, MAX_RINGSIZE); /* Zero Rx/Tx ring page */ + pageptr = lp->page_vaddr_algn; + + lp->rxrcommit = 0; + ringptr = lp->rxrhead = lp->rxrtail = &(lp->rxring[0]); + + /* Initialise Rx Ring */ + for (i = MAX_RX_PDL - 1; i >= 0; i--) { + lp->rxring[i].next = ringptr; + ringptr = &(lp->rxring[i]); + pageptr += hp100_init_rxpdl(dev, ringptr, pageptr); + } + + /* Initialise Tx Ring */ + lp->txrcommit = 0; + ringptr = lp->txrhead = lp->txrtail = &(lp->txring[0]); + for (i = MAX_TX_PDL - 1; i >= 0; i--) { + lp->txring[i].next = ringptr; + ringptr = &(lp->txring[i]); + pageptr += hp100_init_txpdl(dev, ringptr, pageptr); + } + } +} + + +/* These functions "format" the entries in the pdl structure */ +/* They return how much memory the fragments need. */ +static int hp100_init_rxpdl(struct net_device *dev, + register hp100_ring_t * ringptr, + register u32 * pdlptr) +{ + /* pdlptr is starting address for this pdl */ + + if (0 != (((unsigned long) pdlptr) & 0xf)) + printk("hp100: %s: Init rxpdl: Unaligned pdlptr 0x%lx.\n", + dev->name, (unsigned long) pdlptr); + + ringptr->pdl = pdlptr + 1; + ringptr->pdl_paddr = virt_to_whatever(dev, pdlptr + 1); + ringptr->skb = NULL; + + /* + * Write address and length of first PDL Fragment (which is used for + * storing the RX-Header + * We use the 4 bytes _before_ the PDH in the pdl memory area to + * store this information. (PDH is at offset 0x04) + */ + /* Note that pdlptr+1 and not pdlptr is the pointer to the PDH */ + + *(pdlptr + 2) = (u_int) virt_to_whatever(dev, pdlptr); /* Address Frag 1 */ + *(pdlptr + 3) = 4; /* Length Frag 1 */ + + return roundup(MAX_RX_FRAG * 2 + 2, 4); +} + + +static int hp100_init_txpdl(struct net_device *dev, + register hp100_ring_t * ringptr, + register u32 * pdlptr) +{ + if (0 != (((unsigned long) pdlptr) & 0xf)) + printk("hp100: %s: Init txpdl: Unaligned pdlptr 0x%lx.\n", dev->name, (unsigned long) pdlptr); + + ringptr->pdl = pdlptr; /* +1; */ + ringptr->pdl_paddr = virt_to_whatever(dev, pdlptr); /* +1 */ + ringptr->skb = NULL; + + return roundup(MAX_TX_FRAG * 2 + 2, 4); +} + +/* + * hp100_build_rx_pdl allocates an skb_buff of maximum size plus two bytes + * for possible odd word alignment rounding up to next dword and set PDL + * address for fragment#2 + * Returns: 0 if unable to allocate skb_buff + * 1 if successful + */ +static int hp100_build_rx_pdl(hp100_ring_t * ringptr, + struct net_device *dev) +{ +#ifdef HP100_DEBUG_B + int ioaddr = dev->base_addr; +#endif +#ifdef HP100_DEBUG_BM + u_int *p; +#endif + +#ifdef HP100_DEBUG_B + hp100_outw(0x4207, TRACE); + printk("hp100: %s: build rx pdl\n", dev->name); +#endif + + /* Allocate skb buffer of maximum size */ + /* Note: This depends on the alloc_skb functions allocating more + * space than requested, i.e. aligning to 16bytes */ + + ringptr->skb = netdev_alloc_skb(dev, roundup(MAX_ETHER_SIZE + 2, 4)); + + if (NULL != ringptr->skb) { + /* + * Reserve 2 bytes at the head of the buffer to land the IP header + * on a long word boundary (According to the Network Driver section + * in the Linux KHG, this should help to increase performance.) + */ + skb_reserve(ringptr->skb, 2); + + ringptr->skb->data = (u_char *) skb_put(ringptr->skb, MAX_ETHER_SIZE); + + /* ringptr->pdl points to the beginning of the PDL, i.e. the PDH */ + /* Note: 1st Fragment is used for the 4 byte packet status + * (receive header). Its PDL entries are set up by init_rxpdl. So + * here we only have to set up the PDL fragment entries for the data + * part. Those 4 bytes will be stored in the DMA memory region + * directly before the PDL. + */ +#ifdef HP100_DEBUG_BM + printk("hp100: %s: build_rx_pdl: PDH@0x%x, skb->data (len %d) at 0x%x\n", + dev->name, (u_int) ringptr->pdl, + roundup(MAX_ETHER_SIZE + 2, 4), + (unsigned int) ringptr->skb->data); +#endif + + /* Conversion to new PCI API : map skbuf data to PCI bus. + * Doc says it's OK for EISA as well - Jean II */ + ringptr->pdl[0] = 0x00020000; /* Write PDH */ + ringptr->pdl[3] = pdl_map_data(netdev_priv(dev), + ringptr->skb->data); + ringptr->pdl[4] = MAX_ETHER_SIZE; /* Length of Data */ + +#ifdef HP100_DEBUG_BM + for (p = (ringptr->pdl); p < (ringptr->pdl + 5); p++) + printk("hp100: %s: Adr 0x%.8x = 0x%.8x\n", dev->name, (u_int) p, (u_int) * p); +#endif + return 1; + } + /* else: */ + /* alloc_skb failed (no memory) -> still can receive the header + * fragment into PDL memory. make PDL safe by clearing msgptr and + * making the PDL only 1 fragment (i.e. the 4 byte packet status) + */ +#ifdef HP100_DEBUG_BM + printk("hp100: %s: build_rx_pdl: PDH@0x%x, No space for skb.\n", dev->name, (u_int) ringptr->pdl); +#endif + + ringptr->pdl[0] = 0x00010000; /* PDH: Count=1 Fragment */ + + return 0; +} + +/* + * hp100_rxfill - attempt to fill the Rx Ring will empty skb's + * + * Makes assumption that skb's are always contiguous memory areas and + * therefore PDLs contain only 2 physical fragments. + * - While the number of Rx PDLs with buffers is less than maximum + * a. Get a maximum packet size skb + * b. Put the physical address of the buffer into the PDL. + * c. Output physical address of PDL to adapter. + */ +static void hp100_rxfill(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + + struct hp100_private *lp = netdev_priv(dev); + hp100_ring_t *ringptr; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4208, TRACE); + printk("hp100: %s: rxfill\n", dev->name); +#endif + + hp100_page(PERFORMANCE); + + while (lp->rxrcommit < MAX_RX_PDL) { + /* + ** Attempt to get a buffer and build a Rx PDL. + */ + ringptr = lp->rxrtail; + if (0 == hp100_build_rx_pdl(ringptr, dev)) { + return; /* None available, return */ + } + + /* Hand this PDL over to the card */ + /* Note: This needs performance page selected! */ +#ifdef HP100_DEBUG_BM + printk("hp100: %s: rxfill: Hand to card: pdl #%d @0x%x phys:0x%x, buffer: 0x%x\n", + dev->name, lp->rxrcommit, (u_int) ringptr->pdl, + (u_int) ringptr->pdl_paddr, (u_int) ringptr->pdl[3]); +#endif + + hp100_outl((u32) ringptr->pdl_paddr, RX_PDA); + + lp->rxrcommit += 1; + lp->rxrtail = ringptr->next; + } +} + +/* + * BM_shutdown - shutdown bus mastering and leave chip in reset state + */ + +static void hp100_BM_shutdown(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + unsigned long time; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4209, TRACE); + printk("hp100: %s: bm shutdown\n", dev->name); +#endif + + hp100_page(PERFORMANCE); + hp100_outw(0xfefe, IRQ_MASK); /* mask off all ints */ + hp100_outw(0xffff, IRQ_STATUS); /* Ack all ints */ + + /* Ensure Interrupts are off */ + hp100_outw(HP100_INT_EN | HP100_RESET_LB, OPTION_LSW); + + /* Disable all MAC activity */ + hp100_page(MAC_CTRL); + hp100_andb(~(HP100_RX_EN | HP100_TX_EN), MAC_CFG_1); /* stop rx/tx */ + + /* If cascade MMU is not already in reset */ + if (0 != (hp100_inw(OPTION_LSW) & HP100_HW_RST)) { + /* Wait 1.3ms (10Mb max packet time) to ensure MAC is idle so + * MMU pointers will not be reset out from underneath + */ + hp100_page(MAC_CTRL); + for (time = 0; time < 5000; time++) { + if ((hp100_inb(MAC_CFG_1) & (HP100_TX_IDLE | HP100_RX_IDLE)) == (HP100_TX_IDLE | HP100_RX_IDLE)) + break; + } + + /* Shutdown algorithm depends on the generation of Cascade */ + if (lp->chip == HP100_CHIPID_LASSEN) { /* ETR shutdown/reset */ + /* Disable Busmaster mode and wait for bit to go to zero. */ + hp100_page(HW_MAP); + hp100_andb(~HP100_BM_MASTER, BM); + /* 100 ms timeout */ + for (time = 0; time < 32000; time++) { + if (0 == (hp100_inb(BM) & HP100_BM_MASTER)) + break; + } + } else { /* Shasta or Rainier Shutdown/Reset */ + /* To ensure all bus master inloading activity has ceased, + * wait for no Rx PDAs or no Rx packets on card. + */ + hp100_page(PERFORMANCE); + /* 100 ms timeout */ + for (time = 0; time < 10000; time++) { + /* RX_PDL: PDLs not executed. */ + /* RX_PKT_CNT: RX'd packets on card. */ + if ((hp100_inb(RX_PDL) == 0) && (hp100_inb(RX_PKT_CNT) == 0)) + break; + } + + if (time >= 10000) + printk("hp100: %s: BM shutdown error.\n", dev->name); + + /* To ensure all bus master outloading activity has ceased, + * wait until the Tx PDA count goes to zero or no more Tx space + * available in the Tx region of the card. + */ + /* 100 ms timeout */ + for (time = 0; time < 10000; time++) { + if ((0 == hp100_inb(TX_PKT_CNT)) && + (0 != (hp100_inb(TX_MEM_FREE) & HP100_AUTO_COMPARE))) + break; + } + + /* Disable Busmaster mode */ + hp100_page(HW_MAP); + hp100_andb(~HP100_BM_MASTER, BM); + } /* end of shutdown procedure for non-etr parts */ + + hp100_cascade_reset(dev, 1); + } + hp100_page(PERFORMANCE); + /* hp100_outw( HP100_BM_READ | HP100_BM_WRITE | HP100_RESET_HB, OPTION_LSW ); */ + /* Busmaster mode should be shut down now. */ +} + +static int hp100_check_lan(struct net_device *dev) +{ + struct hp100_private *lp = netdev_priv(dev); + + if (lp->lan_type < 0) { /* no LAN type detected yet? */ + hp100_stop_interface(dev); + if ((lp->lan_type = hp100_sense_lan(dev)) < 0) { + printk("hp100: %s: no connection found - check wire\n", dev->name); + hp100_start_interface(dev); /* 10Mb/s RX packets maybe handled */ + return -EIO; + } + if (lp->lan_type == HP100_LAN_100) + lp->hub_status = hp100_login_to_vg_hub(dev, 0); /* relogin */ + hp100_start_interface(dev); + } + return 0; +} + +/* + * transmit functions + */ + +/* tx function for busmaster mode */ +static netdev_tx_t hp100_start_xmit_bm(struct sk_buff *skb, + struct net_device *dev) +{ + unsigned long flags; + int i, ok_flag; + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + hp100_ring_t *ringptr; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4210, TRACE); + printk("hp100: %s: start_xmit_bm\n", dev->name); +#endif + if (skb->len <= 0) + goto drop; + + if (lp->chip == HP100_CHIPID_SHASTA && skb_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + + /* Get Tx ring tail pointer */ + if (lp->txrtail->next == lp->txrhead) { + /* No memory. */ +#ifdef HP100_DEBUG + printk("hp100: %s: start_xmit_bm: No TX PDL available.\n", dev->name); +#endif + /* not waited long enough since last tx? */ + if (time_before(jiffies, dev_trans_start(dev) + HZ)) + goto drop; + + if (hp100_check_lan(dev)) + goto drop; + + if (lp->lan_type == HP100_LAN_100 && lp->hub_status < 0) { + /* we have a 100Mb/s adapter but it isn't connected to hub */ + printk("hp100: %s: login to 100Mb/s hub retry\n", dev->name); + hp100_stop_interface(dev); + lp->hub_status = hp100_login_to_vg_hub(dev, 0); + hp100_start_interface(dev); + } else { + spin_lock_irqsave(&lp->lock, flags); + hp100_ints_off(); /* Useful ? Jean II */ + i = hp100_sense_lan(dev); + hp100_ints_on(); + spin_unlock_irqrestore(&lp->lock, flags); + if (i == HP100_LAN_ERR) + printk("hp100: %s: link down detected\n", dev->name); + else if (lp->lan_type != i) { /* cable change! */ + /* it's very hard - all network settings must be changed!!! */ + printk("hp100: %s: cable change 10Mb/s <-> 100Mb/s detected\n", dev->name); + lp->lan_type = i; + hp100_stop_interface(dev); + if (lp->lan_type == HP100_LAN_100) + lp->hub_status = hp100_login_to_vg_hub(dev, 0); + hp100_start_interface(dev); + } else { + printk("hp100: %s: interface reset\n", dev->name); + hp100_stop_interface(dev); + if (lp->lan_type == HP100_LAN_100) + lp->hub_status = hp100_login_to_vg_hub(dev, 0); + hp100_start_interface(dev); + } + } + + goto drop; + } + + /* + * we have to turn int's off before modifying this, otherwise + * a tx_pdl_cleanup could occur at the same time + */ + spin_lock_irqsave(&lp->lock, flags); + ringptr = lp->txrtail; + lp->txrtail = ringptr->next; + + /* Check whether packet has minimal packet size */ + ok_flag = skb->len >= HP100_MIN_PACKET_SIZE; + i = ok_flag ? skb->len : HP100_MIN_PACKET_SIZE; + + ringptr->skb = skb; + ringptr->pdl[0] = ((1 << 16) | i); /* PDH: 1 Fragment & length */ + if (lp->chip == HP100_CHIPID_SHASTA) { + /* TODO:Could someone who has the EISA card please check if this works? */ + ringptr->pdl[2] = i; + } else { /* Lassen */ + /* In the PDL, don't use the padded size but the real packet size: */ + ringptr->pdl[2] = skb->len; /* 1st Frag: Length of frag */ + } + /* Conversion to new PCI API : map skbuf data to PCI bus. + * Doc says it's OK for EISA as well - Jean II */ + ringptr->pdl[1] = ((u32) pci_map_single(lp->pci_dev, skb->data, ringptr->pdl[2], PCI_DMA_TODEVICE)); /* 1st Frag: Adr. of data */ + + /* Hand this PDL to the card. */ + hp100_outl(ringptr->pdl_paddr, TX_PDA_L); /* Low Prio. Queue */ + + lp->txrcommit++; + + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + + spin_unlock_irqrestore(&lp->lock, flags); + + return NETDEV_TX_OK; + +drop: + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + + +/* clean_txring checks if packets have been sent by the card by reading + * the TX_PDL register from the performance page and comparing it to the + * number of committed packets. It then frees the skb's of the packets that + * obviously have been sent to the network. + * + * Needs the PERFORMANCE page selected. + */ +static void hp100_clean_txring(struct net_device *dev) +{ + struct hp100_private *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + int donecount; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4211, TRACE); + printk("hp100: %s: clean txring\n", dev->name); +#endif + + /* How many PDLs have been transmitted? */ + donecount = (lp->txrcommit) - hp100_inb(TX_PDL); + +#ifdef HP100_DEBUG + if (donecount > MAX_TX_PDL) + printk("hp100: %s: Warning: More PDLs transmitted than committed to card???\n", dev->name); +#endif + + for (; 0 != donecount; donecount--) { +#ifdef HP100_DEBUG_BM + printk("hp100: %s: Free skb: data @0x%.8x txrcommit=0x%x TXPDL=0x%x, done=0x%x\n", + dev->name, (u_int) lp->txrhead->skb->data, + lp->txrcommit, hp100_inb(TX_PDL), donecount); +#endif + /* Conversion to new PCI API : NOP */ + pci_unmap_single(lp->pci_dev, (dma_addr_t) lp->txrhead->pdl[1], lp->txrhead->pdl[2], PCI_DMA_TODEVICE); + dev_consume_skb_any(lp->txrhead->skb); + lp->txrhead->skb = NULL; + lp->txrhead = lp->txrhead->next; + lp->txrcommit--; + } +} + +/* tx function for slave modes */ +static netdev_tx_t hp100_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + unsigned long flags; + int i, ok_flag; + int ioaddr = dev->base_addr; + u_short val; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4212, TRACE); + printk("hp100: %s: start_xmit\n", dev->name); +#endif + if (skb->len <= 0) + goto drop; + + if (hp100_check_lan(dev)) + goto drop; + + /* If there is not enough free memory on the card... */ + i = hp100_inl(TX_MEM_FREE) & 0x7fffffff; + if (!(((i / 2) - 539) > (skb->len + 16) && (hp100_inb(TX_PKT_CNT) < 255))) { +#ifdef HP100_DEBUG + printk("hp100: %s: start_xmit: tx free mem = 0x%x\n", dev->name, i); +#endif + /* not waited long enough since last failed tx try? */ + if (time_before(jiffies, dev_trans_start(dev) + HZ)) { +#ifdef HP100_DEBUG + printk("hp100: %s: trans_start timing problem\n", + dev->name); +#endif + goto drop; + } + if (lp->lan_type == HP100_LAN_100 && lp->hub_status < 0) { + /* we have a 100Mb/s adapter but it isn't connected to hub */ + printk("hp100: %s: login to 100Mb/s hub retry\n", dev->name); + hp100_stop_interface(dev); + lp->hub_status = hp100_login_to_vg_hub(dev, 0); + hp100_start_interface(dev); + } else { + spin_lock_irqsave(&lp->lock, flags); + hp100_ints_off(); /* Useful ? Jean II */ + i = hp100_sense_lan(dev); + hp100_ints_on(); + spin_unlock_irqrestore(&lp->lock, flags); + if (i == HP100_LAN_ERR) + printk("hp100: %s: link down detected\n", dev->name); + else if (lp->lan_type != i) { /* cable change! */ + /* it's very hard - all network setting must be changed!!! */ + printk("hp100: %s: cable change 10Mb/s <-> 100Mb/s detected\n", dev->name); + lp->lan_type = i; + hp100_stop_interface(dev); + if (lp->lan_type == HP100_LAN_100) + lp->hub_status = hp100_login_to_vg_hub(dev, 0); + hp100_start_interface(dev); + } else { + printk("hp100: %s: interface reset\n", dev->name); + hp100_stop_interface(dev); + if (lp->lan_type == HP100_LAN_100) + lp->hub_status = hp100_login_to_vg_hub(dev, 0); + hp100_start_interface(dev); + mdelay(1); + } + } + goto drop; + } + + for (i = 0; i < 6000 && (hp100_inb(OPTION_MSW) & HP100_TX_CMD); i++) { +#ifdef HP100_DEBUG_TX + printk("hp100: %s: start_xmit: busy\n", dev->name); +#endif + } + + spin_lock_irqsave(&lp->lock, flags); + hp100_ints_off(); + val = hp100_inw(IRQ_STATUS); + /* Ack / clear the interrupt TX_COMPLETE interrupt - this interrupt is set + * when the current packet being transmitted on the wire is completed. */ + hp100_outw(HP100_TX_COMPLETE, IRQ_STATUS); +#ifdef HP100_DEBUG_TX + printk("hp100: %s: start_xmit: irq_status=0x%.4x, irqmask=0x%.4x, len=%d\n", + dev->name, val, hp100_inw(IRQ_MASK), (int) skb->len); +#endif + + ok_flag = skb->len >= HP100_MIN_PACKET_SIZE; + i = ok_flag ? skb->len : HP100_MIN_PACKET_SIZE; + + hp100_outw(i, DATA32); /* tell card the total packet length */ + hp100_outw(i, FRAGMENT_LEN); /* and first/only fragment length */ + + if (lp->mode == 2) { /* memory mapped */ + /* Note: The J2585B needs alignment to 32bits here! */ + memcpy_toio(lp->mem_ptr_virt, skb->data, (skb->len + 3) & ~3); + if (!ok_flag) + memset_io(lp->mem_ptr_virt, 0, HP100_MIN_PACKET_SIZE - skb->len); + } else { /* programmed i/o */ + outsl(ioaddr + HP100_REG_DATA32, skb->data, + (skb->len + 3) >> 2); + if (!ok_flag) + for (i = (skb->len + 3) & ~3; i < HP100_MIN_PACKET_SIZE; i += 4) + hp100_outl(0, DATA32); + } + + hp100_outb(HP100_TX_CMD | HP100_SET_LB, OPTION_MSW); /* send packet */ + + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + hp100_ints_on(); + spin_unlock_irqrestore(&lp->lock, flags); + + dev_consume_skb_any(skb); + +#ifdef HP100_DEBUG_TX + printk("hp100: %s: start_xmit: end\n", dev->name); +#endif + + return NETDEV_TX_OK; + +drop: + dev_kfree_skb(skb); + return NETDEV_TX_OK; + +} + + +/* + * Receive Function (Non-Busmaster mode) + * Called when an "Receive Packet" interrupt occurs, i.e. the receive + * packet counter is non-zero. + * For non-busmaster, this function does the whole work of transferring + * the packet to the host memory and then up to higher layers via skb + * and netif_rx. + */ + +static void hp100_rx(struct net_device *dev) +{ + int packets, pkt_len; + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + u_int header; + struct sk_buff *skb; + +#ifdef DEBUG_B + hp100_outw(0x4213, TRACE); + printk("hp100: %s: rx\n", dev->name); +#endif + + /* First get indication of received lan packet */ + /* RX_PKT_CND indicates the number of packets which have been fully */ + /* received onto the card but have not been fully transferred of the card */ + packets = hp100_inb(RX_PKT_CNT); +#ifdef HP100_DEBUG_RX + if (packets > 1) + printk("hp100: %s: rx: waiting packets = %d\n", dev->name, packets); +#endif + + while (packets-- > 0) { + /* If ADV_NXT_PKT is still set, we have to wait until the card has */ + /* really advanced to the next packet. */ + for (pkt_len = 0; pkt_len < 6000 && (hp100_inb(OPTION_MSW) & HP100_ADV_NXT_PKT); pkt_len++) { +#ifdef HP100_DEBUG_RX + printk ("hp100: %s: rx: busy, remaining packets = %d\n", dev->name, packets); +#endif + } + + /* First we get the header, which contains information about the */ + /* actual length of the received packet. */ + if (lp->mode == 2) { /* memory mapped mode */ + header = readl(lp->mem_ptr_virt); + } else /* programmed i/o */ + header = hp100_inl(DATA32); + + pkt_len = ((header & HP100_PKT_LEN_MASK) + 3) & ~3; + +#ifdef HP100_DEBUG_RX + printk("hp100: %s: rx: new packet - length=%d, errors=0x%x, dest=0x%x\n", + dev->name, header & HP100_PKT_LEN_MASK, + (header >> 16) & 0xfff8, (header >> 16) & 7); +#endif + + /* Now we allocate the skb and transfer the data into it. */ + skb = netdev_alloc_skb(dev, pkt_len + 2); + if (skb == NULL) { /* Not enough memory->drop packet */ +#ifdef HP100_DEBUG + printk("hp100: %s: rx: couldn't allocate a sk_buff of size %d\n", + dev->name, pkt_len); +#endif + dev->stats.rx_dropped++; + } else { /* skb successfully allocated */ + + u_char *ptr; + + skb_reserve(skb,2); + + /* ptr to start of the sk_buff data area */ + skb_put(skb, pkt_len); + ptr = skb->data; + + /* Now transfer the data from the card into that area */ + if (lp->mode == 2) + memcpy_fromio(ptr, lp->mem_ptr_virt,pkt_len); + else /* io mapped */ + insl(ioaddr + HP100_REG_DATA32, ptr, pkt_len >> 2); + + skb->protocol = eth_type_trans(skb, dev); + +#ifdef HP100_DEBUG_RX + printk("hp100: %s: rx: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + dev->name, ptr[0], ptr[1], ptr[2], ptr[3], + ptr[4], ptr[5], ptr[6], ptr[7], ptr[8], + ptr[9], ptr[10], ptr[11]); +#endif + netif_rx(skb); + dev->stats.rx_packets++; + dev->stats.rx_bytes += pkt_len; + } + + /* Indicate the card that we have got the packet */ + hp100_outb(HP100_ADV_NXT_PKT | HP100_SET_LB, OPTION_MSW); + + switch (header & 0x00070000) { + case (HP100_MULTI_ADDR_HASH << 16): + case (HP100_MULTI_ADDR_NO_HASH << 16): + dev->stats.multicast++; + break; + } + } /* end of while(there are packets) loop */ +#ifdef HP100_DEBUG_RX + printk("hp100_rx: %s: end\n", dev->name); +#endif +} + +/* + * Receive Function for Busmaster Mode + */ +static void hp100_rx_bm(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + hp100_ring_t *ptr; + u_int header; + int pkt_len; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4214, TRACE); + printk("hp100: %s: rx_bm\n", dev->name); +#endif + +#ifdef HP100_DEBUG + if (0 == lp->rxrcommit) { + printk("hp100: %s: rx_bm called although no PDLs were committed to adapter?\n", dev->name); + return; + } else + /* RX_PKT_CNT states how many PDLs are currently formatted and available to + * the cards BM engine */ + if ((hp100_inw(RX_PKT_CNT) & 0x00ff) >= lp->rxrcommit) { + printk("hp100: %s: More packets received than committed? RX_PKT_CNT=0x%x, commit=0x%x\n", + dev->name, hp100_inw(RX_PKT_CNT) & 0x00ff, + lp->rxrcommit); + return; + } +#endif + + while ((lp->rxrcommit > hp100_inb(RX_PDL))) { + /* + * The packet was received into the pdl pointed to by lp->rxrhead ( + * the oldest pdl in the ring + */ + + /* First we get the header, which contains information about the */ + /* actual length of the received packet. */ + + ptr = lp->rxrhead; + + header = *(ptr->pdl - 1); + pkt_len = (header & HP100_PKT_LEN_MASK); + + /* Conversion to new PCI API : NOP */ + pci_unmap_single(lp->pci_dev, (dma_addr_t) ptr->pdl[3], MAX_ETHER_SIZE, PCI_DMA_FROMDEVICE); + +#ifdef HP100_DEBUG_BM + printk("hp100: %s: rx_bm: header@0x%x=0x%x length=%d, errors=0x%x, dest=0x%x\n", + dev->name, (u_int) (ptr->pdl - 1), (u_int) header, + pkt_len, (header >> 16) & 0xfff8, (header >> 16) & 7); + printk("hp100: %s: RX_PDL_COUNT:0x%x TX_PDL_COUNT:0x%x, RX_PKT_CNT=0x%x PDH=0x%x, Data@0x%x len=0x%x\n", + dev->name, hp100_inb(RX_PDL), hp100_inb(TX_PDL), + hp100_inb(RX_PKT_CNT), (u_int) * (ptr->pdl), + (u_int) * (ptr->pdl + 3), (u_int) * (ptr->pdl + 4)); +#endif + + if ((pkt_len >= MIN_ETHER_SIZE) && + (pkt_len <= MAX_ETHER_SIZE)) { + if (ptr->skb == NULL) { + printk("hp100: %s: rx_bm: skb null\n", dev->name); + /* can happen if we only allocated room for the pdh due to memory shortage. */ + dev->stats.rx_dropped++; + } else { + skb_trim(ptr->skb, pkt_len); /* Shorten it */ + ptr->skb->protocol = + eth_type_trans(ptr->skb, dev); + + netif_rx(ptr->skb); /* Up and away... */ + + dev->stats.rx_packets++; + dev->stats.rx_bytes += pkt_len; + } + + switch (header & 0x00070000) { + case (HP100_MULTI_ADDR_HASH << 16): + case (HP100_MULTI_ADDR_NO_HASH << 16): + dev->stats.multicast++; + break; + } + } else { +#ifdef HP100_DEBUG + printk("hp100: %s: rx_bm: Received bad packet (length=%d)\n", dev->name, pkt_len); +#endif + if (ptr->skb != NULL) + dev_kfree_skb_any(ptr->skb); + dev->stats.rx_errors++; + } + + lp->rxrhead = lp->rxrhead->next; + + /* Allocate a new rx PDL (so lp->rxrcommit stays the same) */ + if (0 == hp100_build_rx_pdl(lp->rxrtail, dev)) { + /* No space for skb, header can still be received. */ +#ifdef HP100_DEBUG + printk("hp100: %s: rx_bm: No space for new PDL.\n", dev->name); +#endif + return; + } else { /* successfully allocated new PDL - put it in ringlist at tail. */ + hp100_outl((u32) lp->rxrtail->pdl_paddr, RX_PDA); + lp->rxrtail = lp->rxrtail->next; + } + + } +} + +/* + * statistics + */ +static struct net_device_stats *hp100_get_stats(struct net_device *dev) +{ + unsigned long flags; + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4215, TRACE); +#endif + + spin_lock_irqsave(&lp->lock, flags); + hp100_ints_off(); /* Useful ? Jean II */ + hp100_update_stats(dev); + hp100_ints_on(); + spin_unlock_irqrestore(&lp->lock, flags); + return &(dev->stats); +} + +static void hp100_update_stats(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + u_short val; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4216, TRACE); + printk("hp100: %s: update-stats\n", dev->name); +#endif + + /* Note: Statistics counters clear when read. */ + hp100_page(MAC_CTRL); + val = hp100_inw(DROPPED) & 0x0fff; + dev->stats.rx_errors += val; + dev->stats.rx_over_errors += val; + val = hp100_inb(CRC); + dev->stats.rx_errors += val; + dev->stats.rx_crc_errors += val; + val = hp100_inb(ABORT); + dev->stats.tx_errors += val; + dev->stats.tx_aborted_errors += val; + hp100_page(PERFORMANCE); +} + +static void hp100_misc_interrupt(struct net_device *dev) +{ +#ifdef HP100_DEBUG_B + int ioaddr = dev->base_addr; +#endif + +#ifdef HP100_DEBUG_B + int ioaddr = dev->base_addr; + hp100_outw(0x4216, TRACE); + printk("hp100: %s: misc_interrupt\n", dev->name); +#endif + + /* Note: Statistics counters clear when read. */ + dev->stats.rx_errors++; + dev->stats.tx_errors++; +} + +static void hp100_clear_stats(struct hp100_private *lp, int ioaddr) +{ + unsigned long flags; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4217, TRACE); + printk("hp100: %s: clear_stats\n", dev->name); +#endif + + spin_lock_irqsave(&lp->lock, flags); + hp100_page(MAC_CTRL); /* get all statistics bytes */ + hp100_inw(DROPPED); + hp100_inb(CRC); + hp100_inb(ABORT); + hp100_page(PERFORMANCE); + spin_unlock_irqrestore(&lp->lock, flags); +} + + +/* + * multicast setup + */ + +/* + * Set or clear the multicast filter for this adapter. + */ + +static void hp100_set_multicast_list(struct net_device *dev) +{ + unsigned long flags; + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4218, TRACE); + printk("hp100: %s: set_mc_list\n", dev->name); +#endif + + spin_lock_irqsave(&lp->lock, flags); + hp100_ints_off(); + hp100_page(MAC_CTRL); + hp100_andb(~(HP100_RX_EN | HP100_TX_EN), MAC_CFG_1); /* stop rx/tx */ + + if (dev->flags & IFF_PROMISC) { + lp->mac2_mode = HP100_MAC2MODE6; /* promiscuous mode = get all good */ + lp->mac1_mode = HP100_MAC1MODE6; /* packets on the net */ + memset(&lp->hash_bytes, 0xff, 8); + } else if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI)) { + lp->mac2_mode = HP100_MAC2MODE5; /* multicast mode = get packets for */ + lp->mac1_mode = HP100_MAC1MODE5; /* me, broadcasts and all multicasts */ +#ifdef HP100_MULTICAST_FILTER /* doesn't work!!! */ + if (dev->flags & IFF_ALLMULTI) { + /* set hash filter to receive all multicast packets */ + memset(&lp->hash_bytes, 0xff, 8); + } else { + int i, idx; + u_char *addrs; + struct netdev_hw_addr *ha; + + memset(&lp->hash_bytes, 0x00, 8); +#ifdef HP100_DEBUG + printk("hp100: %s: computing hash filter - mc_count = %i\n", + dev->name, netdev_mc_count(dev)); +#endif + netdev_for_each_mc_addr(ha, dev) { + addrs = ha->addr; +#ifdef HP100_DEBUG + printk("hp100: %s: multicast = %pM, ", + dev->name, addrs); +#endif + for (i = idx = 0; i < 6; i++) { + idx ^= *addrs++ & 0x3f; + printk(":%02x:", idx); + } +#ifdef HP100_DEBUG + printk("idx = %i\n", idx); +#endif + lp->hash_bytes[idx >> 3] |= (1 << (idx & 7)); + } + } +#else + memset(&lp->hash_bytes, 0xff, 8); +#endif + } else { + lp->mac2_mode = HP100_MAC2MODE3; /* normal mode = get packets for me */ + lp->mac1_mode = HP100_MAC1MODE3; /* and broadcasts */ + memset(&lp->hash_bytes, 0x00, 8); + } + + if (((hp100_inb(MAC_CFG_1) & 0x0f) != lp->mac1_mode) || + (hp100_inb(MAC_CFG_2) != lp->mac2_mode)) { + int i; + + hp100_outb(lp->mac2_mode, MAC_CFG_2); + hp100_andb(HP100_MAC1MODEMASK, MAC_CFG_1); /* clear mac1 mode bits */ + hp100_orb(lp->mac1_mode, MAC_CFG_1); /* and set the new mode */ + + hp100_page(MAC_ADDRESS); + for (i = 0; i < 8; i++) + hp100_outb(lp->hash_bytes[i], HASH_BYTE0 + i); +#ifdef HP100_DEBUG + printk("hp100: %s: mac1 = 0x%x, mac2 = 0x%x, multicast hash = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, lp->mac1_mode, lp->mac2_mode, + lp->hash_bytes[0], lp->hash_bytes[1], + lp->hash_bytes[2], lp->hash_bytes[3], + lp->hash_bytes[4], lp->hash_bytes[5], + lp->hash_bytes[6], lp->hash_bytes[7]); +#endif + + if (lp->lan_type == HP100_LAN_100) { +#ifdef HP100_DEBUG + printk("hp100: %s: 100VG MAC settings have changed - relogin.\n", dev->name); +#endif + lp->hub_status = hp100_login_to_vg_hub(dev, 1); /* force a relogin to the hub */ + } + } else { + int i; + u_char old_hash_bytes[8]; + + hp100_page(MAC_ADDRESS); + for (i = 0; i < 8; i++) + old_hash_bytes[i] = hp100_inb(HASH_BYTE0 + i); + if (memcmp(old_hash_bytes, &lp->hash_bytes, 8)) { + for (i = 0; i < 8; i++) + hp100_outb(lp->hash_bytes[i], HASH_BYTE0 + i); +#ifdef HP100_DEBUG + printk("hp100: %s: multicast hash = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, lp->hash_bytes[0], + lp->hash_bytes[1], lp->hash_bytes[2], + lp->hash_bytes[3], lp->hash_bytes[4], + lp->hash_bytes[5], lp->hash_bytes[6], + lp->hash_bytes[7]); +#endif + + if (lp->lan_type == HP100_LAN_100) { +#ifdef HP100_DEBUG + printk("hp100: %s: 100VG MAC settings have changed - relogin.\n", dev->name); +#endif + lp->hub_status = hp100_login_to_vg_hub(dev, 1); /* force a relogin to the hub */ + } + } + } + + hp100_page(MAC_CTRL); + hp100_orb(HP100_RX_EN | HP100_RX_IDLE | /* enable rx */ + HP100_TX_EN | HP100_TX_IDLE, MAC_CFG_1); /* enable tx */ + + hp100_page(PERFORMANCE); + hp100_ints_on(); + spin_unlock_irqrestore(&lp->lock, flags); +} + +/* + * hardware interrupt handling + */ + +static irqreturn_t hp100_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct hp100_private *lp = netdev_priv(dev); + + int ioaddr; + u_int val; + + if (dev == NULL) + return IRQ_NONE; + ioaddr = dev->base_addr; + + spin_lock(&lp->lock); + + hp100_ints_off(); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4219, TRACE); +#endif + + /* hp100_page( PERFORMANCE ); */ + val = hp100_inw(IRQ_STATUS); +#ifdef HP100_DEBUG_IRQ + printk("hp100: %s: mode=%x,IRQ_STAT=0x%.4x,RXPKTCNT=0x%.2x RXPDL=0x%.2x TXPKTCNT=0x%.2x TXPDL=0x%.2x\n", + dev->name, lp->mode, (u_int) val, hp100_inb(RX_PKT_CNT), + hp100_inb(RX_PDL), hp100_inb(TX_PKT_CNT), hp100_inb(TX_PDL)); +#endif + + if (val == 0) { /* might be a shared interrupt */ + spin_unlock(&lp->lock); + hp100_ints_on(); + return IRQ_NONE; + } + /* We're only interested in those interrupts we really enabled. */ + /* val &= hp100_inw( IRQ_MASK ); */ + + /* + * RX_PDL_FILL_COMPL is set whenever a RX_PDL has been executed. A RX_PDL + * is considered executed whenever the RX_PDL data structure is no longer + * needed. + */ + if (val & HP100_RX_PDL_FILL_COMPL) { + if (lp->mode == 1) + hp100_rx_bm(dev); + else { + printk("hp100: %s: rx_pdl_fill_compl interrupt although not busmaster?\n", dev->name); + } + } + + /* + * The RX_PACKET interrupt is set, when the receive packet counter is + * non zero. We use this interrupt for receiving in slave mode. In + * busmaster mode, we use it to make sure we did not miss any rx_pdl_fill + * interrupts. If rx_pdl_fill_compl is not set and rx_packet is set, then + * we somehow have missed a rx_pdl_fill_compl interrupt. + */ + + if (val & HP100_RX_PACKET) { /* Receive Packet Counter is non zero */ + if (lp->mode != 1) /* non busmaster */ + hp100_rx(dev); + else if (!(val & HP100_RX_PDL_FILL_COMPL)) { + /* Shouldn't happen - maybe we missed a RX_PDL_FILL Interrupt? */ + hp100_rx_bm(dev); + } + } + + /* + * Ack. that we have noticed the interrupt and thereby allow next one. + * Note that this is now done after the slave rx function, since first + * acknowledging and then setting ADV_NXT_PKT caused an extra interrupt + * on the J2573. + */ + hp100_outw(val, IRQ_STATUS); + + /* + * RX_ERROR is set when a packet is dropped due to no memory resources on + * the card or when a RCV_ERR occurs. + * TX_ERROR is set when a TX_ABORT condition occurs in the MAC->exists + * only in the 802.3 MAC and happens when 16 collisions occur during a TX + */ + if (val & (HP100_TX_ERROR | HP100_RX_ERROR)) { +#ifdef HP100_DEBUG_IRQ + printk("hp100: %s: TX/RX Error IRQ\n", dev->name); +#endif + hp100_update_stats(dev); + if (lp->mode == 1) { + hp100_rxfill(dev); + hp100_clean_txring(dev); + } + } + + /* + * RX_PDA_ZERO is set when the PDA count goes from non-zero to zero. + */ + if ((lp->mode == 1) && (val & (HP100_RX_PDA_ZERO))) + hp100_rxfill(dev); + + /* + * HP100_TX_COMPLETE interrupt occurs when packet transmitted on wire + * is completed + */ + if ((lp->mode == 1) && (val & (HP100_TX_COMPLETE))) + hp100_clean_txring(dev); + + /* + * MISC_ERROR is set when either the LAN link goes down or a detected + * bus error occurs. + */ + if (val & HP100_MISC_ERROR) { /* New for J2585B */ +#ifdef HP100_DEBUG_IRQ + printk + ("hp100: %s: Misc. Error Interrupt - Check cabling.\n", + dev->name); +#endif + if (lp->mode == 1) { + hp100_clean_txring(dev); + hp100_rxfill(dev); + } + hp100_misc_interrupt(dev); + } + + spin_unlock(&lp->lock); + hp100_ints_on(); + return IRQ_HANDLED; +} + +/* + * some misc functions + */ + +static void hp100_start_interface(struct net_device *dev) +{ + unsigned long flags; + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4220, TRACE); + printk("hp100: %s: hp100_start_interface\n", dev->name); +#endif + + spin_lock_irqsave(&lp->lock, flags); + + /* Ensure the adapter does not want to request an interrupt when */ + /* enabling the IRQ line to be active on the bus (i.e. not tri-stated) */ + hp100_page(PERFORMANCE); + hp100_outw(0xfefe, IRQ_MASK); /* mask off all ints */ + hp100_outw(0xffff, IRQ_STATUS); /* ack all IRQs */ + hp100_outw(HP100_FAKE_INT | HP100_INT_EN | HP100_RESET_LB, + OPTION_LSW); + /* Un Tri-state int. TODO: Check if shared interrupts can be realised? */ + hp100_outw(HP100_TRI_INT | HP100_RESET_HB, OPTION_LSW); + + if (lp->mode == 1) { + /* Make sure BM bit is set... */ + hp100_page(HW_MAP); + hp100_orb(HP100_BM_MASTER, BM); + hp100_rxfill(dev); + } else if (lp->mode == 2) { + /* Enable memory mapping. Note: Don't do this when busmaster. */ + hp100_outw(HP100_MMAP_DIS | HP100_RESET_HB, OPTION_LSW); + } + + hp100_page(PERFORMANCE); + hp100_outw(0xfefe, IRQ_MASK); /* mask off all ints */ + hp100_outw(0xffff, IRQ_STATUS); /* ack IRQ */ + + /* enable a few interrupts: */ + if (lp->mode == 1) { /* busmaster mode */ + hp100_outw(HP100_RX_PDL_FILL_COMPL | + HP100_RX_PDA_ZERO | HP100_RX_ERROR | + /* HP100_RX_PACKET | */ + /* HP100_RX_EARLY_INT | */ HP100_SET_HB | + /* HP100_TX_PDA_ZERO | */ + HP100_TX_COMPLETE | + /* HP100_MISC_ERROR | */ + HP100_TX_ERROR | HP100_SET_LB, IRQ_MASK); + } else { + hp100_outw(HP100_RX_PACKET | + HP100_RX_ERROR | HP100_SET_HB | + HP100_TX_ERROR | HP100_SET_LB, IRQ_MASK); + } + + /* Note : before hp100_set_multicast_list(), because it will play with + * spinlock itself... Jean II */ + spin_unlock_irqrestore(&lp->lock, flags); + + /* Enable MAC Tx and RX, set MAC modes, ... */ + hp100_set_multicast_list(dev); +} + +static void hp100_stop_interface(struct net_device *dev) +{ + struct hp100_private *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + u_int val; + +#ifdef HP100_DEBUG_B + printk("hp100: %s: hp100_stop_interface\n", dev->name); + hp100_outw(0x4221, TRACE); +#endif + + if (lp->mode == 1) + hp100_BM_shutdown(dev); + else { + /* Note: MMAP_DIS will be reenabled by start_interface */ + hp100_outw(HP100_INT_EN | HP100_RESET_LB | + HP100_TRI_INT | HP100_MMAP_DIS | HP100_SET_HB, + OPTION_LSW); + val = hp100_inw(OPTION_LSW); + + hp100_page(MAC_CTRL); + hp100_andb(~(HP100_RX_EN | HP100_TX_EN), MAC_CFG_1); + + if (!(val & HP100_HW_RST)) + return; /* If reset, imm. return ... */ + /* ... else: busy wait until idle */ + for (val = 0; val < 6000; val++) + if ((hp100_inb(MAC_CFG_1) & (HP100_TX_IDLE | HP100_RX_IDLE)) == (HP100_TX_IDLE | HP100_RX_IDLE)) { + hp100_page(PERFORMANCE); + return; + } + printk("hp100: %s: hp100_stop_interface - timeout\n", dev->name); + hp100_page(PERFORMANCE); + } +} + +static void hp100_load_eeprom(struct net_device *dev, u_short probe_ioaddr) +{ + int i; + int ioaddr = probe_ioaddr > 0 ? probe_ioaddr : dev->base_addr; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4222, TRACE); +#endif + + hp100_page(EEPROM_CTRL); + hp100_andw(~HP100_EEPROM_LOAD, EEPROM_CTRL); + hp100_orw(HP100_EEPROM_LOAD, EEPROM_CTRL); + for (i = 0; i < 10000; i++) + if (!(hp100_inb(OPTION_MSW) & HP100_EE_LOAD)) + return; + printk("hp100: %s: hp100_load_eeprom - timeout\n", dev->name); +} + +/* Sense connection status. + * return values: LAN_10 - Connected to 10Mbit/s network + * LAN_100 - Connected to 100Mbit/s network + * LAN_ERR - not connected or 100Mbit/s Hub down + */ +static int hp100_sense_lan(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + u_short val_VG, val_10; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4223, TRACE); +#endif + + hp100_page(MAC_CTRL); + val_10 = hp100_inb(10_LAN_CFG_1); + val_VG = hp100_inb(VG_LAN_CFG_1); + hp100_page(PERFORMANCE); +#ifdef HP100_DEBUG + printk("hp100: %s: sense_lan: val_VG = 0x%04x, val_10 = 0x%04x\n", + dev->name, val_VG, val_10); +#endif + + if (val_10 & HP100_LINK_BEAT_ST) /* 10Mb connection is active */ + return HP100_LAN_10; + + if (val_10 & HP100_AUI_ST) { /* have we BNC or AUI onboard? */ + /* + * This can be overriden by dos utility, so if this has no effect, + * perhaps you need to download that utility from HP and set card + * back to "auto detect". + */ + val_10 |= HP100_AUI_SEL | HP100_LOW_TH; + hp100_page(MAC_CTRL); + hp100_outb(val_10, 10_LAN_CFG_1); + hp100_page(PERFORMANCE); + return HP100_LAN_COAX; + } + + /* Those cards don't have a 100 Mbit connector */ + if ( !strcmp(lp->id, "HWP1920") || + (lp->pci_dev && + lp->pci_dev->vendor == PCI_VENDOR_ID && + (lp->pci_dev->device == PCI_DEVICE_ID_HP_J2970A || + lp->pci_dev->device == PCI_DEVICE_ID_HP_J2973A))) + return HP100_LAN_ERR; + + if (val_VG & HP100_LINK_CABLE_ST) /* Can hear the HUBs tone. */ + return HP100_LAN_100; + return HP100_LAN_ERR; +} + +static int hp100_down_vg_link(struct net_device *dev) +{ + struct hp100_private *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + unsigned long time; + long savelan, newlan; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4224, TRACE); + printk("hp100: %s: down_vg_link\n", dev->name); +#endif + + hp100_page(MAC_CTRL); + time = jiffies + (HZ / 4); + do { + if (hp100_inb(VG_LAN_CFG_1) & HP100_LINK_CABLE_ST) + break; + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_after(time, jiffies)); + + if (time_after_eq(jiffies, time)) /* no signal->no logout */ + return 0; + + /* Drop the VG Link by clearing the link up cmd and load addr. */ + + hp100_andb(~(HP100_LOAD_ADDR | HP100_LINK_CMD), VG_LAN_CFG_1); + hp100_orb(HP100_VG_SEL, VG_LAN_CFG_1); + + /* Conditionally stall for >250ms on Link-Up Status (to go down) */ + time = jiffies + (HZ / 2); + do { + if (!(hp100_inb(VG_LAN_CFG_1) & HP100_LINK_UP_ST)) + break; + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_after(time, jiffies)); + +#ifdef HP100_DEBUG + if (time_after_eq(jiffies, time)) + printk("hp100: %s: down_vg_link: Link does not go down?\n", dev->name); +#endif + + /* To prevent condition where Rev 1 VG MAC and old hubs do not complete */ + /* logout under traffic (even though all the status bits are cleared), */ + /* do this workaround to get the Rev 1 MAC in its idle state */ + if (lp->chip == HP100_CHIPID_LASSEN) { + /* Reset VG MAC to insure it leaves the logoff state even if */ + /* the Hub is still emitting tones */ + hp100_andb(~HP100_VG_RESET, VG_LAN_CFG_1); + udelay(1500); /* wait for >1ms */ + hp100_orb(HP100_VG_RESET, VG_LAN_CFG_1); /* Release Reset */ + udelay(1500); + } + + /* New: For lassen, switch to 10 Mbps mac briefly to clear training ACK */ + /* to get the VG mac to full reset. This is not req.d with later chips */ + /* Note: It will take the between 1 and 2 seconds for the VG mac to be */ + /* selected again! This will be left to the connect hub function to */ + /* perform if desired. */ + if (lp->chip == HP100_CHIPID_LASSEN) { + /* Have to write to 10 and 100VG control registers simultaneously */ + savelan = newlan = hp100_inl(10_LAN_CFG_1); /* read 10+100 LAN_CFG regs */ + newlan &= ~(HP100_VG_SEL << 16); + newlan |= (HP100_DOT3_MAC) << 8; + hp100_andb(~HP100_AUTO_MODE, MAC_CFG_3); /* Autosel off */ + hp100_outl(newlan, 10_LAN_CFG_1); + + /* Conditionally stall for 5sec on VG selected. */ + time = jiffies + (HZ * 5); + do { + if (!(hp100_inb(MAC_CFG_4) & HP100_MAC_SEL_ST)) + break; + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_after(time, jiffies)); + + hp100_orb(HP100_AUTO_MODE, MAC_CFG_3); /* Autosel back on */ + hp100_outl(savelan, 10_LAN_CFG_1); + } + + time = jiffies + (3 * HZ); /* Timeout 3s */ + do { + if ((hp100_inb(VG_LAN_CFG_1) & HP100_LINK_CABLE_ST) == 0) + break; + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_after(time, jiffies)); + + if (time_before_eq(time, jiffies)) { +#ifdef HP100_DEBUG + printk("hp100: %s: down_vg_link: timeout\n", dev->name); +#endif + return -EIO; + } + + time = jiffies + (2 * HZ); /* This seems to take a while.... */ + do { + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_after(time, jiffies)); + + return 0; +} + +static int hp100_login_to_vg_hub(struct net_device *dev, u_short force_relogin) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + u_short val = 0; + unsigned long time; + int startst; + +#ifdef HP100_DEBUG_B + hp100_outw(0x4225, TRACE); + printk("hp100: %s: login_to_vg_hub\n", dev->name); +#endif + + /* Initiate a login sequence iff VG MAC is enabled and either Load Address + * bit is zero or the force relogin flag is set (e.g. due to MAC address or + * promiscuous mode change) + */ + hp100_page(MAC_CTRL); + startst = hp100_inb(VG_LAN_CFG_1); + if ((force_relogin == 1) || (hp100_inb(MAC_CFG_4) & HP100_MAC_SEL_ST)) { +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: Start training\n", dev->name); +#endif + + /* Ensure VG Reset bit is 1 (i.e., do not reset) */ + hp100_orb(HP100_VG_RESET, VG_LAN_CFG_1); + + /* If Lassen AND auto-select-mode AND VG tones were sensed on */ + /* entry then temporarily put them into force 100Mbit mode */ + if ((lp->chip == HP100_CHIPID_LASSEN) && (startst & HP100_LINK_CABLE_ST)) + hp100_andb(~HP100_DOT3_MAC, 10_LAN_CFG_2); + + /* Drop the VG link by zeroing Link Up Command and Load Address */ + hp100_andb(~(HP100_LINK_CMD /* |HP100_LOAD_ADDR */ ), VG_LAN_CFG_1); + +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: Bring down the link\n", dev->name); +#endif + + /* Wait for link to drop */ + time = jiffies + (HZ / 10); + do { + if (~(hp100_inb(VG_LAN_CFG_1) & HP100_LINK_UP_ST)) + break; + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_after(time, jiffies)); + + /* Start an addressed training and optionally request promiscuous port */ + if ((dev->flags) & IFF_PROMISC) { + hp100_orb(HP100_PROM_MODE, VG_LAN_CFG_2); + if (lp->chip == HP100_CHIPID_LASSEN) + hp100_orw(HP100_MACRQ_PROMSC, TRAIN_REQUEST); + } else { + hp100_andb(~HP100_PROM_MODE, VG_LAN_CFG_2); + /* For ETR parts we need to reset the prom. bit in the training + * register, otherwise promiscious mode won't be disabled. + */ + if (lp->chip == HP100_CHIPID_LASSEN) { + hp100_andw(~HP100_MACRQ_PROMSC, TRAIN_REQUEST); + } + } + + /* With ETR parts, frame format request bits can be set. */ + if (lp->chip == HP100_CHIPID_LASSEN) + hp100_orb(HP100_MACRQ_FRAMEFMT_EITHER, TRAIN_REQUEST); + + hp100_orb(HP100_LINK_CMD | HP100_LOAD_ADDR | HP100_VG_RESET, VG_LAN_CFG_1); + + /* Note: Next wait could be omitted for Hood and earlier chips under */ + /* certain circumstances */ + /* TODO: check if hood/earlier and skip wait. */ + + /* Wait for either short timeout for VG tones or long for login */ + /* Wait for the card hardware to signalise link cable status ok... */ + hp100_page(MAC_CTRL); + time = jiffies + (1 * HZ); /* 1 sec timeout for cable st */ + do { + if (hp100_inb(VG_LAN_CFG_1) & HP100_LINK_CABLE_ST) + break; + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_before(jiffies, time)); + + if (time_after_eq(jiffies, time)) { +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: Link cable status not ok? Training aborted.\n", dev->name); +#endif + } else { +#ifdef HP100_DEBUG_TRAINING + printk + ("hp100: %s: HUB tones detected. Trying to train.\n", + dev->name); +#endif + + time = jiffies + (2 * HZ); /* again a timeout */ + do { + val = hp100_inb(VG_LAN_CFG_1); + if ((val & (HP100_LINK_UP_ST))) { +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: Passed training.\n", dev->name); +#endif + break; + } + if (!in_interrupt()) + schedule_timeout_interruptible(1); + } while (time_after(time, jiffies)); + } + + /* If LINK_UP_ST is set, then we are logged into the hub. */ + if (time_before_eq(jiffies, time) && (val & HP100_LINK_UP_ST)) { +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: Successfully logged into the HUB.\n", dev->name); + if (lp->chip == HP100_CHIPID_LASSEN) { + val = hp100_inw(TRAIN_ALLOW); + printk("hp100: %s: Card supports 100VG MAC Version \"%s\" ", + dev->name, (hp100_inw(TRAIN_REQUEST) & HP100_CARD_MACVER) ? "802.12" : "Pre"); + printk("Driver will use MAC Version \"%s\"\n", (val & HP100_HUB_MACVER) ? "802.12" : "Pre"); + printk("hp100: %s: Frame format is %s.\n", dev->name, (val & HP100_MALLOW_FRAMEFMT) ? "802.5" : "802.3"); + } +#endif + } else { + /* If LINK_UP_ST is not set, login was not successful */ + printk("hp100: %s: Problem logging into the HUB.\n", dev->name); + if (lp->chip == HP100_CHIPID_LASSEN) { + /* Check allowed Register to find out why there is a problem. */ + val = hp100_inw(TRAIN_ALLOW); /* won't work on non-ETR card */ +#ifdef HP100_DEBUG_TRAINING + printk("hp100: %s: MAC Configuration requested: 0x%04x, HUB allowed: 0x%04x\n", dev->name, hp100_inw(TRAIN_REQUEST), val); +#endif + if (val & HP100_MALLOW_ACCDENIED) + printk("hp100: %s: HUB access denied.\n", dev->name); + if (val & HP100_MALLOW_CONFIGURE) + printk("hp100: %s: MAC Configuration is incompatible with the Network.\n", dev->name); + if (val & HP100_MALLOW_DUPADDR) + printk("hp100: %s: Duplicate MAC Address on the Network.\n", dev->name); + } + } + + /* If we have put the chip into forced 100 Mbit mode earlier, go back */ + /* to auto-select mode */ + + if ((lp->chip == HP100_CHIPID_LASSEN) && (startst & HP100_LINK_CABLE_ST)) { + hp100_page(MAC_CTRL); + hp100_orb(HP100_DOT3_MAC, 10_LAN_CFG_2); + } + + val = hp100_inb(VG_LAN_CFG_1); + + /* Clear the MISC_ERROR Interrupt, which might be generated when doing the relogin */ + hp100_page(PERFORMANCE); + hp100_outw(HP100_MISC_ERROR, IRQ_STATUS); + + if (val & HP100_LINK_UP_ST) + return 0; /* login was ok */ + else { + printk("hp100: %s: Training failed.\n", dev->name); + hp100_down_vg_link(dev); + return -EIO; + } + } + /* no forced relogin & already link there->no training. */ + return -EIO; +} + +static void hp100_cascade_reset(struct net_device *dev, u_short enable) +{ + int ioaddr = dev->base_addr; + struct hp100_private *lp = netdev_priv(dev); + +#ifdef HP100_DEBUG_B + hp100_outw(0x4226, TRACE); + printk("hp100: %s: cascade_reset\n", dev->name); +#endif + + if (enable) { + hp100_outw(HP100_HW_RST | HP100_RESET_LB, OPTION_LSW); + if (lp->chip == HP100_CHIPID_LASSEN) { + /* Lassen requires a PCI transmit fifo reset */ + hp100_page(HW_MAP); + hp100_andb(~HP100_PCI_RESET, PCICTRL2); + hp100_orb(HP100_PCI_RESET, PCICTRL2); + /* Wait for min. 300 ns */ + /* we can't use jiffies here, because it may be */ + /* that we have disabled the timer... */ + udelay(400); + hp100_andb(~HP100_PCI_RESET, PCICTRL2); + hp100_page(PERFORMANCE); + } + } else { /* bring out of reset */ + hp100_outw(HP100_HW_RST | HP100_SET_LB, OPTION_LSW); + udelay(400); + hp100_page(PERFORMANCE); + } +} + +#ifdef HP100_DEBUG +void hp100_RegisterDump(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + int Page; + int Register; + + /* Dump common registers */ + printk("hp100: %s: Cascade Register Dump\n", dev->name); + printk("hardware id #1: 0x%.2x\n", hp100_inb(HW_ID)); + printk("hardware id #2/paging: 0x%.2x\n", hp100_inb(PAGING)); + printk("option #1: 0x%.4x\n", hp100_inw(OPTION_LSW)); + printk("option #2: 0x%.4x\n", hp100_inw(OPTION_MSW)); + + /* Dump paged registers */ + for (Page = 0; Page < 8; Page++) { + /* Dump registers */ + printk("page: 0x%.2x\n", Page); + outw(Page, ioaddr + 0x02); + for (Register = 0x8; Register < 0x22; Register += 2) { + /* Display Register contents except data port */ + if (((Register != 0x10) && (Register != 0x12)) || (Page > 0)) { + printk("0x%.2x = 0x%.4x\n", Register, inw(ioaddr + Register)); + } + } + } + hp100_page(PERFORMANCE); +} +#endif + + +static void cleanup_dev(struct net_device *d) +{ + struct hp100_private *p = netdev_priv(d); + + unregister_netdev(d); + release_region(d->base_addr, HP100_REGION_SIZE); + + if (p->mode == 1) /* busmaster */ + pci_free_consistent(p->pci_dev, MAX_RINGSIZE + 0x0f, + p->page_vaddr_algn, + virt_to_whatever(d, p->page_vaddr_algn)); + if (p->mem_ptr_virt) + iounmap(p->mem_ptr_virt); + + free_netdev(d); +} + +#ifdef CONFIG_EISA +static int __init hp100_eisa_probe (struct device *gendev) +{ + struct net_device *dev = alloc_etherdev(sizeof(struct hp100_private)); + struct eisa_device *edev = to_eisa_device(gendev); + int err; + + if (!dev) + return -ENOMEM; + + SET_NETDEV_DEV(dev, &edev->dev); + + err = hp100_probe1(dev, edev->base_addr + 0xC38, HP100_BUS_EISA, NULL); + if (err) + goto out1; + +#ifdef HP100_DEBUG + printk("hp100: %s: EISA adapter found at 0x%x\n", dev->name, + dev->base_addr); +#endif + dev_set_drvdata(gendev, dev); + return 0; + out1: + free_netdev(dev); + return err; +} + +static int hp100_eisa_remove(struct device *gendev) +{ + struct net_device *dev = dev_get_drvdata(gendev); + cleanup_dev(dev); + return 0; +} + +static struct eisa_driver hp100_eisa_driver = { + .id_table = hp100_eisa_tbl, + .driver = { + .name = "hp100", + .probe = hp100_eisa_probe, + .remove = hp100_eisa_remove, + } +}; +#endif + +#ifdef CONFIG_PCI +static int hp100_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct net_device *dev; + int ioaddr; + u_short pci_command; + int err; + + if (pci_enable_device(pdev)) + return -ENODEV; + + dev = alloc_etherdev(sizeof(struct hp100_private)); + if (!dev) { + err = -ENOMEM; + goto out0; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + pci_read_config_word(pdev, PCI_COMMAND, &pci_command); + if (!(pci_command & PCI_COMMAND_IO)) { +#ifdef HP100_DEBUG + printk("hp100: %s: PCI I/O Bit has not been set. Setting...\n", dev->name); +#endif + pci_command |= PCI_COMMAND_IO; + pci_write_config_word(pdev, PCI_COMMAND, pci_command); + } + + if (!(pci_command & PCI_COMMAND_MASTER)) { +#ifdef HP100_DEBUG + printk("hp100: %s: PCI Master Bit has not been set. Setting...\n", dev->name); +#endif + pci_command |= PCI_COMMAND_MASTER; + pci_write_config_word(pdev, PCI_COMMAND, pci_command); + } + + ioaddr = pci_resource_start(pdev, 0); + err = hp100_probe1(dev, ioaddr, HP100_BUS_PCI, pdev); + if (err) + goto out1; + +#ifdef HP100_DEBUG + printk("hp100: %s: PCI adapter found at 0x%x\n", dev->name, ioaddr); +#endif + pci_set_drvdata(pdev, dev); + return 0; + out1: + free_netdev(dev); + out0: + pci_disable_device(pdev); + return err; +} + +static void hp100_pci_remove(struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + + cleanup_dev(dev); + pci_disable_device(pdev); +} + + +static struct pci_driver hp100_pci_driver = { + .name = "hp100", + .id_table = hp100_pci_tbl, + .probe = hp100_pci_probe, + .remove = hp100_pci_remove, +}; +#endif + +/* + * module section + */ + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jaroslav Kysela , " + "Siegfried \"Frieder\" Loeffler (dg1sek) "); +MODULE_DESCRIPTION("HP CASCADE Architecture Driver for 100VG-AnyLan Network Adapters"); + +/* + * Note: to register three isa devices, use: + * option hp100 hp100_port=0,0,0 + * to register one card at io 0x280 as eth239, use: + * option hp100 hp100_port=0x280 + */ +#if defined(MODULE) && defined(CONFIG_ISA) +#define HP100_DEVICES 5 +/* Parameters set by insmod */ +static int hp100_port[HP100_DEVICES] = { 0, [1 ... (HP100_DEVICES-1)] = -1 }; +module_param_array(hp100_port, int, NULL, 0); + +/* List of devices */ +static struct net_device *hp100_devlist[HP100_DEVICES]; + +static int __init hp100_isa_init(void) +{ + struct net_device *dev; + int i, err, cards = 0; + + /* Don't autoprobe ISA bus */ + if (hp100_port[0] == 0) + return -ENODEV; + + /* Loop on all possible base addresses */ + for (i = 0; i < HP100_DEVICES && hp100_port[i] != -1; ++i) { + dev = alloc_etherdev(sizeof(struct hp100_private)); + if (!dev) { + while (cards > 0) + cleanup_dev(hp100_devlist[--cards]); + + return -ENOMEM; + } + + err = hp100_isa_probe(dev, hp100_port[i]); + if (!err) + hp100_devlist[cards++] = dev; + else + free_netdev(dev); + } + + return cards > 0 ? 0 : -ENODEV; +} + +static void hp100_isa_cleanup(void) +{ + int i; + + for (i = 0; i < HP100_DEVICES; i++) { + struct net_device *dev = hp100_devlist[i]; + if (dev) + cleanup_dev(dev); + } +} +#else +#define hp100_isa_init() (0) +#define hp100_isa_cleanup() do { } while(0) +#endif + +static int __init hp100_module_init(void) +{ + int err; + + err = hp100_isa_init(); + if (err && err != -ENODEV) + goto out; +#ifdef CONFIG_EISA + err = eisa_driver_register(&hp100_eisa_driver); + if (err && err != -ENODEV) + goto out2; +#endif +#ifdef CONFIG_PCI + err = pci_register_driver(&hp100_pci_driver); + if (err && err != -ENODEV) + goto out3; +#endif + out: + return err; + out3: +#ifdef CONFIG_EISA + eisa_driver_unregister (&hp100_eisa_driver); + out2: +#endif + hp100_isa_cleanup(); + goto out; +} + + +static void __exit hp100_module_exit(void) +{ + hp100_isa_cleanup(); +#ifdef CONFIG_EISA + eisa_driver_unregister (&hp100_eisa_driver); +#endif +#ifdef CONFIG_PCI + pci_unregister_driver (&hp100_pci_driver); +#endif +} + +module_init(hp100_module_init) +module_exit(hp100_module_exit) diff --git a/linux/drivers/net/ethernet/hp/hp100.h b/linux/drivers/net/ethernet/hp/hp100.h new file mode 100644 index 000000000..b60e96fe3 --- /dev/null +++ b/linux/drivers/net/ethernet/hp/hp100.h @@ -0,0 +1,615 @@ +/* + * hp100.h: Hewlett Packard HP10/100VG ANY LAN ethernet driver for Linux. + * + * $Id: hp100.h,v 1.51 1997/04/08 14:26:42 floeff Exp floeff $ + * + * Authors: Jaroslav Kysela, + * Siegfried Loeffler + * + * This driver is based on the 'hpfepkt' crynwr packet driver. + * + * This source/code is public free; you can distribute it and/or modify + * it under terms of the GNU General Public License (published by the + * Free Software Foundation) either version two of this License, or any + * later version. + */ + +/**************************************************************************** + * Hardware Constants + ****************************************************************************/ + +/* + * Page Identifiers + * (Swap Paging Register, PAGING, bits 3:0, Offset 0x02) + */ + +#define HP100_PAGE_PERFORMANCE 0x0 /* Page 0 */ +#define HP100_PAGE_MAC_ADDRESS 0x1 /* Page 1 */ +#define HP100_PAGE_HW_MAP 0x2 /* Page 2 */ +#define HP100_PAGE_EEPROM_CTRL 0x3 /* Page 3 */ +#define HP100_PAGE_MAC_CTRL 0x4 /* Page 4 */ +#define HP100_PAGE_MMU_CFG 0x5 /* Page 5 */ +#define HP100_PAGE_ID_MAC_ADDR 0x6 /* Page 6 */ +#define HP100_PAGE_MMU_POINTER 0x7 /* Page 7 */ + + +/* Registers that are present on all pages */ + +#define HP100_REG_HW_ID 0x00 /* R: (16) Unique card ID */ +#define HP100_REG_TRACE 0x00 /* W: (16) Used for debug output */ +#define HP100_REG_PAGING 0x02 /* R: (16),15:4 Card ID */ + /* W: (16),3:0 Switch pages */ +#define HP100_REG_OPTION_LSW 0x04 /* RW: (16) Select card functions */ +#define HP100_REG_OPTION_MSW 0x06 /* RW: (16) Select card functions */ + +/* Page 0 - Performance */ + +#define HP100_REG_IRQ_STATUS 0x08 /* RW: (16) Which ints are pending */ +#define HP100_REG_IRQ_MASK 0x0a /* RW: (16) Select ints to allow */ +#define HP100_REG_FRAGMENT_LEN 0x0c /* W: (16)12:0 Current fragment len */ +/* Note: For 32 bit systems, fragment len and offset registers are available */ +/* at offset 0x28 and 0x2c, where they can be written as 32bit values. */ +#define HP100_REG_OFFSET 0x0e /* RW: (16)12:0 Offset to start read */ +#define HP100_REG_DATA32 0x10 /* RW: (32) I/O mode data port */ +#define HP100_REG_DATA16 0x12 /* RW: WORDs must be read from here */ +#define HP100_REG_TX_MEM_FREE 0x14 /* RD: (32) Amount of free Tx mem */ +#define HP100_REG_TX_PDA_L 0x14 /* W: (32) BM: Ptr to PDL, Low Pri */ +#define HP100_REG_TX_PDA_H 0x1c /* W: (32) BM: Ptr to PDL, High Pri */ +#define HP100_REG_RX_PKT_CNT 0x18 /* RD: (8) Rx count of pkts on card */ +#define HP100_REG_TX_PKT_CNT 0x19 /* RD: (8) Tx count of pkts on card */ +#define HP100_REG_RX_PDL 0x1a /* R: (8) BM: # rx pdl not executed */ +#define HP100_REG_TX_PDL 0x1b /* R: (8) BM: # tx pdl not executed */ +#define HP100_REG_RX_PDA 0x18 /* W: (32) BM: Up to 31 addresses */ + /* which point to a PDL */ +#define HP100_REG_SL_EARLY 0x1c /* (32) Enhanced Slave Early Rx */ +#define HP100_REG_STAT_DROPPED 0x20 /* R (12) Dropped Packet Counter */ +#define HP100_REG_STAT_ERRORED 0x22 /* R (8) Errored Packet Counter */ +#define HP100_REG_STAT_ABORT 0x23 /* R (8) Abort Counter/OW Coll. Flag */ +#define HP100_REG_RX_RING 0x24 /* W (32) Slave: RX Ring Pointers */ +#define HP100_REG_32_FRAGMENT_LEN 0x28 /* W (13) Slave: Fragment Length Reg */ +#define HP100_REG_32_OFFSET 0x2c /* W (16) Slave: Offset Register */ + +/* Page 1 - MAC Address/Hash Table */ + +#define HP100_REG_MAC_ADDR 0x08 /* RW: (8) Cards MAC address */ +#define HP100_REG_HASH_BYTE0 0x10 /* RW: (8) Cards multicast filter */ + +/* Page 2 - Hardware Mapping */ + +#define HP100_REG_MEM_MAP_LSW 0x08 /* RW: (16) LSW of cards mem addr */ +#define HP100_REG_MEM_MAP_MSW 0x0a /* RW: (16) MSW of cards mem addr */ +#define HP100_REG_IO_MAP 0x0c /* RW: (8) Cards I/O address */ +#define HP100_REG_IRQ_CHANNEL 0x0d /* RW: (8) IRQ and edge/level int */ +#define HP100_REG_SRAM 0x0e /* RW: (8) How much RAM on card */ +#define HP100_REG_BM 0x0f /* RW: (8) Controls BM functions */ + +/* New on Page 2 for ETR chips: */ +#define HP100_REG_MODECTRL1 0x10 /* RW: (8) Mode Control 1 */ +#define HP100_REG_MODECTRL2 0x11 /* RW: (8) Mode Control 2 */ +#define HP100_REG_PCICTRL1 0x12 /* RW: (8) PCI Cfg 1 */ +#define HP100_REG_PCICTRL2 0x13 /* RW: (8) PCI Cfg 2 */ +#define HP100_REG_PCIBUSMLAT 0x15 /* RW: (8) PCI Bus Master Latency */ +#define HP100_REG_EARLYTXCFG 0x16 /* RW: (16) Early TX Cfg/Cntrl Reg */ +#define HP100_REG_EARLYRXCFG 0x18 /* RW: (8) Early RX Cfg/Cntrl Reg */ +#define HP100_REG_ISAPNPCFG1 0x1a /* RW: (8) ISA PnP Cfg/Cntrl Reg 1 */ +#define HP100_REG_ISAPNPCFG2 0x1b /* RW: (8) ISA PnP Cfg/Cntrl Reg 2 */ + +/* Page 3 - EEPROM/Boot ROM */ + +#define HP100_REG_EEPROM_CTRL 0x08 /* RW: (16) Used to load EEPROM */ +#define HP100_REG_BOOTROM_CTRL 0x0a + +/* Page 4 - LAN Configuration (MAC_CTRL) */ + +#define HP100_REG_10_LAN_CFG_1 0x08 /* RW: (8) Set 10M XCVR functions */ +#define HP100_REG_10_LAN_CFG_2 0x09 /* RW: (8) 10M XCVR functions */ +#define HP100_REG_VG_LAN_CFG_1 0x0a /* RW: (8) Set 100M XCVR functions */ +#define HP100_REG_VG_LAN_CFG_2 0x0b /* RW: (8) 100M LAN Training cfgregs */ +#define HP100_REG_MAC_CFG_1 0x0c /* RW: (8) Types of pkts to accept */ +#define HP100_REG_MAC_CFG_2 0x0d /* RW: (8) Misc MAC functions */ +#define HP100_REG_MAC_CFG_3 0x0e /* RW: (8) Misc MAC functions */ +#define HP100_REG_MAC_CFG_4 0x0f /* R: (8) Misc MAC states */ +#define HP100_REG_DROPPED 0x10 /* R: (16),11:0 Pkts can't fit in mem */ +#define HP100_REG_CRC 0x12 /* R: (8) Pkts with CRC */ +#define HP100_REG_ABORT 0x13 /* R: (8) Aborted Tx pkts */ +#define HP100_REG_TRAIN_REQUEST 0x14 /* RW: (16) Endnode MAC register. */ +#define HP100_REG_TRAIN_ALLOW 0x16 /* R: (16) Hub allowed register */ + +/* Page 5 - MMU */ + +#define HP100_REG_RX_MEM_STOP 0x0c /* RW: (16) End of Rx ring addr */ +#define HP100_REG_TX_MEM_STOP 0x0e /* RW: (16) End of Tx ring addr */ +#define HP100_REG_PDL_MEM_STOP 0x10 /* Not used by 802.12 devices */ +#define HP100_REG_ECB_MEM_STOP 0x14 /* I've no idea what this is */ + +/* Page 6 - Card ID/Physical LAN Address */ + +#define HP100_REG_BOARD_ID 0x08 /* R: (8) EISA/ISA card ID */ +#define HP100_REG_BOARD_IO_CHCK 0x0c /* R: (8) Added to ID to get FFh */ +#define HP100_REG_SOFT_MODEL 0x0d /* R: (8) Config program defined */ +#define HP100_REG_LAN_ADDR 0x10 /* R: (8) MAC addr of card */ +#define HP100_REG_LAN_ADDR_CHCK 0x16 /* R: (8) Added to addr to get FFh */ + +/* Page 7 - MMU Current Pointers */ + +#define HP100_REG_PTR_RXSTART 0x08 /* R: (16) Current begin of Rx ring */ +#define HP100_REG_PTR_RXEND 0x0a /* R: (16) Current end of Rx ring */ +#define HP100_REG_PTR_TXSTART 0x0c /* R: (16) Current begin of Tx ring */ +#define HP100_REG_PTR_TXEND 0x0e /* R: (16) Current end of Rx ring */ +#define HP100_REG_PTR_RPDLSTART 0x10 +#define HP100_REG_PTR_RPDLEND 0x12 +#define HP100_REG_PTR_RINGPTRS 0x14 +#define HP100_REG_PTR_MEMDEBUG 0x1a +/* ------------------------------------------------------------------------ */ + + +/* + * Hardware ID Register I (Always available, HW_ID, Offset 0x00) + */ +#define HP100_HW_ID_CASCADE 0x4850 /* Identifies Cascade Chip */ + +/* + * Hardware ID Register 2 & Paging Register + * (Always available, PAGING, Offset 0x02) + * Bits 15:4 are for the Chip ID + */ +#define HP100_CHIPID_MASK 0xFFF0 +#define HP100_CHIPID_SHASTA 0x5350 /* Not 802.12 compliant */ + /* EISA BM/SL, MCA16/32 SL, ISA SL */ +#define HP100_CHIPID_RAINIER 0x5360 /* Not 802.12 compliant EISA BM, */ + /* PCI SL, MCA16/32 SL, ISA SL */ +#define HP100_CHIPID_LASSEN 0x5370 /* 802.12 compliant PCI BM, PCI SL */ + /* LRF supported */ + +/* + * Option Registers I and II + * (Always available, OPTION_LSW, Offset 0x04-0x05) + */ +#define HP100_DEBUG_EN 0x8000 /* 0:Dis., 1:Enable Debug Dump Ptr. */ +#define HP100_RX_HDR 0x4000 /* 0:Dis., 1:Enable putting pkt into */ + /* system mem. before Rx interrupt */ +#define HP100_MMAP_DIS 0x2000 /* 0:Enable, 1:Disable mem.mapping. */ + /* MMAP_DIS must be 0 and MEM_EN */ + /* must be 1 for memory-mapped */ + /* mode to be enabled */ +#define HP100_EE_EN 0x1000 /* 0:Disable,1:Enable EEPROM writing */ +#define HP100_BM_WRITE 0x0800 /* 0:Slave, 1:Bus Master for Tx data */ +#define HP100_BM_READ 0x0400 /* 0:Slave, 1:Bus Master for Rx data */ +#define HP100_TRI_INT 0x0200 /* 0:Don't, 1:Do tri-state the int */ +#define HP100_MEM_EN 0x0040 /* Config program set this to */ + /* 0:Disable, 1:Enable mem map. */ + /* See MMAP_DIS. */ +#define HP100_IO_EN 0x0020 /* 1:Enable I/O transfers */ +#define HP100_BOOT_EN 0x0010 /* 1:Enable boot ROM access */ +#define HP100_FAKE_INT 0x0008 /* 1:int */ +#define HP100_INT_EN 0x0004 /* 1:Enable ints from card */ +#define HP100_HW_RST 0x0002 /* 0:Reset, 1:Out of reset */ + /* NIC reset on 0 to 1 transition */ + +/* + * Option Register III + * (Always available, OPTION_MSW, Offset 0x06) + */ +#define HP100_PRIORITY_TX 0x0080 /* 1:Do all Tx pkts as priority */ +#define HP100_EE_LOAD 0x0040 /* 1:EEPROM loading, 0 when done */ +#define HP100_ADV_NXT_PKT 0x0004 /* 1:Advance to next pkt in Rx queue */ + /* h/w will set to 0 when done */ +#define HP100_TX_CMD 0x0002 /* 1:Tell h/w download done, h/w */ + /* will set to 0 when done */ + +/* + * Interrupt Status Registers I and II + * (Page PERFORMANCE, IRQ_STATUS, Offset 0x08-0x09) + * Note: With old chips, these Registers will clear when 1 is written to them + * with new chips this depends on setting of CLR_ISMODE + */ +#define HP100_RX_EARLY_INT 0x2000 +#define HP100_RX_PDA_ZERO 0x1000 +#define HP100_RX_PDL_FILL_COMPL 0x0800 +#define HP100_RX_PACKET 0x0400 /* 0:No, 1:Yes pkt has been Rx */ +#define HP100_RX_ERROR 0x0200 /* 0:No, 1:Yes Rx pkt had error */ +#define HP100_TX_PDA_ZERO 0x0020 /* 1 when PDA count goes to zero */ +#define HP100_TX_SPACE_AVAIL 0x0010 /* 0:<8192, 1:>=8192 Tx free bytes */ +#define HP100_TX_COMPLETE 0x0008 /* 0:No, 1:Yes a Tx has completed */ +#define HP100_MISC_ERROR 0x0004 /* 0:No, 1:Lan Link down or bus error */ +#define HP100_TX_ERROR 0x0002 /* 0:No, 1:Yes Tx pkt had error */ + +/* + * Xmit Memory Free Count + * (Page PERFORMANCE, TX_MEM_FREE, Offset 0x14) (Read only, 32bit) + */ +#define HP100_AUTO_COMPARE 0x80000000 /* Tx Space avail & pkts<255 */ +#define HP100_FREE_SPACE 0x7fffffe0 /* Tx free memory */ + +/* + * IRQ Channel + * (Page HW_MAP, IRQ_CHANNEL, Offset 0x0d) + */ +#define HP100_ZERO_WAIT_EN 0x80 /* 0:No, 1:Yes asserts NOWS signal */ +#define HP100_IRQ_SCRAMBLE 0x40 +#define HP100_BOND_HP 0x20 +#define HP100_LEVEL_IRQ 0x10 /* 0:Edge, 1:Level type interrupts. */ + /* (Only valid on EISA cards) */ +#define HP100_IRQMASK 0x0F /* Isolate the IRQ bits */ + +/* + * SRAM Parameters + * (Page HW_MAP, SRAM, Offset 0x0e) + */ +#define HP100_RAM_SIZE_MASK 0xe0 /* AND to get SRAM size index */ +#define HP100_RAM_SIZE_SHIFT 0x05 /* Shift count(put index in lwr bits) */ + +/* + * Bus Master Register + * (Page HW_MAP, BM, Offset 0x0f) + */ +#define HP100_BM_BURST_RD 0x01 /* EISA only: 1=Use burst trans. fm system */ + /* memory to chip (tx) */ +#define HP100_BM_BURST_WR 0x02 /* EISA only: 1=Use burst trans. fm system */ + /* memory to chip (rx) */ +#define HP100_BM_MASTER 0x04 /* 0:Slave, 1:BM mode */ +#define HP100_BM_PAGE_CK 0x08 /* This bit should be set whenever in */ + /* an EISA system */ +#define HP100_BM_PCI_8CLK 0x40 /* ... cycles 8 clocks apart */ + + +/* + * Mode Control Register I + * (Page HW_MAP, MODECTRL1, Offset0x10) + */ +#define HP100_TX_DUALQ 0x10 + /* If set and BM -> dual tx pda queues */ +#define HP100_ISR_CLRMODE 0x02 /* If set ISR will clear all pending */ + /* interrupts on read (etr only?) */ +#define HP100_EE_NOLOAD 0x04 /* Status whether res will be loaded */ + /* from the eeprom */ +#define HP100_TX_CNT_FLG 0x08 /* Controls Early TX Reg Cnt Field */ +#define HP100_PDL_USE3 0x10 /* If set BM engine will read only */ + /* first three data elements of a PDL */ + /* on the first access. */ +#define HP100_BUSTYPE_MASK 0xe0 /* Three bit bus type info */ + +/* + * Mode Control Register II + * (Page HW_MAP, MODECTRL2, Offset0x11) + */ +#define HP100_EE_MASK 0x0f /* Tell EEPROM circuit not to load */ + /* certain resources */ +#define HP100_DIS_CANCEL 0x20 /* For tx dualq mode operation */ +#define HP100_EN_PDL_WB 0x40 /* 1: Status of PDL completion may be */ + /* written back to system mem */ +#define HP100_EN_BUS_FAIL 0x80 /* Enables bus-fail portion of misc */ + /* interrupt */ + +/* + * PCI Configuration and Control Register I + * (Page HW_MAP, PCICTRL1, Offset 0x12) + */ +#define HP100_LO_MEM 0x01 /* 1: Mapped Mem requested below 1MB */ +#define HP100_NO_MEM 0x02 /* 1: Disables Req for sysmem to PCI */ + /* bios */ +#define HP100_USE_ISA 0x04 /* 1: isa type decodes will occur */ + /* simultaneously with PCI decodes */ +#define HP100_IRQ_HI_MASK 0xf0 /* pgmed by pci bios */ +#define HP100_PCI_IRQ_HI_MASK 0x78 /* Isolate 4 bits for PCI IRQ */ + +/* + * PCI Configuration and Control Register II + * (Page HW_MAP, PCICTRL2, Offset 0x13) + */ +#define HP100_RD_LINE_PDL 0x01 /* 1: PCI command Memory Read Line en */ +#define HP100_RD_TX_DATA_MASK 0x06 /* choose PCI memread cmds for TX */ +#define HP100_MWI 0x08 /* 1: en. PCI memory write invalidate */ +#define HP100_ARB_MODE 0x10 /* Select PCI arbitor type */ +#define HP100_STOP_EN 0x20 /* Enables PCI state machine to issue */ + /* pci stop if cascade not ready */ +#define HP100_IGNORE_PAR 0x40 /* 1: PCI state machine ignores parity */ +#define HP100_PCI_RESET 0x80 /* 0->1: Reset PCI block */ + +/* + * Early TX Configuration and Control Register + * (Page HW_MAP, EARLYTXCFG, Offset 0x16) + */ +#define HP100_EN_EARLY_TX 0x8000 /* 1=Enable Early TX */ +#define HP100_EN_ADAPTIVE 0x4000 /* 1=Enable adaptive mode */ +#define HP100_EN_TX_UR_IRQ 0x2000 /* reserved, must be 0 */ +#define HP100_EN_LOW_TX 0x1000 /* reserved, must be 0 */ +#define HP100_ET_CNT_MASK 0x0fff /* bits 11..0: ET counters */ + +/* + * Early RX Configuration and Control Register + * (Page HW_MAP, EARLYRXCFG, Offset 0x18) + */ +#define HP100_EN_EARLY_RX 0x80 /* 1=Enable Early RX */ +#define HP100_EN_LOW_RX 0x40 /* reserved, must be 0 */ +#define HP100_RX_TRIP_MASK 0x1f /* bits 4..0: threshold at which the + * early rx circuit will start the + * dma of received packet into system + * memory for BM */ + +/* + * Serial Devices Control Register + * (Page EEPROM_CTRL, EEPROM_CTRL, Offset 0x08) + */ +#define HP100_EEPROM_LOAD 0x0001 /* 0->1 loads EEPROM into registers. */ + /* When it goes back to 0, load is */ + /* complete. This should take ~600us. */ + +/* + * 10MB LAN Control and Configuration Register I + * (Page MAC_CTRL, 10_LAN_CFG_1, Offset 0x08) + */ +#define HP100_MAC10_SEL 0xc0 /* Get bits to indicate MAC */ +#define HP100_AUI_SEL 0x20 /* Status of AUI selection */ +#define HP100_LOW_TH 0x10 /* 0:No, 1:Yes allow better cabling */ +#define HP100_LINK_BEAT_DIS 0x08 /* 0:Enable, 1:Disable link beat */ +#define HP100_LINK_BEAT_ST 0x04 /* 0:No, 1:Yes link beat being Rx */ +#define HP100_R_ROL_ST 0x02 /* 0:No, 1:Yes Rx twisted pair has */ + /* been reversed */ +#define HP100_AUI_ST 0x01 /* 0:No, 1:Yes use AUI on TP card */ + +/* + * 10 MB LAN Control and Configuration Register II + * (Page MAC_CTRL, 10_LAN_CFG_2, Offset 0x09) + */ +#define HP100_SQU_ST 0x01 /* 0:No, 1:Yes collision signal sent */ + /* after Tx.Only used for AUI. */ +#define HP100_FULLDUP 0x02 /* 1: LXT901 XCVR fullduplx enabled */ +#define HP100_DOT3_MAC 0x04 /* 1: DOT 3 Mac sel. unless Autosel */ + +/* + * MAC Selection, use with MAC10_SEL bits + */ +#define HP100_AUTO_SEL_10 0x0 /* Auto select */ +#define HP100_XCVR_LXT901_10 0x1 /* LXT901 10BaseT transceiver */ +#define HP100_XCVR_7213 0x2 /* 7213 transceiver */ +#define HP100_XCVR_82503 0x3 /* 82503 transceiver */ + +/* + * 100MB LAN Training Register + * (Page MAC_CTRL, VG_LAN_CFG_2, Offset 0x0b) (old, pre 802.12) + */ +#define HP100_FRAME_FORMAT 0x08 /* 0:802.3, 1:802.5 frames */ +#define HP100_BRIDGE 0x04 /* 0:No, 1:Yes tell hub i am a bridge */ +#define HP100_PROM_MODE 0x02 /* 0:No, 1:Yes tell hub card is */ + /* promiscuous */ +#define HP100_REPEATER 0x01 /* 0:No, 1:Yes tell hub MAC wants to */ + /* be a cascaded repeater */ + +/* + * 100MB LAN Control and Configuration Register + * (Page MAC_CTRL, VG_LAN_CFG_1, Offset 0x0a) + */ +#define HP100_VG_SEL 0x80 /* 0:No, 1:Yes use 100 Mbit MAC */ +#define HP100_LINK_UP_ST 0x40 /* 0:No, 1:Yes endnode logged in */ +#define HP100_LINK_CABLE_ST 0x20 /* 0:No, 1:Yes cable can hear tones */ + /* from hub */ +#define HP100_LOAD_ADDR 0x10 /* 0->1 card addr will be sent */ + /* 100ms later the link status */ + /* bits are valid */ +#define HP100_LINK_CMD 0x08 /* 0->1 link will attempt to log in. */ + /* 100ms later the link status */ + /* bits are valid */ +#define HP100_TRN_DONE 0x04 /* NEW ETR-Chips only: Will be reset */ + /* after LinkUp Cmd is given and set */ + /* when training has completed. */ +#define HP100_LINK_GOOD_ST 0x02 /* 0:No, 1:Yes cable passed training */ +#define HP100_VG_RESET 0x01 /* 0:Yes, 1:No reset the 100VG MAC */ + + +/* + * MAC Configuration Register I + * (Page MAC_CTRL, MAC_CFG_1, Offset 0x0c) + */ +#define HP100_RX_IDLE 0x80 /* 0:Yes, 1:No currently receiving pkts */ +#define HP100_TX_IDLE 0x40 /* 0:Yes, 1:No currently Txing pkts */ +#define HP100_RX_EN 0x20 /* 1: allow receiving of pkts */ +#define HP100_TX_EN 0x10 /* 1: allow transmitting of pkts */ +#define HP100_ACC_ERRORED 0x08 /* 0:No, 1:Yes allow Rx of errored pkts */ +#define HP100_ACC_MC 0x04 /* 0:No, 1:Yes allow Rx of multicast pkts */ +#define HP100_ACC_BC 0x02 /* 0:No, 1:Yes allow Rx of broadcast pkts */ +#define HP100_ACC_PHY 0x01 /* 0:No, 1:Yes allow Rx of ALL phys. pkts */ +#define HP100_MAC1MODEMASK 0xf0 /* Hide ACC bits */ +#define HP100_MAC1MODE1 0x00 /* Receive nothing, must also disable RX */ +#define HP100_MAC1MODE2 0x00 +#define HP100_MAC1MODE3 HP100_MAC1MODE2 | HP100_ACC_BC +#define HP100_MAC1MODE4 HP100_MAC1MODE3 | HP100_ACC_MC +#define HP100_MAC1MODE5 HP100_MAC1MODE4 /* set mc hash to all ones also */ +#define HP100_MAC1MODE6 HP100_MAC1MODE5 | HP100_ACC_PHY /* Promiscuous */ +/* Note MODE6 will receive all GOOD packets on the LAN. This really needs + a mode 7 defined to be LAN Analyzer mode, which will receive errored and + runt packets, and keep the CRC bytes. */ +#define HP100_MAC1MODE7 HP100_MAC1MODE6 | HP100_ACC_ERRORED + +/* + * MAC Configuration Register II + * (Page MAC_CTRL, MAC_CFG_2, Offset 0x0d) + */ +#define HP100_TR_MODE 0x80 /* 0:No, 1:Yes support Token Ring formats */ +#define HP100_TX_SAME 0x40 /* 0:No, 1:Yes Tx same packet continuous */ +#define HP100_LBK_XCVR 0x20 /* 0:No, 1:Yes loopback through MAC & */ + /* transceiver */ +#define HP100_LBK_MAC 0x10 /* 0:No, 1:Yes loopback through MAC */ +#define HP100_CRC_I 0x08 /* 0:No, 1:Yes inhibit CRC on Tx packets */ +#define HP100_ACCNA 0x04 /* 1: For 802.5: Accept only token ring + * group addr that maches NA mask */ +#define HP100_KEEP_CRC 0x02 /* 0:No, 1:Yes keep CRC on Rx packets. */ + /* The length will reflect this. */ +#define HP100_ACCFA 0x01 /* 1: For 802.5: Accept only functional + * addrs that match FA mask (page1) */ +#define HP100_MAC2MODEMASK 0x02 +#define HP100_MAC2MODE1 0x00 +#define HP100_MAC2MODE2 0x00 +#define HP100_MAC2MODE3 0x00 +#define HP100_MAC2MODE4 0x00 +#define HP100_MAC2MODE5 0x00 +#define HP100_MAC2MODE6 0x00 +#define HP100_MAC2MODE7 KEEP_CRC + +/* + * MAC Configuration Register III + * (Page MAC_CTRL, MAC_CFG_3, Offset 0x0e) + */ +#define HP100_PACKET_PACE 0x03 /* Packet Pacing: + * 00: No packet pacing + * 01: 8 to 16 uS delay + * 10: 16 to 32 uS delay + * 11: 32 to 64 uS delay + */ +#define HP100_LRF_EN 0x04 /* 1: External LAN Rcv Filter and + * TCP/IP Checksumming enabled. */ +#define HP100_AUTO_MODE 0x10 /* 1: AutoSelect between 10/100 */ + +/* + * MAC Configuration Register IV + * (Page MAC_CTRL, MAC_CFG_4, Offset 0x0f) + */ +#define HP100_MAC_SEL_ST 0x01 /* (R): Status of external VGSEL + * Signal, 1=100VG, 0=10Mbit sel. */ +#define HP100_LINK_FAIL_ST 0x02 /* (R): Status of Link Fail portion + * of the Misc. Interrupt */ + +/* + * 100 MB LAN Training Request/Allowed Registers + * (Page MAC_CTRL, TRAIN_REQUEST and TRAIN_ALLOW, Offset 0x14-0x16)(ETR parts only) + */ +#define HP100_MACRQ_REPEATER 0x0001 /* 1: MAC tells HUB it wants to be + * a cascaded repeater + * 0: ... wants to be a DTE */ +#define HP100_MACRQ_PROMSC 0x0006 /* 2 bits: Promiscious mode + * 00: Rcv only unicast packets + * specifically addr to this + * endnode + * 10: Rcv all pckts fwded by + * the local repeater */ +#define HP100_MACRQ_FRAMEFMT_EITHER 0x0018 /* 11: either format allowed */ +#define HP100_MACRQ_FRAMEFMT_802_3 0x0000 /* 00: 802.3 is requested */ +#define HP100_MACRQ_FRAMEFMT_802_5 0x0010 /* 10: 802.5 format is requested */ +#define HP100_CARD_MACVER 0xe000 /* R: 3 bit Cards 100VG MAC version */ +#define HP100_MALLOW_REPEATER 0x0001 /* If reset, requested access as an + * end node is allowed */ +#define HP100_MALLOW_PROMSC 0x0004 /* 2 bits: Promiscious mode + * 00: Rcv only unicast packets + * specifically addr to this + * endnode + * 10: Rcv all pckts fwded by + * the local repeater */ +#define HP100_MALLOW_FRAMEFMT 0x00e0 /* 2 bits: Frame Format + * 00: 802.3 format will be used + * 10: 802.5 format will be used */ +#define HP100_MALLOW_ACCDENIED 0x0400 /* N bit */ +#define HP100_MALLOW_CONFIGURE 0x0f00 /* C bit */ +#define HP100_MALLOW_DUPADDR 0x1000 /* D bit */ +#define HP100_HUB_MACVER 0xe000 /* R: 3 bit 802.12 MAC/RMAC training */ + /* protocol of repeater */ + +/* ****************************************************************************** */ + +/* + * Set/Reset bits + */ +#define HP100_SET_HB 0x0100 /* 0:Set fields to 0 whose mask is 1 */ +#define HP100_SET_LB 0x0001 /* HB sets upper byte, LB sets lower byte */ +#define HP100_RESET_HB 0x0000 /* For readability when resetting bits */ +#define HP100_RESET_LB 0x0000 /* For readability when resetting bits */ + +/* + * Misc. Constants + */ +#define HP100_LAN_100 100 /* lan_type value for VG */ +#define HP100_LAN_10 10 /* lan_type value for 10BaseT */ +#define HP100_LAN_COAX 9 /* lan_type value for Coax */ +#define HP100_LAN_ERR (-1) /* lan_type value for link down */ + +/* + * Bus Master Data Structures ---------------------------------------------- + */ + +#define MAX_RX_PDL 30 /* Card limit = 31 */ +#define MAX_RX_FRAG 2 /* Don't need more... */ +#define MAX_TX_PDL 29 +#define MAX_TX_FRAG 2 /* Limit = 31 */ + +/* Define total PDL area size in bytes (should be 4096) */ +/* This is the size of kernel (dma) memory that will be allocated. */ +#define MAX_RINGSIZE ((MAX_RX_FRAG*8+4+4)*MAX_RX_PDL+(MAX_TX_FRAG*8+4+4)*MAX_TX_PDL)+16 + +/* Ethernet Packet Sizes */ +#define MIN_ETHER_SIZE 60 +#define MAX_ETHER_SIZE 1514 /* Needed for preallocation of */ + /* skb buffer when busmastering */ + +/* Tx or Rx Ring Entry */ +typedef struct hp100_ring { + u_int *pdl; /* Address of PDLs PDH, dword before + * this address is used for rx hdr */ + u_int pdl_paddr; /* Physical address of PDL */ + struct sk_buff *skb; + struct hp100_ring *next; +} hp100_ring_t; + + + +/* Mask for Header Descriptor */ +#define HP100_PKT_LEN_MASK 0x1FFF /* AND with RxLength to get length */ + + +/* Receive Packet Status. Note, the error bits are only valid if ACC_ERRORED + bit in the MAC Configuration Register 1 is set. */ +#define HP100_RX_PRI 0x8000 /* 0:No, 1:Yes packet is priority */ +#define HP100_SDF_ERR 0x4000 /* 0:No, 1:Yes start of frame error */ +#define HP100_SKEW_ERR 0x2000 /* 0:No, 1:Yes skew out of range */ +#define HP100_BAD_SYMBOL_ERR 0x1000 /* 0:No, 1:Yes invalid symbol received */ +#define HP100_RCV_IPM_ERR 0x0800 /* 0:No, 1:Yes pkt had an invalid packet */ + /* marker */ +#define HP100_SYMBOL_BAL_ERR 0x0400 /* 0:No, 1:Yes symbol balance error */ +#define HP100_VG_ALN_ERR 0x0200 /* 0:No, 1:Yes non-octet received */ +#define HP100_TRUNC_ERR 0x0100 /* 0:No, 1:Yes the packet was truncated */ +#define HP100_RUNT_ERR 0x0040 /* 0:No, 1:Yes pkt length < Min Pkt */ + /* Length Reg. */ +#define HP100_ALN_ERR 0x0010 /* 0:No, 1:Yes align error. */ +#define HP100_CRC_ERR 0x0008 /* 0:No, 1:Yes CRC occurred. */ + +/* The last three bits indicate the type of destination address */ + +#define HP100_MULTI_ADDR_HASH 0x0006 /* 110: Addr multicast, matched hash */ +#define HP100_BROADCAST_ADDR 0x0003 /* x11: Addr broadcast */ +#define HP100_MULTI_ADDR_NO_HASH 0x0002 /* 010: Addr multicast, didn't match hash */ +#define HP100_PHYS_ADDR_MATCH 0x0001 /* x01: Addr was physical and mine */ +#define HP100_PHYS_ADDR_NO_MATCH 0x0000 /* x00: Addr was physical but not mine */ + +/* + * macros + */ + +#define hp100_inb( reg ) \ + inb( ioaddr + HP100_REG_##reg ) +#define hp100_inw( reg ) \ + inw( ioaddr + HP100_REG_##reg ) +#define hp100_inl( reg ) \ + inl( ioaddr + HP100_REG_##reg ) +#define hp100_outb( data, reg ) \ + outb( data, ioaddr + HP100_REG_##reg ) +#define hp100_outw( data, reg ) \ + outw( data, ioaddr + HP100_REG_##reg ) +#define hp100_outl( data, reg ) \ + outl( data, ioaddr + HP100_REG_##reg ) +#define hp100_orb( data, reg ) \ + outb( inb( ioaddr + HP100_REG_##reg ) | (data), ioaddr + HP100_REG_##reg ) +#define hp100_orw( data, reg ) \ + outw( inw( ioaddr + HP100_REG_##reg ) | (data), ioaddr + HP100_REG_##reg ) +#define hp100_andb( data, reg ) \ + outb( inb( ioaddr + HP100_REG_##reg ) & (data), ioaddr + HP100_REG_##reg ) +#define hp100_andw( data, reg ) \ + outw( inw( ioaddr + HP100_REG_##reg ) & (data), ioaddr + HP100_REG_##reg ) + +#define hp100_page( page ) \ + outw( HP100_PAGE_##page, ioaddr + HP100_REG_PAGING ) +#define hp100_ints_off() \ + outw( HP100_INT_EN | HP100_RESET_LB, ioaddr + HP100_REG_OPTION_LSW ) +#define hp100_ints_on() \ + outw( HP100_INT_EN | HP100_SET_LB, ioaddr + HP100_REG_OPTION_LSW ) +#define hp100_mem_map_enable() \ + outw( HP100_MMAP_DIS | HP100_RESET_HB, ioaddr + HP100_REG_OPTION_LSW ) +#define hp100_mem_map_disable() \ + outw( HP100_MMAP_DIS | HP100_SET_HB, ioaddr + HP100_REG_OPTION_LSW ) diff --git a/linux/drivers/net/ethernet/hp/hp100.txt b/linux/drivers/net/ethernet/hp/hp100.txt new file mode 100644 index 000000000..bbfc43c30 --- /dev/null +++ b/linux/drivers/net/ethernet/hp/hp100.txt @@ -0,0 +1,4 @@ +KBUILD_MODNAME not defined +--set pre.cppflags[+] '-DKBUILD_MODNAME="hp100"' + +hp100_bm_netdev_ops, hp100_netdev_ops functions not called, assignment on line 686/688 doesn't spawn diff --git a/linux/drivers/net/ethernet/sis/sis900.c b/linux/drivers/net/ethernet/sis/sis900.c new file mode 100644 index 000000000..fd812d2e5 --- /dev/null +++ b/linux/drivers/net/ethernet/sis/sis900.c @@ -0,0 +1,2517 @@ +/* sis900.c: A SiS 900/7016 PCI Fast Ethernet driver for Linux. + Copyright 1999 Silicon Integrated System Corporation + Revision: 1.08.10 Apr. 2 2006 + + Modified from the driver which is originally written by Donald Becker. + + This software may be used and distributed according to the terms + of the GNU General Public License (GPL), incorporated herein by reference. + Drivers based on this skeleton fall under the GPL and must retain + the authorship (implicit copyright) notice. + + References: + SiS 7016 Fast Ethernet PCI Bus 10/100 Mbps LAN Controller with OnNow Support, + preliminary Rev. 1.0 Jan. 14, 1998 + SiS 900 Fast Ethernet PCI Bus 10/100 Mbps LAN Single Chip with OnNow Support, + preliminary Rev. 1.0 Nov. 10, 1998 + SiS 7014 Single Chip 100BASE-TX/10BASE-T Physical Layer Solution, + preliminary Rev. 1.0 Jan. 18, 1998 + + Rev 1.08.10 Apr. 2 2006 Daniele Venzano add vlan (jumbo packets) support + Rev 1.08.09 Sep. 19 2005 Daniele Venzano add Wake on LAN support + Rev 1.08.08 Jan. 22 2005 Daniele Venzano use netif_msg for debugging messages + Rev 1.08.07 Nov. 2 2003 Daniele Venzano add suspend/resume support + Rev 1.08.06 Sep. 24 2002 Mufasa Yang bug fix for Tx timeout & add SiS963 support + Rev 1.08.05 Jun. 6 2002 Mufasa Yang bug fix for read_eeprom & Tx descriptor over-boundary + Rev 1.08.04 Apr. 25 2002 Mufasa Yang added SiS962 support + Rev 1.08.03 Feb. 1 2002 Matt Domsch update to use library crc32 function + Rev 1.08.02 Nov. 30 2001 Hui-Fen Hsu workaround for EDB & bug fix for dhcp problem + Rev 1.08.01 Aug. 25 2001 Hui-Fen Hsu update for 630ET & workaround for ICS1893 PHY + Rev 1.08.00 Jun. 11 2001 Hui-Fen Hsu workaround for RTL8201 PHY and some bug fix + Rev 1.07.11 Apr. 2 2001 Hui-Fen Hsu updates PCI drivers to use the new pci_set_dma_mask for kernel 2.4.3 + Rev 1.07.10 Mar. 1 2001 Hui-Fen Hsu some bug fix & 635M/B support + Rev 1.07.09 Feb. 9 2001 Dave Jones PCI enable cleanup + Rev 1.07.08 Jan. 8 2001 Lei-Chun Chang added RTL8201 PHY support + Rev 1.07.07 Nov. 29 2000 Lei-Chun Chang added kernel-doc extractable documentation and 630 workaround fix + Rev 1.07.06 Nov. 7 2000 Jeff Garzik some bug fix and cleaning + Rev 1.07.05 Nov. 6 2000 metapirat contribute media type select by ifconfig + Rev 1.07.04 Sep. 6 2000 Lei-Chun Chang added ICS1893 PHY support + Rev 1.07.03 Aug. 24 2000 Lei-Chun Chang (lcchang@sis.com.tw) modified 630E equalizer workaround rule + Rev 1.07.01 Aug. 08 2000 Ollie Lho minor update for SiS 630E and SiS 630E A1 + Rev 1.07 Mar. 07 2000 Ollie Lho bug fix in Rx buffer ring + Rev 1.06.04 Feb. 11 2000 Jeff Garzik softnet and init for kernel 2.4 + Rev 1.06.03 Dec. 23 1999 Ollie Lho Third release + Rev 1.06.02 Nov. 23 1999 Ollie Lho bug in mac probing fixed + Rev 1.06.01 Nov. 16 1999 Ollie Lho CRC calculation provide by Joseph Zbiciak (im14u2c@primenet.com) + Rev 1.06 Nov. 4 1999 Ollie Lho (ollie@sis.com.tw) Second release + Rev 1.05.05 Oct. 29 1999 Ollie Lho (ollie@sis.com.tw) Single buffer Tx/Rx + Chin-Shan Li (lcs@sis.com.tw) Added AMD Am79c901 HomePNA PHY support + Rev 1.05 Aug. 7 1999 Jim Huang (cmhuang@sis.com.tw) Initial release +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* Processor type for cache alignment. */ +#include +#include +#include /* User space memory access functions */ + +#include "sis900.h" + +#define SIS900_MODULE_NAME "sis900" +#define SIS900_DRV_VERSION "v1.08.10 Apr. 2 2006" + +static const char version[] = + KERN_INFO "sis900.c: " SIS900_DRV_VERSION "\n"; + +static int max_interrupt_work = 40; +static int multicast_filter_limit = 128; + +static int sis900_debug = -1; /* Use SIS900_DEF_MSG as value */ + +#define SIS900_DEF_MSG \ + (NETIF_MSG_DRV | \ + NETIF_MSG_LINK | \ + NETIF_MSG_RX_ERR | \ + NETIF_MSG_TX_ERR) + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT (4*HZ) + +enum { + SIS_900 = 0, + SIS_7016 +}; +static const char * card_names[] = { + "SiS 900 PCI Fast Ethernet", + "SiS 7016 PCI Fast Ethernet" +}; + +static const struct pci_device_id sis900_pci_tbl[] = { + {PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_900, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, SIS_900}, + {PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7016, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, SIS_7016}, + {0,} +}; +MODULE_DEVICE_TABLE (pci, sis900_pci_tbl); + +static void sis900_read_mode(struct net_device *net_dev, int *speed, int *duplex); + +static const struct mii_chip_info { + const char * name; + u16 phy_id0; + u16 phy_id1; + u8 phy_types; +#define HOME 0x0001 +#define LAN 0x0002 +#define MIX 0x0003 +#define UNKNOWN 0x0 +} mii_chip_table[] = { + { "SiS 900 Internal MII PHY", 0x001d, 0x8000, LAN }, + { "SiS 7014 Physical Layer Solution", 0x0016, 0xf830, LAN }, + { "SiS 900 on Foxconn 661 7MI", 0x0143, 0xBC70, LAN }, + { "Altimata AC101LF PHY", 0x0022, 0x5520, LAN }, + { "ADM 7001 LAN PHY", 0x002e, 0xcc60, LAN }, + { "AMD 79C901 10BASE-T PHY", 0x0000, 0x6B70, LAN }, + { "AMD 79C901 HomePNA PHY", 0x0000, 0x6B90, HOME}, + { "ICS LAN PHY", 0x0015, 0xF440, LAN }, + { "ICS LAN PHY", 0x0143, 0xBC70, LAN }, + { "NS 83851 PHY", 0x2000, 0x5C20, MIX }, + { "NS 83847 PHY", 0x2000, 0x5C30, MIX }, + { "Realtek RTL8201 PHY", 0x0000, 0x8200, LAN }, + { "VIA 6103 PHY", 0x0101, 0x8f20, LAN }, + {NULL,}, +}; + +struct mii_phy { + struct mii_phy * next; + int phy_addr; + u16 phy_id0; + u16 phy_id1; + u16 status; + u8 phy_types; +}; + +typedef struct _BufferDesc { + u32 link; + u32 cmdsts; + u32 bufptr; +} BufferDesc; + +struct sis900_private { + struct pci_dev * pci_dev; + + spinlock_t lock; + + struct mii_phy * mii; + struct mii_phy * first_mii; /* record the first mii structure */ + unsigned int cur_phy; + struct mii_if_info mii_info; + + void __iomem *ioaddr; + + struct timer_list timer; /* Link status detection timer. */ + u8 autong_complete; /* 1: auto-negotiate complete */ + + u32 msg_enable; + + unsigned int cur_rx, dirty_rx; /* producer/comsumer pointers for Tx/Rx ring */ + unsigned int cur_tx, dirty_tx; + + /* The saved address of a sent/receive-in-place packet buffer */ + struct sk_buff *tx_skbuff[NUM_TX_DESC]; + struct sk_buff *rx_skbuff[NUM_RX_DESC]; + BufferDesc *tx_ring; + BufferDesc *rx_ring; + + dma_addr_t tx_ring_dma; + dma_addr_t rx_ring_dma; + + unsigned int tx_full; /* The Tx queue is full. */ + u8 host_bridge_rev; + u8 chipset_rev; +}; + +MODULE_AUTHOR("Jim Huang , Ollie Lho "); +MODULE_DESCRIPTION("SiS 900 PCI Fast Ethernet driver"); +MODULE_LICENSE("GPL"); + +module_param(multicast_filter_limit, int, 0444); +module_param(max_interrupt_work, int, 0444); +module_param(sis900_debug, int, 0444); +MODULE_PARM_DESC(multicast_filter_limit, "SiS 900/7016 maximum number of filtered multicast addresses"); +MODULE_PARM_DESC(max_interrupt_work, "SiS 900/7016 maximum events handled per interrupt"); +MODULE_PARM_DESC(sis900_debug, "SiS 900/7016 bitmapped debugging message level"); + +#define sw32(reg, val) iowrite32(val, ioaddr + (reg)) +#define sw8(reg, val) iowrite8(val, ioaddr + (reg)) +#define sr32(reg) ioread32(ioaddr + (reg)) +#define sr16(reg) ioread16(ioaddr + (reg)) + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void sis900_poll(struct net_device *dev); +#endif +static int sis900_open(struct net_device *net_dev); +static int sis900_mii_probe (struct net_device * net_dev); +static void sis900_init_rxfilter (struct net_device * net_dev); +static u16 read_eeprom(void __iomem *ioaddr, int location); +static int mdio_read(struct net_device *net_dev, int phy_id, int location); +static void mdio_write(struct net_device *net_dev, int phy_id, int location, int val); +static void sis900_timer(unsigned long data); +static void sis900_check_mode (struct net_device *net_dev, struct mii_phy *mii_phy); +static void sis900_tx_timeout(struct net_device *net_dev); +static void sis900_init_tx_ring(struct net_device *net_dev); +static void sis900_init_rx_ring(struct net_device *net_dev); +static netdev_tx_t sis900_start_xmit(struct sk_buff *skb, + struct net_device *net_dev); +static int sis900_rx(struct net_device *net_dev); +static void sis900_finish_xmit (struct net_device *net_dev); +static irqreturn_t sis900_interrupt(int irq, void *dev_instance); +static int sis900_close(struct net_device *net_dev); +static int mii_ioctl(struct net_device *net_dev, struct ifreq *rq, int cmd); +static u16 sis900_mcast_bitnr(u8 *addr, u8 revision); +static void set_rx_mode(struct net_device *net_dev); +static void sis900_reset(struct net_device *net_dev); +static void sis630_set_eq(struct net_device *net_dev, u8 revision); +static int sis900_set_config(struct net_device *dev, struct ifmap *map); +static u16 sis900_default_phy(struct net_device * net_dev); +static void sis900_set_capability( struct net_device *net_dev ,struct mii_phy *phy); +static u16 sis900_reset_phy(struct net_device *net_dev, int phy_addr); +static void sis900_auto_negotiate(struct net_device *net_dev, int phy_addr); +static void sis900_set_mode(struct sis900_private *, int speed, int duplex); +static const struct ethtool_ops sis900_ethtool_ops; + +/** + * sis900_get_mac_addr - Get MAC address for stand alone SiS900 model + * @pci_dev: the sis900 pci device + * @net_dev: the net device to get address for + * + * Older SiS900 and friends, use EEPROM to store MAC address. + * MAC address is read from read_eeprom() into @net_dev->dev_addr. + */ + +static int sis900_get_mac_addr(struct pci_dev *pci_dev, + struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + u16 signature; + int i; + + /* check to see if we have sane EEPROM */ + signature = (u16) read_eeprom(ioaddr, EEPROMSignature); + if (signature == 0xffff || signature == 0x0000) { + printk (KERN_WARNING "%s: Error EERPOM read %x\n", + pci_name(pci_dev), signature); + return 0; + } + + /* get MAC address from EEPROM */ + for (i = 0; i < 3; i++) + ((u16 *)(net_dev->dev_addr))[i] = read_eeprom(ioaddr, i+EEPROMMACAddr); + + return 1; +} + +/** + * sis630e_get_mac_addr - Get MAC address for SiS630E model + * @pci_dev: the sis900 pci device + * @net_dev: the net device to get address for + * + * SiS630E model, use APC CMOS RAM to store MAC address. + * APC CMOS RAM is accessed through ISA bridge. + * MAC address is read into @net_dev->dev_addr. + */ + +static int sis630e_get_mac_addr(struct pci_dev *pci_dev, + struct net_device *net_dev) +{ + struct pci_dev *isa_bridge = NULL; + u8 reg; + int i; + + isa_bridge = pci_get_device(PCI_VENDOR_ID_SI, 0x0008, isa_bridge); + if (!isa_bridge) + isa_bridge = pci_get_device(PCI_VENDOR_ID_SI, 0x0018, isa_bridge); + if (!isa_bridge) { + printk(KERN_WARNING "%s: Can not find ISA bridge\n", + pci_name(pci_dev)); + return 0; + } + pci_read_config_byte(isa_bridge, 0x48, ®); + pci_write_config_byte(isa_bridge, 0x48, reg | 0x40); + + for (i = 0; i < 6; i++) { + outb(0x09 + i, 0x70); + ((u8 *)(net_dev->dev_addr))[i] = inb(0x71); + } + + pci_write_config_byte(isa_bridge, 0x48, reg & ~0x40); + pci_dev_put(isa_bridge); + + return 1; +} + + +/** + * sis635_get_mac_addr - Get MAC address for SIS635 model + * @pci_dev: the sis900 pci device + * @net_dev: the net device to get address for + * + * SiS635 model, set MAC Reload Bit to load Mac address from APC + * to rfdr. rfdr is accessed through rfcr. MAC address is read into + * @net_dev->dev_addr. + */ + +static int sis635_get_mac_addr(struct pci_dev *pci_dev, + struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + u32 rfcrSave; + u32 i; + + rfcrSave = sr32(rfcr); + + sw32(cr, rfcrSave | RELOAD); + sw32(cr, 0); + + /* disable packet filtering before setting filter */ + sw32(rfcr, rfcrSave & ~RFEN); + + /* load MAC addr to filter data register */ + for (i = 0 ; i < 3 ; i++) { + sw32(rfcr, (i << RFADDR_shift)); + *( ((u16 *)net_dev->dev_addr) + i) = sr16(rfdr); + } + + /* enable packet filtering */ + sw32(rfcr, rfcrSave | RFEN); + + return 1; +} + +/** + * sis96x_get_mac_addr - Get MAC address for SiS962 or SiS963 model + * @pci_dev: the sis900 pci device + * @net_dev: the net device to get address for + * + * SiS962 or SiS963 model, use EEPROM to store MAC address. And EEPROM + * is shared by + * LAN and 1394. When access EEPROM, send EEREQ signal to hardware first + * and wait for EEGNT. If EEGNT is ON, EEPROM is permitted to be access + * by LAN, otherwise is not. After MAC address is read from EEPROM, send + * EEDONE signal to refuse EEPROM access by LAN. + * The EEPROM map of SiS962 or SiS963 is different to SiS900. + * The signature field in SiS962 or SiS963 spec is meaningless. + * MAC address is read into @net_dev->dev_addr. + */ + +static int sis96x_get_mac_addr(struct pci_dev *pci_dev, + struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + int wait, rc = 0; + + sw32(mear, EEREQ); + for (wait = 0; wait < 2000; wait++) { + if (sr32(mear) & EEGNT) { + u16 *mac = (u16 *)net_dev->dev_addr; + int i; + + /* get MAC address from EEPROM */ + for (i = 0; i < 3; i++) + mac[i] = read_eeprom(ioaddr, i + EEPROMMACAddr); + + rc = 1; + break; + } + udelay(1); + } + sw32(mear, EEDONE); + return rc; +} + +static const struct net_device_ops sis900_netdev_ops = { + .ndo_open = sis900_open, + .ndo_stop = sis900_close, + .ndo_start_xmit = sis900_start_xmit, + .ndo_set_config = sis900_set_config, + .ndo_set_rx_mode = set_rx_mode, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = eth_mac_addr, + .ndo_do_ioctl = mii_ioctl, + .ndo_tx_timeout = sis900_tx_timeout, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = sis900_poll, +#endif +}; + +/** + * sis900_probe - Probe for sis900 device + * @pci_dev: the sis900 pci device + * @pci_id: the pci device ID + * + * Check and probe sis900 net device for @pci_dev. + * Get mac address according to the chip revision, + * and assign SiS900-specific entries in the device structure. + * ie: sis900_open(), sis900_start_xmit(), sis900_close(), etc. + */ + +static int sis900_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct sis900_private *sis_priv; + struct net_device *net_dev; + struct pci_dev *dev; + dma_addr_t ring_dma; + void *ring_space; + void __iomem *ioaddr; + int i, ret; + const char *card_name = card_names[pci_id->driver_data]; + const char *dev_name = pci_name(pci_dev); + +/* when built into the kernel, we only print version if device is found */ +#ifndef MODULE + static int printed_version; + if (!printed_version++) + printk(version); +#endif + + /* setup various bits in PCI command register */ + ret = pci_enable_device(pci_dev); + if(ret) return ret; + + i = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32)); + if(i){ + printk(KERN_ERR "sis900.c: architecture does not support " + "32bit PCI busmaster DMA\n"); + return i; + } + + pci_set_master(pci_dev); + + net_dev = alloc_etherdev(sizeof(struct sis900_private)); + if (!net_dev) + return -ENOMEM; + SET_NETDEV_DEV(net_dev, &pci_dev->dev); + + /* We do a request_region() to register /proc/ioports info. */ + ret = pci_request_regions(pci_dev, "sis900"); + if (ret) + goto err_out; + + /* IO region. */ + ioaddr = pci_iomap(pci_dev, 0, 0); + if (!ioaddr) { + ret = -ENOMEM; + goto err_out_cleardev; + } + + sis_priv = netdev_priv(net_dev); + sis_priv->ioaddr = ioaddr; + sis_priv->pci_dev = pci_dev; + spin_lock_init(&sis_priv->lock); + + pci_set_drvdata(pci_dev, net_dev); + + ring_space = pci_alloc_consistent(pci_dev, TX_TOTAL_SIZE, &ring_dma); + if (!ring_space) { + ret = -ENOMEM; + goto err_out_unmap; + } + sis_priv->tx_ring = ring_space; + sis_priv->tx_ring_dma = ring_dma; + + ring_space = pci_alloc_consistent(pci_dev, RX_TOTAL_SIZE, &ring_dma); + if (!ring_space) { + ret = -ENOMEM; + goto err_unmap_tx; + } + sis_priv->rx_ring = ring_space; + sis_priv->rx_ring_dma = ring_dma; + + /* The SiS900-specific entries in the device structure. */ + net_dev->netdev_ops = &sis900_netdev_ops; + net_dev->watchdog_timeo = TX_TIMEOUT; + net_dev->ethtool_ops = &sis900_ethtool_ops; + + if (sis900_debug > 0) + sis_priv->msg_enable = sis900_debug; + else + sis_priv->msg_enable = SIS900_DEF_MSG; + + sis_priv->mii_info.dev = net_dev; + sis_priv->mii_info.mdio_read = mdio_read; + sis_priv->mii_info.mdio_write = mdio_write; + sis_priv->mii_info.phy_id_mask = 0x1f; + sis_priv->mii_info.reg_num_mask = 0x1f; + + /* Get Mac address according to the chip revision */ + sis_priv->chipset_rev = pci_dev->revision; + if(netif_msg_probe(sis_priv)) + printk(KERN_DEBUG "%s: detected revision %2.2x, " + "trying to get MAC address...\n", + dev_name, sis_priv->chipset_rev); + + ret = 0; + if (sis_priv->chipset_rev == SIS630E_900_REV) + ret = sis630e_get_mac_addr(pci_dev, net_dev); + else if ((sis_priv->chipset_rev > 0x81) && (sis_priv->chipset_rev <= 0x90) ) + ret = sis635_get_mac_addr(pci_dev, net_dev); + else if (sis_priv->chipset_rev == SIS96x_900_REV) + ret = sis96x_get_mac_addr(pci_dev, net_dev); + else + ret = sis900_get_mac_addr(pci_dev, net_dev); + + if (!ret || !is_valid_ether_addr(net_dev->dev_addr)) { + eth_hw_addr_random(net_dev); + printk(KERN_WARNING "%s: Unreadable or invalid MAC address," + "using random generated one\n", dev_name); + } + + /* 630ET : set the mii access mode as software-mode */ + if (sis_priv->chipset_rev == SIS630ET_900_REV) + sw32(cr, ACCESSMODE | sr32(cr)); + + /* probe for mii transceiver */ + if (sis900_mii_probe(net_dev) == 0) { + printk(KERN_WARNING "%s: Error probing MII device.\n", + dev_name); + ret = -ENODEV; + goto err_unmap_rx; + } + + /* save our host bridge revision */ + dev = pci_get_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_630, NULL); + if (dev) { + sis_priv->host_bridge_rev = dev->revision; + pci_dev_put(dev); + } + + ret = register_netdev(net_dev); + if (ret) + goto err_unmap_rx; + + /* print some information about our NIC */ + printk(KERN_INFO "%s: %s at 0x%p, IRQ %d, %pM\n", + net_dev->name, card_name, ioaddr, pci_dev->irq, + net_dev->dev_addr); + + /* Detect Wake on Lan support */ + ret = (sr32(CFGPMC) & PMESP) >> 27; + if (netif_msg_probe(sis_priv) && (ret & PME_D3C) == 0) + printk(KERN_INFO "%s: Wake on LAN only available from suspend to RAM.", net_dev->name); + + return 0; + +err_unmap_rx: + pci_free_consistent(pci_dev, RX_TOTAL_SIZE, sis_priv->rx_ring, + sis_priv->rx_ring_dma); +err_unmap_tx: + pci_free_consistent(pci_dev, TX_TOTAL_SIZE, sis_priv->tx_ring, + sis_priv->tx_ring_dma); +err_out_unmap: + pci_iounmap(pci_dev, ioaddr); +err_out_cleardev: + pci_release_regions(pci_dev); + err_out: + free_netdev(net_dev); + return ret; +} + +/** + * sis900_mii_probe - Probe MII PHY for sis900 + * @net_dev: the net device to probe for + * + * Search for total of 32 possible mii phy addresses. + * Identify and set current phy if found one, + * return error if it failed to found. + */ + +static int sis900_mii_probe(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + const char *dev_name = pci_name(sis_priv->pci_dev); + u16 poll_bit = MII_STAT_LINK, status = 0; + unsigned long timeout = jiffies + 5 * HZ; + int phy_addr; + + sis_priv->mii = NULL; + + /* search for total of 32 possible mii phy addresses */ + for (phy_addr = 0; phy_addr < 32; phy_addr++) { + struct mii_phy * mii_phy = NULL; + u16 mii_status; + int i; + + mii_phy = NULL; + for(i = 0; i < 2; i++) + mii_status = mdio_read(net_dev, phy_addr, MII_STATUS); + + if (mii_status == 0xffff || mii_status == 0x0000) { + if (netif_msg_probe(sis_priv)) + printk(KERN_DEBUG "%s: MII at address %d" + " not accessible\n", + dev_name, phy_addr); + continue; + } + + if ((mii_phy = kmalloc(sizeof(struct mii_phy), GFP_KERNEL)) == NULL) { + mii_phy = sis_priv->first_mii; + while (mii_phy) { + struct mii_phy *phy; + phy = mii_phy; + mii_phy = mii_phy->next; + kfree(phy); + } + return 0; + } + + mii_phy->phy_id0 = mdio_read(net_dev, phy_addr, MII_PHY_ID0); + mii_phy->phy_id1 = mdio_read(net_dev, phy_addr, MII_PHY_ID1); + mii_phy->phy_addr = phy_addr; + mii_phy->status = mii_status; + mii_phy->next = sis_priv->mii; + sis_priv->mii = mii_phy; + sis_priv->first_mii = mii_phy; + + for (i = 0; mii_chip_table[i].phy_id1; i++) + if ((mii_phy->phy_id0 == mii_chip_table[i].phy_id0 ) && + ((mii_phy->phy_id1 & 0xFFF0) == mii_chip_table[i].phy_id1)){ + mii_phy->phy_types = mii_chip_table[i].phy_types; + if (mii_chip_table[i].phy_types == MIX) + mii_phy->phy_types = + (mii_status & (MII_STAT_CAN_TX_FDX | MII_STAT_CAN_TX)) ? LAN : HOME; + printk(KERN_INFO "%s: %s transceiver found " + "at address %d.\n", + dev_name, + mii_chip_table[i].name, + phy_addr); + break; + } + + if( !mii_chip_table[i].phy_id1 ) { + printk(KERN_INFO "%s: Unknown PHY transceiver found at address %d.\n", + dev_name, phy_addr); + mii_phy->phy_types = UNKNOWN; + } + } + + if (sis_priv->mii == NULL) { + printk(KERN_INFO "%s: No MII transceivers found!\n", dev_name); + return 0; + } + + /* select default PHY for mac */ + sis_priv->mii = NULL; + sis900_default_phy( net_dev ); + + /* Reset phy if default phy is internal sis900 */ + if ((sis_priv->mii->phy_id0 == 0x001D) && + ((sis_priv->mii->phy_id1&0xFFF0) == 0x8000)) + status = sis900_reset_phy(net_dev, sis_priv->cur_phy); + + /* workaround for ICS1893 PHY */ + if ((sis_priv->mii->phy_id0 == 0x0015) && + ((sis_priv->mii->phy_id1&0xFFF0) == 0xF440)) + mdio_write(net_dev, sis_priv->cur_phy, 0x0018, 0xD200); + + if(status & MII_STAT_LINK){ + while (poll_bit) { + yield(); + + poll_bit ^= (mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS) & poll_bit); + if (time_after_eq(jiffies, timeout)) { + printk(KERN_WARNING "%s: reset phy and link down now\n", + dev_name); + return -ETIME; + } + } + } + + if (sis_priv->chipset_rev == SIS630E_900_REV) { + /* SiS 630E has some bugs on default value of PHY registers */ + mdio_write(net_dev, sis_priv->cur_phy, MII_ANADV, 0x05e1); + mdio_write(net_dev, sis_priv->cur_phy, MII_CONFIG1, 0x22); + mdio_write(net_dev, sis_priv->cur_phy, MII_CONFIG2, 0xff00); + mdio_write(net_dev, sis_priv->cur_phy, MII_MASK, 0xffc0); + //mdio_write(net_dev, sis_priv->cur_phy, MII_CONTROL, 0x1000); + } + + if (sis_priv->mii->status & MII_STAT_LINK) + netif_carrier_on(net_dev); + else + netif_carrier_off(net_dev); + + return 1; +} + +/** + * sis900_default_phy - Select default PHY for sis900 mac. + * @net_dev: the net device to probe for + * + * Select first detected PHY with link as default. + * If no one is link on, select PHY whose types is HOME as default. + * If HOME doesn't exist, select LAN. + */ + +static u16 sis900_default_phy(struct net_device * net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + struct mii_phy *phy = NULL, *phy_home = NULL, + *default_phy = NULL, *phy_lan = NULL; + u16 status; + + for (phy=sis_priv->first_mii; phy; phy=phy->next) { + status = mdio_read(net_dev, phy->phy_addr, MII_STATUS); + status = mdio_read(net_dev, phy->phy_addr, MII_STATUS); + + /* Link ON & Not select default PHY & not ghost PHY */ + if ((status & MII_STAT_LINK) && !default_phy && + (phy->phy_types != UNKNOWN)) + default_phy = phy; + else { + status = mdio_read(net_dev, phy->phy_addr, MII_CONTROL); + mdio_write(net_dev, phy->phy_addr, MII_CONTROL, + status | MII_CNTL_AUTO | MII_CNTL_ISOLATE); + if (phy->phy_types == HOME) + phy_home = phy; + else if(phy->phy_types == LAN) + phy_lan = phy; + } + } + + if (!default_phy && phy_home) + default_phy = phy_home; + else if (!default_phy && phy_lan) + default_phy = phy_lan; + else if (!default_phy) + default_phy = sis_priv->first_mii; + + if (sis_priv->mii != default_phy) { + sis_priv->mii = default_phy; + sis_priv->cur_phy = default_phy->phy_addr; + printk(KERN_INFO "%s: Using transceiver found at address %d as default\n", + pci_name(sis_priv->pci_dev), sis_priv->cur_phy); + } + + sis_priv->mii_info.phy_id = sis_priv->cur_phy; + + status = mdio_read(net_dev, sis_priv->cur_phy, MII_CONTROL); + status &= (~MII_CNTL_ISOLATE); + + mdio_write(net_dev, sis_priv->cur_phy, MII_CONTROL, status); + status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS); + status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS); + + return status; +} + + +/** + * sis900_set_capability - set the media capability of network adapter. + * @net_dev : the net device to probe for + * @phy : default PHY + * + * Set the media capability of network adapter according to + * mii status register. It's necessary before auto-negotiate. + */ + +static void sis900_set_capability(struct net_device *net_dev, struct mii_phy *phy) +{ + u16 cap; + u16 status; + + status = mdio_read(net_dev, phy->phy_addr, MII_STATUS); + status = mdio_read(net_dev, phy->phy_addr, MII_STATUS); + + cap = MII_NWAY_CSMA_CD | + ((phy->status & MII_STAT_CAN_TX_FDX)? MII_NWAY_TX_FDX:0) | + ((phy->status & MII_STAT_CAN_TX) ? MII_NWAY_TX:0) | + ((phy->status & MII_STAT_CAN_T_FDX) ? MII_NWAY_T_FDX:0)| + ((phy->status & MII_STAT_CAN_T) ? MII_NWAY_T:0); + + mdio_write(net_dev, phy->phy_addr, MII_ANADV, cap); +} + + +/* Delay between EEPROM clock transitions. */ +#define eeprom_delay() sr32(mear) + +/** + * read_eeprom - Read Serial EEPROM + * @ioaddr: base i/o address + * @location: the EEPROM location to read + * + * Read Serial EEPROM through EEPROM Access Register. + * Note that location is in word (16 bits) unit + */ + +static u16 read_eeprom(void __iomem *ioaddr, int location) +{ + u32 read_cmd = location | EEread; + int i; + u16 retval = 0; + + sw32(mear, 0); + eeprom_delay(); + sw32(mear, EECS); + eeprom_delay(); + + /* Shift the read command (9) bits out. */ + for (i = 8; i >= 0; i--) { + u32 dataval = (read_cmd & (1 << i)) ? EEDI | EECS : EECS; + + sw32(mear, dataval); + eeprom_delay(); + sw32(mear, dataval | EECLK); + eeprom_delay(); + } + sw32(mear, EECS); + eeprom_delay(); + + /* read the 16-bits data in */ + for (i = 16; i > 0; i--) { + sw32(mear, EECS); + eeprom_delay(); + sw32(mear, EECS | EECLK); + eeprom_delay(); + retval = (retval << 1) | ((sr32(mear) & EEDO) ? 1 : 0); + eeprom_delay(); + } + + /* Terminate the EEPROM access. */ + sw32(mear, 0); + eeprom_delay(); + + return retval; +} + +/* Read and write the MII management registers using software-generated + serial MDIO protocol. Note that the command bits and data bits are + send out separately */ +#define mdio_delay() sr32(mear) + +static void mdio_idle(struct sis900_private *sp) +{ + void __iomem *ioaddr = sp->ioaddr; + + sw32(mear, MDIO | MDDIR); + mdio_delay(); + sw32(mear, MDIO | MDDIR | MDC); +} + +/* Synchronize the MII management interface by shifting 32 one bits out. */ +static void mdio_reset(struct sis900_private *sp) +{ + void __iomem *ioaddr = sp->ioaddr; + int i; + + for (i = 31; i >= 0; i--) { + sw32(mear, MDDIR | MDIO); + mdio_delay(); + sw32(mear, MDDIR | MDIO | MDC); + mdio_delay(); + } +} + +/** + * mdio_read - read MII PHY register + * @net_dev: the net device to read + * @phy_id: the phy address to read + * @location: the phy regiester id to read + * + * Read MII registers through MDIO and MDC + * using MDIO management frame structure and protocol(defined by ISO/IEC). + * Please see SiS7014 or ICS spec + */ + +static int mdio_read(struct net_device *net_dev, int phy_id, int location) +{ + int mii_cmd = MIIread|(phy_id<ioaddr; + u16 retval = 0; + int i; + + mdio_reset(sp); + mdio_idle(sp); + + for (i = 15; i >= 0; i--) { + int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR; + + sw32(mear, dataval); + mdio_delay(); + sw32(mear, dataval | MDC); + mdio_delay(); + } + + /* Read the 16 data bits. */ + for (i = 16; i > 0; i--) { + sw32(mear, 0); + mdio_delay(); + retval = (retval << 1) | ((sr32(mear) & MDIO) ? 1 : 0); + sw32(mear, MDC); + mdio_delay(); + } + sw32(mear, 0x00); + + return retval; +} + +/** + * mdio_write - write MII PHY register + * @net_dev: the net device to write + * @phy_id: the phy address to write + * @location: the phy regiester id to write + * @value: the register value to write with + * + * Write MII registers with @value through MDIO and MDC + * using MDIO management frame structure and protocol(defined by ISO/IEC) + * please see SiS7014 or ICS spec + */ + +static void mdio_write(struct net_device *net_dev, int phy_id, int location, + int value) +{ + int mii_cmd = MIIwrite|(phy_id<ioaddr; + int i; + + mdio_reset(sp); + mdio_idle(sp); + + /* Shift the command bits out. */ + for (i = 15; i >= 0; i--) { + int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR; + + sw8(mear, dataval); + mdio_delay(); + sw8(mear, dataval | MDC); + mdio_delay(); + } + mdio_delay(); + + /* Shift the value bits out. */ + for (i = 15; i >= 0; i--) { + int dataval = (value & (1 << i)) ? MDDIR | MDIO : MDDIR; + + sw32(mear, dataval); + mdio_delay(); + sw32(mear, dataval | MDC); + mdio_delay(); + } + mdio_delay(); + + /* Clear out extra bits. */ + for (i = 2; i > 0; i--) { + sw8(mear, 0); + mdio_delay(); + sw8(mear, MDC); + mdio_delay(); + } + sw32(mear, 0x00); +} + + +/** + * sis900_reset_phy - reset sis900 mii phy. + * @net_dev: the net device to write + * @phy_addr: default phy address + * + * Some specific phy can't work properly without reset. + * This function will be called during initialization and + * link status change from ON to DOWN. + */ + +static u16 sis900_reset_phy(struct net_device *net_dev, int phy_addr) +{ + int i; + u16 status; + + for (i = 0; i < 2; i++) + status = mdio_read(net_dev, phy_addr, MII_STATUS); + + mdio_write( net_dev, phy_addr, MII_CONTROL, MII_CNTL_RESET ); + + return status; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Polling 'interrupt' - used by things like netconsole to send skbs + * without having to re-enable interrupts. It's not called while + * the interrupt routine is executing. +*/ +static void sis900_poll(struct net_device *dev) +{ + struct sis900_private *sp = netdev_priv(dev); + const int irq = sp->pci_dev->irq; + + disable_irq(irq); + sis900_interrupt(irq, dev); + enable_irq(irq); +} +#endif + +/** + * sis900_open - open sis900 device + * @net_dev: the net device to open + * + * Do some initialization and start net interface. + * enable interrupts and set sis900 timer. + */ + +static int +sis900_open(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + int ret; + + /* Soft reset the chip. */ + sis900_reset(net_dev); + + /* Equalizer workaround Rule */ + sis630_set_eq(net_dev, sis_priv->chipset_rev); + + ret = request_irq(sis_priv->pci_dev->irq, sis900_interrupt, IRQF_SHARED, + net_dev->name, net_dev); + if (ret) + return ret; + + sis900_init_rxfilter(net_dev); + + sis900_init_tx_ring(net_dev); + sis900_init_rx_ring(net_dev); + + set_rx_mode(net_dev); + + netif_start_queue(net_dev); + + /* Workaround for EDB */ + sis900_set_mode(sis_priv, HW_SPEED_10_MBPS, FDX_CAPABLE_HALF_SELECTED); + + /* Enable all known interrupts by setting the interrupt mask. */ + sw32(imr, RxSOVR | RxORN | RxERR | RxOK | TxURN | TxERR | TxIDLE); + sw32(cr, RxENA | sr32(cr)); + sw32(ier, IE); + + sis900_check_mode(net_dev, sis_priv->mii); + + /* Set the timer to switch to check for link beat and perhaps switch + to an alternate media type. */ + init_timer(&sis_priv->timer); + sis_priv->timer.expires = jiffies + HZ; + sis_priv->timer.data = (unsigned long)net_dev; + sis_priv->timer.function = sis900_timer; + add_timer(&sis_priv->timer); + + return 0; +} + +/** + * sis900_init_rxfilter - Initialize the Rx filter + * @net_dev: the net device to initialize for + * + * Set receive filter address to our MAC address + * and enable packet filtering. + */ + +static void +sis900_init_rxfilter (struct net_device * net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + u32 rfcrSave; + u32 i; + + rfcrSave = sr32(rfcr); + + /* disable packet filtering before setting filter */ + sw32(rfcr, rfcrSave & ~RFEN); + + /* load MAC addr to filter data register */ + for (i = 0 ; i < 3 ; i++) { + u32 w = (u32) *((u16 *)(net_dev->dev_addr)+i); + + sw32(rfcr, i << RFADDR_shift); + sw32(rfdr, w); + + if (netif_msg_hw(sis_priv)) { + printk(KERN_DEBUG "%s: Receive Filter Addrss[%d]=%x\n", + net_dev->name, i, sr32(rfdr)); + } + } + + /* enable packet filtering */ + sw32(rfcr, rfcrSave | RFEN); +} + +/** + * sis900_init_tx_ring - Initialize the Tx descriptor ring + * @net_dev: the net device to initialize for + * + * Initialize the Tx descriptor ring, + */ + +static void +sis900_init_tx_ring(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + int i; + + sis_priv->tx_full = 0; + sis_priv->dirty_tx = sis_priv->cur_tx = 0; + + for (i = 0; i < NUM_TX_DESC; i++) { + sis_priv->tx_skbuff[i] = NULL; + + sis_priv->tx_ring[i].link = sis_priv->tx_ring_dma + + ((i+1)%NUM_TX_DESC)*sizeof(BufferDesc); + sis_priv->tx_ring[i].cmdsts = 0; + sis_priv->tx_ring[i].bufptr = 0; + } + + /* load Transmit Descriptor Register */ + sw32(txdp, sis_priv->tx_ring_dma); + if (netif_msg_hw(sis_priv)) + printk(KERN_DEBUG "%s: TX descriptor register loaded with: %8.8x\n", + net_dev->name, sr32(txdp)); +} + +/** + * sis900_init_rx_ring - Initialize the Rx descriptor ring + * @net_dev: the net device to initialize for + * + * Initialize the Rx descriptor ring, + * and pre-allocate recevie buffers (socket buffer) + */ + +static void +sis900_init_rx_ring(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + int i; + + sis_priv->cur_rx = 0; + sis_priv->dirty_rx = 0; + + /* init RX descriptor */ + for (i = 0; i < NUM_RX_DESC; i++) { + sis_priv->rx_skbuff[i] = NULL; + + sis_priv->rx_ring[i].link = sis_priv->rx_ring_dma + + ((i+1)%NUM_RX_DESC)*sizeof(BufferDesc); + sis_priv->rx_ring[i].cmdsts = 0; + sis_priv->rx_ring[i].bufptr = 0; + } + + /* allocate sock buffers */ + for (i = 0; i < NUM_RX_DESC; i++) { + struct sk_buff *skb; + + if ((skb = netdev_alloc_skb(net_dev, RX_BUF_SIZE)) == NULL) { + /* not enough memory for skbuff, this makes a "hole" + on the buffer ring, it is not clear how the + hardware will react to this kind of degenerated + buffer */ + break; + } + sis_priv->rx_skbuff[i] = skb; + sis_priv->rx_ring[i].cmdsts = RX_BUF_SIZE; + sis_priv->rx_ring[i].bufptr = pci_map_single(sis_priv->pci_dev, + skb->data, RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + if (unlikely(pci_dma_mapping_error(sis_priv->pci_dev, + sis_priv->rx_ring[i].bufptr))) { + dev_kfree_skb(skb); + sis_priv->rx_skbuff[i] = NULL; + break; + } + } + sis_priv->dirty_rx = (unsigned int) (i - NUM_RX_DESC); + + /* load Receive Descriptor Register */ + sw32(rxdp, sis_priv->rx_ring_dma); + if (netif_msg_hw(sis_priv)) + printk(KERN_DEBUG "%s: RX descriptor register loaded with: %8.8x\n", + net_dev->name, sr32(rxdp)); +} + +/** + * sis630_set_eq - set phy equalizer value for 630 LAN + * @net_dev: the net device to set equalizer value + * @revision: 630 LAN revision number + * + * 630E equalizer workaround rule(Cyrus Huang 08/15) + * PHY register 14h(Test) + * Bit 14: 0 -- Automatically detect (default) + * 1 -- Manually set Equalizer filter + * Bit 13: 0 -- (Default) + * 1 -- Speed up convergence of equalizer setting + * Bit 9 : 0 -- (Default) + * 1 -- Disable Baseline Wander + * Bit 3~7 -- Equalizer filter setting + * Link ON: Set Bit 9, 13 to 1, Bit 14 to 0 + * Then calculate equalizer value + * Then set equalizer value, and set Bit 14 to 1, Bit 9 to 0 + * Link Off:Set Bit 13 to 1, Bit 14 to 0 + * Calculate Equalizer value: + * When Link is ON and Bit 14 is 0, SIS900PHY will auto-detect proper equalizer value. + * When the equalizer is stable, this value is not a fixed value. It will be within + * a small range(eg. 7~9). Then we get a minimum and a maximum value(eg. min=7, max=9) + * 0 <= max <= 4 --> set equalizer to max + * 5 <= max <= 14 --> set equalizer to max+1 or set equalizer to max+2 if max == min + * max >= 15 --> set equalizer to max+5 or set equalizer to max+6 if max == min + */ + +static void sis630_set_eq(struct net_device *net_dev, u8 revision) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + u16 reg14h, eq_value=0, max_value=0, min_value=0; + int i, maxcount=10; + + if ( !(revision == SIS630E_900_REV || revision == SIS630EA1_900_REV || + revision == SIS630A_900_REV || revision == SIS630ET_900_REV) ) + return; + + if (netif_carrier_ok(net_dev)) { + reg14h = mdio_read(net_dev, sis_priv->cur_phy, MII_RESV); + mdio_write(net_dev, sis_priv->cur_phy, MII_RESV, + (0x2200 | reg14h) & 0xBFFF); + for (i=0; i < maxcount; i++) { + eq_value = (0x00F8 & mdio_read(net_dev, + sis_priv->cur_phy, MII_RESV)) >> 3; + if (i == 0) + max_value=min_value=eq_value; + max_value = (eq_value > max_value) ? + eq_value : max_value; + min_value = (eq_value < min_value) ? + eq_value : min_value; + } + /* 630E rule to determine the equalizer value */ + if (revision == SIS630E_900_REV || revision == SIS630EA1_900_REV || + revision == SIS630ET_900_REV) { + if (max_value < 5) + eq_value = max_value; + else if (max_value >= 5 && max_value < 15) + eq_value = (max_value == min_value) ? + max_value+2 : max_value+1; + else if (max_value >= 15) + eq_value=(max_value == min_value) ? + max_value+6 : max_value+5; + } + /* 630B0&B1 rule to determine the equalizer value */ + if (revision == SIS630A_900_REV && + (sis_priv->host_bridge_rev == SIS630B0 || + sis_priv->host_bridge_rev == SIS630B1)) { + if (max_value == 0) + eq_value = 3; + else + eq_value = (max_value + min_value + 1)/2; + } + /* write equalizer value and setting */ + reg14h = mdio_read(net_dev, sis_priv->cur_phy, MII_RESV); + reg14h = (reg14h & 0xFF07) | ((eq_value << 3) & 0x00F8); + reg14h = (reg14h | 0x6000) & 0xFDFF; + mdio_write(net_dev, sis_priv->cur_phy, MII_RESV, reg14h); + } else { + reg14h = mdio_read(net_dev, sis_priv->cur_phy, MII_RESV); + if (revision == SIS630A_900_REV && + (sis_priv->host_bridge_rev == SIS630B0 || + sis_priv->host_bridge_rev == SIS630B1)) + mdio_write(net_dev, sis_priv->cur_phy, MII_RESV, + (reg14h | 0x2200) & 0xBFFF); + else + mdio_write(net_dev, sis_priv->cur_phy, MII_RESV, + (reg14h | 0x2000) & 0xBFFF); + } +} + +/** + * sis900_timer - sis900 timer routine + * @data: pointer to sis900 net device + * + * On each timer ticks we check two things, + * link status (ON/OFF) and link mode (10/100/Full/Half) + */ + +static void sis900_timer(unsigned long data) +{ + struct net_device *net_dev = (struct net_device *)data; + struct sis900_private *sis_priv = netdev_priv(net_dev); + struct mii_phy *mii_phy = sis_priv->mii; + static const int next_tick = 5*HZ; + int speed = 0, duplex = 0; + u16 status; + + status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS); + status = mdio_read(net_dev, sis_priv->cur_phy, MII_STATUS); + + /* Link OFF -> ON */ + if (!netif_carrier_ok(net_dev)) { + LookForLink: + /* Search for new PHY */ + status = sis900_default_phy(net_dev); + mii_phy = sis_priv->mii; + + if (status & MII_STAT_LINK) { + WARN_ON(!(status & MII_STAT_AUTO_DONE)); + + sis900_read_mode(net_dev, &speed, &duplex); + if (duplex) { + sis900_set_mode(sis_priv, speed, duplex); + sis630_set_eq(net_dev, sis_priv->chipset_rev); + netif_carrier_on(net_dev); + } + } + } else { + /* Link ON -> OFF */ + if (!(status & MII_STAT_LINK)){ + netif_carrier_off(net_dev); + if(netif_msg_link(sis_priv)) + printk(KERN_INFO "%s: Media Link Off\n", net_dev->name); + + /* Change mode issue */ + if ((mii_phy->phy_id0 == 0x001D) && + ((mii_phy->phy_id1 & 0xFFF0) == 0x8000)) + sis900_reset_phy(net_dev, sis_priv->cur_phy); + + sis630_set_eq(net_dev, sis_priv->chipset_rev); + + goto LookForLink; + } + } + + sis_priv->timer.expires = jiffies + next_tick; + add_timer(&sis_priv->timer); +} + +/** + * sis900_check_mode - check the media mode for sis900 + * @net_dev: the net device to be checked + * @mii_phy: the mii phy + * + * Older driver gets the media mode from mii status output + * register. Now we set our media capability and auto-negotiate + * to get the upper bound of speed and duplex between two ends. + * If the types of mii phy is HOME, it doesn't need to auto-negotiate + * and autong_complete should be set to 1. + */ + +static void sis900_check_mode(struct net_device *net_dev, struct mii_phy *mii_phy) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + int speed, duplex; + + if (mii_phy->phy_types == LAN) { + sw32(cfg, ~EXD & sr32(cfg)); + sis900_set_capability(net_dev , mii_phy); + sis900_auto_negotiate(net_dev, sis_priv->cur_phy); + } else { + sw32(cfg, EXD | sr32(cfg)); + speed = HW_SPEED_HOME; + duplex = FDX_CAPABLE_HALF_SELECTED; + sis900_set_mode(sis_priv, speed, duplex); + sis_priv->autong_complete = 1; + } +} + +/** + * sis900_set_mode - Set the media mode of mac register. + * @sp: the device private data + * @speed : the transmit speed to be determined + * @duplex: the duplex mode to be determined + * + * Set the media mode of mac register txcfg/rxcfg according to + * speed and duplex of phy. Bit EDB_MASTER_EN indicates the EDB + * bus is used instead of PCI bus. When this bit is set 1, the + * Max DMA Burst Size for TX/RX DMA should be no larger than 16 + * double words. + */ + +static void sis900_set_mode(struct sis900_private *sp, int speed, int duplex) +{ + void __iomem *ioaddr = sp->ioaddr; + u32 tx_flags = 0, rx_flags = 0; + + if (sr32( cfg) & EDB_MASTER_EN) { + tx_flags = TxATP | (DMA_BURST_64 << TxMXDMA_shift) | + (TX_FILL_THRESH << TxFILLT_shift); + rx_flags = DMA_BURST_64 << RxMXDMA_shift; + } else { + tx_flags = TxATP | (DMA_BURST_512 << TxMXDMA_shift) | + (TX_FILL_THRESH << TxFILLT_shift); + rx_flags = DMA_BURST_512 << RxMXDMA_shift; + } + + if (speed == HW_SPEED_HOME || speed == HW_SPEED_10_MBPS) { + rx_flags |= (RxDRNT_10 << RxDRNT_shift); + tx_flags |= (TxDRNT_10 << TxDRNT_shift); + } else { + rx_flags |= (RxDRNT_100 << RxDRNT_shift); + tx_flags |= (TxDRNT_100 << TxDRNT_shift); + } + + if (duplex == FDX_CAPABLE_FULL_SELECTED) { + tx_flags |= (TxCSI | TxHBI); + rx_flags |= RxATX; + } + +#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE) + /* Can accept Jumbo packet */ + rx_flags |= RxAJAB; +#endif + + sw32(txcfg, tx_flags); + sw32(rxcfg, rx_flags); +} + +/** + * sis900_auto_negotiate - Set the Auto-Negotiation Enable/Reset bit. + * @net_dev: the net device to read mode for + * @phy_addr: mii phy address + * + * If the adapter is link-on, set the auto-negotiate enable/reset bit. + * autong_complete should be set to 0 when starting auto-negotiation. + * autong_complete should be set to 1 if we didn't start auto-negotiation. + * sis900_timer will wait for link on again if autong_complete = 0. + */ + +static void sis900_auto_negotiate(struct net_device *net_dev, int phy_addr) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + int i = 0; + u32 status; + + for (i = 0; i < 2; i++) + status = mdio_read(net_dev, phy_addr, MII_STATUS); + + if (!(status & MII_STAT_LINK)){ + if(netif_msg_link(sis_priv)) + printk(KERN_INFO "%s: Media Link Off\n", net_dev->name); + sis_priv->autong_complete = 1; + netif_carrier_off(net_dev); + return; + } + + /* (Re)start AutoNegotiate */ + mdio_write(net_dev, phy_addr, MII_CONTROL, + MII_CNTL_AUTO | MII_CNTL_RST_AUTO); + sis_priv->autong_complete = 0; +} + + +/** + * sis900_read_mode - read media mode for sis900 internal phy + * @net_dev: the net device to read mode for + * @speed : the transmit speed to be determined + * @duplex : the duplex mode to be determined + * + * The capability of remote end will be put in mii register autorec + * after auto-negotiation. Use AND operation to get the upper bound + * of speed and duplex between two ends. + */ + +static void sis900_read_mode(struct net_device *net_dev, int *speed, int *duplex) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + struct mii_phy *phy = sis_priv->mii; + int phy_addr = sis_priv->cur_phy; + u32 status; + u16 autoadv, autorec; + int i; + + for (i = 0; i < 2; i++) + status = mdio_read(net_dev, phy_addr, MII_STATUS); + + if (!(status & MII_STAT_LINK)) + return; + + /* AutoNegotiate completed */ + autoadv = mdio_read(net_dev, phy_addr, MII_ANADV); + autorec = mdio_read(net_dev, phy_addr, MII_ANLPAR); + status = autoadv & autorec; + + *speed = HW_SPEED_10_MBPS; + *duplex = FDX_CAPABLE_HALF_SELECTED; + + if (status & (MII_NWAY_TX | MII_NWAY_TX_FDX)) + *speed = HW_SPEED_100_MBPS; + if (status & ( MII_NWAY_TX_FDX | MII_NWAY_T_FDX)) + *duplex = FDX_CAPABLE_FULL_SELECTED; + + sis_priv->autong_complete = 1; + + /* Workaround for Realtek RTL8201 PHY issue */ + if ((phy->phy_id0 == 0x0000) && ((phy->phy_id1 & 0xFFF0) == 0x8200)) { + if (mdio_read(net_dev, phy_addr, MII_CONTROL) & MII_CNTL_FDX) + *duplex = FDX_CAPABLE_FULL_SELECTED; + if (mdio_read(net_dev, phy_addr, 0x0019) & 0x01) + *speed = HW_SPEED_100_MBPS; + } + + if(netif_msg_link(sis_priv)) + printk(KERN_INFO "%s: Media Link On %s %s-duplex\n", + net_dev->name, + *speed == HW_SPEED_100_MBPS ? + "100mbps" : "10mbps", + *duplex == FDX_CAPABLE_FULL_SELECTED ? + "full" : "half"); +} + +/** + * sis900_tx_timeout - sis900 transmit timeout routine + * @net_dev: the net device to transmit + * + * print transmit timeout status + * disable interrupts and do some tasks + */ + +static void sis900_tx_timeout(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + unsigned long flags; + int i; + + if (netif_msg_tx_err(sis_priv)) { + printk(KERN_INFO "%s: Transmit timeout, status %8.8x %8.8x\n", + net_dev->name, sr32(cr), sr32(isr)); + } + + /* Disable interrupts by clearing the interrupt mask. */ + sw32(imr, 0x0000); + + /* use spinlock to prevent interrupt handler accessing buffer ring */ + spin_lock_irqsave(&sis_priv->lock, flags); + + /* discard unsent packets */ + sis_priv->dirty_tx = sis_priv->cur_tx = 0; + for (i = 0; i < NUM_TX_DESC; i++) { + struct sk_buff *skb = sis_priv->tx_skbuff[i]; + + if (skb) { + pci_unmap_single(sis_priv->pci_dev, + sis_priv->tx_ring[i].bufptr, skb->len, + PCI_DMA_TODEVICE); + dev_kfree_skb_irq(skb); + sis_priv->tx_skbuff[i] = NULL; + sis_priv->tx_ring[i].cmdsts = 0; + sis_priv->tx_ring[i].bufptr = 0; + net_dev->stats.tx_dropped++; + } + } + sis_priv->tx_full = 0; + netif_wake_queue(net_dev); + + spin_unlock_irqrestore(&sis_priv->lock, flags); + + net_dev->trans_start = jiffies; /* prevent tx timeout */ + + /* load Transmit Descriptor Register */ + sw32(txdp, sis_priv->tx_ring_dma); + + /* Enable all known interrupts by setting the interrupt mask. */ + sw32(imr, RxSOVR | RxORN | RxERR | RxOK | TxURN | TxERR | TxIDLE); +} + +/** + * sis900_start_xmit - sis900 start transmit routine + * @skb: socket buffer pointer to put the data being transmitted + * @net_dev: the net device to transmit with + * + * Set the transmit buffer descriptor, + * and write TxENA to enable transmit state machine. + * tell upper layer if the buffer is full + */ + +static netdev_tx_t +sis900_start_xmit(struct sk_buff *skb, struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + unsigned int entry; + unsigned long flags; + unsigned int index_cur_tx, index_dirty_tx; + unsigned int count_dirty_tx; + + spin_lock_irqsave(&sis_priv->lock, flags); + + /* Calculate the next Tx descriptor entry. */ + entry = sis_priv->cur_tx % NUM_TX_DESC; + sis_priv->tx_skbuff[entry] = skb; + + /* set the transmit buffer descriptor and enable Transmit State Machine */ + sis_priv->tx_ring[entry].bufptr = pci_map_single(sis_priv->pci_dev, + skb->data, skb->len, PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(sis_priv->pci_dev, + sis_priv->tx_ring[entry].bufptr))) { + dev_kfree_skb_any(skb); + sis_priv->tx_skbuff[entry] = NULL; + net_dev->stats.tx_dropped++; + spin_unlock_irqrestore(&sis_priv->lock, flags); + return NETDEV_TX_OK; + } + sis_priv->tx_ring[entry].cmdsts = (OWN | skb->len); + sw32(cr, TxENA | sr32(cr)); + + sis_priv->cur_tx ++; + index_cur_tx = sis_priv->cur_tx; + index_dirty_tx = sis_priv->dirty_tx; + + for (count_dirty_tx = 0; index_cur_tx != index_dirty_tx; index_dirty_tx++) + count_dirty_tx ++; + + if (index_cur_tx == index_dirty_tx) { + /* dirty_tx is met in the cycle of cur_tx, buffer full */ + sis_priv->tx_full = 1; + netif_stop_queue(net_dev); + } else if (count_dirty_tx < NUM_TX_DESC) { + /* Typical path, tell upper layer that more transmission is possible */ + netif_start_queue(net_dev); + } else { + /* buffer full, tell upper layer no more transmission */ + sis_priv->tx_full = 1; + netif_stop_queue(net_dev); + } + + spin_unlock_irqrestore(&sis_priv->lock, flags); + + if (netif_msg_tx_queued(sis_priv)) + printk(KERN_DEBUG "%s: Queued Tx packet at %p size %d " + "to slot %d.\n", + net_dev->name, skb->data, (int)skb->len, entry); + + return NETDEV_TX_OK; +} + +/** + * sis900_interrupt - sis900 interrupt handler + * @irq: the irq number + * @dev_instance: the client data object + * + * The interrupt handler does all of the Rx thread work, + * and cleans up after the Tx thread + */ + +static irqreturn_t sis900_interrupt(int irq, void *dev_instance) +{ + struct net_device *net_dev = dev_instance; + struct sis900_private *sis_priv = netdev_priv(net_dev); + int boguscnt = max_interrupt_work; + void __iomem *ioaddr = sis_priv->ioaddr; + u32 status; + unsigned int handled = 0; + + spin_lock (&sis_priv->lock); + + do { + status = sr32(isr); + + if ((status & (HIBERR|TxURN|TxERR|TxIDLE|RxORN|RxERR|RxOK)) == 0) + /* nothing intresting happened */ + break; + handled = 1; + + /* why dow't we break after Tx/Rx case ?? keyword: full-duplex */ + if (status & (RxORN | RxERR | RxOK)) + /* Rx interrupt */ + sis900_rx(net_dev); + + if (status & (TxURN | TxERR | TxIDLE)) + /* Tx interrupt */ + sis900_finish_xmit(net_dev); + + /* something strange happened !!! */ + if (status & HIBERR) { + if(netif_msg_intr(sis_priv)) + printk(KERN_INFO "%s: Abnormal interrupt, " + "status %#8.8x.\n", net_dev->name, status); + break; + } + if (--boguscnt < 0) { + if(netif_msg_intr(sis_priv)) + printk(KERN_INFO "%s: Too much work at interrupt, " + "interrupt status = %#8.8x.\n", + net_dev->name, status); + break; + } + } while (1); + + if(netif_msg_intr(sis_priv)) + printk(KERN_DEBUG "%s: exiting interrupt, " + "interrupt status = %#8.8x\n", + net_dev->name, sr32(isr)); + + spin_unlock (&sis_priv->lock); + return IRQ_RETVAL(handled); +} + +/** + * sis900_rx - sis900 receive routine + * @net_dev: the net device which receives data + * + * Process receive interrupt events, + * put buffer to higher layer and refill buffer pool + * Note: This function is called by interrupt handler, + * don't do "too much" work here + */ + +static int sis900_rx(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + unsigned int entry = sis_priv->cur_rx % NUM_RX_DESC; + u32 rx_status = sis_priv->rx_ring[entry].cmdsts; + int rx_work_limit; + + if (netif_msg_rx_status(sis_priv)) + printk(KERN_DEBUG "sis900_rx, cur_rx:%4.4d, dirty_rx:%4.4d " + "status:0x%8.8x\n", + sis_priv->cur_rx, sis_priv->dirty_rx, rx_status); + rx_work_limit = sis_priv->dirty_rx + NUM_RX_DESC - sis_priv->cur_rx; + + while (rx_status & OWN) { + unsigned int rx_size; + unsigned int data_size; + + if (--rx_work_limit < 0) + break; + + data_size = rx_status & DSIZE; + rx_size = data_size - CRC_SIZE; + +#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE) + /* ``TOOLONG'' flag means jumbo packet received. */ + if ((rx_status & TOOLONG) && data_size <= MAX_FRAME_SIZE) + rx_status &= (~ ((unsigned int)TOOLONG)); +#endif + + if (rx_status & (ABORT|OVERRUN|TOOLONG|RUNT|RXISERR|CRCERR|FAERR)) { + /* corrupted packet received */ + if (netif_msg_rx_err(sis_priv)) + printk(KERN_DEBUG "%s: Corrupted packet " + "received, buffer status = 0x%8.8x/%d.\n", + net_dev->name, rx_status, data_size); + net_dev->stats.rx_errors++; + if (rx_status & OVERRUN) + net_dev->stats.rx_over_errors++; + if (rx_status & (TOOLONG|RUNT)) + net_dev->stats.rx_length_errors++; + if (rx_status & (RXISERR | FAERR)) + net_dev->stats.rx_frame_errors++; + if (rx_status & CRCERR) + net_dev->stats.rx_crc_errors++; + /* reset buffer descriptor state */ + sis_priv->rx_ring[entry].cmdsts = RX_BUF_SIZE; + } else { + struct sk_buff * skb; + struct sk_buff * rx_skb; + + pci_unmap_single(sis_priv->pci_dev, + sis_priv->rx_ring[entry].bufptr, RX_BUF_SIZE, + PCI_DMA_FROMDEVICE); + + /* refill the Rx buffer, what if there is not enough + * memory for new socket buffer ?? */ + if ((skb = netdev_alloc_skb(net_dev, RX_BUF_SIZE)) == NULL) { + /* + * Not enough memory to refill the buffer + * so we need to recycle the old one so + * as to avoid creating a memory hole + * in the rx ring + */ + skb = sis_priv->rx_skbuff[entry]; + net_dev->stats.rx_dropped++; + goto refill_rx_ring; + } + + /* This situation should never happen, but due to + some unknown bugs, it is possible that + we are working on NULL sk_buff :-( */ + if (sis_priv->rx_skbuff[entry] == NULL) { + if (netif_msg_rx_err(sis_priv)) + printk(KERN_WARNING "%s: NULL pointer " + "encountered in Rx ring\n" + "cur_rx:%4.4d, dirty_rx:%4.4d\n", + net_dev->name, sis_priv->cur_rx, + sis_priv->dirty_rx); + dev_kfree_skb(skb); + break; + } + + /* give the socket buffer to upper layers */ + rx_skb = sis_priv->rx_skbuff[entry]; + skb_put(rx_skb, rx_size); + rx_skb->protocol = eth_type_trans(rx_skb, net_dev); + netif_rx(rx_skb); + + /* some network statistics */ + if ((rx_status & BCAST) == MCAST) + net_dev->stats.multicast++; + net_dev->stats.rx_bytes += rx_size; + net_dev->stats.rx_packets++; + sis_priv->dirty_rx++; +refill_rx_ring: + sis_priv->rx_skbuff[entry] = skb; + sis_priv->rx_ring[entry].cmdsts = RX_BUF_SIZE; + sis_priv->rx_ring[entry].bufptr = + pci_map_single(sis_priv->pci_dev, skb->data, + RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + if (unlikely(pci_dma_mapping_error(sis_priv->pci_dev, + sis_priv->rx_ring[entry].bufptr))) { + dev_kfree_skb_irq(skb); + sis_priv->rx_skbuff[entry] = NULL; + break; + } + } + sis_priv->cur_rx++; + entry = sis_priv->cur_rx % NUM_RX_DESC; + rx_status = sis_priv->rx_ring[entry].cmdsts; + } // while + + /* refill the Rx buffer, what if the rate of refilling is slower + * than consuming ?? */ + for (; sis_priv->cur_rx != sis_priv->dirty_rx; sis_priv->dirty_rx++) { + struct sk_buff *skb; + + entry = sis_priv->dirty_rx % NUM_RX_DESC; + + if (sis_priv->rx_skbuff[entry] == NULL) { + skb = netdev_alloc_skb(net_dev, RX_BUF_SIZE); + if (skb == NULL) { + /* not enough memory for skbuff, this makes a + * "hole" on the buffer ring, it is not clear + * how the hardware will react to this kind + * of degenerated buffer */ + net_dev->stats.rx_dropped++; + break; + } + sis_priv->rx_skbuff[entry] = skb; + sis_priv->rx_ring[entry].cmdsts = RX_BUF_SIZE; + sis_priv->rx_ring[entry].bufptr = + pci_map_single(sis_priv->pci_dev, skb->data, + RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + if (unlikely(pci_dma_mapping_error(sis_priv->pci_dev, + sis_priv->rx_ring[entry].bufptr))) { + dev_kfree_skb_irq(skb); + sis_priv->rx_skbuff[entry] = NULL; + break; + } + } + } + /* re-enable the potentially idle receive state matchine */ + sw32(cr , RxENA | sr32(cr)); + + return 0; +} + +/** + * sis900_finish_xmit - finish up transmission of packets + * @net_dev: the net device to be transmitted on + * + * Check for error condition and free socket buffer etc + * schedule for more transmission as needed + * Note: This function is called by interrupt handler, + * don't do "too much" work here + */ + +static void sis900_finish_xmit (struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + + for (; sis_priv->dirty_tx != sis_priv->cur_tx; sis_priv->dirty_tx++) { + struct sk_buff *skb; + unsigned int entry; + u32 tx_status; + + entry = sis_priv->dirty_tx % NUM_TX_DESC; + tx_status = sis_priv->tx_ring[entry].cmdsts; + + if (tx_status & OWN) { + /* The packet is not transmitted yet (owned by hardware) ! + * Note: the interrupt is generated only when Tx Machine + * is idle, so this is an almost impossible case */ + break; + } + + if (tx_status & (ABORT | UNDERRUN | OWCOLL)) { + /* packet unsuccessfully transmitted */ + if (netif_msg_tx_err(sis_priv)) + printk(KERN_DEBUG "%s: Transmit " + "error, Tx status %8.8x.\n", + net_dev->name, tx_status); + net_dev->stats.tx_errors++; + if (tx_status & UNDERRUN) + net_dev->stats.tx_fifo_errors++; + if (tx_status & ABORT) + net_dev->stats.tx_aborted_errors++; + if (tx_status & NOCARRIER) + net_dev->stats.tx_carrier_errors++; + if (tx_status & OWCOLL) + net_dev->stats.tx_window_errors++; + } else { + /* packet successfully transmitted */ + net_dev->stats.collisions += (tx_status & COLCNT) >> 16; + net_dev->stats.tx_bytes += tx_status & DSIZE; + net_dev->stats.tx_packets++; + } + /* Free the original skb. */ + skb = sis_priv->tx_skbuff[entry]; + pci_unmap_single(sis_priv->pci_dev, + sis_priv->tx_ring[entry].bufptr, skb->len, + PCI_DMA_TODEVICE); + dev_kfree_skb_irq(skb); + sis_priv->tx_skbuff[entry] = NULL; + sis_priv->tx_ring[entry].bufptr = 0; + sis_priv->tx_ring[entry].cmdsts = 0; + } + + if (sis_priv->tx_full && netif_queue_stopped(net_dev) && + sis_priv->cur_tx - sis_priv->dirty_tx < NUM_TX_DESC - 4) { + /* The ring is no longer full, clear tx_full and schedule + * more transmission by netif_wake_queue(net_dev) */ + sis_priv->tx_full = 0; + netif_wake_queue (net_dev); + } +} + +/** + * sis900_close - close sis900 device + * @net_dev: the net device to be closed + * + * Disable interrupts, stop the Tx and Rx Status Machine + * free Tx and RX socket buffer + */ + +static int sis900_close(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + struct pci_dev *pdev = sis_priv->pci_dev; + void __iomem *ioaddr = sis_priv->ioaddr; + struct sk_buff *skb; + int i; + + netif_stop_queue(net_dev); + + /* Disable interrupts by clearing the interrupt mask. */ + sw32(imr, 0x0000); + sw32(ier, 0x0000); + + /* Stop the chip's Tx and Rx Status Machine */ + sw32(cr, RxDIS | TxDIS | sr32(cr)); + + del_timer(&sis_priv->timer); + + free_irq(pdev->irq, net_dev); + + /* Free Tx and RX skbuff */ + for (i = 0; i < NUM_RX_DESC; i++) { + skb = sis_priv->rx_skbuff[i]; + if (skb) { + pci_unmap_single(pdev, sis_priv->rx_ring[i].bufptr, + RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + dev_kfree_skb(skb); + sis_priv->rx_skbuff[i] = NULL; + } + } + for (i = 0; i < NUM_TX_DESC; i++) { + skb = sis_priv->tx_skbuff[i]; + if (skb) { + pci_unmap_single(pdev, sis_priv->tx_ring[i].bufptr, + skb->len, PCI_DMA_TODEVICE); + dev_kfree_skb(skb); + sis_priv->tx_skbuff[i] = NULL; + } + } + + /* Green! Put the chip in low-power mode. */ + + return 0; +} + +/** + * sis900_get_drvinfo - Return information about driver + * @net_dev: the net device to probe + * @info: container for info returned + * + * Process ethtool command such as "ehtool -i" to show information + */ + +static void sis900_get_drvinfo(struct net_device *net_dev, + struct ethtool_drvinfo *info) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + + strlcpy(info->driver, SIS900_MODULE_NAME, sizeof(info->driver)); + strlcpy(info->version, SIS900_DRV_VERSION, sizeof(info->version)); + strlcpy(info->bus_info, pci_name(sis_priv->pci_dev), + sizeof(info->bus_info)); +} + +static u32 sis900_get_msglevel(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + return sis_priv->msg_enable; +} + +static void sis900_set_msglevel(struct net_device *net_dev, u32 value) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + sis_priv->msg_enable = value; +} + +static u32 sis900_get_link(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + return mii_link_ok(&sis_priv->mii_info); +} + +static int sis900_get_settings(struct net_device *net_dev, + struct ethtool_cmd *cmd) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + spin_lock_irq(&sis_priv->lock); + mii_ethtool_gset(&sis_priv->mii_info, cmd); + spin_unlock_irq(&sis_priv->lock); + return 0; +} + +static int sis900_set_settings(struct net_device *net_dev, + struct ethtool_cmd *cmd) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + int rt; + spin_lock_irq(&sis_priv->lock); + rt = mii_ethtool_sset(&sis_priv->mii_info, cmd); + spin_unlock_irq(&sis_priv->lock); + return rt; +} + +static int sis900_nway_reset(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + return mii_nway_restart(&sis_priv->mii_info); +} + +/** + * sis900_set_wol - Set up Wake on Lan registers + * @net_dev: the net device to probe + * @wol: container for info passed to the driver + * + * Process ethtool command "wol" to setup wake on lan features. + * SiS900 supports sending WoL events if a correct packet is received, + * but there is no simple way to filter them to only a subset (broadcast, + * multicast, unicast or arp). + */ + +static int sis900_set_wol(struct net_device *net_dev, struct ethtool_wolinfo *wol) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + u32 cfgpmcsr = 0, pmctrl_bits = 0; + + if (wol->wolopts == 0) { + pci_read_config_dword(sis_priv->pci_dev, CFGPMCSR, &cfgpmcsr); + cfgpmcsr &= ~PME_EN; + pci_write_config_dword(sis_priv->pci_dev, CFGPMCSR, cfgpmcsr); + sw32(pmctrl, pmctrl_bits); + if (netif_msg_wol(sis_priv)) + printk(KERN_DEBUG "%s: Wake on LAN disabled\n", net_dev->name); + return 0; + } + + if (wol->wolopts & (WAKE_MAGICSECURE | WAKE_UCAST | WAKE_MCAST + | WAKE_BCAST | WAKE_ARP)) + return -EINVAL; + + if (wol->wolopts & WAKE_MAGIC) + pmctrl_bits |= MAGICPKT; + if (wol->wolopts & WAKE_PHY) + pmctrl_bits |= LINKON; + + sw32(pmctrl, pmctrl_bits); + + pci_read_config_dword(sis_priv->pci_dev, CFGPMCSR, &cfgpmcsr); + cfgpmcsr |= PME_EN; + pci_write_config_dword(sis_priv->pci_dev, CFGPMCSR, cfgpmcsr); + if (netif_msg_wol(sis_priv)) + printk(KERN_DEBUG "%s: Wake on LAN enabled\n", net_dev->name); + + return 0; +} + +static void sis900_get_wol(struct net_device *net_dev, struct ethtool_wolinfo *wol) +{ + struct sis900_private *sp = netdev_priv(net_dev); + void __iomem *ioaddr = sp->ioaddr; + u32 pmctrl_bits; + + pmctrl_bits = sr32(pmctrl); + if (pmctrl_bits & MAGICPKT) + wol->wolopts |= WAKE_MAGIC; + if (pmctrl_bits & LINKON) + wol->wolopts |= WAKE_PHY; + + wol->supported = (WAKE_PHY | WAKE_MAGIC); +} + +static const struct ethtool_ops sis900_ethtool_ops = { + .get_drvinfo = sis900_get_drvinfo, + .get_msglevel = sis900_get_msglevel, + .set_msglevel = sis900_set_msglevel, + .get_link = sis900_get_link, + .get_settings = sis900_get_settings, + .set_settings = sis900_set_settings, + .nway_reset = sis900_nway_reset, + .get_wol = sis900_get_wol, + .set_wol = sis900_set_wol +}; + +/** + * mii_ioctl - process MII i/o control command + * @net_dev: the net device to command for + * @rq: parameter for command + * @cmd: the i/o command + * + * Process MII command like read/write MII register + */ + +static int mii_ioctl(struct net_device *net_dev, struct ifreq *rq, int cmd) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + struct mii_ioctl_data *data = if_mii(rq); + + switch(cmd) { + case SIOCGMIIPHY: /* Get address of MII PHY in use. */ + data->phy_id = sis_priv->mii->phy_addr; + /* Fall Through */ + + case SIOCGMIIREG: /* Read MII PHY register. */ + data->val_out = mdio_read(net_dev, data->phy_id & 0x1f, data->reg_num & 0x1f); + return 0; + + case SIOCSMIIREG: /* Write MII PHY register. */ + mdio_write(net_dev, data->phy_id & 0x1f, data->reg_num & 0x1f, data->val_in); + return 0; + default: + return -EOPNOTSUPP; + } +} + +/** + * sis900_set_config - Set media type by net_device.set_config + * @dev: the net device for media type change + * @map: ifmap passed by ifconfig + * + * Set media type to 10baseT, 100baseT or 0(for auto) by ifconfig + * we support only port changes. All other runtime configuration + * changes will be ignored + */ + +static int sis900_set_config(struct net_device *dev, struct ifmap *map) +{ + struct sis900_private *sis_priv = netdev_priv(dev); + struct mii_phy *mii_phy = sis_priv->mii; + + u16 status; + + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + /* we switch on the ifmap->port field. I couldn't find anything + * like a definition or standard for the values of that field. + * I think the meaning of those values is device specific. But + * since I would like to change the media type via the ifconfig + * command I use the definition from linux/netdevice.h + * (which seems to be different from the ifport(pcmcia) definition) */ + switch(map->port){ + case IF_PORT_UNKNOWN: /* use auto here */ + dev->if_port = map->port; + /* we are going to change the media type, so the Link + * will be temporary down and we need to reflect that + * here. When the Link comes up again, it will be + * sensed by the sis_timer procedure, which also does + * all the rest for us */ + netif_carrier_off(dev); + + /* read current state */ + status = mdio_read(dev, mii_phy->phy_addr, MII_CONTROL); + + /* enable auto negotiation and reset the negotioation + * (I don't really know what the auto negatiotiation + * reset really means, but it sounds for me right to + * do one here) */ + mdio_write(dev, mii_phy->phy_addr, + MII_CONTROL, status | MII_CNTL_AUTO | MII_CNTL_RST_AUTO); + + break; + + case IF_PORT_10BASET: /* 10BaseT */ + dev->if_port = map->port; + + /* we are going to change the media type, so the Link + * will be temporary down and we need to reflect that + * here. When the Link comes up again, it will be + * sensed by the sis_timer procedure, which also does + * all the rest for us */ + netif_carrier_off(dev); + + /* set Speed to 10Mbps */ + /* read current state */ + status = mdio_read(dev, mii_phy->phy_addr, MII_CONTROL); + + /* disable auto negotiation and force 10MBit mode*/ + mdio_write(dev, mii_phy->phy_addr, + MII_CONTROL, status & ~(MII_CNTL_SPEED | + MII_CNTL_AUTO)); + break; + + case IF_PORT_100BASET: /* 100BaseT */ + case IF_PORT_100BASETX: /* 100BaseTx */ + dev->if_port = map->port; + + /* we are going to change the media type, so the Link + * will be temporary down and we need to reflect that + * here. When the Link comes up again, it will be + * sensed by the sis_timer procedure, which also does + * all the rest for us */ + netif_carrier_off(dev); + + /* set Speed to 100Mbps */ + /* disable auto negotiation and enable 100MBit Mode */ + status = mdio_read(dev, mii_phy->phy_addr, MII_CONTROL); + mdio_write(dev, mii_phy->phy_addr, + MII_CONTROL, (status & ~MII_CNTL_SPEED) | + MII_CNTL_SPEED); + + break; + + case IF_PORT_10BASE2: /* 10Base2 */ + case IF_PORT_AUI: /* AUI */ + case IF_PORT_100BASEFX: /* 100BaseFx */ + /* These Modes are not supported (are they?)*/ + return -EOPNOTSUPP; + + default: + return -EINVAL; + } + } + return 0; +} + +/** + * sis900_mcast_bitnr - compute hashtable index + * @addr: multicast address + * @revision: revision id of chip + * + * SiS 900 uses the most sigificant 7 bits to index a 128 bits multicast + * hash table, which makes this function a little bit different from other drivers + * SiS 900 B0 & 635 M/B uses the most significat 8 bits to index 256 bits + * multicast hash table. + */ + +static inline u16 sis900_mcast_bitnr(u8 *addr, u8 revision) +{ + + u32 crc = ether_crc(6, addr); + + /* leave 8 or 7 most siginifant bits */ + if ((revision >= SIS635A_900_REV) || (revision == SIS900B_900_REV)) + return (int)(crc >> 24); + else + return (int)(crc >> 25); +} + +/** + * set_rx_mode - Set SiS900 receive mode + * @net_dev: the net device to be set + * + * Set SiS900 receive mode for promiscuous, multicast, or broadcast mode. + * And set the appropriate multicast filter. + * Multicast hash table changes from 128 to 256 bits for 635M/B & 900B0. + */ + +static void set_rx_mode(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + u16 mc_filter[16] = {0}; /* 256/128 bits multicast hash table */ + int i, table_entries; + u32 rx_mode; + + /* 635 Hash Table entries = 256(2^16) */ + if((sis_priv->chipset_rev >= SIS635A_900_REV) || + (sis_priv->chipset_rev == SIS900B_900_REV)) + table_entries = 16; + else + table_entries = 8; + + if (net_dev->flags & IFF_PROMISC) { + /* Accept any kinds of packets */ + rx_mode = RFPromiscuous; + for (i = 0; i < table_entries; i++) + mc_filter[i] = 0xffff; + } else if ((netdev_mc_count(net_dev) > multicast_filter_limit) || + (net_dev->flags & IFF_ALLMULTI)) { + /* too many multicast addresses or accept all multicast packet */ + rx_mode = RFAAB | RFAAM; + for (i = 0; i < table_entries; i++) + mc_filter[i] = 0xffff; + } else { + /* Accept Broadcast packet, destination address matchs our + * MAC address, use Receive Filter to reject unwanted MCAST + * packets */ + struct netdev_hw_addr *ha; + rx_mode = RFAAB; + + netdev_for_each_mc_addr(ha, net_dev) { + unsigned int bit_nr; + + bit_nr = sis900_mcast_bitnr(ha->addr, + sis_priv->chipset_rev); + mc_filter[bit_nr >> 4] |= (1 << (bit_nr & 0xf)); + } + } + + /* update Multicast Hash Table in Receive Filter */ + for (i = 0; i < table_entries; i++) { + /* why plus 0x04 ??, That makes the correct value for hash table. */ + sw32(rfcr, (u32)(0x00000004 + i) << RFADDR_shift); + sw32(rfdr, mc_filter[i]); + } + + sw32(rfcr, RFEN | rx_mode); + + /* sis900 is capable of looping back packets at MAC level for + * debugging purpose */ + if (net_dev->flags & IFF_LOOPBACK) { + u32 cr_saved; + /* We must disable Tx/Rx before setting loopback mode */ + cr_saved = sr32(cr); + sw32(cr, cr_saved | TxDIS | RxDIS); + /* enable loopback */ + sw32(txcfg, sr32(txcfg) | TxMLB); + sw32(rxcfg, sr32(rxcfg) | RxATX); + /* restore cr */ + sw32(cr, cr_saved); + } +} + +/** + * sis900_reset - Reset sis900 MAC + * @net_dev: the net device to reset + * + * reset sis900 MAC and wait until finished + * reset through command register + * change backoff algorithm for 900B0 & 635 M/B + */ + +static void sis900_reset(struct net_device *net_dev) +{ + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + u32 status = TxRCMP | RxRCMP; + int i; + + sw32(ier, 0); + sw32(imr, 0); + sw32(rfcr, 0); + + sw32(cr, RxRESET | TxRESET | RESET | sr32(cr)); + + /* Check that the chip has finished the reset. */ + for (i = 0; status && (i < 1000); i++) + status ^= sr32(isr) & status; + + if (sis_priv->chipset_rev >= SIS635A_900_REV || + sis_priv->chipset_rev == SIS900B_900_REV) + sw32(cfg, PESEL | RND_CNT); + else + sw32(cfg, PESEL); +} + +/** + * sis900_remove - Remove sis900 device + * @pci_dev: the pci device to be removed + * + * remove and release SiS900 net device + */ + +static void sis900_remove(struct pci_dev *pci_dev) +{ + struct net_device *net_dev = pci_get_drvdata(pci_dev); + struct sis900_private *sis_priv = netdev_priv(net_dev); + + unregister_netdev(net_dev); + + while (sis_priv->first_mii) { + struct mii_phy *phy = sis_priv->first_mii; + + sis_priv->first_mii = phy->next; + kfree(phy); + } + + pci_free_consistent(pci_dev, RX_TOTAL_SIZE, sis_priv->rx_ring, + sis_priv->rx_ring_dma); + pci_free_consistent(pci_dev, TX_TOTAL_SIZE, sis_priv->tx_ring, + sis_priv->tx_ring_dma); + pci_iounmap(pci_dev, sis_priv->ioaddr); + free_netdev(net_dev); + pci_release_regions(pci_dev); +} + +#ifdef CONFIG_PM + +static int sis900_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct net_device *net_dev = pci_get_drvdata(pci_dev); + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + + if(!netif_running(net_dev)) + return 0; + + netif_stop_queue(net_dev); + netif_device_detach(net_dev); + + /* Stop the chip's Tx and Rx Status Machine */ + sw32(cr, RxDIS | TxDIS | sr32(cr)); + + pci_set_power_state(pci_dev, PCI_D3hot); + pci_save_state(pci_dev); + + return 0; +} + +static int sis900_resume(struct pci_dev *pci_dev) +{ + struct net_device *net_dev = pci_get_drvdata(pci_dev); + struct sis900_private *sis_priv = netdev_priv(net_dev); + void __iomem *ioaddr = sis_priv->ioaddr; + + if(!netif_running(net_dev)) + return 0; + pci_restore_state(pci_dev); + pci_set_power_state(pci_dev, PCI_D0); + + sis900_init_rxfilter(net_dev); + + sis900_init_tx_ring(net_dev); + sis900_init_rx_ring(net_dev); + + set_rx_mode(net_dev); + + netif_device_attach(net_dev); + netif_start_queue(net_dev); + + /* Workaround for EDB */ + sis900_set_mode(sis_priv, HW_SPEED_10_MBPS, FDX_CAPABLE_HALF_SELECTED); + + /* Enable all known interrupts by setting the interrupt mask. */ + sw32(imr, RxSOVR | RxORN | RxERR | RxOK | TxURN | TxERR | TxIDLE); + sw32(cr, RxENA | sr32(cr)); + sw32(ier, IE); + + sis900_check_mode(net_dev, sis_priv->mii); + + return 0; +} +#endif /* CONFIG_PM */ + +static struct pci_driver sis900_pci_driver = { + .name = SIS900_MODULE_NAME, + .id_table = sis900_pci_tbl, + .probe = sis900_probe, + .remove = sis900_remove, +#ifdef CONFIG_PM + .suspend = sis900_suspend, + .resume = sis900_resume, +#endif /* CONFIG_PM */ +}; + +static int __init sis900_init_module(void) +{ +/* when a module, this is printed whether or not devices are found in probe */ +#ifdef MODULE + printk(version); +#endif + + return pci_register_driver(&sis900_pci_driver); +} + +static void __exit sis900_cleanup_module(void) +{ + pci_unregister_driver(&sis900_pci_driver); +} + +module_init(sis900_init_module); +module_exit(sis900_cleanup_module); + diff --git a/linux/drivers/net/ethernet/sis/sis900.h b/linux/drivers/net/ethernet/sis/sis900.h new file mode 100644 index 000000000..1341f33e6 --- /dev/null +++ b/linux/drivers/net/ethernet/sis/sis900.h @@ -0,0 +1,329 @@ +/* sis900.h Definitions for SiS ethernet controllers including 7014/7016 and 900 + * Copyright 1999 Silicon Integrated System Corporation + * References: + * SiS 7016 Fast Ethernet PCI Bus 10/100 Mbps LAN Controller with OnNow Support, + * preliminary Rev. 1.0 Jan. 14, 1998 + * SiS 900 Fast Ethernet PCI Bus 10/100 Mbps LAN Single Chip with OnNow Support, + * preliminary Rev. 1.0 Nov. 10, 1998 + * SiS 7014 Single Chip 100BASE-TX/10BASE-T Physical Layer Solution, + * preliminary Rev. 1.0 Jan. 18, 1998 + * http://www.sis.com.tw/support/databook.htm + */ + +/* + * SiS 7016 and SiS 900 ethernet controller registers + */ + +/* The I/O extent, SiS 900 needs 256 bytes of io address */ +#define SIS900_TOTAL_SIZE 0x100 + +/* Symbolic offsets to registers. */ +enum sis900_registers { + cr=0x0, //Command Register + cfg=0x4, //Configuration Register + mear=0x8, //EEPROM Access Register + ptscr=0xc, //PCI Test Control Register + isr=0x10, //Interrupt Status Register + imr=0x14, //Interrupt Mask Register + ier=0x18, //Interrupt Enable Register + epar=0x18, //Enhanced PHY Access Register + txdp=0x20, //Transmit Descriptor Pointer Register + txcfg=0x24, //Transmit Configuration Register + rxdp=0x30, //Receive Descriptor Pointer Register + rxcfg=0x34, //Receive Configuration Register + flctrl=0x38, //Flow Control Register + rxlen=0x3c, //Receive Packet Length Register + rfcr=0x48, //Receive Filter Control Register + rfdr=0x4C, //Receive Filter Data Register + pmctrl=0xB0, //Power Management Control Register + pmer=0xB4 //Power Management Wake-up Event Register +}; + +/* Symbolic names for bits in various registers */ +enum sis900_command_register_bits { + RELOAD = 0x00000400, ACCESSMODE = 0x00000200,/* ET */ + RESET = 0x00000100, SWI = 0x00000080, RxRESET = 0x00000020, + TxRESET = 0x00000010, RxDIS = 0x00000008, RxENA = 0x00000004, + TxDIS = 0x00000002, TxENA = 0x00000001 +}; + +enum sis900_configuration_register_bits { + DESCRFMT = 0x00000100 /* 7016 specific */, REQALG = 0x00000080, + SB = 0x00000040, POW = 0x00000020, EXD = 0x00000010, + PESEL = 0x00000008, LPM = 0x00000004, BEM = 0x00000001, + /* 635 & 900B Specific */ + RND_CNT = 0x00000400, FAIR_BACKOFF = 0x00000200, + EDB_MASTER_EN = 0x00002000 +}; + +enum sis900_eeprom_access_reigster_bits { + MDC = 0x00000040, MDDIR = 0x00000020, MDIO = 0x00000010, /* 7016 specific */ + EECS = 0x00000008, EECLK = 0x00000004, EEDO = 0x00000002, + EEDI = 0x00000001 +}; + +enum sis900_interrupt_register_bits { + WKEVT = 0x10000000, TxPAUSEEND = 0x08000000, TxPAUSE = 0x04000000, + TxRCMP = 0x02000000, RxRCMP = 0x01000000, DPERR = 0x00800000, + SSERR = 0x00400000, RMABT = 0x00200000, RTABT = 0x00100000, + RxSOVR = 0x00010000, HIBERR = 0x00008000, SWINT = 0x00001000, + MIBINT = 0x00000800, TxURN = 0x00000400, TxIDLE = 0x00000200, + TxERR = 0x00000100, TxDESC = 0x00000080, TxOK = 0x00000040, + RxORN = 0x00000020, RxIDLE = 0x00000010, RxEARLY = 0x00000008, + RxERR = 0x00000004, RxDESC = 0x00000002, RxOK = 0x00000001 +}; + +enum sis900_interrupt_enable_reigster_bits { + IE = 0x00000001 +}; + +/* maximum dma burst for transmission and receive */ +#define MAX_DMA_RANGE 7 /* actually 0 means MAXIMUM !! */ +#define TxMXDMA_shift 20 +#define RxMXDMA_shift 20 + +enum sis900_tx_rx_dma{ + DMA_BURST_512 = 0, DMA_BURST_64 = 5 +}; + +/* transmit FIFO thresholds */ +#define TX_FILL_THRESH 16 /* 1/4 FIFO size */ +#define TxFILLT_shift 8 +#define TxDRNT_shift 0 +#define TxDRNT_100 48 /* 3/4 FIFO size */ +#define TxDRNT_10 16 /* 1/2 FIFO size */ + +enum sis900_transmit_config_register_bits { + TxCSI = 0x80000000, TxHBI = 0x40000000, TxMLB = 0x20000000, + TxATP = 0x10000000, TxIFG = 0x0C000000, TxFILLT = 0x00003F00, + TxDRNT = 0x0000003F +}; + +/* recevie FIFO thresholds */ +#define RxDRNT_shift 1 +#define RxDRNT_100 16 /* 1/2 FIFO size */ +#define RxDRNT_10 24 /* 3/4 FIFO size */ + +enum sis900_reveive_config_register_bits { + RxAEP = 0x80000000, RxARP = 0x40000000, RxATX = 0x10000000, + RxAJAB = 0x08000000, RxDRNT = 0x0000007F +}; + +#define RFAA_shift 28 +#define RFADDR_shift 16 + +enum sis900_receive_filter_control_register_bits { + RFEN = 0x80000000, RFAAB = 0x40000000, RFAAM = 0x20000000, + RFAAP = 0x10000000, RFPromiscuous = (RFAAB|RFAAM|RFAAP) +}; + +enum sis900_reveive_filter_data_mask { + RFDAT = 0x0000FFFF +}; + +/* EEPROM Addresses */ +enum sis900_eeprom_address { + EEPROMSignature = 0x00, EEPROMVendorID = 0x02, EEPROMDeviceID = 0x03, + EEPROMMACAddr = 0x08, EEPROMChecksum = 0x0b +}; + +/* The EEPROM commands include the alway-set leading bit. Refer to NM93Cxx datasheet */ +enum sis900_eeprom_command { + EEread = 0x0180, EEwrite = 0x0140, EEerase = 0x01C0, + EEwriteEnable = 0x0130, EEwriteDisable = 0x0100, + EEeraseAll = 0x0120, EEwriteAll = 0x0110, + EEaddrMask = 0x013F, EEcmdShift = 16 +}; + +/* For SiS962 or SiS963, request the eeprom software access */ +enum sis96x_eeprom_command { + EEREQ = 0x00000400, EEDONE = 0x00000200, EEGNT = 0x00000100 +}; + +/* PCI Registers */ +enum sis900_pci_registers { + CFGPMC = 0x40, + CFGPMCSR = 0x44 +}; + +/* Power management capabilities bits */ +enum sis900_cfgpmc_register_bits { + PMVER = 0x00070000, + DSI = 0x00100000, + PMESP = 0xf8000000 +}; + +enum sis900_pmesp_bits { + PME_D0 = 0x1, + PME_D1 = 0x2, + PME_D2 = 0x4, + PME_D3H = 0x8, + PME_D3C = 0x10 +}; + +/* Power management control/status bits */ +enum sis900_cfgpmcsr_register_bits { + PMESTS = 0x00004000, + PME_EN = 0x00000100, // Power management enable + PWR_STA = 0x00000003 // Current power state +}; + +/* Wake-on-LAN support. */ +enum sis900_power_management_control_register_bits { + LINKLOSS = 0x00000001, + LINKON = 0x00000002, + MAGICPKT = 0x00000400, + ALGORITHM = 0x00000800, + FRM1EN = 0x00100000, + FRM2EN = 0x00200000, + FRM3EN = 0x00400000, + FRM1ACS = 0x01000000, + FRM2ACS = 0x02000000, + FRM3ACS = 0x04000000, + WAKEALL = 0x40000000, + GATECLK = 0x80000000 +}; + +/* Management Data I/O (mdio) frame */ +#define MIIread 0x6000 +#define MIIwrite 0x5002 +#define MIIpmdShift 7 +#define MIIregShift 2 +#define MIIcmdLen 16 +#define MIIcmdShift 16 + +/* Buffer Descriptor Status*/ +enum sis900_buffer_status { + OWN = 0x80000000, MORE = 0x40000000, INTR = 0x20000000, + SUPCRC = 0x10000000, INCCRC = 0x10000000, + OK = 0x08000000, DSIZE = 0x00000FFF +}; +/* Status for TX Buffers */ +enum sis900_tx_buffer_status { + ABORT = 0x04000000, UNDERRUN = 0x02000000, NOCARRIER = 0x01000000, + DEFERD = 0x00800000, EXCDEFER = 0x00400000, OWCOLL = 0x00200000, + EXCCOLL = 0x00100000, COLCNT = 0x000F0000 +}; + +enum sis900_rx_buffer_status { + OVERRUN = 0x02000000, DEST = 0x00800000, BCAST = 0x01800000, + MCAST = 0x01000000, UNIMATCH = 0x00800000, TOOLONG = 0x00400000, + RUNT = 0x00200000, RXISERR = 0x00100000, CRCERR = 0x00080000, + FAERR = 0x00040000, LOOPBK = 0x00020000, RXCOL = 0x00010000 +}; + +/* MII register offsets */ +enum mii_registers { + MII_CONTROL = 0x0000, MII_STATUS = 0x0001, MII_PHY_ID0 = 0x0002, + MII_PHY_ID1 = 0x0003, MII_ANADV = 0x0004, MII_ANLPAR = 0x0005, + MII_ANEXT = 0x0006 +}; + +/* mii registers specific to SiS 900 */ +enum sis_mii_registers { + MII_CONFIG1 = 0x0010, MII_CONFIG2 = 0x0011, MII_STSOUT = 0x0012, + MII_MASK = 0x0013, MII_RESV = 0x0014 +}; + +/* mii registers specific to ICS 1893 */ +enum ics_mii_registers { + MII_EXTCTRL = 0x0010, MII_QPDSTS = 0x0011, MII_10BTOP = 0x0012, + MII_EXTCTRL2 = 0x0013 +}; + +/* mii registers specific to AMD 79C901 */ +enum amd_mii_registers { + MII_STATUS_SUMMARY = 0x0018 +}; + +/* MII Control register bit definitions. */ +enum mii_control_register_bits { + MII_CNTL_FDX = 0x0100, MII_CNTL_RST_AUTO = 0x0200, + MII_CNTL_ISOLATE = 0x0400, MII_CNTL_PWRDWN = 0x0800, + MII_CNTL_AUTO = 0x1000, MII_CNTL_SPEED = 0x2000, + MII_CNTL_LPBK = 0x4000, MII_CNTL_RESET = 0x8000 +}; + +/* MII Status register bit */ +enum mii_status_register_bits { + MII_STAT_EXT = 0x0001, MII_STAT_JAB = 0x0002, + MII_STAT_LINK = 0x0004, MII_STAT_CAN_AUTO = 0x0008, + MII_STAT_FAULT = 0x0010, MII_STAT_AUTO_DONE = 0x0020, + MII_STAT_CAN_T = 0x0800, MII_STAT_CAN_T_FDX = 0x1000, + MII_STAT_CAN_TX = 0x2000, MII_STAT_CAN_TX_FDX = 0x4000, + MII_STAT_CAN_T4 = 0x8000 +}; + +#define MII_ID1_OUI_LO 0xFC00 /* low bits of OUI mask */ +#define MII_ID1_MODEL 0x03F0 /* model number */ +#define MII_ID1_REV 0x000F /* model number */ + +/* MII NWAY Register Bits ... + valid for the ANAR (Auto-Negotiation Advertisement) and + ANLPAR (Auto-Negotiation Link Partner) registers */ +enum mii_nway_register_bits { + MII_NWAY_NODE_SEL = 0x001f, MII_NWAY_CSMA_CD = 0x0001, + MII_NWAY_T = 0x0020, MII_NWAY_T_FDX = 0x0040, + MII_NWAY_TX = 0x0080, MII_NWAY_TX_FDX = 0x0100, + MII_NWAY_T4 = 0x0200, MII_NWAY_PAUSE = 0x0400, + MII_NWAY_RF = 0x2000, MII_NWAY_ACK = 0x4000, + MII_NWAY_NP = 0x8000 +}; + +enum mii_stsout_register_bits { + MII_STSOUT_LINK_FAIL = 0x4000, + MII_STSOUT_SPD = 0x0080, MII_STSOUT_DPLX = 0x0040 +}; + +enum mii_stsics_register_bits { + MII_STSICS_SPD = 0x8000, MII_STSICS_DPLX = 0x4000, + MII_STSICS_LINKSTS = 0x0001 +}; + +enum mii_stssum_register_bits { + MII_STSSUM_LINK = 0x0008, MII_STSSUM_DPLX = 0x0004, + MII_STSSUM_AUTO = 0x0002, MII_STSSUM_SPD = 0x0001 +}; + +enum sis900_revision_id { + SIS630A_900_REV = 0x80, SIS630E_900_REV = 0x81, + SIS630S_900_REV = 0x82, SIS630EA1_900_REV = 0x83, + SIS630ET_900_REV = 0x84, SIS635A_900_REV = 0x90, + SIS96x_900_REV = 0X91, SIS900B_900_REV = 0x03 +}; + +enum sis630_revision_id { + SIS630A0 = 0x00, SIS630A1 = 0x01, + SIS630B0 = 0x10, SIS630B1 = 0x11 +}; + +#define FDX_CAPABLE_DUPLEX_UNKNOWN 0 +#define FDX_CAPABLE_HALF_SELECTED 1 +#define FDX_CAPABLE_FULL_SELECTED 2 + +#define HW_SPEED_UNCONFIG 0 +#define HW_SPEED_HOME 1 +#define HW_SPEED_10_MBPS 10 +#define HW_SPEED_100_MBPS 100 +#define HW_SPEED_DEFAULT (HW_SPEED_100_MBPS) + +#define CRC_SIZE 4 +#define MAC_HEADER_SIZE 14 + +#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE) +#define MAX_FRAME_SIZE (1518 + 4) +#else +#define MAX_FRAME_SIZE 1518 +#endif /* CONFIG_VLAN_802_1Q */ + +#define TX_BUF_SIZE (MAX_FRAME_SIZE+18) +#define RX_BUF_SIZE (MAX_FRAME_SIZE+18) + +#define NUM_TX_DESC 16 /* Number of Tx descriptor registers. */ +#define NUM_RX_DESC 16 /* Number of Rx descriptor registers. */ +#define TX_TOTAL_SIZE NUM_TX_DESC*sizeof(BufferDesc) +#define RX_TOTAL_SIZE NUM_RX_DESC*sizeof(BufferDesc) + +/* PCI stuff, should be move to pci.h */ +#define SIS630_VENDOR_ID 0x1039 +#define SIS630_DEVICE_ID 0x0630 diff --git a/linux/drivers/net/ethernet/sis/sis900.txt b/linux/drivers/net/ethernet/sis/sis900.txt new file mode 100644 index 000000000..44985830f --- /dev/null +++ b/linux/drivers/net/ethernet/sis/sis900.txt @@ -0,0 +1,4 @@ +KBUILD_MODNAME not defined +--set pre.cppflags[+] '-DKBUILD_MODNAME="sis900"' + +sis900_netdev_ops functions not called, assignment on line 498 doesn't spawn diff --git a/linux/drivers/net/plip/plip.c b/linux/drivers/net/plip/plip.c new file mode 100644 index 000000000..040b8978d --- /dev/null +++ b/linux/drivers/net/plip/plip.c @@ -0,0 +1,1400 @@ +/* $Id: plip.c,v 1.3.6.2 1997/04/16 15:07:56 phil Exp $ */ +/* PLIP: A parallel port "network" driver for Linux. */ +/* This driver is for parallel port with 5-bit cable (LapLink (R) cable). */ +/* + * Authors: Donald Becker + * Tommy Thorn + * Tanabe Hiroyasu + * Alan Cox + * Peter Bauer <100136.3530@compuserve.com> + * Niibe Yutaka + * Nimrod Zimerman + * + * Enhancements: + * Modularization and ifreq/ifmap support by Alan Cox. + * Rewritten by Niibe Yutaka. + * parport-sharing awareness code by Philip Blundell. + * SMP locking by Niibe Yutaka. + * Support for parallel ports with no IRQ (poll mode), + * Modifications to use the parallel port API + * by Nimrod Zimerman. + * + * Fixes: + * Niibe Yutaka + * - Module initialization. + * - MTU fix. + * - Make sure other end is OK, before sending a packet. + * - Fix immediate timer problem. + * + * Al Viro + * - Changed {enable,disable}_irq handling to make it work + * with new ("stack") semantics. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* + * Original version and the name 'PLIP' from Donald Becker + * inspired by Russ Nelson's parallel port packet driver. + * + * NOTE: + * Tanabe Hiroyasu had changed the protocol, and it was in Linux v1.0. + * Because of the necessity to communicate to DOS machines with the + * Crynwr packet driver, Peter Bauer changed the protocol again + * back to original protocol. + * + * This version follows original PLIP protocol. + * So, this PLIP can't communicate the PLIP of Linux v1.0. + */ + +/* + * To use with DOS box, please do (Turn on ARP switch): + * # ifconfig plip[0-2] arp + */ +static const char version[] = "NET3 PLIP version 2.4-parport gniibe@mri.co.jp\n"; + +/* + Sources: + Ideas and protocols came from Russ Nelson's + "parallel.asm" parallel port packet driver. + + The "Crynwr" parallel port standard specifies the following protocol: + Trigger by sending nibble '0x8' (this causes interrupt on other end) + count-low octet + count-high octet + ... data octets + checksum octet + Each octet is sent as + >4)&0x0F)> + + The packet is encapsulated as if it were ethernet. + + The cable used is a de facto standard parallel null cable -- sold as + a "LapLink" cable by various places. You'll need a 12-conductor cable to + make one yourself. The wiring is: + SLCTIN 17 - 17 + GROUND 25 - 25 + D0->ERROR 2 - 15 15 - 2 + D1->SLCT 3 - 13 13 - 3 + D2->PAPOUT 4 - 12 12 - 4 + D3->ACK 5 - 10 10 - 5 + D4->BUSY 6 - 11 11 - 6 + Do not connect the other pins. They are + D5,D6,D7 are 7,8,9 + STROBE is 1, FEED is 14, INIT is 16 + extra grounds are 18,19,20,21,22,23,24 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* Maximum number of devices to support. */ +#define PLIP_MAX 8 + +/* Use 0 for production, 1 for verification, >2 for debug */ +#ifndef NET_DEBUG +#define NET_DEBUG 1 +#endif +static const unsigned int net_debug = NET_DEBUG; + +#define ENABLE(irq) if (irq != -1) enable_irq(irq) +#define DISABLE(irq) if (irq != -1) disable_irq(irq) + +/* In micro second */ +#define PLIP_DELAY_UNIT 1 + +/* Connection time out = PLIP_TRIGGER_WAIT * PLIP_DELAY_UNIT usec */ +#define PLIP_TRIGGER_WAIT 500 + +/* Nibble time out = PLIP_NIBBLE_WAIT * PLIP_DELAY_UNIT usec */ +#define PLIP_NIBBLE_WAIT 3000 + +/* Bottom halves */ +static void plip_kick_bh(struct work_struct *work); +static void plip_bh(struct work_struct *work); +static void plip_timer_bh(struct work_struct *work); + +/* Interrupt handler */ +static void plip_interrupt(void *dev_id); + +/* Functions for DEV methods */ +static int plip_tx_packet(struct sk_buff *skb, struct net_device *dev); +static int plip_hard_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned len); +static int plip_hard_header_cache(const struct neighbour *neigh, + struct hh_cache *hh, __be16 type); +static int plip_open(struct net_device *dev); +static int plip_close(struct net_device *dev); +static int plip_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); +static int plip_preempt(void *handle); +static void plip_wakeup(void *handle); + +enum plip_connection_state { + PLIP_CN_NONE=0, + PLIP_CN_RECEIVE, + PLIP_CN_SEND, + PLIP_CN_CLOSING, + PLIP_CN_ERROR +}; + +enum plip_packet_state { + PLIP_PK_DONE=0, + PLIP_PK_TRIGGER, + PLIP_PK_LENGTH_LSB, + PLIP_PK_LENGTH_MSB, + PLIP_PK_DATA, + PLIP_PK_CHECKSUM +}; + +enum plip_nibble_state { + PLIP_NB_BEGIN, + PLIP_NB_1, + PLIP_NB_2, +}; + +struct plip_local { + enum plip_packet_state state; + enum plip_nibble_state nibble; + union { + struct { +#if defined(__LITTLE_ENDIAN) + unsigned char lsb; + unsigned char msb; +#elif defined(__BIG_ENDIAN) + unsigned char msb; + unsigned char lsb; +#else +#error "Please fix the endianness defines in " +#endif + } b; + unsigned short h; + } length; + unsigned short byte; + unsigned char checksum; + unsigned char data; + struct sk_buff *skb; +}; + +struct net_local { + struct net_device *dev; + struct work_struct immediate; + struct delayed_work deferred; + struct delayed_work timer; + struct plip_local snd_data; + struct plip_local rcv_data; + struct pardevice *pardev; + unsigned long trigger; + unsigned long nibble; + enum plip_connection_state connection; + unsigned short timeout_count; + int is_deferred; + int port_owner; + int should_relinquish; + spinlock_t lock; + atomic_t kill_timer; + struct completion killed_timer_cmp; +}; + +static inline void enable_parport_interrupts (struct net_device *dev) +{ + if (dev->irq != -1) + { + struct parport *port = + ((struct net_local *)netdev_priv(dev))->pardev->port; + port->ops->enable_irq (port); + } +} + +static inline void disable_parport_interrupts (struct net_device *dev) +{ + if (dev->irq != -1) + { + struct parport *port = + ((struct net_local *)netdev_priv(dev))->pardev->port; + port->ops->disable_irq (port); + } +} + +static inline void write_data (struct net_device *dev, unsigned char data) +{ + struct parport *port = + ((struct net_local *)netdev_priv(dev))->pardev->port; + + port->ops->write_data (port, data); +} + +static inline unsigned char read_status (struct net_device *dev) +{ + struct parport *port = + ((struct net_local *)netdev_priv(dev))->pardev->port; + + return port->ops->read_status (port); +} + +static const struct header_ops plip_header_ops = { + .create = plip_hard_header, + .cache = plip_hard_header_cache, +}; + +static const struct net_device_ops plip_netdev_ops = { + .ndo_open = plip_open, + .ndo_stop = plip_close, + .ndo_start_xmit = plip_tx_packet, + .ndo_do_ioctl = plip_ioctl, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +/* Entry point of PLIP driver. + Probe the hardware, and register/initialize the driver. + + PLIP is rather weird, because of the way it interacts with the parport + system. It is _not_ initialised from Space.c. Instead, plip_init() + is called, and that function makes up a "struct net_device" for each port, and + then calls us here. + + */ +static void +plip_init_netdev(struct net_device *dev) +{ + struct net_local *nl = netdev_priv(dev); + + /* Then, override parts of it */ + dev->tx_queue_len = 10; + dev->flags = IFF_POINTOPOINT|IFF_NOARP; + memset(dev->dev_addr, 0xfc, ETH_ALEN); + + dev->netdev_ops = &plip_netdev_ops; + dev->header_ops = &plip_header_ops; + + + nl->port_owner = 0; + + /* Initialize constants */ + nl->trigger = PLIP_TRIGGER_WAIT; + nl->nibble = PLIP_NIBBLE_WAIT; + + /* Initialize task queue structures */ + INIT_WORK(&nl->immediate, plip_bh); + INIT_DELAYED_WORK(&nl->deferred, plip_kick_bh); + + if (dev->irq == -1) + INIT_DELAYED_WORK(&nl->timer, plip_timer_bh); + + spin_lock_init(&nl->lock); +} + +/* Bottom half handler for the delayed request. + This routine is kicked by do_timer(). + Request `plip_bh' to be invoked. */ +static void +plip_kick_bh(struct work_struct *work) +{ + struct net_local *nl = + container_of(work, struct net_local, deferred.work); + + if (nl->is_deferred) + schedule_work(&nl->immediate); +} + +/* Forward declarations of internal routines */ +static int plip_none(struct net_device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_receive_packet(struct net_device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_send_packet(struct net_device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_connection_close(struct net_device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_error(struct net_device *, struct net_local *, + struct plip_local *, struct plip_local *); +static int plip_bh_timeout_error(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, + struct plip_local *rcv, + int error); + +#define OK 0 +#define TIMEOUT 1 +#define ERROR 2 +#define HS_TIMEOUT 3 + +typedef int (*plip_func)(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv); + +static const plip_func connection_state_table[] = +{ + plip_none, + plip_receive_packet, + plip_send_packet, + plip_connection_close, + plip_error +}; + +/* Bottom half handler of PLIP. */ +static void +plip_bh(struct work_struct *work) +{ + struct net_local *nl = container_of(work, struct net_local, immediate); + struct plip_local *snd = &nl->snd_data; + struct plip_local *rcv = &nl->rcv_data; + plip_func f; + int r; + + nl->is_deferred = 0; + f = connection_state_table[nl->connection]; + if ((r = (*f)(nl->dev, nl, snd, rcv)) != OK && + (r = plip_bh_timeout_error(nl->dev, nl, snd, rcv, r)) != OK) { + nl->is_deferred = 1; + schedule_delayed_work(&nl->deferred, 1); + } +} + +static void +plip_timer_bh(struct work_struct *work) +{ + struct net_local *nl = + container_of(work, struct net_local, timer.work); + + if (!(atomic_read (&nl->kill_timer))) { + plip_interrupt (nl->dev); + + schedule_delayed_work(&nl->timer, 1); + } + else { + complete(&nl->killed_timer_cmp); + } +} + +static int +plip_bh_timeout_error(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv, + int error) +{ + unsigned char c0; + /* + * This is tricky. If we got here from the beginning of send (either + * with ERROR or HS_TIMEOUT) we have IRQ enabled. Otherwise it's + * already disabled. With the old variant of {enable,disable}_irq() + * extra disable_irq() was a no-op. Now it became mortal - it's + * unbalanced and thus we'll never re-enable IRQ (until rmmod plip, + * that is). So we have to treat HS_TIMEOUT and ERROR from send + * in a special way. + */ + + spin_lock_irq(&nl->lock); + if (nl->connection == PLIP_CN_SEND) { + + if (error != ERROR) { /* Timeout */ + nl->timeout_count++; + if ((error == HS_TIMEOUT && nl->timeout_count <= 10) || + nl->timeout_count <= 3) { + spin_unlock_irq(&nl->lock); + /* Try again later */ + return TIMEOUT; + } + c0 = read_status(dev); + printk(KERN_WARNING "%s: transmit timeout(%d,%02x)\n", + dev->name, snd->state, c0); + } else + error = HS_TIMEOUT; + dev->stats.tx_errors++; + dev->stats.tx_aborted_errors++; + } else if (nl->connection == PLIP_CN_RECEIVE) { + if (rcv->state == PLIP_PK_TRIGGER) { + /* Transmission was interrupted. */ + spin_unlock_irq(&nl->lock); + return OK; + } + if (error != ERROR) { /* Timeout */ + if (++nl->timeout_count <= 3) { + spin_unlock_irq(&nl->lock); + /* Try again later */ + return TIMEOUT; + } + c0 = read_status(dev); + printk(KERN_WARNING "%s: receive timeout(%d,%02x)\n", + dev->name, rcv->state, c0); + } + dev->stats.rx_dropped++; + } + rcv->state = PLIP_PK_DONE; + if (rcv->skb) { + kfree_skb(rcv->skb); + rcv->skb = NULL; + } + snd->state = PLIP_PK_DONE; + if (snd->skb) { + dev_kfree_skb(snd->skb); + snd->skb = NULL; + } + spin_unlock_irq(&nl->lock); + if (error == HS_TIMEOUT) { + DISABLE(dev->irq); + synchronize_irq(dev->irq); + } + disable_parport_interrupts (dev); + netif_stop_queue (dev); + nl->connection = PLIP_CN_ERROR; + write_data (dev, 0x00); + + return TIMEOUT; +} + +static int +plip_none(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + return OK; +} + +/* PLIP_RECEIVE --- receive a byte(two nibbles) + Returns OK on success, TIMEOUT on timeout */ +static inline int +plip_receive(unsigned short nibble_timeout, struct net_device *dev, + enum plip_nibble_state *ns_p, unsigned char *data_p) +{ + unsigned char c0, c1; + unsigned int cx; + + switch (*ns_p) { + case PLIP_NB_BEGIN: + cx = nibble_timeout; + while (1) { + c0 = read_status(dev); + udelay(PLIP_DELAY_UNIT); + if ((c0 & 0x80) == 0) { + c1 = read_status(dev); + if (c0 == c1) + break; + } + if (--cx == 0) + return TIMEOUT; + } + *data_p = (c0 >> 3) & 0x0f; + write_data (dev, 0x10); /* send ACK */ + *ns_p = PLIP_NB_1; + + case PLIP_NB_1: + cx = nibble_timeout; + while (1) { + c0 = read_status(dev); + udelay(PLIP_DELAY_UNIT); + if (c0 & 0x80) { + c1 = read_status(dev); + if (c0 == c1) + break; + } + if (--cx == 0) + return TIMEOUT; + } + *data_p |= (c0 << 1) & 0xf0; + write_data (dev, 0x00); /* send ACK */ + *ns_p = PLIP_NB_BEGIN; + case PLIP_NB_2: + break; + } + return OK; +} + +/* + * Determine the packet's protocol ID. The rule here is that we + * assume 802.3 if the type field is short enough to be a length. + * This is normal practice and works for any 'now in use' protocol. + * + * PLIP is ethernet ish but the daddr might not be valid if unicast. + * PLIP fortunately has no bus architecture (its Point-to-point). + * + * We can't fix the daddr thing as that quirk (more bug) is embedded + * in far too many old systems not all even running Linux. + */ + +static __be16 plip_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + struct ethhdr *eth; + unsigned char *rawp; + + skb_reset_mac_header(skb); + skb_pull(skb,dev->hard_header_len); + eth = eth_hdr(skb); + + if(is_multicast_ether_addr(eth->h_dest)) + { + if(ether_addr_equal_64bits(eth->h_dest, dev->broadcast)) + skb->pkt_type=PACKET_BROADCAST; + else + skb->pkt_type=PACKET_MULTICAST; + } + + /* + * This ALLMULTI check should be redundant by 1.4 + * so don't forget to remove it. + */ + + if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) + return eth->h_proto; + + rawp = skb->data; + + /* + * This is a magic hack to spot IPX packets. Older Novell breaks + * the protocol design and runs IPX over 802.3 without an 802.2 LLC + * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This + * won't work for fault tolerant netware but does for the rest. + */ + if (*(unsigned short *)rawp == 0xFFFF) + return htons(ETH_P_802_3); + + /* + * Real 802.2 LLC + */ + return htons(ETH_P_802_2); +} + +/* PLIP_RECEIVE_PACKET --- receive a packet */ +static int +plip_receive_packet(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + unsigned short nibble_timeout = nl->nibble; + unsigned char *lbuf; + + switch (rcv->state) { + case PLIP_PK_TRIGGER: + DISABLE(dev->irq); + /* Don't need to synchronize irq, as we can safely ignore it */ + disable_parport_interrupts (dev); + write_data (dev, 0x01); /* send ACK */ + if (net_debug > 2) + printk(KERN_DEBUG "%s: receive start\n", dev->name); + rcv->state = PLIP_PK_LENGTH_LSB; + rcv->nibble = PLIP_NB_BEGIN; + + case PLIP_PK_LENGTH_LSB: + if (snd->state != PLIP_PK_DONE) { + if (plip_receive(nl->trigger, dev, + &rcv->nibble, &rcv->length.b.lsb)) { + /* collision, here dev->tbusy == 1 */ + rcv->state = PLIP_PK_DONE; + nl->is_deferred = 1; + nl->connection = PLIP_CN_SEND; + schedule_delayed_work(&nl->deferred, 1); + enable_parport_interrupts (dev); + ENABLE(dev->irq); + return OK; + } + } else { + if (plip_receive(nibble_timeout, dev, + &rcv->nibble, &rcv->length.b.lsb)) + return TIMEOUT; + } + rcv->state = PLIP_PK_LENGTH_MSB; + + case PLIP_PK_LENGTH_MSB: + if (plip_receive(nibble_timeout, dev, + &rcv->nibble, &rcv->length.b.msb)) + return TIMEOUT; + if (rcv->length.h > dev->mtu + dev->hard_header_len || + rcv->length.h < 8) { + printk(KERN_WARNING "%s: bogus packet size %d.\n", dev->name, rcv->length.h); + return ERROR; + } + /* Malloc up new buffer. */ + rcv->skb = dev_alloc_skb(rcv->length.h + 2); + if (rcv->skb == NULL) { + printk(KERN_ERR "%s: Memory squeeze.\n", dev->name); + return ERROR; + } + skb_reserve(rcv->skb, 2); /* Align IP on 16 byte boundaries */ + skb_put(rcv->skb,rcv->length.h); + rcv->skb->dev = dev; + rcv->state = PLIP_PK_DATA; + rcv->byte = 0; + rcv->checksum = 0; + + case PLIP_PK_DATA: + lbuf = rcv->skb->data; + do { + if (plip_receive(nibble_timeout, dev, + &rcv->nibble, &lbuf[rcv->byte])) + return TIMEOUT; + } while (++rcv->byte < rcv->length.h); + do { + rcv->checksum += lbuf[--rcv->byte]; + } while (rcv->byte); + rcv->state = PLIP_PK_CHECKSUM; + + case PLIP_PK_CHECKSUM: + if (plip_receive(nibble_timeout, dev, + &rcv->nibble, &rcv->data)) + return TIMEOUT; + if (rcv->data != rcv->checksum) { + dev->stats.rx_crc_errors++; + if (net_debug) + printk(KERN_DEBUG "%s: checksum error\n", dev->name); + return ERROR; + } + rcv->state = PLIP_PK_DONE; + + case PLIP_PK_DONE: + /* Inform the upper layer for the arrival of a packet. */ + rcv->skb->protocol=plip_type_trans(rcv->skb, dev); + netif_rx_ni(rcv->skb); + dev->stats.rx_bytes += rcv->length.h; + dev->stats.rx_packets++; + rcv->skb = NULL; + if (net_debug > 2) + printk(KERN_DEBUG "%s: receive end\n", dev->name); + + /* Close the connection. */ + write_data (dev, 0x00); + spin_lock_irq(&nl->lock); + if (snd->state != PLIP_PK_DONE) { + nl->connection = PLIP_CN_SEND; + spin_unlock_irq(&nl->lock); + schedule_work(&nl->immediate); + enable_parport_interrupts (dev); + ENABLE(dev->irq); + return OK; + } else { + nl->connection = PLIP_CN_NONE; + spin_unlock_irq(&nl->lock); + enable_parport_interrupts (dev); + ENABLE(dev->irq); + return OK; + } + } + return OK; +} + +/* PLIP_SEND --- send a byte (two nibbles) + Returns OK on success, TIMEOUT when timeout */ +static inline int +plip_send(unsigned short nibble_timeout, struct net_device *dev, + enum plip_nibble_state *ns_p, unsigned char data) +{ + unsigned char c0; + unsigned int cx; + + switch (*ns_p) { + case PLIP_NB_BEGIN: + write_data (dev, data & 0x0f); + *ns_p = PLIP_NB_1; + + case PLIP_NB_1: + write_data (dev, 0x10 | (data & 0x0f)); + cx = nibble_timeout; + while (1) { + c0 = read_status(dev); + if ((c0 & 0x80) == 0) + break; + if (--cx == 0) + return TIMEOUT; + udelay(PLIP_DELAY_UNIT); + } + write_data (dev, 0x10 | (data >> 4)); + *ns_p = PLIP_NB_2; + + case PLIP_NB_2: + write_data (dev, (data >> 4)); + cx = nibble_timeout; + while (1) { + c0 = read_status(dev); + if (c0 & 0x80) + break; + if (--cx == 0) + return TIMEOUT; + udelay(PLIP_DELAY_UNIT); + } + *ns_p = PLIP_NB_BEGIN; + return OK; + } + return OK; +} + +/* PLIP_SEND_PACKET --- send a packet */ +static int +plip_send_packet(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + unsigned short nibble_timeout = nl->nibble; + unsigned char *lbuf; + unsigned char c0; + unsigned int cx; + + if (snd->skb == NULL || (lbuf = snd->skb->data) == NULL) { + printk(KERN_DEBUG "%s: send skb lost\n", dev->name); + snd->state = PLIP_PK_DONE; + snd->skb = NULL; + return ERROR; + } + + switch (snd->state) { + case PLIP_PK_TRIGGER: + if ((read_status(dev) & 0xf8) != 0x80) + return HS_TIMEOUT; + + /* Trigger remote rx interrupt. */ + write_data (dev, 0x08); + cx = nl->trigger; + while (1) { + udelay(PLIP_DELAY_UNIT); + spin_lock_irq(&nl->lock); + if (nl->connection == PLIP_CN_RECEIVE) { + spin_unlock_irq(&nl->lock); + /* Interrupted. */ + dev->stats.collisions++; + return OK; + } + c0 = read_status(dev); + if (c0 & 0x08) { + spin_unlock_irq(&nl->lock); + DISABLE(dev->irq); + synchronize_irq(dev->irq); + if (nl->connection == PLIP_CN_RECEIVE) { + /* Interrupted. + We don't need to enable irq, + as it is soon disabled. */ + /* Yes, we do. New variant of + {enable,disable}_irq *counts* + them. -- AV */ + ENABLE(dev->irq); + dev->stats.collisions++; + return OK; + } + disable_parport_interrupts (dev); + if (net_debug > 2) + printk(KERN_DEBUG "%s: send start\n", dev->name); + snd->state = PLIP_PK_LENGTH_LSB; + snd->nibble = PLIP_NB_BEGIN; + nl->timeout_count = 0; + break; + } + spin_unlock_irq(&nl->lock); + if (--cx == 0) { + write_data (dev, 0x00); + return HS_TIMEOUT; + } + } + + case PLIP_PK_LENGTH_LSB: + if (plip_send(nibble_timeout, dev, + &snd->nibble, snd->length.b.lsb)) + return TIMEOUT; + snd->state = PLIP_PK_LENGTH_MSB; + + case PLIP_PK_LENGTH_MSB: + if (plip_send(nibble_timeout, dev, + &snd->nibble, snd->length.b.msb)) + return TIMEOUT; + snd->state = PLIP_PK_DATA; + snd->byte = 0; + snd->checksum = 0; + + case PLIP_PK_DATA: + do { + if (plip_send(nibble_timeout, dev, + &snd->nibble, lbuf[snd->byte])) + return TIMEOUT; + } while (++snd->byte < snd->length.h); + do { + snd->checksum += lbuf[--snd->byte]; + } while (snd->byte); + snd->state = PLIP_PK_CHECKSUM; + + case PLIP_PK_CHECKSUM: + if (plip_send(nibble_timeout, dev, + &snd->nibble, snd->checksum)) + return TIMEOUT; + + dev->stats.tx_bytes += snd->skb->len; + dev_kfree_skb(snd->skb); + dev->stats.tx_packets++; + snd->state = PLIP_PK_DONE; + + case PLIP_PK_DONE: + /* Close the connection */ + write_data (dev, 0x00); + snd->skb = NULL; + if (net_debug > 2) + printk(KERN_DEBUG "%s: send end\n", dev->name); + nl->connection = PLIP_CN_CLOSING; + nl->is_deferred = 1; + schedule_delayed_work(&nl->deferred, 1); + enable_parport_interrupts (dev); + ENABLE(dev->irq); + return OK; + } + return OK; +} + +static int +plip_connection_close(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + spin_lock_irq(&nl->lock); + if (nl->connection == PLIP_CN_CLOSING) { + nl->connection = PLIP_CN_NONE; + netif_wake_queue (dev); + } + spin_unlock_irq(&nl->lock); + if (nl->should_relinquish) { + nl->should_relinquish = nl->port_owner = 0; + parport_release(nl->pardev); + } + return OK; +} + +/* PLIP_ERROR --- wait till other end settled */ +static int +plip_error(struct net_device *dev, struct net_local *nl, + struct plip_local *snd, struct plip_local *rcv) +{ + unsigned char status; + + status = read_status(dev); + if ((status & 0xf8) == 0x80) { + if (net_debug > 2) + printk(KERN_DEBUG "%s: reset interface.\n", dev->name); + nl->connection = PLIP_CN_NONE; + nl->should_relinquish = 0; + netif_start_queue (dev); + enable_parport_interrupts (dev); + ENABLE(dev->irq); + netif_wake_queue (dev); + } else { + nl->is_deferred = 1; + schedule_delayed_work(&nl->deferred, 1); + } + + return OK; +} + +/* Handle the parallel port interrupts. */ +static void +plip_interrupt(void *dev_id) +{ + struct net_device *dev = dev_id; + struct net_local *nl; + struct plip_local *rcv; + unsigned char c0; + unsigned long flags; + + nl = netdev_priv(dev); + rcv = &nl->rcv_data; + + spin_lock_irqsave (&nl->lock, flags); + + c0 = read_status(dev); + if ((c0 & 0xf8) != 0xc0) { + if ((dev->irq != -1) && (net_debug > 1)) + printk(KERN_DEBUG "%s: spurious interrupt\n", dev->name); + spin_unlock_irqrestore (&nl->lock, flags); + return; + } + + if (net_debug > 3) + printk(KERN_DEBUG "%s: interrupt.\n", dev->name); + + switch (nl->connection) { + case PLIP_CN_CLOSING: + netif_wake_queue (dev); + case PLIP_CN_NONE: + case PLIP_CN_SEND: + rcv->state = PLIP_PK_TRIGGER; + nl->connection = PLIP_CN_RECEIVE; + nl->timeout_count = 0; + schedule_work(&nl->immediate); + break; + + case PLIP_CN_RECEIVE: + /* May occur because there is race condition + around test and set of dev->interrupt. + Ignore this interrupt. */ + break; + + case PLIP_CN_ERROR: + printk(KERN_ERR "%s: receive interrupt in error state\n", dev->name); + break; + } + + spin_unlock_irqrestore(&nl->lock, flags); +} + +static int +plip_tx_packet(struct sk_buff *skb, struct net_device *dev) +{ + struct net_local *nl = netdev_priv(dev); + struct plip_local *snd = &nl->snd_data; + + if (netif_queue_stopped(dev)) + return NETDEV_TX_BUSY; + + /* We may need to grab the bus */ + if (!nl->port_owner) { + if (parport_claim(nl->pardev)) + return NETDEV_TX_BUSY; + nl->port_owner = 1; + } + + netif_stop_queue (dev); + + if (skb->len > dev->mtu + dev->hard_header_len) { + printk(KERN_WARNING "%s: packet too big, %d.\n", dev->name, (int)skb->len); + netif_start_queue (dev); + return NETDEV_TX_BUSY; + } + + if (net_debug > 2) + printk(KERN_DEBUG "%s: send request\n", dev->name); + + spin_lock_irq(&nl->lock); + snd->skb = skb; + snd->length.h = skb->len; + snd->state = PLIP_PK_TRIGGER; + if (nl->connection == PLIP_CN_NONE) { + nl->connection = PLIP_CN_SEND; + nl->timeout_count = 0; + } + schedule_work(&nl->immediate); + spin_unlock_irq(&nl->lock); + + return NETDEV_TX_OK; +} + +static void +plip_rewrite_address(const struct net_device *dev, struct ethhdr *eth) +{ + const struct in_device *in_dev; + + rcu_read_lock(); + in_dev = __in_dev_get_rcu(dev); + if (in_dev) { + /* Any address will do - we take the first */ + const struct in_ifaddr *ifa = in_dev->ifa_list; + if (ifa) { + memcpy(eth->h_source, dev->dev_addr, ETH_ALEN); + memset(eth->h_dest, 0xfc, 2); + memcpy(eth->h_dest+2, &ifa->ifa_address, 4); + } + } + rcu_read_unlock(); +} + +static int +plip_hard_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned len) +{ + int ret; + + ret = eth_header(skb, dev, type, daddr, saddr, len); + if (ret >= 0) + plip_rewrite_address (dev, (struct ethhdr *)skb->data); + + return ret; +} + +static int plip_hard_header_cache(const struct neighbour *neigh, + struct hh_cache *hh, __be16 type) +{ + int ret; + + ret = eth_header_cache(neigh, hh, type); + if (ret == 0) { + struct ethhdr *eth; + + eth = (struct ethhdr*)(((u8*)hh->hh_data) + + HH_DATA_OFF(sizeof(*eth))); + plip_rewrite_address (neigh->dev, eth); + } + + return ret; +} + +/* Open/initialize the board. This is called (in the current kernel) + sometime after booting when the 'ifconfig' program is run. + + This routine gets exclusive access to the parallel port by allocating + its IRQ line. + */ +static int +plip_open(struct net_device *dev) +{ + struct net_local *nl = netdev_priv(dev); + struct in_device *in_dev; + + /* Grab the port */ + if (!nl->port_owner) { + if (parport_claim(nl->pardev)) return -EAGAIN; + nl->port_owner = 1; + } + + nl->should_relinquish = 0; + + /* Clear the data port. */ + write_data (dev, 0x00); + + /* Enable rx interrupt. */ + enable_parport_interrupts (dev); + if (dev->irq == -1) + { + atomic_set (&nl->kill_timer, 0); + schedule_delayed_work(&nl->timer, 1); + } + + /* Initialize the state machine. */ + nl->rcv_data.state = nl->snd_data.state = PLIP_PK_DONE; + nl->rcv_data.skb = nl->snd_data.skb = NULL; + nl->connection = PLIP_CN_NONE; + nl->is_deferred = 0; + + /* Fill in the MAC-level header. + We used to abuse dev->broadcast to store the point-to-point + MAC address, but we no longer do it. Instead, we fetch the + interface address whenever it is needed, which is cheap enough + because we use the hh_cache. Actually, abusing dev->broadcast + didn't work, because when using plip_open the point-to-point + address isn't yet known. + PLIP doesn't have a real MAC address, but we need it to be + DOS compatible, and to properly support taps (otherwise, + when the device address isn't identical to the address of a + received frame, the kernel incorrectly drops it). */ + + in_dev=__in_dev_get_rtnl(dev); + if (in_dev) { + /* Any address will do - we take the first. We already + have the first two bytes filled with 0xfc, from + plip_init_dev(). */ + struct in_ifaddr *ifa=in_dev->ifa_list; + if (ifa != NULL) { + memcpy(dev->dev_addr+2, &ifa->ifa_local, 4); + } + } + + netif_start_queue (dev); + + return 0; +} + +/* The inverse routine to plip_open (). */ +static int +plip_close(struct net_device *dev) +{ + struct net_local *nl = netdev_priv(dev); + struct plip_local *snd = &nl->snd_data; + struct plip_local *rcv = &nl->rcv_data; + + netif_stop_queue (dev); + DISABLE(dev->irq); + synchronize_irq(dev->irq); + + if (dev->irq == -1) + { + init_completion(&nl->killed_timer_cmp); + atomic_set (&nl->kill_timer, 1); + wait_for_completion(&nl->killed_timer_cmp); + } + +#ifdef NOTDEF + outb(0x00, PAR_DATA(dev)); +#endif + nl->is_deferred = 0; + nl->connection = PLIP_CN_NONE; + if (nl->port_owner) { + parport_release(nl->pardev); + nl->port_owner = 0; + } + + snd->state = PLIP_PK_DONE; + if (snd->skb) { + dev_kfree_skb(snd->skb); + snd->skb = NULL; + } + rcv->state = PLIP_PK_DONE; + if (rcv->skb) { + kfree_skb(rcv->skb); + rcv->skb = NULL; + } + +#ifdef NOTDEF + /* Reset. */ + outb(0x00, PAR_CONTROL(dev)); +#endif + return 0; +} + +static int +plip_preempt(void *handle) +{ + struct net_device *dev = (struct net_device *)handle; + struct net_local *nl = netdev_priv(dev); + + /* Stand our ground if a datagram is on the wire */ + if (nl->connection != PLIP_CN_NONE) { + nl->should_relinquish = 1; + return 1; + } + + nl->port_owner = 0; /* Remember that we released the bus */ + return 0; +} + +static void +plip_wakeup(void *handle) +{ + struct net_device *dev = (struct net_device *)handle; + struct net_local *nl = netdev_priv(dev); + + if (nl->port_owner) { + /* Why are we being woken up? */ + printk(KERN_DEBUG "%s: why am I being woken up?\n", dev->name); + if (!parport_claim(nl->pardev)) + /* bus_owner is already set (but why?) */ + printk(KERN_DEBUG "%s: I'm broken.\n", dev->name); + else + return; + } + + if (!(dev->flags & IFF_UP)) + /* Don't need the port when the interface is down */ + return; + + if (!parport_claim(nl->pardev)) { + nl->port_owner = 1; + /* Clear the data port. */ + write_data (dev, 0x00); + } +} + +static int +plip_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct net_local *nl = netdev_priv(dev); + struct plipconf *pc = (struct plipconf *) &rq->ifr_ifru; + + if (cmd != SIOCDEVPLIP) + return -EOPNOTSUPP; + + switch(pc->pcmd) { + case PLIP_GET_TIMEOUT: + pc->trigger = nl->trigger; + pc->nibble = nl->nibble; + break; + case PLIP_SET_TIMEOUT: + if(!capable(CAP_NET_ADMIN)) + return -EPERM; + nl->trigger = pc->trigger; + nl->nibble = pc->nibble; + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int parport[PLIP_MAX] = { [0 ... PLIP_MAX-1] = -1 }; +static int timid; + +module_param_array(parport, int, NULL, 0); +module_param(timid, int, 0); +MODULE_PARM_DESC(parport, "List of parport device numbers to use by plip"); + +static struct net_device *dev_plip[PLIP_MAX] = { NULL, }; + +static inline int +plip_searchfor(int list[], int a) +{ + int i; + for (i = 0; i < PLIP_MAX && list[i] != -1; i++) { + if (list[i] == a) return 1; + } + return 0; +} + +/* plip_attach() is called (by the parport code) when a port is + * available to use. */ +static void plip_attach (struct parport *port) +{ + static int unit; + struct net_device *dev; + struct net_local *nl; + char name[IFNAMSIZ]; + + if ((parport[0] == -1 && (!timid || !port->devices)) || + plip_searchfor(parport, port->number)) { + if (unit == PLIP_MAX) { + printk(KERN_ERR "plip: too many devices\n"); + return; + } + + sprintf(name, "plip%d", unit); + dev = alloc_etherdev(sizeof(struct net_local)); + if (!dev) + return; + + strcpy(dev->name, name); + + dev->irq = port->irq; + dev->base_addr = port->base; + if (port->irq == -1) { + printk(KERN_INFO "plip: %s has no IRQ. Using IRQ-less mode," + "which is fairly inefficient!\n", port->name); + } + + nl = netdev_priv(dev); + nl->dev = dev; + nl->pardev = parport_register_device(port, dev->name, plip_preempt, + plip_wakeup, plip_interrupt, + 0, dev); + + if (!nl->pardev) { + printk(KERN_ERR "%s: parport_register failed\n", name); + goto err_free_dev; + } + + plip_init_netdev(dev); + + if (register_netdev(dev)) { + printk(KERN_ERR "%s: network register failed\n", name); + goto err_parport_unregister; + } + + printk(KERN_INFO "%s", version); + if (dev->irq != -1) + printk(KERN_INFO "%s: Parallel port at %#3lx, " + "using IRQ %d.\n", + dev->name, dev->base_addr, dev->irq); + else + printk(KERN_INFO "%s: Parallel port at %#3lx, " + "not using IRQ.\n", + dev->name, dev->base_addr); + dev_plip[unit++] = dev; + } + return; + +err_parport_unregister: + parport_unregister_device(nl->pardev); +err_free_dev: + free_netdev(dev); +} + +/* plip_detach() is called (by the parport code) when a port is + * no longer available to use. */ +static void plip_detach (struct parport *port) +{ + /* Nothing to do */ +} + +static struct parport_driver plip_driver = { + .name = "plip", + .attach = plip_attach, + .detach = plip_detach +}; + +static void __exit plip_cleanup_module (void) +{ + struct net_device *dev; + int i; + + parport_unregister_driver (&plip_driver); + + for (i=0; i < PLIP_MAX; i++) { + if ((dev = dev_plip[i])) { + struct net_local *nl = netdev_priv(dev); + unregister_netdev(dev); + if (nl->port_owner) + parport_release(nl->pardev); + parport_unregister_device(nl->pardev); + free_netdev(dev); + dev_plip[i] = NULL; + } + } +} + +#ifndef MODULE + +static int parport_ptr; + +static int __init plip_setup(char *str) +{ + int ints[4]; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + /* Ugh. */ + if (!strncmp(str, "parport", 7)) { + int n = simple_strtoul(str+7, NULL, 10); + if (parport_ptr < PLIP_MAX) + parport[parport_ptr++] = n; + else + printk(KERN_INFO "plip: too many ports, %s ignored.\n", + str); + } else if (!strcmp(str, "timid")) { + timid = 1; + } else { + if (ints[0] == 0 || ints[1] == 0) { + /* disable driver on "plip=" or "plip=0" */ + parport[0] = -2; + } else { + printk(KERN_WARNING "warning: 'plip=0x%x' ignored\n", + ints[1]); + } + } + return 1; +} + +__setup("plip=", plip_setup); + +#endif /* !MODULE */ + +static int __init plip_init (void) +{ + if (parport[0] == -2) + return 0; + + if (parport[0] != -1 && timid) { + printk(KERN_WARNING "plip: warning, ignoring `timid' since specific ports given.\n"); + timid = 0; + } + + if (parport_register_driver (&plip_driver)) { + printk (KERN_WARNING "plip: couldn't register driver\n"); + return 1; + } + + return 0; +} + +module_init(plip_init); +module_exit(plip_cleanup_module); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/net/plip/plip.txt b/linux/drivers/net/plip/plip.txt new file mode 100644 index 000000000..f70a1791d --- /dev/null +++ b/linux/drivers/net/plip/plip.txt @@ -0,0 +1 @@ +plip_netdev_ops functions not called, assignment on line 297 doesn't spawn diff --git a/linux/drivers/net/slip/slip.c b/linux/drivers/net/slip/slip.c new file mode 100644 index 000000000..05387b1e2 --- /dev/null +++ b/linux/drivers/net/slip/slip.c @@ -0,0 +1,1460 @@ +/* + * slip.c This module implements the SLIP protocol for kernel-based + * devices like TTY. It interfaces between a raw TTY, and the + * kernel's INET protocol layers. + * + * Version: @(#)slip.c 0.8.3 12/24/94 + * + * Authors: Laurence Culhane, + * Fred N. van Kempen, + * + * Fixes: + * Alan Cox : Sanity checks and avoid tx overruns. + * Has a new sl->mtu field. + * Alan Cox : Found cause of overrun. ifconfig sl0 + * mtu upwards. Driver now spots this + * and grows/shrinks its buffers(hack!). + * Memory leak if you run out of memory + * setting up a slip driver fixed. + * Matt Dillon : Printable slip (borrowed from NET2E) + * Pauline Middelink : Slip driver fixes. + * Alan Cox : Honours the old SL_COMPRESSED flag + * Alan Cox : KISS AX.25 and AXUI IP support + * Michael Riepe : Automatic CSLIP recognition added + * Charles Hedrick : CSLIP header length problem fix. + * Alan Cox : Corrected non-IP cases of the above. + * Alan Cox : Now uses hardware type as per FvK. + * Alan Cox : Default to 192.168.0.0 (RFC 1597) + * A.N.Kuznetsov : dev_tint() recursion fix. + * Dmitry Gorodchanin : SLIP memory leaks + * Dmitry Gorodchanin : Code cleanup. Reduce tty driver + * buffering from 4096 to 256 bytes. + * Improving SLIP response time. + * CONFIG_SLIP_MODE_SLIP6. + * ifconfig sl? up & down now works + * correctly. + * Modularization. + * Alan Cox : Oops - fix AX.25 buffer lengths + * Dmitry Gorodchanin : Even more cleanups. Preserve CSLIP + * statistics. Include CSLIP code only + * if it really needed. + * Alan Cox : Free slhc buffers in the right place. + * Alan Cox : Allow for digipeated IP over AX.25 + * Matti Aarnio : Dynamic SLIP devices, with ideas taken + * from Jim Freeman's + * dynamic PPP devices. We do NOT kfree() + * device entries, just reg./unreg. them + * as they are needed. We kfree() them + * at module cleanup. + * With MODULE-loading ``insmod'', user + * can issue parameter: slip_maxdev=1024 + * (Or how much he/she wants.. Default + * is 256) + * Stanislav Voronyi : Slip line checking, with ideas taken + * from multislip BSDI driver which was + * written by Igor Chechik, RELCOM Corp. + * Only algorithms have been ported to + * Linux SLIP driver. + * Vitaly E. Lavrov : Sane behaviour on tty hangup. + * Alexey Kuznetsov : Cleanup interfaces to tty & netdevice + * modules. + */ + +#define SL_CHECK_TRANSMIT +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "slip.h" +#ifdef CONFIG_INET +#include +#include +#include +#endif + +#define SLIP_VERSION "0.8.4-NET3.019-NEWTTY" + +static struct net_device **slip_devs; + +static int slip_maxdev = SL_NRUNIT; +module_param(slip_maxdev, int, 0); +MODULE_PARM_DESC(slip_maxdev, "Maximum number of slip devices"); + +static int slip_esc(unsigned char *p, unsigned char *d, int len); +static void slip_unesc(struct slip *sl, unsigned char c); +#ifdef CONFIG_SLIP_MODE_SLIP6 +static int slip_esc6(unsigned char *p, unsigned char *d, int len); +static void slip_unesc6(struct slip *sl, unsigned char c); +#endif +#ifdef CONFIG_SLIP_SMART +static void sl_keepalive(unsigned long sls); +static void sl_outfill(unsigned long sls); +static int sl_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +#endif + +/******************************** +* Buffer administration routines: +* sl_alloc_bufs() +* sl_free_bufs() +* sl_realloc_bufs() +* +* NOTE: sl_realloc_bufs != sl_free_bufs + sl_alloc_bufs, because +* sl_realloc_bufs provides strong atomicity and reallocation +* on actively running device. +*********************************/ + +/* + Allocate channel buffers. + */ + +static int sl_alloc_bufs(struct slip *sl, int mtu) +{ + int err = -ENOBUFS; + unsigned long len; + char *rbuff = NULL; + char *xbuff = NULL; +#ifdef SL_INCLUDE_CSLIP + char *cbuff = NULL; + struct slcompress *slcomp = NULL; +#endif + + /* + * Allocate the SLIP frame buffers: + * + * rbuff Receive buffer. + * xbuff Transmit buffer. + * cbuff Temporary compression buffer. + */ + len = mtu * 2; + + /* + * allow for arrival of larger UDP packets, even if we say not to + * also fixes a bug in which SunOS sends 512-byte packets even with + * an MSS of 128 + */ + if (len < 576 * 2) + len = 576 * 2; + rbuff = kmalloc(len + 4, GFP_KERNEL); + if (rbuff == NULL) + goto err_exit; + xbuff = kmalloc(len + 4, GFP_KERNEL); + if (xbuff == NULL) + goto err_exit; +#ifdef SL_INCLUDE_CSLIP + cbuff = kmalloc(len + 4, GFP_KERNEL); + if (cbuff == NULL) + goto err_exit; + slcomp = slhc_init(16, 16); + if (slcomp == NULL) + goto err_exit; +#endif + spin_lock_bh(&sl->lock); + if (sl->tty == NULL) { + spin_unlock_bh(&sl->lock); + err = -ENODEV; + goto err_exit; + } + sl->mtu = mtu; + sl->buffsize = len; + sl->rcount = 0; + sl->xleft = 0; + rbuff = xchg(&sl->rbuff, rbuff); + xbuff = xchg(&sl->xbuff, xbuff); +#ifdef SL_INCLUDE_CSLIP + cbuff = xchg(&sl->cbuff, cbuff); + slcomp = xchg(&sl->slcomp, slcomp); +#endif +#ifdef CONFIG_SLIP_MODE_SLIP6 + sl->xdata = 0; + sl->xbits = 0; +#endif + spin_unlock_bh(&sl->lock); + err = 0; + + /* Cleanup */ +err_exit: +#ifdef SL_INCLUDE_CSLIP + kfree(cbuff); + slhc_free(slcomp); +#endif + kfree(xbuff); + kfree(rbuff); + return err; +} + +/* Free a SLIP channel buffers. */ +static void sl_free_bufs(struct slip *sl) +{ + /* Free all SLIP frame buffers. */ + kfree(xchg(&sl->rbuff, NULL)); + kfree(xchg(&sl->xbuff, NULL)); +#ifdef SL_INCLUDE_CSLIP + kfree(xchg(&sl->cbuff, NULL)); + slhc_free(xchg(&sl->slcomp, NULL)); +#endif +} + +/* + Reallocate slip channel buffers. + */ + +static int sl_realloc_bufs(struct slip *sl, int mtu) +{ + int err = 0; + struct net_device *dev = sl->dev; + unsigned char *xbuff, *rbuff; +#ifdef SL_INCLUDE_CSLIP + unsigned char *cbuff; +#endif + int len = mtu * 2; + +/* + * allow for arrival of larger UDP packets, even if we say not to + * also fixes a bug in which SunOS sends 512-byte packets even with + * an MSS of 128 + */ + if (len < 576 * 2) + len = 576 * 2; + + xbuff = kmalloc(len + 4, GFP_ATOMIC); + rbuff = kmalloc(len + 4, GFP_ATOMIC); +#ifdef SL_INCLUDE_CSLIP + cbuff = kmalloc(len + 4, GFP_ATOMIC); +#endif + + +#ifdef SL_INCLUDE_CSLIP + if (xbuff == NULL || rbuff == NULL || cbuff == NULL) { +#else + if (xbuff == NULL || rbuff == NULL) { +#endif + if (mtu > sl->mtu) { + printk(KERN_WARNING "%s: unable to grow slip buffers, MTU change cancelled.\n", + dev->name); + err = -ENOBUFS; + } + goto done; + } + spin_lock_bh(&sl->lock); + + err = -ENODEV; + if (sl->tty == NULL) + goto done_on_bh; + + xbuff = xchg(&sl->xbuff, xbuff); + rbuff = xchg(&sl->rbuff, rbuff); +#ifdef SL_INCLUDE_CSLIP + cbuff = xchg(&sl->cbuff, cbuff); +#endif + if (sl->xleft) { + if (sl->xleft <= len) { + memcpy(sl->xbuff, sl->xhead, sl->xleft); + } else { + sl->xleft = 0; + dev->stats.tx_dropped++; + } + } + sl->xhead = sl->xbuff; + + if (sl->rcount) { + if (sl->rcount <= len) { + memcpy(sl->rbuff, rbuff, sl->rcount); + } else { + sl->rcount = 0; + dev->stats.rx_over_errors++; + set_bit(SLF_ERROR, &sl->flags); + } + } + sl->mtu = mtu; + dev->mtu = mtu; + sl->buffsize = len; + err = 0; + +done_on_bh: + spin_unlock_bh(&sl->lock); + +done: + kfree(xbuff); + kfree(rbuff); +#ifdef SL_INCLUDE_CSLIP + kfree(cbuff); +#endif + return err; +} + + +/* Set the "sending" flag. This must be atomic hence the set_bit. */ +static inline void sl_lock(struct slip *sl) +{ + netif_stop_queue(sl->dev); +} + + +/* Clear the "sending" flag. This must be atomic, hence the ASM. */ +static inline void sl_unlock(struct slip *sl) +{ + netif_wake_queue(sl->dev); +} + +/* Send one completely decapsulated IP datagram to the IP layer. */ +static void sl_bump(struct slip *sl) +{ + struct net_device *dev = sl->dev; + struct sk_buff *skb; + int count; + + count = sl->rcount; +#ifdef SL_INCLUDE_CSLIP + if (sl->mode & (SL_MODE_ADAPTIVE | SL_MODE_CSLIP)) { + unsigned char c = sl->rbuff[0]; + if (c & SL_TYPE_COMPRESSED_TCP) { + /* ignore compressed packets when CSLIP is off */ + if (!(sl->mode & SL_MODE_CSLIP)) { + printk(KERN_WARNING "%s: compressed packet ignored\n", dev->name); + return; + } + /* make sure we've reserved enough space for uncompress + to use */ + if (count + 80 > sl->buffsize) { + dev->stats.rx_over_errors++; + return; + } + count = slhc_uncompress(sl->slcomp, sl->rbuff, count); + if (count <= 0) + return; + } else if (c >= SL_TYPE_UNCOMPRESSED_TCP) { + if (!(sl->mode & SL_MODE_CSLIP)) { + /* turn on header compression */ + sl->mode |= SL_MODE_CSLIP; + sl->mode &= ~SL_MODE_ADAPTIVE; + printk(KERN_INFO "%s: header compression turned on\n", dev->name); + } + sl->rbuff[0] &= 0x4f; + if (slhc_remember(sl->slcomp, sl->rbuff, count) <= 0) + return; + } + } +#endif /* SL_INCLUDE_CSLIP */ + + dev->stats.rx_bytes += count; + + skb = dev_alloc_skb(count); + if (skb == NULL) { + printk(KERN_WARNING "%s: memory squeeze, dropping packet.\n", dev->name); + dev->stats.rx_dropped++; + return; + } + skb->dev = dev; + memcpy(skb_put(skb, count), sl->rbuff, count); + skb_reset_mac_header(skb); + skb->protocol = htons(ETH_P_IP); + netif_rx_ni(skb); + dev->stats.rx_packets++; +} + +/* Encapsulate one IP datagram and stuff into a TTY queue. */ +static void sl_encaps(struct slip *sl, unsigned char *icp, int len) +{ + unsigned char *p; + int actual, count; + + if (len > sl->mtu) { /* Sigh, shouldn't occur BUT ... */ + printk(KERN_WARNING "%s: truncating oversized transmit packet!\n", sl->dev->name); + sl->dev->stats.tx_dropped++; + sl_unlock(sl); + return; + } + + p = icp; +#ifdef SL_INCLUDE_CSLIP + if (sl->mode & SL_MODE_CSLIP) + len = slhc_compress(sl->slcomp, p, len, sl->cbuff, &p, 1); +#endif +#ifdef CONFIG_SLIP_MODE_SLIP6 + if (sl->mode & SL_MODE_SLIP6) + count = slip_esc6(p, sl->xbuff, len); + else +#endif + count = slip_esc(p, sl->xbuff, len); + + /* Order of next two lines is *very* important. + * When we are sending a little amount of data, + * the transfer may be completed inside the ops->write() + * routine, because it's running with interrupts enabled. + * In this case we *never* got WRITE_WAKEUP event, + * if we did not request it before write operation. + * 14 Oct 1994 Dmitry Gorodchanin. + */ + set_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); + actual = sl->tty->ops->write(sl->tty, sl->xbuff, count); +#ifdef SL_CHECK_TRANSMIT + sl->dev->trans_start = jiffies; +#endif + sl->xleft = count - actual; + sl->xhead = sl->xbuff + actual; +#ifdef CONFIG_SLIP_SMART + /* VSV */ + clear_bit(SLF_OUTWAIT, &sl->flags); /* reset outfill flag */ +#endif +} + +/* Write out any remaining transmit buffer. Scheduled when tty is writable */ +static void slip_transmit(struct work_struct *work) +{ + struct slip *sl = container_of(work, struct slip, tx_work); + int actual; + + spin_lock_bh(&sl->lock); + /* First make sure we're connected. */ + if (!sl->tty || sl->magic != SLIP_MAGIC || !netif_running(sl->dev)) { + spin_unlock_bh(&sl->lock); + return; + } + + if (sl->xleft <= 0) { + /* Now serial buffer is almost free & we can start + * transmission of another packet */ + sl->dev->stats.tx_packets++; + clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); + spin_unlock_bh(&sl->lock); + sl_unlock(sl); + return; + } + + actual = sl->tty->ops->write(sl->tty, sl->xhead, sl->xleft); + sl->xleft -= actual; + sl->xhead += actual; + spin_unlock_bh(&sl->lock); +} + +/* + * Called by the driver when there's room for more data. + * Schedule the transmit. + */ +static void slip_write_wakeup(struct tty_struct *tty) +{ + struct slip *sl = tty->disc_data; + + schedule_work(&sl->tx_work); +} + +static void sl_tx_timeout(struct net_device *dev) +{ + struct slip *sl = netdev_priv(dev); + + spin_lock(&sl->lock); + + if (netif_queue_stopped(dev)) { + if (!netif_running(dev)) + goto out; + + /* May be we must check transmitter timeout here ? + * 14 Oct 1994 Dmitry Gorodchanin. + */ +#ifdef SL_CHECK_TRANSMIT + if (time_before(jiffies, dev_trans_start(dev) + 20 * HZ)) { + /* 20 sec timeout not reached */ + goto out; + } + printk(KERN_WARNING "%s: transmit timed out, %s?\n", + dev->name, + (tty_chars_in_buffer(sl->tty) || sl->xleft) ? + "bad line quality" : "driver error"); + sl->xleft = 0; + clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); + sl_unlock(sl); +#endif + } +out: + spin_unlock(&sl->lock); +} + + +/* Encapsulate an IP datagram and kick it into a TTY queue. */ +static netdev_tx_t +sl_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct slip *sl = netdev_priv(dev); + + spin_lock(&sl->lock); + if (!netif_running(dev)) { + spin_unlock(&sl->lock); + printk(KERN_WARNING "%s: xmit call when iface is down\n", dev->name); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + if (sl->tty == NULL) { + spin_unlock(&sl->lock); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + + sl_lock(sl); + dev->stats.tx_bytes += skb->len; + sl_encaps(sl, skb->data, skb->len); + spin_unlock(&sl->lock); + + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + + +/****************************************** + * Routines looking at netdevice side. + ******************************************/ + +/* Netdevice UP -> DOWN routine */ + +static int +sl_close(struct net_device *dev) +{ + struct slip *sl = netdev_priv(dev); + + spin_lock_bh(&sl->lock); + if (sl->tty) + /* TTY discipline is running. */ + clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); + netif_stop_queue(dev); + sl->rcount = 0; + sl->xleft = 0; + spin_unlock_bh(&sl->lock); + + return 0; +} + +/* Netdevice DOWN -> UP routine */ + +static int sl_open(struct net_device *dev) +{ + struct slip *sl = netdev_priv(dev); + + if (sl->tty == NULL) + return -ENODEV; + + sl->flags &= (1 << SLF_INUSE); + netif_start_queue(dev); + return 0; +} + +/* Netdevice change MTU request */ + +static int sl_change_mtu(struct net_device *dev, int new_mtu) +{ + struct slip *sl = netdev_priv(dev); + + if (new_mtu < 68 || new_mtu > 65534) + return -EINVAL; + + if (new_mtu != dev->mtu) + return sl_realloc_bufs(sl, new_mtu); + return 0; +} + +/* Netdevice get statistics request */ + +static struct rtnl_link_stats64 * +sl_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) +{ + struct net_device_stats *devstats = &dev->stats; +#ifdef SL_INCLUDE_CSLIP + struct slip *sl = netdev_priv(dev); + struct slcompress *comp = sl->slcomp; +#endif + stats->rx_packets = devstats->rx_packets; + stats->tx_packets = devstats->tx_packets; + stats->rx_bytes = devstats->rx_bytes; + stats->tx_bytes = devstats->tx_bytes; + stats->rx_dropped = devstats->rx_dropped; + stats->tx_dropped = devstats->tx_dropped; + stats->tx_errors = devstats->tx_errors; + stats->rx_errors = devstats->rx_errors; + stats->rx_over_errors = devstats->rx_over_errors; + +#ifdef SL_INCLUDE_CSLIP + if (comp) { + /* Generic compressed statistics */ + stats->rx_compressed = comp->sls_i_compressed; + stats->tx_compressed = comp->sls_o_compressed; + + /* Are we really still needs this? */ + stats->rx_fifo_errors += comp->sls_i_compressed; + stats->rx_dropped += comp->sls_i_tossed; + stats->tx_fifo_errors += comp->sls_o_compressed; + stats->collisions += comp->sls_o_misses; + } +#endif + return stats; +} + +/* Netdevice register callback */ + +static int sl_init(struct net_device *dev) +{ + struct slip *sl = netdev_priv(dev); + + /* + * Finish setting up the DEVICE info. + */ + + dev->mtu = sl->mtu; + dev->type = ARPHRD_SLIP + sl->mode; +#ifdef SL_CHECK_TRANSMIT + dev->watchdog_timeo = 20*HZ; +#endif + return 0; +} + + +static void sl_uninit(struct net_device *dev) +{ + struct slip *sl = netdev_priv(dev); + + sl_free_bufs(sl); +} + +/* Hook the destructor so we can free slip devices at the right point in time */ +static void sl_free_netdev(struct net_device *dev) +{ + int i = dev->base_addr; + free_netdev(dev); + slip_devs[i] = NULL; +} + +static const struct net_device_ops sl_netdev_ops = { + .ndo_init = sl_init, + .ndo_uninit = sl_uninit, + .ndo_open = sl_open, + .ndo_stop = sl_close, + .ndo_start_xmit = sl_xmit, + .ndo_get_stats64 = sl_get_stats64, + .ndo_change_mtu = sl_change_mtu, + .ndo_tx_timeout = sl_tx_timeout, +#ifdef CONFIG_SLIP_SMART + .ndo_do_ioctl = sl_ioctl, +#endif +}; + + +static void sl_setup(struct net_device *dev) +{ + dev->netdev_ops = &sl_netdev_ops; + dev->destructor = sl_free_netdev; + + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->tx_queue_len = 10; + + /* New-style flags. */ + dev->flags = IFF_NOARP|IFF_POINTOPOINT|IFF_MULTICAST; +} + +/****************************************** + Routines looking at TTY side. + ******************************************/ + + +/* + * Handle the 'receiver data ready' interrupt. + * This function is called by the 'tty_io' module in the kernel when + * a block of SLIP data has been received, which can now be decapsulated + * and sent on to some IP layer for further processing. This will not + * be re-entered while running but other ldisc functions may be called + * in parallel + */ + +static void slip_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct slip *sl = tty->disc_data; + + if (!sl || sl->magic != SLIP_MAGIC || !netif_running(sl->dev)) + return; + + /* Read the characters out of the buffer */ + while (count--) { + if (fp && *fp++) { + if (!test_and_set_bit(SLF_ERROR, &sl->flags)) + sl->dev->stats.rx_errors++; + cp++; + continue; + } +#ifdef CONFIG_SLIP_MODE_SLIP6 + if (sl->mode & SL_MODE_SLIP6) + slip_unesc6(sl, *cp++); + else +#endif + slip_unesc(sl, *cp++); + } +} + +/************************************ + * slip_open helper routines. + ************************************/ + +/* Collect hanged up channels */ +static void sl_sync(void) +{ + int i; + struct net_device *dev; + struct slip *sl; + + for (i = 0; i < slip_maxdev; i++) { + dev = slip_devs[i]; + if (dev == NULL) + break; + + sl = netdev_priv(dev); + if (sl->tty || sl->leased) + continue; + if (dev->flags & IFF_UP) + dev_close(dev); + } +} + + +/* Find a free SLIP channel, and link in this `tty' line. */ +static struct slip *sl_alloc(dev_t line) +{ + int i; + char name[IFNAMSIZ]; + struct net_device *dev = NULL; + struct slip *sl; + + for (i = 0; i < slip_maxdev; i++) { + dev = slip_devs[i]; + if (dev == NULL) + break; + } + /* Sorry, too many, all slots in use */ + if (i >= slip_maxdev) + return NULL; + + sprintf(name, "sl%d", i); + dev = alloc_netdev(sizeof(*sl), name, NET_NAME_UNKNOWN, sl_setup); + if (!dev) + return NULL; + + dev->base_addr = i; + sl = netdev_priv(dev); + + /* Initialize channel control data */ + sl->magic = SLIP_MAGIC; + sl->dev = dev; + spin_lock_init(&sl->lock); + INIT_WORK(&sl->tx_work, slip_transmit); + sl->mode = SL_MODE_DEFAULT; +#ifdef CONFIG_SLIP_SMART + /* initialize timer_list struct */ + init_timer(&sl->keepalive_timer); + sl->keepalive_timer.data = (unsigned long)sl; + sl->keepalive_timer.function = sl_keepalive; + init_timer(&sl->outfill_timer); + sl->outfill_timer.data = (unsigned long)sl; + sl->outfill_timer.function = sl_outfill; +#endif + slip_devs[i] = dev; + return sl; +} + +/* + * Open the high-level part of the SLIP channel. + * This function is called by the TTY module when the + * SLIP line discipline is called for. Because we are + * sure the tty line exists, we only have to link it to + * a free SLIP channel... + * + * Called in process context serialized from other ldisc calls. + */ + +static int slip_open(struct tty_struct *tty) +{ + struct slip *sl; + int err; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (tty->ops->write == NULL) + return -EOPNOTSUPP; + + /* RTnetlink lock is misused here to serialize concurrent + opens of slip channels. There are better ways, but it is + the simplest one. + */ + rtnl_lock(); + + /* Collect hanged up channels. */ + sl_sync(); + + sl = tty->disc_data; + + err = -EEXIST; + /* First make sure we're not already connected. */ + if (sl && sl->magic == SLIP_MAGIC) + goto err_exit; + + /* OK. Find a free SLIP channel to use. */ + err = -ENFILE; + sl = sl_alloc(tty_devnum(tty)); + if (sl == NULL) + goto err_exit; + + sl->tty = tty; + tty->disc_data = sl; + sl->pid = current->pid; + + if (!test_bit(SLF_INUSE, &sl->flags)) { + /* Perform the low-level SLIP initialization. */ + err = sl_alloc_bufs(sl, SL_MTU); + if (err) + goto err_free_chan; + + set_bit(SLF_INUSE, &sl->flags); + + err = register_netdevice(sl->dev); + if (err) + goto err_free_bufs; + } + +#ifdef CONFIG_SLIP_SMART + if (sl->keepalive) { + sl->keepalive_timer.expires = jiffies + sl->keepalive * HZ; + add_timer(&sl->keepalive_timer); + } + if (sl->outfill) { + sl->outfill_timer.expires = jiffies + sl->outfill * HZ; + add_timer(&sl->outfill_timer); + } +#endif + + /* Done. We have linked the TTY line to a channel. */ + rtnl_unlock(); + tty->receive_room = 65536; /* We don't flow control */ + + /* TTY layer expects 0 on success */ + return 0; + +err_free_bufs: + sl_free_bufs(sl); + +err_free_chan: + sl->tty = NULL; + tty->disc_data = NULL; + clear_bit(SLF_INUSE, &sl->flags); + +err_exit: + rtnl_unlock(); + + /* Count references from TTY module */ + return err; +} + +/* + * Close down a SLIP channel. + * This means flushing out any pending queues, and then returning. This + * call is serialized against other ldisc functions. + * + * We also use this method fo a hangup event + */ + +static void slip_close(struct tty_struct *tty) +{ + struct slip *sl = tty->disc_data; + + /* First make sure we're connected. */ + if (!sl || sl->magic != SLIP_MAGIC || sl->tty != tty) + return; + + spin_lock_bh(&sl->lock); + tty->disc_data = NULL; + sl->tty = NULL; + spin_unlock_bh(&sl->lock); + + flush_work(&sl->tx_work); + + /* VSV = very important to remove timers */ +#ifdef CONFIG_SLIP_SMART + del_timer_sync(&sl->keepalive_timer); + del_timer_sync(&sl->outfill_timer); +#endif + /* Flush network side */ + unregister_netdev(sl->dev); + /* This will complete via sl_free_netdev */ +} + +static int slip_hangup(struct tty_struct *tty) +{ + slip_close(tty); + return 0; +} + /************************************************************************ + * STANDARD SLIP ENCAPSULATION * + ************************************************************************/ + +static int slip_esc(unsigned char *s, unsigned char *d, int len) +{ + unsigned char *ptr = d; + unsigned char c; + + /* + * Send an initial END character to flush out any + * data that may have accumulated in the receiver + * due to line noise. + */ + + *ptr++ = END; + + /* + * For each byte in the packet, send the appropriate + * character sequence, according to the SLIP protocol. + */ + + while (len-- > 0) { + switch (c = *s++) { + case END: + *ptr++ = ESC; + *ptr++ = ESC_END; + break; + case ESC: + *ptr++ = ESC; + *ptr++ = ESC_ESC; + break; + default: + *ptr++ = c; + break; + } + } + *ptr++ = END; + return ptr - d; +} + +static void slip_unesc(struct slip *sl, unsigned char s) +{ + + switch (s) { + case END: +#ifdef CONFIG_SLIP_SMART + /* drop keeptest bit = VSV */ + if (test_bit(SLF_KEEPTEST, &sl->flags)) + clear_bit(SLF_KEEPTEST, &sl->flags); +#endif + + if (!test_and_clear_bit(SLF_ERROR, &sl->flags) && + (sl->rcount > 2)) + sl_bump(sl); + clear_bit(SLF_ESCAPE, &sl->flags); + sl->rcount = 0; + return; + + case ESC: + set_bit(SLF_ESCAPE, &sl->flags); + return; + case ESC_ESC: + if (test_and_clear_bit(SLF_ESCAPE, &sl->flags)) + s = ESC; + break; + case ESC_END: + if (test_and_clear_bit(SLF_ESCAPE, &sl->flags)) + s = END; + break; + } + if (!test_bit(SLF_ERROR, &sl->flags)) { + if (sl->rcount < sl->buffsize) { + sl->rbuff[sl->rcount++] = s; + return; + } + sl->dev->stats.rx_over_errors++; + set_bit(SLF_ERROR, &sl->flags); + } +} + + +#ifdef CONFIG_SLIP_MODE_SLIP6 +/************************************************************************ + * 6 BIT SLIP ENCAPSULATION * + ************************************************************************/ + +static int slip_esc6(unsigned char *s, unsigned char *d, int len) +{ + unsigned char *ptr = d; + unsigned char c; + int i; + unsigned short v = 0; + short bits = 0; + + /* + * Send an initial END character to flush out any + * data that may have accumulated in the receiver + * due to line noise. + */ + + *ptr++ = 0x70; + + /* + * Encode the packet into printable ascii characters + */ + + for (i = 0; i < len; ++i) { + v = (v << 8) | s[i]; + bits += 8; + while (bits >= 6) { + bits -= 6; + c = 0x30 + ((v >> bits) & 0x3F); + *ptr++ = c; + } + } + if (bits) { + c = 0x30 + ((v << (6 - bits)) & 0x3F); + *ptr++ = c; + } + *ptr++ = 0x70; + return ptr - d; +} + +static void slip_unesc6(struct slip *sl, unsigned char s) +{ + unsigned char c; + + if (s == 0x70) { +#ifdef CONFIG_SLIP_SMART + /* drop keeptest bit = VSV */ + if (test_bit(SLF_KEEPTEST, &sl->flags)) + clear_bit(SLF_KEEPTEST, &sl->flags); +#endif + + if (!test_and_clear_bit(SLF_ERROR, &sl->flags) && + (sl->rcount > 2)) + sl_bump(sl); + sl->rcount = 0; + sl->xbits = 0; + sl->xdata = 0; + } else if (s >= 0x30 && s < 0x70) { + sl->xdata = (sl->xdata << 6) | ((s - 0x30) & 0x3F); + sl->xbits += 6; + if (sl->xbits >= 8) { + sl->xbits -= 8; + c = (unsigned char)(sl->xdata >> sl->xbits); + if (!test_bit(SLF_ERROR, &sl->flags)) { + if (sl->rcount < sl->buffsize) { + sl->rbuff[sl->rcount++] = c; + return; + } + sl->dev->stats.rx_over_errors++; + set_bit(SLF_ERROR, &sl->flags); + } + } + } +} +#endif /* CONFIG_SLIP_MODE_SLIP6 */ + +/* Perform I/O control on an active SLIP channel. */ +static int slip_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct slip *sl = tty->disc_data; + unsigned int tmp; + int __user *p = (int __user *)arg; + + /* First make sure we're connected. */ + if (!sl || sl->magic != SLIP_MAGIC) + return -EINVAL; + + switch (cmd) { + case SIOCGIFNAME: + tmp = strlen(sl->dev->name) + 1; + if (copy_to_user((void __user *)arg, sl->dev->name, tmp)) + return -EFAULT; + return 0; + + case SIOCGIFENCAP: + if (put_user(sl->mode, p)) + return -EFAULT; + return 0; + + case SIOCSIFENCAP: + if (get_user(tmp, p)) + return -EFAULT; +#ifndef SL_INCLUDE_CSLIP + if (tmp & (SL_MODE_CSLIP|SL_MODE_ADAPTIVE)) + return -EINVAL; +#else + if ((tmp & (SL_MODE_ADAPTIVE | SL_MODE_CSLIP)) == + (SL_MODE_ADAPTIVE | SL_MODE_CSLIP)) + /* return -EINVAL; */ + tmp &= ~SL_MODE_ADAPTIVE; +#endif +#ifndef CONFIG_SLIP_MODE_SLIP6 + if (tmp & SL_MODE_SLIP6) + return -EINVAL; +#endif + sl->mode = tmp; + sl->dev->type = ARPHRD_SLIP + sl->mode; + return 0; + + case SIOCSIFHWADDR: + return -EINVAL; + +#ifdef CONFIG_SLIP_SMART + /* VSV changes start here */ + case SIOCSKEEPALIVE: + if (get_user(tmp, p)) + return -EFAULT; + if (tmp > 255) /* max for unchar */ + return -EINVAL; + + spin_lock_bh(&sl->lock); + if (!sl->tty) { + spin_unlock_bh(&sl->lock); + return -ENODEV; + } + sl->keepalive = (u8)tmp; + if (sl->keepalive != 0) { + mod_timer(&sl->keepalive_timer, + jiffies + sl->keepalive * HZ); + set_bit(SLF_KEEPTEST, &sl->flags); + } else + del_timer(&sl->keepalive_timer); + spin_unlock_bh(&sl->lock); + return 0; + + case SIOCGKEEPALIVE: + if (put_user(sl->keepalive, p)) + return -EFAULT; + return 0; + + case SIOCSOUTFILL: + if (get_user(tmp, p)) + return -EFAULT; + if (tmp > 255) /* max for unchar */ + return -EINVAL; + spin_lock_bh(&sl->lock); + if (!sl->tty) { + spin_unlock_bh(&sl->lock); + return -ENODEV; + } + sl->outfill = (u8)tmp; + if (sl->outfill != 0) { + mod_timer(&sl->outfill_timer, + jiffies + sl->outfill * HZ); + set_bit(SLF_OUTWAIT, &sl->flags); + } else + del_timer(&sl->outfill_timer); + spin_unlock_bh(&sl->lock); + return 0; + + case SIOCGOUTFILL: + if (put_user(sl->outfill, p)) + return -EFAULT; + return 0; + /* VSV changes end */ +#endif + default: + return tty_mode_ioctl(tty, file, cmd, arg); + } +} + +#ifdef CONFIG_COMPAT +static long slip_compat_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case SIOCGIFNAME: + case SIOCGIFENCAP: + case SIOCSIFENCAP: + case SIOCSIFHWADDR: + case SIOCSKEEPALIVE: + case SIOCGKEEPALIVE: + case SIOCSOUTFILL: + case SIOCGOUTFILL: + return slip_ioctl(tty, file, cmd, + (unsigned long)compat_ptr(arg)); + } + + return -ENOIOCTLCMD; +} +#endif + +/* VSV changes start here */ +#ifdef CONFIG_SLIP_SMART +/* function do_ioctl called from net/core/dev.c + to allow get/set outfill/keepalive parameter + by ifconfig */ + +static int sl_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct slip *sl = netdev_priv(dev); + unsigned long *p = (unsigned long *)&rq->ifr_ifru; + + if (sl == NULL) /* Allocation failed ?? */ + return -ENODEV; + + spin_lock_bh(&sl->lock); + + if (!sl->tty) { + spin_unlock_bh(&sl->lock); + return -ENODEV; + } + + switch (cmd) { + case SIOCSKEEPALIVE: + /* max for unchar */ + if ((unsigned)*p > 255) { + spin_unlock_bh(&sl->lock); + return -EINVAL; + } + sl->keepalive = (u8)*p; + if (sl->keepalive != 0) { + sl->keepalive_timer.expires = + jiffies + sl->keepalive * HZ; + mod_timer(&sl->keepalive_timer, + jiffies + sl->keepalive * HZ); + set_bit(SLF_KEEPTEST, &sl->flags); + } else + del_timer(&sl->keepalive_timer); + break; + + case SIOCGKEEPALIVE: + *p = sl->keepalive; + break; + + case SIOCSOUTFILL: + if ((unsigned)*p > 255) { /* max for unchar */ + spin_unlock_bh(&sl->lock); + return -EINVAL; + } + sl->outfill = (u8)*p; + if (sl->outfill != 0) { + mod_timer(&sl->outfill_timer, + jiffies + sl->outfill * HZ); + set_bit(SLF_OUTWAIT, &sl->flags); + } else + del_timer(&sl->outfill_timer); + break; + + case SIOCGOUTFILL: + *p = sl->outfill; + break; + + case SIOCSLEASE: + /* Resolve race condition, when ioctl'ing hanged up + and opened by another process device. + */ + if (sl->tty != current->signal->tty && + sl->pid != current->pid) { + spin_unlock_bh(&sl->lock); + return -EPERM; + } + sl->leased = 0; + if (*p) + sl->leased = 1; + break; + + case SIOCGLEASE: + *p = sl->leased; + } + spin_unlock_bh(&sl->lock); + return 0; +} +#endif +/* VSV changes end */ + +static struct tty_ldisc_ops sl_ldisc = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "slip", + .open = slip_open, + .close = slip_close, + .hangup = slip_hangup, + .ioctl = slip_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = slip_compat_ioctl, +#endif + .receive_buf = slip_receive_buf, + .write_wakeup = slip_write_wakeup, +}; + +static int __init slip_init(void) +{ + int status; + + if (slip_maxdev < 4) + slip_maxdev = 4; /* Sanity */ + + printk(KERN_INFO "SLIP: version %s (dynamic channels, max=%d)" +#ifdef CONFIG_SLIP_MODE_SLIP6 + " (6 bit encapsulation enabled)" +#endif + ".\n", + SLIP_VERSION, slip_maxdev); +#if defined(SL_INCLUDE_CSLIP) + printk(KERN_INFO "CSLIP: code copyright 1989 Regents of the University of California.\n"); +#endif +#ifdef CONFIG_SLIP_SMART + printk(KERN_INFO "SLIP linefill/keepalive option.\n"); +#endif + + slip_devs = kzalloc(sizeof(struct net_device *)*slip_maxdev, + GFP_KERNEL); + if (!slip_devs) + return -ENOMEM; + + /* Fill in our line protocol discipline, and register it */ + status = tty_register_ldisc(N_SLIP, &sl_ldisc); + if (status != 0) { + printk(KERN_ERR "SLIP: can't register line discipline (err = %d)\n", status); + kfree(slip_devs); + } + return status; +} + +static void __exit slip_exit(void) +{ + int i; + struct net_device *dev; + struct slip *sl; + unsigned long timeout = jiffies + HZ; + int busy = 0; + + if (slip_devs == NULL) + return; + + /* First of all: check for active disciplines and hangup them. + */ + do { + if (busy) + msleep_interruptible(100); + + busy = 0; + for (i = 0; i < slip_maxdev; i++) { + dev = slip_devs[i]; + if (!dev) + continue; + sl = netdev_priv(dev); + spin_lock_bh(&sl->lock); + if (sl->tty) { + busy++; + tty_hangup(sl->tty); + } + spin_unlock_bh(&sl->lock); + } + } while (busy && time_before(jiffies, timeout)); + + /* FIXME: hangup is async so we should wait when doing this second + phase */ + + for (i = 0; i < slip_maxdev; i++) { + dev = slip_devs[i]; + if (!dev) + continue; + slip_devs[i] = NULL; + + sl = netdev_priv(dev); + if (sl->tty) { + printk(KERN_ERR "%s: tty discipline still running\n", + dev->name); + /* Intentionally leak the control block. */ + dev->destructor = NULL; + } + + unregister_netdev(dev); + } + + kfree(slip_devs); + slip_devs = NULL; + + i = tty_unregister_ldisc(N_SLIP); + if (i != 0) + printk(KERN_ERR "SLIP: can't unregister line discipline (err = %d)\n", i); +} + +module_init(slip_init); +module_exit(slip_exit); + +#ifdef CONFIG_SLIP_SMART +/* + * This is start of the code for multislip style line checking + * added by Stanislav Voronyi. All changes before marked VSV + */ + +static void sl_outfill(unsigned long sls) +{ + struct slip *sl = (struct slip *)sls; + + spin_lock(&sl->lock); + + if (sl->tty == NULL) + goto out; + + if (sl->outfill) { + if (test_bit(SLF_OUTWAIT, &sl->flags)) { + /* no packets were transmitted, do outfill */ +#ifdef CONFIG_SLIP_MODE_SLIP6 + unsigned char s = (sl->mode & SL_MODE_SLIP6)?0x70:END; +#else + unsigned char s = END; +#endif + /* put END into tty queue. Is it right ??? */ + if (!netif_queue_stopped(sl->dev)) { + /* if device busy no outfill */ + sl->tty->ops->write(sl->tty, &s, 1); + } + } else + set_bit(SLF_OUTWAIT, &sl->flags); + + mod_timer(&sl->outfill_timer, jiffies+sl->outfill*HZ); + } +out: + spin_unlock(&sl->lock); +} + +static void sl_keepalive(unsigned long sls) +{ + struct slip *sl = (struct slip *)sls; + + spin_lock(&sl->lock); + + if (sl->tty == NULL) + goto out; + + if (sl->keepalive) { + if (test_bit(SLF_KEEPTEST, &sl->flags)) { + /* keepalive still high :(, we must hangup */ + if (sl->outfill) + /* outfill timer must be deleted too */ + (void)del_timer(&sl->outfill_timer); + printk(KERN_DEBUG "%s: no packets received during keepalive timeout, hangup.\n", sl->dev->name); + /* this must hangup tty & close slip */ + tty_hangup(sl->tty); + /* I think we need not something else */ + goto out; + } else + set_bit(SLF_KEEPTEST, &sl->flags); + + mod_timer(&sl->keepalive_timer, jiffies+sl->keepalive*HZ); + } +out: + spin_unlock(&sl->lock); +} + +#endif +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_SLIP); diff --git a/linux/drivers/net/slip/slip.h b/linux/drivers/net/slip/slip.h new file mode 100644 index 000000000..cf32aadf5 --- /dev/null +++ b/linux/drivers/net/slip/slip.h @@ -0,0 +1,102 @@ +/* + * slip.h Define the SLIP device driver interface and constants. + * + * NOTE: THIS FILE WILL BE MOVED TO THE LINUX INCLUDE DIRECTORY + * AS SOON AS POSSIBLE! + * + * Version: @(#)slip.h 1.2.0 03/28/93 + * + * Fixes: + * Alan Cox : Added slip mtu field. + * Matt Dillon : Printable slip (borrowed from net2e) + * Alan Cox : Added SL_SLIP_LOTS + * Dmitry Gorodchanin : A lot of changes in the 'struct slip' + * Dmitry Gorodchanin : Added CSLIP statistics. + * Stanislav Voronyi : Make line checking as created by + * Igor Chechik, RELCOM Corp. + * Craig Schlenter : Fixed #define bug that caused + * CSLIP telnets to hang in 1.3.61-6 + * + * Author: Fred N. van Kempen, + */ +#ifndef _LINUX_SLIP_H +#define _LINUX_SLIP_H + + +#if defined(CONFIG_INET) && defined(CONFIG_SLIP_COMPRESSED) +# define SL_INCLUDE_CSLIP +#endif + +#ifdef SL_INCLUDE_CSLIP +# define SL_MODE_DEFAULT SL_MODE_ADAPTIVE +#else +# define SL_MODE_DEFAULT SL_MODE_SLIP +#endif + +/* SLIP configuration. */ +#define SL_NRUNIT 256 /* MAX number of SLIP channels; + This can be overridden with + insmod -oslip_maxdev=nnn */ +#define SL_MTU 296 /* 296; I am used to 600- FvK */ + +/* SLIP protocol characters. */ +#define END 0300 /* indicates end of frame */ +#define ESC 0333 /* indicates byte stuffing */ +#define ESC_END 0334 /* ESC ESC_END means END 'data' */ +#define ESC_ESC 0335 /* ESC ESC_ESC means ESC 'data' */ + + +struct slip { + int magic; + + /* Various fields. */ + struct tty_struct *tty; /* ptr to TTY structure */ + struct net_device *dev; /* easy for intr handling */ + spinlock_t lock; + struct work_struct tx_work; /* Flushes transmit buffer */ + +#ifdef SL_INCLUDE_CSLIP + struct slcompress *slcomp; /* for header compression */ + unsigned char *cbuff; /* compression buffer */ +#endif + + /* These are pointers to the malloc()ed frame buffers. */ + unsigned char *rbuff; /* receiver buffer */ + int rcount; /* received chars counter */ + unsigned char *xbuff; /* transmitter buffer */ + unsigned char *xhead; /* pointer to next byte to XMIT */ + int xleft; /* bytes left in XMIT queue */ + int mtu; /* Our mtu (to spot changes!) */ + int buffsize; /* Max buffers sizes */ + +#ifdef CONFIG_SLIP_MODE_SLIP6 + int xdata, xbits; /* 6 bit slip controls */ +#endif + + unsigned long flags; /* Flag values/ mode etc */ +#define SLF_INUSE 0 /* Channel in use */ +#define SLF_ESCAPE 1 /* ESC received */ +#define SLF_ERROR 2 /* Parity, etc. error */ +#define SLF_KEEPTEST 3 /* Keepalive test flag */ +#define SLF_OUTWAIT 4 /* is outpacket was flag */ + + unsigned char mode; /* SLIP mode */ + unsigned char leased; + pid_t pid; +#define SL_MODE_SLIP 0 +#define SL_MODE_CSLIP 1 +#define SL_MODE_SLIP6 2 /* Matt Dillon's printable slip */ +#define SL_MODE_CSLIP6 (SL_MODE_SLIP6|SL_MODE_CSLIP) +#define SL_MODE_AX25 4 +#define SL_MODE_ADAPTIVE 8 +#ifdef CONFIG_SLIP_SMART + unsigned char outfill; /* # of sec between outfill packet */ + unsigned char keepalive; /* keepalive seconds */ + struct timer_list outfill_timer; + struct timer_list keepalive_timer; +#endif +}; + +#define SLIP_MAGIC 0x5302 + +#endif /* _LINUX_SLIP.H */ diff --git a/linux/drivers/net/slip/slip.txt b/linux/drivers/net/slip/slip.txt new file mode 100644 index 000000000..5130dcb01 --- /dev/null +++ b/linux/drivers/net/slip/slip.txt @@ -0,0 +1 @@ +sl_netdev_ops functions not called, assignment on line 659 doesn't spawn diff --git a/linux/drivers/net/stub.c b/linux/drivers/net/stub.c new file mode 100644 index 000000000..637ab904c --- /dev/null +++ b/linux/drivers/net/stub.c @@ -0,0 +1,43 @@ +#include +#include +#include + +struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, + unsigned char name_assign_type, + void (*setup)(struct net_device *), + unsigned int txqs, unsigned int rxqs) { + struct net_device *dev; + size_t alloc_size; + struct net_device *p; + + alloc_size = sizeof(struct net_device); + p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT); + if (!p) + return NULL; + + dev = p; + + setup(dev); + return dev; +} + +void ether_setup(struct net_device *dev) +{ + dev->header_ops = ð_header_ops; + dev->type = ARPHRD_ETHER; + dev->hard_header_len = ETH_HLEN; + dev->mtu = ETH_DATA_LEN; + dev->addr_len = ETH_ALEN; + dev->tx_queue_len = 1000; /* Ethernet wants good queues */ + dev->flags = IFF_BROADCAST|IFF_MULTICAST; + dev->priv_flags |= IFF_TX_SKB_SHARING; + + memset(dev->broadcast, 0xFF, ETH_ALEN); +} + +struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs, + unsigned int rxqs) +{ + return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN, + ether_setup, txqs, rxqs); +} \ No newline at end of file diff --git a/update_bench_incremental.rb b/update_bench_incremental.rb index e57d26242..0bb82328f 100755 --- a/update_bench_incremental.rb +++ b/update_bench_incremental.rb @@ -191,7 +191,7 @@ def print_res (i) benchmarks.each do |name, spec| info = spec['info'] path = spec['path'] - params = spec['params'] + params = spec['param'] params = '' if params.nil? fullpath = File.expand_path(path, $bench_path) size = "#{`wc -l #{path}`.split[0]} lines"