diff options
Diffstat (limited to 'drivers/net/sunhme.c')
-rw-r--r-- | drivers/net/sunhme.c | 738 |
1 files changed, 598 insertions, 140 deletions
diff --git a/drivers/net/sunhme.c b/drivers/net/sunhme.c index 0b2eb82fc..580fb2dca 100644 --- a/drivers/net/sunhme.c +++ b/drivers/net/sunhme.c @@ -2,11 +2,11 @@ * auto carrier detecting ethernet driver. Also known as the * "Happy Meal Ethernet" found on SunSwift SBUS cards. * - * Copyright (C) 1996 David S. Miller (davem@caipfs.rutgers.edu) + * Copyright (C) 1996, 1998 David S. Miller (davem@caipfs.rutgers.edu) */ static char *version = - "sunhme.c:v1.2 10/Oct/96 David S. Miller (davem@caipfs.rutgers.edu)\n"; + "sunhme.c:v1.10 27/Jan/99 David S. Miller (davem@caipfs.rutgers.edu)\n"; #include <linux/module.h> @@ -35,12 +35,13 @@ static char *version = #include <asm/openprom.h> #include <asm/oplib.h> #include <asm/auxio.h> -#include <asm/system.h> #include <asm/pgtable.h> #include <asm/irq.h> #ifndef __sparc_v9__ #include <asm/io-unit.h> #endif +#include <asm/ethtool.h> +#include <asm/uaccess.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> @@ -57,6 +58,11 @@ static char *version = static struct happy_meal *root_happy_dev = NULL; #endif +static struct quattro *qfe_sbus_list = NULL; +#ifdef CONFIG_PCI +static struct quattro *qfe_pci_list = NULL; +#endif + #undef HMEDEBUG #undef SXDEBUG #undef RXDEBUG @@ -363,6 +369,15 @@ static int try_next_permutation(struct happy_meal *hp, struct hmeal_tcvregs *tre { hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR); + /* Downgrade from full to half duplex. Only possible + * via ethtool. + */ + if(hp->sw_bmcr & BMCR_FULLDPLX) { + hp->sw_bmcr &= ~(BMCR_FULLDPLX); + happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr); + return 0; + } + /* Downgrade from 100 to 10. */ if(hp->sw_bmcr & BMCR_SPEED100) { hp->sw_bmcr &= ~(BMCR_SPEED100); @@ -1049,7 +1064,7 @@ static void happy_meal_init_rings(struct happy_meal *hp, int from_irq) for(i = 0; i < RX_RING_SIZE; i++) { struct sk_buff *skb; - skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, gfp_flags); + skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, gfp_flags | GFP_DMA); if(!skb) continue; hp->rx_skbs[i] = skb; @@ -1077,7 +1092,7 @@ static void happy_meal_init_rings(struct happy_meal *hp, int from_irq) } else #endif { - hb->happy_meal_rxd[i].rx_addr = (u32)((unsigned long) skb->data); + hb->happy_meal_rxd[i].rx_addr = sbus_dvma_addr(skb->data); hb->happy_meal_rxd[i].rx_flags = (RXFLAG_OWN | ((RX_BUF_ALLOC_SIZE - RX_OFFSET) << 16)); } @@ -1115,7 +1130,8 @@ static void sun4c_happy_meal_init_rings(struct happy_meal *hp) #endif static void happy_meal_begin_auto_negotiation(struct happy_meal *hp, - struct hmeal_tcvregs *tregs) + struct hmeal_tcvregs *tregs, + struct ethtool_cmd *ep) { int timeout; @@ -1124,93 +1140,108 @@ static void happy_meal_begin_auto_negotiation(struct happy_meal *hp, hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR); hp->sw_physid1 = happy_meal_tcvr_read(hp, tregs, DP83840_PHYSID1); hp->sw_physid2 = happy_meal_tcvr_read(hp, tregs, DP83840_PHYSID2); - hp->sw_advertise = happy_meal_tcvr_read(hp, tregs, DP83840_ADVERTISE); /* XXX Check BMSR_ANEGCAPABLE, should not be necessary though. */ - /* Advertise everything we can support. */ - if(hp->sw_bmsr & BMSR_10HALF) - hp->sw_advertise |= (ADVERTISE_10HALF); - else - hp->sw_advertise &= ~(ADVERTISE_10HALF); + hp->sw_advertise = happy_meal_tcvr_read(hp, tregs, DP83840_ADVERTISE); + if(ep == NULL || ep->autoneg == AUTONEG_ENABLE) { + /* Advertise everything we can support. */ + if(hp->sw_bmsr & BMSR_10HALF) + hp->sw_advertise |= (ADVERTISE_10HALF); + else + hp->sw_advertise &= ~(ADVERTISE_10HALF); - if(hp->sw_bmsr & BMSR_10FULL) - hp->sw_advertise |= (ADVERTISE_10FULL); - else - hp->sw_advertise &= ~(ADVERTISE_10FULL); - if(hp->sw_bmsr & BMSR_100HALF) - hp->sw_advertise |= (ADVERTISE_100HALF); - else - hp->sw_advertise &= ~(ADVERTISE_100HALF); - if(hp->sw_bmsr & BMSR_100FULL) - hp->sw_advertise |= (ADVERTISE_100FULL); - else - hp->sw_advertise &= ~(ADVERTISE_100FULL); + if(hp->sw_bmsr & BMSR_10FULL) + hp->sw_advertise |= (ADVERTISE_10FULL); + else + hp->sw_advertise &= ~(ADVERTISE_10FULL); + if(hp->sw_bmsr & BMSR_100HALF) + hp->sw_advertise |= (ADVERTISE_100HALF); + else + hp->sw_advertise &= ~(ADVERTISE_100HALF); + if(hp->sw_bmsr & BMSR_100FULL) + hp->sw_advertise |= (ADVERTISE_100FULL); + else + hp->sw_advertise &= ~(ADVERTISE_100FULL); + happy_meal_tcvr_write(hp, tregs, DP83840_ADVERTISE, hp->sw_advertise); - /* XXX Currently no Happy Meal cards I know off support 100BaseT4, - * XXX and this is because the DP83840 does not support it, changes - * XXX would need to be made to the tx/rx logic in the driver as well - * XXX so I completely skip checking for it in the BMSR for now. - */ + /* XXX Currently no Happy Meal cards I know off support 100BaseT4, + * XXX and this is because the DP83840 does not support it, changes + * XXX would need to be made to the tx/rx logic in the driver as well + * XXX so I completely skip checking for it in the BMSR for now. + */ #ifdef AUTO_SWITCH_DEBUG - ASD(("%s: Advertising [ ", hp->dev->name)); - if(hp->sw_advertise & ADVERTISE_10HALF) - ASD(("10H ")); - if(hp->sw_advertise & ADVERTISE_10FULL) - ASD(("10F ")); - if(hp->sw_advertise & ADVERTISE_100HALF) - ASD(("100H ")); - if(hp->sw_advertise & ADVERTISE_100FULL) - ASD(("100F ")); + ASD(("%s: Advertising [ ", hp->dev->name)); + if(hp->sw_advertise & ADVERTISE_10HALF) + ASD(("10H ")); + if(hp->sw_advertise & ADVERTISE_10FULL) + ASD(("10F ")); + if(hp->sw_advertise & ADVERTISE_100HALF) + ASD(("100H ")); + if(hp->sw_advertise & ADVERTISE_100FULL) + ASD(("100F ")); #endif - happy_meal_tcvr_write(hp, tregs, DP83840_ADVERTISE, hp->sw_advertise); + /* Enable Auto-Negotiation, this is usually on already... */ + hp->sw_bmcr |= BMCR_ANENABLE; + happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr); - /* Enable Auto-Negotiation, this is usually on already... */ - hp->sw_bmcr |= BMCR_ANENABLE; - happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr); + /* Restart it to make sure it is going. */ + hp->sw_bmcr |= BMCR_ANRESTART; + happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr); - /* Restart it to make sure it is going. */ - hp->sw_bmcr |= BMCR_ANRESTART; - happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr); + /* BMCR_ANRESTART self clears when the process has begun. */ - /* BMCR_ANRESTART self clears when the process has begun. */ + timeout = 64; /* More than enough. */ + while(--timeout) { + hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR); + if(!(hp->sw_bmcr & BMCR_ANRESTART)) + break; /* got it. */ + udelay(10); + } + if(!timeout) { + printk("%s: Happy Meal would not start auto negotiation " + "BMCR=0x%04x\n", hp->dev->name, hp->sw_bmcr); + printk("%s: Performing force link detection.\n", + hp->dev->name); + goto force_link; + } else { + hp->timer_state = arbwait; + } + } else { +force_link: + /* Force the link up, trying first a particular mode. + * Either we are here at the request of ethtool or + * because the Happy Meal would not start to autoneg. + */ - timeout = 64; /* More than enough. */ - while(--timeout) { - hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR); - if(!(hp->sw_bmcr & BMCR_ANRESTART)) - break; /* got it. */ - udelay(10); - } - if(!timeout) { - printk("%s: Happy Meal would not start auto negotiation BMCR=0x%04x\n", - hp->dev->name, hp->sw_bmcr); - printk("%s: Performing force link detection.\n", hp->dev->name); - - /* Disable auto-negotiation in BMCR, enable FULL duplex and 100mb/s, - * setup the timer state machine, and fire it off. - * - * XXX Should probably reset the DP83840 first - * XXX as this is a gross fatal error... + /* Disable auto-negotiation in BMCR, enable the duplex and + * speed setting, init the timer state machine, and fire it off. */ - hp->sw_bmcr = BMCR_SPEED100; + if(ep == NULL || ep->autoneg == AUTONEG_ENABLE) { + hp->sw_bmcr = BMCR_SPEED100; + } else { + if(ep->speed == SPEED_100) + hp->sw_bmcr = BMCR_SPEED100; + else + hp->sw_bmcr = 0; + if(ep->duplex == DUPLEX_FULL) + hp->sw_bmcr |= BMCR_FULLDPLX; + } happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr); /* OK, seems we need do disable the transceiver for the first * tick to make sure we get an accurate link state at the * second tick. */ - hp->sw_csconfig = happy_meal_tcvr_read(hp, tregs, DP83840_CSCONFIG); - printk("%s: CSCONFIG [%04x], disabling transceiver\n", hp->dev->name, - hp->sw_csconfig); + hp->sw_csconfig = happy_meal_tcvr_read(hp, tregs, + DP83840_CSCONFIG); hp->sw_csconfig &= ~(CSCONFIG_TCVDISAB); - happy_meal_tcvr_write(hp, tregs, DP83840_CSCONFIG, hp->sw_csconfig); + happy_meal_tcvr_write(hp, tregs, DP83840_CSCONFIG, + hp->sw_csconfig); hp->timer_state = ltrywait; - } else { - hp->timer_state = arbwait; } hp->timer_ticks = 0; @@ -1220,6 +1251,9 @@ static void happy_meal_begin_auto_negotiation(struct happy_meal *hp, add_timer(&hp->happy_timer); } +#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */ +#define CRC_POLYNOMIAL_LE 0xedb88320UL /* Ethernet CRC, little endian */ + static int happy_meal_init(struct happy_meal *hp, int from_irq) { struct hmeal_gregs *gregs = hp->gregs; @@ -1227,9 +1261,12 @@ static int happy_meal_init(struct happy_meal *hp, int from_irq) struct hmeal_erxregs *erxregs = hp->erxregs; struct hmeal_bigmacregs *bregs = hp->bigmacregs; struct hmeal_tcvregs *tregs = hp->tcvregs; - unsigned long regtmp; + unsigned long regtmp, rxcfg; unsigned char *e = &hp->dev->dev_addr[0]; + /* If auto-negotiation timer is running, kill it. */ + del_timer(&hp->happy_timer); + HMD(("happy_meal_init: happy_flags[%08x] ", hp->happy_flags)); if(!(hp->happy_flags & HFLAG_INIT)) { @@ -1322,12 +1359,54 @@ static int happy_meal_init(struct happy_meal *hp, int from_irq) hme_write32(hp, &bregs->mac_addr1, ((e[2] << 8) | e[3])); hme_write32(hp, &bregs->mac_addr0, ((e[0] << 8) | e[1])); - /* Ick, figure out how to properly program the hash table later... */ HMD(("htable, ")); - hme_write32(hp, &bregs->htable3, 0); - hme_write32(hp, &bregs->htable2, 0); - hme_write32(hp, &bregs->htable1, 0); - hme_write32(hp, &bregs->htable0, 0); + if((hp->dev->flags & IFF_ALLMULTI) || + (hp->dev->mc_count > 64)) { + hme_write32(hp, &bregs->htable0, 0xffff); + hme_write32(hp, &bregs->htable1, 0xffff); + hme_write32(hp, &bregs->htable2, 0xffff); + hme_write32(hp, &bregs->htable3, 0xffff); + } else if((hp->dev->flags & IFF_PROMISC) == 0) { + u16 hash_table[4]; + struct dev_mc_list *dmi = hp->dev->mc_list; + char *addrs; + int i, j, bit, byte; + u32 crc, poly = CRC_POLYNOMIAL_LE; + + for(i = 0; i < 4; i++) + hash_table[i] = 0; + + for(i = 0; i < hp->dev->mc_count; i++) { + addrs = dmi->dmi_addr; + dmi = dmi->next; + + if(!(*addrs & 1)) + continue; + + crc = 0xffffffffU; + for(byte = 0; byte < 6; byte++) { + for(bit = *addrs++, j = 0; j < 8; j++, bit >>= 1) { + int test; + + test = ((bit ^ crc) & 0x01); + crc >>= 1; + if(test) + crc = crc ^ poly; + } + } + crc >>= 26; + hash_table[crc >> 4] |= 1 << (crc & 0xf); + } + hme_write32(hp, &bregs->htable0, hash_table[0]); + hme_write32(hp, &bregs->htable1, hash_table[1]); + hme_write32(hp, &bregs->htable2, hash_table[2]); + hme_write32(hp, &bregs->htable3, hash_table[3]); + } else { + hme_write32(hp, &bregs->htable3, 0); + hme_write32(hp, &bregs->htable2, 0); + hme_write32(hp, &bregs->htable1, 0); + hme_write32(hp, &bregs->htable0, 0); + } /* Set the RX and TX ring ptrs. */ HMD(("ring ptrs rxr[%08x] txr[%08x]\n", @@ -1343,9 +1422,22 @@ static int happy_meal_init(struct happy_meal *hp, int from_irq) hme_read32(hp, &gregs->cfg))); #ifdef __sparc_v9__ + /* XXX Can sun4d do these too? */ if(hp->happy_bursts & DMA_BURST64) { + u32 gcfg = GREG_CFG_BURST64; + + /* I have no idea if I should set the extended + * transfer mode bit for Cheerio, so for now I + * do not. -DaveM + */ + if((hp->happy_flags & HFLAG_PCI) == 0) { + mmu_set_sbus64(hp->happy_sbus_dev, + hp->happy_bursts); + gcfg |= GREG_CFG_64BIT; + } + HMD(("64>")); - hme_write32(hp, &gregs->cfg, GREG_CFG_BURST64); + hme_write32(hp, &gregs->cfg, gcfg); } else #endif if(hp->happy_bursts & DMA_BURST32) { @@ -1396,7 +1488,10 @@ static int happy_meal_init(struct happy_meal *hp, int from_irq) /* Enable Big Mac hash table filter. */ HMD(("happy_meal_init: enable hash rx_cfg_old[%08x], ", hme_read32(hp, &bregs->rx_cfg))); - hme_write32(hp, &bregs->rx_cfg, BIGMAC_RXCFG_HENABLE); + rxcfg = BIGMAC_RXCFG_HENABLE; + if(hp->dev->flags & IFF_PROMISC) + rxcfg |= BIGMAC_RXCFG_PMISC; + hme_write32(hp, &bregs->rx_cfg, rxcfg); /* Let the bits settle in the chip. */ udelay(10); @@ -1433,7 +1528,7 @@ static int happy_meal_init(struct happy_meal *hp, int from_irq) hme_read32(hp, &bregs->rx_cfg) | BIGMAC_RXCFG_ENABLE); /* Get the autonegotiation started, and the watch timer ticking. */ - happy_meal_begin_auto_negotiation(hp, tregs); + happy_meal_begin_auto_negotiation(hp, tregs, NULL); /* Success. */ return 0; @@ -1505,7 +1600,7 @@ static int happy_meal_is_not_so_happy(struct happy_meal *hp, /* Only print messages for non-counter related interrupts. */ if(status & (GREG_STAT_RFIFOVF | GREG_STAT_STSTERR | GREG_STAT_TFIFO_UND | - GREG_STAT_MAXPKTERR | GREG_STAT_NORXD | GREG_STAT_RXERR | + GREG_STAT_MAXPKTERR | GREG_STAT_RXERR | GREG_STAT_RXPERR | GREG_STAT_RXTERR | GREG_STAT_EOPERR | GREG_STAT_MIFIRQ | GREG_STAT_TXEACK | GREG_STAT_TXLERR | GREG_STAT_TXPERR | GREG_STAT_TXTERR | GREG_STAT_SLVERR | @@ -1541,13 +1636,14 @@ static int happy_meal_is_not_so_happy(struct happy_meal *hp, } if(status & GREG_STAT_NORXD) { - /* AIEEE, out of receive descriptors. Check out our drop - * processing in happy_meal_rx to see how we try very hard - * to avoid this situation. + /* This is harmless, it just means the system is + * quite loaded and the incomming packet rate was + * faster than the interrupt handler could keep up + * with. */ - printk("%s: Happy Meal out of receive descriptors, aieee!\n", + printk(KERN_INFO "%s: Happy Meal out of receive " + "descriptors, packet dropped.\n", hp->dev->name); - reset = 1; } if(status & (GREG_STAT_RXERR|GREG_STAT_RXPERR|GREG_STAT_RXTERR)) { @@ -1662,13 +1758,6 @@ static inline void happy_meal_tx(struct happy_meal *hp) hp->tx_skbs[elem] = NULL; hp->net_stats.tx_bytes+=skb->len; -#ifdef NEED_DMA_SYNCHRONIZATION -#ifdef CONFIG_PCI - if(!(hp->happy_flags & HFLAG_PCI)) -#endif - mmu_sync_dma(kva_to_hva(hp, skb->data), - skb->len, hp->happy_sbus_dev->my_bus); -#endif dev_kfree_skb(skb); hp->net_stats.tx_packets++; @@ -1797,7 +1886,8 @@ static inline void happy_meal_rx(struct happy_meal *hp, struct device *dev, struct sk_buff *new_skb; /* Now refill the entry, if we can. */ - new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC); + new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, + (GFP_DMA|GFP_ATOMIC)); if(!new_skb) { drops++; goto drop_it; @@ -1814,7 +1904,7 @@ static inline void happy_meal_rx(struct happy_meal *hp, struct device *dev, /* Trim the original skb for the netif. */ skb_trim(skb, len); } else { - struct sk_buff *copy_skb = dev_alloc_skb(len+2); + struct sk_buff *copy_skb = dev_alloc_skb(len + 2); if(!copy_skb) { drops++; @@ -1908,7 +1998,8 @@ static inline void pci_happy_meal_rx(struct happy_meal *hp, struct device *dev, struct sk_buff *new_skb; /* Now refill the entry, if we can. */ - new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC); + new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, + (GFP_DMA|GFP_ATOMIC)); if(!new_skb) { drops++; goto drop_it; @@ -1925,7 +2016,7 @@ static inline void pci_happy_meal_rx(struct happy_meal *hp, struct device *dev, /* Trim the original skb for the netif. */ skb_trim(skb, len); } else { - struct sk_buff *copy_skb = dev_alloc_skb(len+2); + struct sk_buff *copy_skb = dev_alloc_skb(len + 2); if(!copy_skb) { drops++; @@ -2083,7 +2174,8 @@ static inline void sun4d_happy_meal_rx(struct happy_meal *hp, struct device *dev struct sk_buff *new_skb; /* Now refill the entry, if we can. */ - new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC); + new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, + (GFP_DMA | GFP_ATOMIC)); if(!new_skb) { drops++; goto drop_it; @@ -2103,7 +2195,7 @@ static inline void sun4d_happy_meal_rx(struct happy_meal *hp, struct device *dev /* Trim the original skb for the netif. */ skb_trim(skb, len); } else { - struct sk_buff *copy_skb = dev_alloc_skb(len+2); + struct sk_buff *copy_skb = dev_alloc_skb(len + 2); if(!copy_skb) { drops++; @@ -2328,12 +2420,39 @@ static void sun4d_happy_meal_interrupt(int irq, void *dev_id, struct pt_regs *re } #endif +static void quattro_sbus_interrupt(int irq, void *cookie, struct pt_regs *ptregs) +{ + struct quattro *qp = (struct quattro *)cookie; + int i; + + for(i = 0; i < 4; i++) { + struct device *hdev = qp->happy_meals[i]; + struct happy_meal *hp = (struct happy_meal *) hdev->priv; + volatile u32 *sreg = qp->irq_status[i]; + + if(sreg && + (hme_read32(hp, sreg) & (GREG_STAT_ERRORS | + GREG_STAT_MIFIRQ | + GREG_STAT_TXALL | + GREG_STAT_RXTOHOST)) != 0) + qp->handler(irq, hdev, ptregs); + } +} + static int happy_meal_open(struct device *dev) { struct happy_meal *hp = (struct happy_meal *) dev->priv; int res; HMD(("happy_meal_open: ")); + + /* On SBUS Quattro QFE cards, all hme interrupts are concentrated + * into a single source which we register handling at probe time. + */ + if((hp->happy_flags & (HFLAG_QUATTRO|HFLAG_PCI)) == HFLAG_QUATTRO) { + hp->qfe_parent->irq_status[hp->qfe_ent] = &hp->gregs->stat; + goto after_request_irq; + } #ifndef __sparc_v9__ if(sparc_cpu_model == sun4c) { if(request_irq(dev->irq, &sun4c_happy_meal_interrupt, @@ -2370,8 +2489,8 @@ static int happy_meal_open(struct device *dev) __irq_itoa(dev->irq)); return -EAGAIN; } - HMD(("Init happy timer\n")); - init_timer(&hp->happy_timer); + +after_request_irq: HMD(("to happy_meal_init\n")); res = happy_meal_init(hp, 0); if(!res) { @@ -2390,7 +2509,17 @@ static int happy_meal_close(struct device *dev) /* If auto-negotiation timer is running, kill it. */ del_timer(&hp->happy_timer); - free_irq(dev->irq, (void *)dev); + /* On Quattro QFE cards, all hme interrupts are concentrated + * into a single source which we register handling at probe + * time and never unregister. + */ + if((hp->happy_flags & (HFLAG_QUATTRO|HFLAG_PCI)) != HFLAG_QUATTRO) { + free_irq(dev->irq, (void *)dev); + } else { + /* Zap the status register pointer. */ + hp->qfe_parent->irq_status[hp->qfe_ent] = NULL; + } + MOD_DEC_USE_COUNT; return 0; } @@ -2429,10 +2558,23 @@ static int happy_meal_start_xmit(struct sk_buff *skb, struct device *dev) tx_add_log(hp, TXLOG_ACTION_TXMIT|TXLOG_ACTION_NBUFS, 0); return 1; } +#ifdef __sparc_v9__ + if ((unsigned long)(skb->data + skb->len) >= MAX_DMA_ADDRESS) { + struct sk_buff *new_skb = skb_copy(skb, GFP_DMA | GFP_ATOMIC); + if (!new_skb) + return 1; + dev_kfree_skb(skb); + skb = new_skb; + } +#endif len = skb->len; entry = hp->tx_new; SXD(("SX<l[%d]e[%d]>", len, entry)); +#ifdef NEED_DMA_SYNCHRONIZATION + mmu_sync_dma(kva_to_hva(hp, skb->data), + skb->len, hp->happy_sbus_dev->my_bus); +#endif hp->tx_skbs[entry] = skb; hp->happy_block->happy_meal_txd[entry].tx_addr = kva_to_hva(hp, skb->data); hp->happy_block->happy_meal_txd[entry].tx_flags = @@ -2622,9 +2764,6 @@ static struct net_device_stats *happy_meal_get_stats(struct device *dev) return &hp->net_stats; } -#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */ -#define CRC_POLYNOMIAL_LE 0xedb88320UL /* Ethernet CRC, little endian */ - static void happy_meal_set_multicast(struct device *dev) { struct happy_meal *hp = (struct happy_meal *) dev->priv; @@ -2634,10 +2773,6 @@ static void happy_meal_set_multicast(struct device *dev) int i, j, bit, byte; u32 crc, poly = CRC_POLYNOMIAL_LE; - /* Let the transmits drain. */ - while(dev->tbusy) - schedule(); - /* Lock out others. */ set_bit(0, (void *) &dev->tbusy); @@ -2686,13 +2821,262 @@ static void happy_meal_set_multicast(struct device *dev) dev->tbusy = 0; } +/* Ethtool support... */ +static int happy_meal_ioctl(struct device *dev, + struct ifreq *rq, int cmd) +{ + struct happy_meal *hp = (struct happy_meal *) dev->priv; + struct ethtool_cmd *ep_user = (struct ethtool_cmd *) rq->ifr_data; + struct ethtool_cmd ecmd; + + if(cmd != SIOCETHTOOL) + return -EOPNOTSUPP; + if(copy_from_user(&ecmd, ep_user, sizeof(ecmd))) + return -EFAULT; + + if(ecmd.cmd == SPARC_ETH_GSET) { + ecmd.supported = + (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII); + + /* XXX hardcoded stuff for now */ + ecmd.port = PORT_TP; /* XXX no MII support */ + ecmd.transceiver = XCVR_INTERNAL; /* XXX no external xcvr support */ + ecmd.phy_address = 0; /* XXX fixed PHYAD */ + + /* Record PHY settings. */ + hp->sw_bmcr = happy_meal_tcvr_read(hp, hp->tcvregs, DP83840_BMCR); + hp->sw_lpa = happy_meal_tcvr_read(hp, hp->tcvregs, DP83840_LPA); + if(hp->sw_bmcr & BMCR_ANENABLE) { + ecmd.autoneg = AUTONEG_ENABLE; + ecmd.speed = + (hp->sw_lpa & (LPA_100HALF | LPA_100FULL)) ? + SPEED_100 : SPEED_10; + if(ecmd.speed == SPEED_100) + ecmd.duplex = + (hp->sw_lpa & (LPA_100FULL)) ? + DUPLEX_FULL : DUPLEX_HALF; + else + ecmd.duplex = + (hp->sw_lpa & (LPA_10FULL)) ? + DUPLEX_FULL : DUPLEX_HALF; + } else { + ecmd.autoneg = AUTONEG_DISABLE; + ecmd.speed = + (hp->sw_bmcr & BMCR_SPEED100) ? + SPEED_100 : SPEED_10; + ecmd.duplex = + (hp->sw_bmcr & BMCR_FULLDPLX) ? + DUPLEX_FULL : DUPLEX_HALF; + } + if(copy_to_user(ep_user, &ecmd, sizeof(ecmd))) + return -EFAULT; + return 0; + } else if(ecmd.cmd == SPARC_ETH_SSET) { + if(!capable(CAP_NET_ADMIN)) + return -EPERM; + + /* Verify the settings we care about. */ + if(ecmd.autoneg != AUTONEG_ENABLE && + ecmd.autoneg != AUTONEG_DISABLE) + return -EINVAL; + if(ecmd.autoneg == AUTONEG_DISABLE && + ((ecmd.speed != SPEED_100 && + ecmd.speed != SPEED_10) || + (ecmd.duplex != DUPLEX_HALF && + ecmd.duplex != DUPLEX_FULL))) + return -EINVAL; + + /* Ok, do it to it. */ + del_timer(&hp->happy_timer); + happy_meal_begin_auto_negotiation(hp, + hp->tcvregs, + &ecmd); + + return 0; + } else + return -EOPNOTSUPP; +} + +void __init quattro_get_ranges(struct quattro *qp) +{ + int err; + + err = prom_getproperty(qp->quattro_sbus_dev->prom_node, + "ranges", + (char *)&qp->ranges[0], + sizeof(qp->ranges)); + if(err == 0 || err == -1) { + qp->nranges = 0; + return; + } + qp->nranges = (err / sizeof(struct linux_prom_ranges)); +} + +static void __init quattro_apply_ranges(struct quattro *qp, struct happy_meal *hp) +{ + struct linux_sbus_device *sdev = hp->happy_sbus_dev; + int rng; + + for(rng = 0; rng < qp->nranges; rng++) { + struct linux_prom_ranges *rngp = &qp->ranges[rng]; + int reg; + + for(reg = 0; reg < 5; reg++) { + if(sdev->reg_addrs[reg].which_io == + rngp->ot_child_space) + break; + } + if(reg == 5) + continue; + + sdev->reg_addrs[reg].which_io = rngp->ot_parent_space; + sdev->reg_addrs[reg].phys_addr += rngp->ot_parent_base; + } +} + +/* Given a happy meal sbus device, find it's quattro parent. + * If none exist, allocate and return a new one. + * + * Return NULL on failure. + */ +static struct quattro * __init quattro_sbus_find(struct linux_sbus_device *goal_sdev) +{ + struct linux_sbus *sbus; + struct linux_sbus_device *sdev; + struct quattro *qp; + + for(qp = qfe_sbus_list; qp != NULL; qp = qp->next) { + for(sdev = qp->quattro_sbus_dev->child; + sdev != NULL; + sdev = sdev->next) { + if(sdev == goal_sdev) + return qp; + } + } + for_each_sbus(sbus) { + for_each_sbusdev(sdev, sbus) { + if(sdev->child != NULL) { + struct linux_sbus_device *p; + + for(p = sdev->child; p != NULL; p = p->next) + if(p == goal_sdev) + goto found; + } + } + } + + /* Cannot find quattro parent, fail. */ + return NULL; + +found: + qp = kmalloc(sizeof(struct quattro), GFP_KERNEL); + if(qp != NULL) { + int i; + + for(i = 0; i < 4; i++) { + qp->irq_status[i] = NULL; + qp->happy_meals[i] = NULL; + } + qp->handler = NULL; + qp->quattro_sbus_dev = sdev; +#ifdef CONFIG_PCI + qp->quattro_pci_dev = NULL; +#endif + qp->next = qfe_sbus_list; + qfe_sbus_list = qp; + quattro_get_ranges(qp); + } + return qp; +} + +#ifdef CONFIG_PCI +static struct quattro * __init quattro_pci_find(struct pci_dev *pdev) +{ + struct pci_dev *bdev = pdev->bus->self; + struct quattro *qp; + + if (!bdev) return NULL; + for(qp = qfe_pci_list; qp != NULL; qp = qp->next) { + if(qp->quattro_pci_dev == bdev) + return qp; + } + qp = kmalloc(sizeof(struct quattro), GFP_KERNEL); + if(qp != NULL) { + int i; + + for(i = 0; i < 4; i++) { + qp->irq_status[i] = NULL; + qp->happy_meals[i] = NULL; + } + qp->handler = NULL; + qp->quattro_sbus_dev = NULL; + qp->quattro_pci_dev = bdev; + qp->next = qfe_pci_list; + qfe_pci_list = qp; + + /* No range tricks necessary on PCI. */ + qp->nranges = 0; + } + return qp; +} +#endif + +/* After all quattro cards have been probed, we call these functions + * to register the IRQ handlers. + */ +static void __init quattro_sbus_register_irqs(void) +{ + struct quattro *qp; + + for(qp = qfe_sbus_list; qp != NULL; qp = qp->next) { + int err; + + /* Set the handler. */ +#ifndef __sparc_v9__ + if(sparc_cpu_model == sun4c) + qp->handler = sun4c_happy_meal_interrupt; + else if(sparc_cpu_model == sun4c) + qp->handler = sun4d_happy_meal_interrupt; + else +#endif +#ifdef CONFIG_PCI + if(qp->quattro_pci_dev != NULL) + panic("QFE: PCI qfe in sbus_register_irqs!"); + else +#endif + qp->handler = happy_meal_interrupt; + + err = request_irq(qp->quattro_sbus_dev->irqs[0], + quattro_sbus_interrupt, + SA_SHIRQ, "Quattro", + qp); + if(err != 0) { + printk("Quattro: Fatal IRQ registery error %d.\n", err); + panic("QFE request irq"); + } + } +} + static unsigned hme_version_printed = 0; -static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_device *sdev) +static int __init happy_meal_ether_init(struct device *dev, struct linux_sbus_device *sdev, int is_qfe) { + struct quattro *qp = NULL; struct happy_meal *hp; - int i; - + int i, qfe_slot = -1; + + if(is_qfe) { + qp = quattro_sbus_find(sdev); + if(qp == NULL) + return ENODEV; + for(qfe_slot = 0; qfe_slot < 4; qfe_slot++) + if(qp->happy_meals[qfe_slot] == NULL) + break; + if(qfe_slot == 4) + return ENODEV; + } if(dev == NULL) { dev = init_etherdev(0, sizeof(struct happy_meal)); } else { @@ -2703,9 +3087,16 @@ static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_de if(hme_version_printed++ == 0) printk(version); - printk("%s: HAPPY MEAL (SBUS) 10/100baseT Ethernet ", dev->name); + if(qfe_slot != -1) + printk("%s: Quattro HME slot %d (SBUS) 10/100baseT Ethernet", + dev->name, qfe_slot); + else + printk("%s: HAPPY MEAL (SBUS) 10/100baseT Ethernet ", + dev->name); dev->base_addr = (long) sdev; + + /* XXX Check for local-mac-address property on Quattro... -DaveM */ for(i = 0; i < 6; i++) printk("%2.2x%c", dev->dev_addr[i] = idprom->id_ethaddr[i], @@ -2727,6 +3118,13 @@ static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_de return ENODEV; } + if(qp != NULL) { + hp->qfe_parent = qp; + hp->qfe_ent = qfe_slot; + qp->happy_meals[qfe_slot] = dev; + quattro_apply_ranges(qp, hp); + } + prom_apply_sbus_ranges(sdev->my_bus, &sdev->reg_addrs[0], sdev->num_registers, sdev); hp->gregs = sparc_alloc_io(sdev->reg_addrs[0].phys_addr, 0, @@ -2784,6 +3182,9 @@ static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_de else if(hp->hm_revision != 0xa0) hp->happy_flags = HFLAG_NOT_A0; + if(qp != NULL) + hp->happy_flags |= HFLAG_QUATTRO; + /* Get the supported DVMA burst sizes from our Happy SBUS. */ hp->happy_bursts = prom_getintdefault(hp->happy_sbus_dev->my_bus->prom_node, "burst-sizes", 0x00); @@ -2817,6 +3218,8 @@ static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_de */ happy_meal_set_initial_advertisement(hp); + init_timer(&hp->happy_timer); + hp->dev = dev; dev->open = &happy_meal_open; dev->stop = &happy_meal_close; @@ -2830,6 +3233,7 @@ static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_de dev->hard_start_xmit = &happy_meal_start_xmit; dev->get_stats = &happy_meal_get_stats; dev->set_multicast_list = &happy_meal_set_multicast; + dev->do_ioctl = &happy_meal_ioctl; dev->irq = sdev->irqs[0]; dev->dma = 0; @@ -2846,14 +3250,35 @@ static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_de } #ifdef CONFIG_PCI -__initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)) +static int __init happy_meal_pci_init(struct device *dev, struct pci_dev *pdev) { + struct quattro *qp = NULL; struct pcidev_cookie *pcp; struct happy_meal *hp; unsigned long hpreg_base; unsigned short pci_command; - int i, node; + int i, node, qfe_slot = -1; + char prom_name[64]; + /* Now make sure pci_dev cookie is there. */ + pcp = pdev->sysdata; + if(pcp == NULL || pcp->prom_node == -1) { + printk("happymeal(PCI): Some PCI device info missing\n"); + return ENODEV; + } + node = pcp->prom_node; + + prom_getstring(node, "name", prom_name, sizeof(prom_name)); + if (!strcmp(prom_name, "SUNW,qfe") || !strcmp(prom_name, "qfe")) { + qp = quattro_pci_find(pdev); + if(qp == NULL) + return ENODEV; + for(qfe_slot = 0; qfe_slot < 4; qfe_slot++) + if(qp->happy_meals[qfe_slot] == NULL) + break; + if(qfe_slot == 4) + return ENODEV; + } if(dev == NULL) { dev = init_etherdev(0, sizeof(struct happy_meal)); } else { @@ -2864,15 +3289,28 @@ __initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)) if(hme_version_printed++ == 0) printk(version); - printk("%s: HAPPY MEAL (PCI/CheerIO) 10/100BaseT Ethernet ", dev->name); + if (!qfe_slot) { + prom_name[0] = 0; + if (!strncmp(dev->name, "eth", 3)) { + int i = simple_strtoul(dev->name + 3, NULL, 10); + sprintf(prom_name, "-%d", i + 3); + } + printk("%s%s: Quattro HME (PCI/CheerIO) 10/100baseT Ethernet ", dev->name, prom_name); + if (qp->quattro_pci_dev->vendor == PCI_VENDOR_ID_DEC && + qp->quattro_pci_dev->device == PCI_DEVICE_ID_DEC_21153) + printk("DEC 21153 PCI Bridge\n"); + else + printk("unknown bridge %04x.%04x\n", + qp->quattro_pci_dev->vendor, qp->quattro_pci_dev->device); + } + if(qfe_slot != -1) + printk("%s: Quattro HME slot %d (PCI/CheerIO) 10/100baseT Ethernet ", + dev->name, qfe_slot); + else + printk("%s: HAPPY MEAL (PCI/CheerIO) 10/100BaseT Ethernet ", + dev->name); dev->base_addr = (long) pdev; - for(i = 0; i < 6; i++) - printk("%2.2x%c", - dev->dev_addr[i] = idprom->id_ethaddr[i], - i == 5 ? ' ' : ':'); - - printk("\n"); hp = (struct happy_meal *)dev->priv; memset(hp, 0, sizeof(*hp)); @@ -2880,6 +3318,12 @@ __initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)) hp->happy_sbus_dev = NULL; hp->happy_pci_dev = pdev; + if(qp != NULL) { + hp->qfe_parent = qp; + hp->qfe_ent = qfe_slot; + qp->happy_meals[qfe_slot] = dev; + } + hpreg_base = pdev->base_address[0]; if((hpreg_base & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) { printk("happymeal(PCI): Cannot find proper PCI device base address.\n"); @@ -2887,13 +3331,14 @@ __initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)) } hpreg_base &= PCI_BASE_ADDRESS_MEM_MASK; - /* Now make sure pci_dev cookie is there. */ - pcp = pdev->sysdata; - if(pcp == NULL || pcp->prom_node == -1) { - printk("happymeal(PCI): Some PCI device info missing\n"); - return ENODEV; - } - node = pcp->prom_node; + if (qfe_slot != -1 && prom_getproplen(node, "local-mac-address") == 6) + prom_getproperty(node, "local-mac-address", dev->dev_addr, 6); + else + memcpy(dev->dev_addr, idprom->id_ethaddr, 6); + for(i = 0; i < 6; i++) + printk("%2.2x%c", dev->dev_addr[i], i == 5 ? ' ' : ':'); + + printk("\n"); /* Layout registers. */ hp->gregs = (struct hmeal_gregs *) (hpreg_base + 0x0000); @@ -2912,6 +3357,9 @@ __initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)) else if(hp->hm_revision != 0xa0) hp->happy_flags = HFLAG_NOT_A0; + if(qp != NULL) + hp->happy_flags |= HFLAG_QUATTRO; + /* And of course, indicate this is PCI. */ hp->happy_flags |= HFLAG_PCI; @@ -2937,29 +3385,32 @@ __initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)) hp->timer_ticks = 0; happy_meal_set_initial_advertisement(hp); + init_timer(&hp->happy_timer); + hp->dev = dev; dev->open = &happy_meal_open; dev->stop = &happy_meal_close; dev->hard_start_xmit = &pci_happy_meal_start_xmit; dev->get_stats = &happy_meal_get_stats; dev->set_multicast_list = &happy_meal_set_multicast; + dev->do_ioctl = &happy_meal_ioctl; dev->irq = pdev->irq; dev->dma = 0; ether_setup(dev); /* If we don't do this, nothing works. */ - pcibios_read_config_word(pdev->bus->number, - pdev->devfn, - PCI_COMMAND, &pci_command); + pci_read_config_word(pdev, PCI_COMMAND, &pci_command); pci_command |= PCI_COMMAND_MASTER; - pcibios_write_config_word(pdev->bus->number, - pdev->devfn, - PCI_COMMAND, pci_command); + pci_write_config_word(pdev, PCI_COMMAND, pci_command); - /* Set the latency timer as well, PROM leaves it at zero. */ - pcibios_write_config_byte(pdev->bus->number, - pdev->devfn, - PCI_LATENCY_TIMER, 240); + /* Set the latency timer and cache line size as well, + * PROM leaves it at zero. + */ + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 128); +#ifdef __sparc_v9__ + /* NOTE: Cache line size is in 32-bit word units. */ + pci_write_config_byte(pdev, PCI_CACHE_LINE_SIZE, 0x10); +#endif #ifdef MODULE /* We are home free at this point, link us in to the happy @@ -2973,7 +3424,7 @@ __initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)) } #endif -__initfunc(int happy_meal_probe(struct device *dev)) +int __init happy_meal_probe(struct device *dev) { struct linux_sbus *bus; struct linux_sbus_device *sdev = 0; @@ -2990,11 +3441,18 @@ __initfunc(int happy_meal_probe(struct device *dev)) dev = NULL; if(!strcmp(sdev->prom_name, "SUNW,hme")) { cards++; - if((v = happy_meal_ether_init(dev, sdev))) + if((v = happy_meal_ether_init(dev, sdev, 0))) + return v; + } else if(!strcmp(sdev->prom_name, "qfe") || + !strcmp(sdev->prom_name, "SUNW,qfe")) { + cards++; + if((v = happy_meal_ether_init(dev, sdev, 1))) return v; } } } + if(cards != 0) + quattro_sbus_register_irqs(); #ifdef CONFIG_PCI if(pci_present()) { struct pci_dev *pdev; |