#include <sys/param.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/interrupt.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/resource.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/systm.h>
#include "pcidevs.h"
#include <bus/pci/pcireg.h>
#include <bus/pci/pcivar.h>
#include <machine/clock.h>
#include <net/bpf.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/ifq_var.h>
#include <vm/pmap.h>
#include <vm/vm.h>
#include "if_slnreg.h"
#include "if_slnvar.h"
#define SL_USEIOSPACE
#ifdef SLN_DEBUG
#define PDEBUG(fmt, args...) kprintf("%s: " fmt "\n" , __func__ , ## args)
#else
#define PDEBUG(fmt, args...)
#endif
static const struct sln_dev {
uint16_t vid;
uint16_t did;
const char *desc;
} sln_devs[] = {
{PCI_VENDOR_SILAN, PCI_PRODUCT_SILAN_SC92031,
"Silan SC92031 Fast Ethernet" },
{PCI_VENDOR_SILAN, PCI_PRODUCT_SILAN_8139D,
"Silan Rsltek 8139D Fast Ethernet" },
{0, 0, NULL}
};
static int sln_probe(device_t);
static int sln_attach(device_t);
static int sln_detach(device_t);
static int sln_shutdown(device_t);
static int sln_suspend(device_t);
static int sln_resume(device_t);
static void sln_reset(struct sln_softc *);
static void sln_init(void *);
static void sln_tx(struct ifnet *, struct ifaltq_subque *);
static void sln_rx(struct sln_softc *);
static void sln_tx_intr(struct sln_softc *);
static void sln_media_intr(struct sln_softc *);
static void sln_interrupt(void *);
static int sln_ioctl(struct ifnet *, u_long, caddr_t, struct ucred *);
static void sln_stop(struct sln_softc *);
static void sln_watchdog(struct ifnet *);
static int sln_media_upd(struct ifnet *);
static void sln_media_stat(struct ifnet *, struct ifmediareq *);
static void sln_mii_cmd(struct sln_softc *, uint32_t, u_long *);
static void sln_media_cfg(struct sln_softc *);
static void sln_mac_cfg(struct sln_softc *);
static uint32_t sln_ether_crc32(caddr_t);
static void sln_set_multi(struct sln_softc *);
static void sln_init_tx(struct sln_softc *);
static void sln_tick(void *);
#ifdef SL_USEIOSPACE
#define SL_RID SL_PCI_IOAD
#define SL_RES SYS_RES_IOPORT
#else
#define SL_RID SL_PCI_MEMAD
#define SL_RES SYS_RES_MEMORY
#endif
static device_method_t sln_methods[] = {
DEVMETHOD(device_probe, sln_probe),
DEVMETHOD(device_attach, sln_attach),
DEVMETHOD(device_detach, sln_detach),
DEVMETHOD(device_shutdown, sln_shutdown),
DEVMETHOD(device_suspend, sln_suspend),
DEVMETHOD(device_resume, sln_resume),
DEVMETHOD(bus_print_child, bus_generic_print_child),
DEVMETHOD(bus_driver_added, bus_generic_driver_added),
DEVMETHOD_END
};
static driver_t sln_driver = {
"sln",
sln_methods,
sizeof(struct sln_softc)
};
static devclass_t sln_devclass;
DRIVER_MODULE(sln, pci, sln_driver, sln_devclass, NULL, NULL);
static int
sln_probe(device_t dev)
{
const struct sln_dev *d;
uint16_t did, vid;
vid = pci_get_vendor(dev);
did = pci_get_device(dev);
for (d = sln_devs; d->desc != NULL; d++) {
if (vid == d->vid && did == d->did) {
device_set_desc(dev, d->desc);
return 0;
}
}
return ENXIO;
}
static void
sln_reset(struct sln_softc *sc)
{
SLN_WRITE_4(sc, SL_CFG0, SL_SOFT_RESET);
DELAY(200000);
SLN_WRITE_4(sc, SL_CFG0, 0x0);
DELAY(10000);
}
static int
sln_attach(device_t dev)
{
struct sln_softc *sc = device_get_softc(dev);
struct ifnet *ifp = &sc->arpcom.ac_if;
unsigned char eaddr[ETHER_ADDR_LEN];
int rid;
int error = 0;
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
pci_enable_busmaster(dev);
rid = SL_RID;
sc->sln_res = bus_alloc_resource_any(dev, SL_RES, &rid, RF_ACTIVE);
if (sc->sln_res == NULL) {
device_printf(dev, "couldn't map ports/memory\n");
error = ENXIO;
goto fail;
}
sc->sln_bustag = rman_get_bustag(sc->sln_res);
sc->sln_bushandle = rman_get_bushandle(sc->sln_res);
rid = 0;
sc->sln_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_SHAREABLE | RF_ACTIVE);
if (sc->sln_irq == NULL) {
device_printf(dev, "couldn't map interrupt\n");
bus_release_resource(dev, SL_RES, SL_RID, sc->sln_res);
error = ENXIO;
goto fail;
}
((uint32_t *)(&eaddr))[0] = be32toh(SLN_READ_4(sc, SL_MAC_ADDR0));
((uint16_t *)(&eaddr))[2] = be16toh(SLN_READ_4(sc, SL_MAC_ADDR1));
sc->sln_bufdata.sln_rx_buf = contigmalloc(SL_RX_BUFLEN,
M_DEVBUF, M_WAITOK, 0, 0xffffffff, PAGE_SIZE, 0);
if (sc->sln_bufdata.sln_rx_buf == NULL) {
device_printf(dev, "no memory for rx buffers!\n");
bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sln_irq);
bus_release_resource(dev, SL_RES, SL_RID, sc->sln_res);
error = ENXIO;
goto fail;
}
callout_init(&sc->sln_state);
ifp->if_softc = sc;
ifp->if_mtu = ETHERMTU;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_init = sln_init;
ifp->if_start = sln_tx;
ifp->if_ioctl = sln_ioctl;
ifp->if_watchdog = sln_watchdog;
ifq_set_maxlen(&ifp->if_snd, IFQ_MAXLEN);
ifq_set_ready(&ifp->if_snd);
ifmedia_init(&sc->ifmedia, 0, sln_media_upd, sln_media_stat);
ifmedia_add(&sc->ifmedia, IFM_ETHER | IFM_AUTO, 0, NULL);
ifmedia_add(&sc->ifmedia, IFM_ETHER | IFM_10_T, 0, NULL);
ifmedia_add(&sc->ifmedia, IFM_ETHER | IFM_10_T | IFM_HDX, 0, NULL);
ifmedia_add(&sc->ifmedia, IFM_ETHER | IFM_10_T | IFM_FDX, 0, NULL);
ifmedia_add(&sc->ifmedia, IFM_ETHER | IFM_100_TX, 0, NULL);
ifmedia_add(&sc->ifmedia, IFM_ETHER | IFM_100_TX | IFM_HDX, 0, NULL);
ifmedia_add(&sc->ifmedia, IFM_ETHER | IFM_100_TX | IFM_FDX, 0, NULL);
ifmedia_set(&sc->ifmedia, IFM_ETHER | IFM_AUTO);
ether_ifattach(ifp, eaddr, NULL);
ifq_set_cpuid(&ifp->if_snd, rman_get_cpuid(sc->sln_irq));
error = bus_setup_intr(dev, sc->sln_irq, INTR_MPSAFE, sln_interrupt, sc,
&sc->sln_intrhand, ifp->if_serializer);
if (error) {
bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sln_irq);
bus_release_resource(dev, SL_RES, SL_RID, sc->sln_res);
ether_ifdetach(ifp);
device_printf(dev, "couldn't set up irq\n");
goto fail;
}
return 0;
fail:
return error;
}
static void
sln_stop(struct sln_softc *sc)
{
struct ifnet *ifp = &sc->arpcom.ac_if;
int i;
ASSERT_SERIALIZED(ifp->if_serializer);
ifp->if_timer = 0;
callout_stop(&sc->sln_state);
sc->txcfg &= ~SL_TXCFG_EN;
sc->rxcfg &= ~SL_RXCFG_EN;
SLN_WRITE_4(sc, SL_TX_CONFIG, sc->txcfg);
SLN_WRITE_4(sc, SL_RX_CONFIG, sc->rxcfg);
SLN_WRITE_4(sc, SL_INT_MASK, 0);
SLN_READ_4(sc, SL_INT_STATUS);
for (i = 0; i < SL_TXD_CNT; i++) {
if (sc->sln_bufdata.sln_tx_buf[i] != NULL) {
m_freem(sc->sln_bufdata.sln_tx_buf[i]);
sc->sln_bufdata.sln_tx_buf[i] = NULL;
SLN_WRITE_4(sc, SL_TSAD0 + i * 4, 0);
}
}
ifp->if_flags &= ~IFF_RUNNING;
ifq_clr_oactive(&ifp->if_snd);
}
static int
sln_detach(device_t dev)
{
struct sln_softc *sc = device_get_softc(dev);
struct ifnet *ifp = &sc->arpcom.ac_if;
lwkt_serialize_enter(ifp->if_serializer);
sln_stop(sc);
bus_teardown_intr(dev, sc->sln_irq, sc->sln_intrhand);
lwkt_serialize_exit(ifp->if_serializer);
ether_ifdetach(ifp);
bus_generic_detach(dev);
bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sln_irq);
bus_release_resource(dev, SL_RES, SL_RID, sc->sln_res);
contigfree(sc->sln_bufdata.sln_rx_buf, SL_RX_BUFLEN, M_DEVBUF);
return 0;
}
static int
sln_media_upd(struct ifnet *ifp)
{
struct sln_softc *sc = ifp->if_softc;
struct ifmedia *ifm = &sc->ifmedia;
if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER)
return EINVAL;
if (ifp->if_flags & IFF_UP)
sln_init(sc);
return 0;
}
static void
sln_media_stat(struct ifnet *ifp, struct ifmediareq *ifmr)
{
struct sln_softc *sc = ifp->if_softc;
u_long phys[2];
uint32_t temp;
ifmr->ifm_status = IFM_AVALID;
ifmr->ifm_active = IFM_ETHER;
phys[0] = SL_MII_STAT;
sln_mii_cmd(sc, SL_MII0_READ, phys);
if (phys[1] & SL_MIISTAT_LINK)
ifmr->ifm_status |= IFM_ACTIVE;
temp = SLN_READ_4(sc, SL_PHY_CTRL);
if ((temp & (SL_PHYCTL_DUX | SL_PHYCTL_SPD100 | SL_PHYCTL_SPD10)) == 0x60800000)
ifmr->ifm_active |= IFM_AUTO;
else if ((temp & (SL_PHYCTL_DUX | SL_PHYCTL_SPD100)) == 0x40800000)
ifmr->ifm_active |= IFM_100_TX | IFM_FDX;
else if ((temp & SL_PHYCTL_SPD100) == 0x40000000)
ifmr->ifm_active |= IFM_100_TX | IFM_HDX;
else if ((temp & (SL_PHYCTL_DUX | SL_PHYCTL_SPD10)) == 0x20800000)
ifmr->ifm_active |= IFM_10_T | IFM_FDX;
else if ((temp & SL_PHYCTL_SPD10) == 0x20000000)
ifmr->ifm_active |= IFM_10_T | IFM_HDX;
sln_mii_cmd(sc, SL_MII0_SCAN, phys);
}
static void
sln_mii_cmd(struct sln_softc *sc, uint32_t cmd, u_long *phys)
{
uint32_t mii_status;
SLN_WRITE_4(sc, SL_MII_CMD0, SL_MII0_DIVEDER);
do {
mii_status = 0;
DELAY(10);
mii_status = SLN_READ_4(sc, SL_MII_STATUS);
} while (mii_status & SL_MIISTAT_BUSY);
switch (cmd) {
case SL_MII0_SCAN:
SLN_WRITE_4(sc, SL_MII_CMD1, 0x1 << 6);
SLN_WRITE_4(sc, SL_MII_CMD0, SL_MII0_DIVEDER | SL_MII0_SCAN);
break;
case SL_MII0_READ:
SLN_WRITE_4(sc, SL_MII_CMD1, phys[0] << 6);
SLN_WRITE_4(sc, SL_MII_CMD0, SL_MII0_DIVEDER | SL_MII0_READ);
break;
default:
SLN_WRITE_4(sc, SL_MII_CMD1, phys[0] << 6 | phys[1] << 11);
SLN_WRITE_4(sc, SL_MII_CMD0, SL_MII0_DIVEDER | SL_MII0_WRITE);
break;
}
do {
DELAY(10);
mii_status = SLN_READ_4(sc, SL_MII_STATUS);
} while (mii_status & SL_MIISTAT_BUSY);
if (SL_MII0_READ == cmd)
phys[1] = (mii_status >> 13) & 0xffff;
}
static void
sln_media_cfg(struct sln_softc *sc)
{
u_long phys[2];
uint32_t mediatype;
uint32_t temp;
mediatype = (&sc->ifmedia)->ifm_cur->ifm_media;
temp = SLN_READ_4(sc, SL_PHY_CTRL);
temp &= ~(SL_PHYCTL_DUX | SL_PHYCTL_SPD100 | SL_PHYCTL_SPD10);
temp |= (SL_PHYCTL_ANE | SL_PHYCTL_RESET);
switch (mediatype) {
case 0x00000020:
PDEBUG(" autoselet supported\n");
temp |= (SL_PHYCTL_DUX | SL_PHYCTL_SPD100 | SL_PHYCTL_SPD10);
sc->ifmedia.ifm_media = IFM_ETHER | IFM_AUTO;
ifmedia_set(&sc->ifmedia, IFM_ETHER | IFM_AUTO);
break;
case 0x23:
case 0x00200023:
PDEBUG(" 10Mbps half_duplex supported\n");
temp |= SL_PHYCTL_SPD10;
sc->ifmedia.ifm_media = IFM_ETHER | IFM_10_T | IFM_HDX;
ifmedia_set(&sc->ifmedia, IFM_ETHER | IFM_10_T | IFM_HDX);
break;
case 0x00100023:
PDEBUG("10Mbps full_duplex supported\n");
temp |= (SL_PHYCTL_SPD10 | SL_PHYCTL_DUX);
sc->ifmedia.ifm_media = IFM_ETHER | IFM_10_T | IFM_FDX;
ifmedia_set(&sc->ifmedia, IFM_ETHER | IFM_10_T | IFM_FDX);
break;
case 0x26:
case 0x00200026:
PDEBUG("100Mbps half_duplex supported\n");
temp |= SL_PHYCTL_SPD100;
sc->ifmedia.ifm_media = IFM_ETHER | IFM_100_TX | IFM_HDX;
ifmedia_set(&sc->ifmedia, IFM_ETHER | IFM_100_TX | IFM_HDX);
break;
case 0x00100026:
PDEBUG("100Mbps full_duplex supported\n");
temp |= (SL_PHYCTL_SPD100 | SL_PHYCTL_DUX);
sc->ifmedia.ifm_media = IFM_ETHER | IFM_100_TX | IFM_FDX;
ifmedia_set(&sc->ifmedia, IFM_ETHER | IFM_100_TX | IFM_FDX);
break;
default:
break;
}
SLN_WRITE_4(sc, SL_PHY_CTRL, temp);
DELAY(10000);
temp &= ~SL_PHYCTL_RESET;
SLN_WRITE_4(sc, SL_PHY_CTRL, temp);
DELAY(1000);
phys[0] = SL_MII_JAB;
phys[1] = SL_PHY_16_JAB_ENB | SL_PHY_16_PORT_ENB;
sln_mii_cmd(sc, SL_MII0_WRITE, phys);
sc->connect = 0;
sln_mii_cmd(sc, SL_MII0_SCAN, phys);
}
static void
sln_mac_cfg(struct sln_softc *sc)
{
struct ifnet *ifp = &sc->arpcom.ac_if;
u_long flowcfg = 0;
sc->rxcfg = SL_RXCFG_LOW_THRESHOLD | SL_RXCFG_HIGH_THRESHOLD;
sc->txcfg = TX_CFG_DEFAULT;
if (sc->txenablepad)
sc->txcfg |= 0x20000000;
if (sc->media_speed == IFM_10_T)
sc->txcfg |= SL_TXCFG_DATARATE;
if (sc->media_duplex == IFM_FDX) {
sc->rxcfg |= SL_RXCFG_FULLDX;
sc->txcfg |= SL_TXCFG_FULLDX;
flowcfg = SL_FLOWCTL_FULLDX | SL_FLOWCTL_EN;
} else {
sc->rxcfg &= ~SL_RXCFG_FULLDX;
sc->txcfg &= ~SL_TXCFG_FULLDX;
}
if (ifp->if_flags & IFF_PROMISC)
sc->rxcfg |= (SL_RXCFG_EN | SL_RXCFG_RCV_SMALL | SL_RXCFG_RCV_HUGE | SL_RXCFG_RCV_ERR | SL_RXCFG_RCV_BROAD | SL_RXCFG_RCV_MULTI | SL_RXCFG_RCV_ALL);
else
sc->rxcfg &= ~(SL_RXCFG_EN | SL_RXCFG_RCV_SMALL | SL_RXCFG_RCV_HUGE | SL_RXCFG_RCV_ERR | SL_RXCFG_RCV_BROAD | SL_RXCFG_RCV_MULTI | SL_RXCFG_RCV_ALL);
if (ifp->if_flags & IFF_BROADCAST)
sc->rxcfg |= SL_RXCFG_EN | SL_RXCFG_RCV_BROAD;
else
sc->rxcfg &= ~(SL_RXCFG_EN | SL_RXCFG_RCV_BROAD);
sln_set_multi(sc);
SLN_WRITE_4(sc, SL_RX_CONFIG, sc->rxcfg);
SLN_WRITE_4(sc, SL_TX_CONFIG, sc->txcfg);
SLN_WRITE_4(sc, SL_FLOW_CTRL, flowcfg);
}
static u_char shade_map[] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf };
static uint32_t
sln_ether_crc32(caddr_t addr)
{
uint32_t crc, crcr;
int i, j;
unsigned char data = 0;
crc = 0xFFFFFFFF;
for (i = ETHER_ADDR_LEN; i > 0; i--) {
data = *addr++;
for (j = 0; j < 8; j++) {
if (((data & 0x1) ^ (crc & 0x1)) != 0) {
crc >>= 1;
crc ^= 0xEDB88320;
} else {
crc >>= 1;
}
data >>= 1;
}
}
crcr = shade_map[crc >> 28];
crcr |= (shade_map[(crc >> 24) & 0xf] << 4);
crcr |= (shade_map[(crc >> 20) & 0xf] << 8);
crcr |= (shade_map[(crc >> 16) & 0xf] << 12);
crcr |= (shade_map[(crc >> 12) & 0xf] << 16);
crcr |= (shade_map[(crc >> 8) & 0xf] << 20);
crcr |= (shade_map[(crc >> 4) & 0xf] << 24);
crcr |= (shade_map[crc & 0xf] << 28);
return crcr;
}
static void
sln_set_multi(struct sln_softc *sc)
{
struct ifnet *ifp = &sc->arpcom.ac_if;
uint32_t crc = 0;
uint32_t mc_g[2] = {0, 0};
struct ifmultiaddr *ifma;
int j;
if (ifp->if_flags & IFF_PROMISC) {
kprintf("Promisc mode is enabled\n");
sc->rxcfg |= SL_RXCFG_EN | SL_RXCFG_RCV_MULTI;
mc_g[0] = mc_g[1] = 0xFFFFFFFF;
} else if (ifp->if_flags & IFF_ALLMULTI) {
kprintf("Allmulti mode is enabled\n");
sc->rxcfg |= SL_RXCFG_EN | SL_RXCFG_RCV_MULTI;
mc_g[0] = mc_g[1] = 0xFFFFFFFF;
} else if (ifp->if_flags & IFF_MULTICAST) {
kprintf("Multicast mode is enabled\n");
sc->rxcfg |= SL_RXCFG_EN | SL_RXCFG_RCV_MULTI;
mc_g[0] = mc_g[1] = 0;
TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
j = 0;
if ((ifma->ifma_addr->sa_family) != AF_LINK)
continue;
crc = ~sln_ether_crc32(LLADDR((struct sockaddr_dl *)ifma->ifma_addr));
crc >>= 24;
if (crc & 0x1)
j |= 0x2;
if (crc & 0x2)
j |= 0x1;
if (crc & 0x10)
j |= 0x20;
if (crc & 0x20)
j |= 0x10;
if (crc & 0x40)
j |= 0x8;
if (crc & 0x80)
j |= 0x4;
if (j > 31)
mc_g[0] |= (0x1 << (j - 32));
else
mc_g[1] |= (0x1 << j);
}
} else {
sc->rxcfg &= ~(SL_RXCFG_EN | SL_RXCFG_RCV_MULTI);
}
SLN_WRITE_4(sc, SL_RX_CONFIG, sc->rxcfg);
SLN_WRITE_4(sc, SL_MULTI_GROUP0, mc_g[0]);
SLN_WRITE_4(sc, SL_MULTI_GROUP1, mc_g[1]);
}
static void
sln_init_tx(struct sln_softc *sc)
{
int i;
sc->sln_bufdata.cur_tx = 0;
sc->sln_bufdata.dirty_tx = 0;
for (i = 0; i < SL_TXD_CNT; i++) {
sc->sln_bufdata.sln_tx_buf[i] = NULL;
SLN_WRITE_4(sc, SL_TSAD0 + (i * 4), 0);
}
}
static void
sln_init(void *x)
{
struct sln_softc *sc = x;
struct ifnet *ifp = &sc->arpcom.ac_if;
PDEBUG("sln_init\n");
ASSERT_SERIALIZED(ifp->if_serializer);
sln_stop(sc);
sln_reset(sc);
SLN_WRITE_4(sc, SL_INT_MASK, 0);
SLN_WRITE_4(sc, SL_MULTI_GROUP0, 0);
SLN_WRITE_4(sc, SL_MULTI_GROUP1, 0);
SLN_WRITE_4(sc, SL_RBSA, vtophys(sc->sln_bufdata.sln_rx_buf));
sc->sln_bufdata.dirty_rx = vtophys(sc->sln_bufdata.sln_rx_buf);
sln_init_tx(sc);
if (sc->tx_early_ctrl && sc->rx_early_ctrl)
SLN_WRITE_4(sc, SL_CFG1, SL_EARLY_RX | SL_EARLY_TX | SL_RXBUF_64 | SL_RXFIFO_1024BYTES);
else if (sc->tx_early_ctrl)
SLN_WRITE_4(sc, SL_CFG1, SL_EARLY_TX | SL_RXBUF_64);
else if (sc->rx_early_ctrl)
SLN_WRITE_4(sc, SL_CFG1, SL_EARLY_RX | SL_RXBUF_64 | SL_RXFIFO_1024BYTES);
else
SLN_WRITE_4(sc, SL_CFG1, SL_RXBUF_64);
sln_media_cfg(sc);
if (sc->connect) {
sc->rxcfg |= SL_RXCFG_EN;
sc->txcfg |= SL_TXCFG_EN;
} else {
sc->rxcfg &= ~SL_RXCFG_EN;
sc->txcfg &= ~SL_TXCFG_EN;
}
SLN_WRITE_4(sc, SL_TX_CONFIG, sc->txcfg);
SLN_WRITE_4(sc, SL_RX_CONFIG, sc->rxcfg);
SLN_WRITE_4(sc, SL_INT_MASK, SL_INRTS);
sc->suspended = 0;
ifp->if_flags |= IFF_RUNNING;
ifq_clr_oactive(&ifp->if_snd);
callout_reset(&sc->sln_state, hz, sln_tick, sc);
}
static void
sln_tx(struct ifnet *ifp, struct ifaltq_subque *ifsq)
{
struct sln_softc *sc = ifp->if_softc;
struct mbuf *m_head = NULL;
struct mbuf *m_new = NULL;
int entry;
ASSERT_ALTQ_SQ_DEFAULT(ifp, ifsq);
ASSERT_SERIALIZED(ifp->if_serializer);
if (!sc->connect) {
ifq_purge(&ifp->if_snd);
return;
}
if ((ifp->if_flags & IFF_RUNNING) == 0 || ifq_is_oactive(&ifp->if_snd))
return;
while (SL_CUR_TXBUF(sc) == NULL) {
entry = sc->sln_bufdata.cur_tx;
m_head = ifq_dequeue(&ifp->if_snd);
if (m_head == NULL)
break;
MGETHDR(m_new, M_NOWAIT, MT_DATA);
if (m_new == NULL) {
if_printf(ifp, "no memory for tx descriptor");
m_freem(m_head);
break;
}
if ((m_head->m_pkthdr.len > MHLEN) || (60 > MHLEN)) {
MCLGET(m_new, M_NOWAIT);
if (!(m_new->m_flags & M_EXT)) {
m_freem(m_new);
m_freem(m_head);
if_printf(ifp, "no memory for tx descriptor");
break;
}
}
m_copydata(m_head, 0, m_head->m_pkthdr.len, mtod(m_new, void *));
m_new->m_pkthdr.len = m_new->m_len = m_head->m_pkthdr.len;
m_freem(m_head);
m_head = m_new;
SL_CUR_TXBUF(sc) = m_head;
BPF_MTAP(ifp, SL_CUR_TXBUF(sc));
SLN_WRITE_4(sc, ((entry * 4) + SL_TSAD0),
vtophys(mtod(SL_CUR_TXBUF(sc), caddr_t)));
if ((SL_CUR_TXBUF(sc)->m_pkthdr.len < 60) && (!sc->txenablepad)) {
memset(mtod(m_head, char *)+m_head->m_pkthdr.len, 0x20, 60 - m_head->m_pkthdr.len);
SLN_WRITE_4(sc, (entry * 4) + SL_TSD0, 60);
} else if (SL_CUR_TXBUF(sc)->m_pkthdr.len < 100)
SLN_WRITE_4(sc, (entry * 4) + SL_TSD0, SL_CUR_TXBUF(sc)->m_pkthdr.len);
else if (SL_CUR_TXBUF(sc)->m_pkthdr.len < 300)
SLN_WRITE_4(sc, (entry * 4) + SL_TSD0, 0x30000 | SL_CUR_TXBUF(sc)->m_pkthdr.len);
else
SLN_WRITE_4(sc, (entry * 4) + SL_TSD0, 0x50000 | SL_CUR_TXBUF(sc)->m_pkthdr.len);
sc->sln_bufdata.cur_tx = (entry + 1) % SL_TXD_CNT;
PDEBUG("Queue tx packet size %d to tx-descriptor %d.\n", m_head->m_pkthdr.len, entry);
}
if (SL_CUR_TXBUF(sc) != NULL)
ifq_set_oactive(&ifp->if_snd);
ifp->if_timer = 5;
}
static void
sln_rx(struct sln_softc *sc)
{
struct mbuf *m;
struct ifnet *ifp = &sc->arpcom.ac_if;
uint32_t rxstat = 0;
uint32_t rx_offset;
caddr_t rx_bufpos = NULL;
uint32_t cur_rx = 0;
uint32_t dirty_rx;
long rx_len;
u_long rx_space;
u_long rx_size = 0;
u_long rx_size_align = 0;
u_long pkt_size = 0;
cur_rx = SLN_READ_4(sc, SL_RBW_PTR);
dirty_rx = sc->sln_bufdata.dirty_rx;
cur_rx |= vtophys(sc->sln_bufdata.sln_rx_buf) & (~(u_long) (SL_RX_BUFLEN - 1));
if (cur_rx < vtophys(sc->sln_bufdata.sln_rx_buf))
cur_rx += SL_RX_BUFLEN;
if (cur_rx >= dirty_rx)
rx_len = (long)(cur_rx - dirty_rx);
else
rx_len = SL_RX_BUFLEN - (long)(dirty_rx - cur_rx);
if ((rx_len > SL_RX_BUFLEN) || (rx_len < 0)) {
if_printf(ifp, "rx len is fail\n");
return;
}
if (rx_len == 0)
return;
rx_offset = (dirty_rx - vtophys(sc->sln_bufdata.sln_rx_buf)) & (u_long) (SL_RX_BUFLEN - 1);
while (rx_len > 0) {
#ifdef SLN_DEBUG
u_long ipkts;
#endif
rx_bufpos = sc->sln_bufdata.sln_rx_buf + rx_offset;
rxstat = *(uint32_t *) rx_bufpos;
rx_size = (rxstat >> 20) & 0x0FFF;
rx_size_align = (rx_size + 3) & ~3;
pkt_size = rx_size - ETHER_CRC_LEN;
PDEBUG("rx len: %ld rx frame size:%ld rx state:0x%x\n", rx_len, rx_size, rxstat);
if (rxstat == 0 || rx_size < 16 || !(rxstat & SL_RXSTAT_RXOK)) {
IFNET_STAT_INC(ifp, ierrors, 1);
if (!(rxstat & SL_RXSTAT_RXOK))
if_printf(ifp, "receiver ok error\n");
if (!(rxstat & SL_RXSTAT_CRCOK))
if_printf(ifp, "crc error\n");
if (rxstat & SL_RXSTAT_ALIGNERR)
if_printf(ifp, "frame alignment error\n");
if (rxstat & (SL_RXSTAT_HUGEFRM | SL_RXSTAT_SMALLFRM))
if_printf(ifp, "received frame length is error\n");
break;
}
rx_len -= (long)(rx_size_align + 4);
if (rx_len < 0) {
kprintf("rx packets len is too small\n");
break;
}
#ifdef SLN_PDEBUG
caddr_t p = NULL;
if_printf(ifp, "rx frame content\n");
p = rx_bufpos;
for (i = 0; i < 30; i++, p++) {
if (i % 10 == 0)
kprintf("\n");
if_printf(ifp, "%x ", (u_char)*p);
}
if_printf(ifp, "\n");
#endif
if (rx_bufpos == (sc->sln_bufdata.sln_rx_buf + SL_RX_BUFLEN))
rx_bufpos = sc->sln_bufdata.sln_rx_buf;
rx_bufpos = rx_bufpos + 4;
rx_space = (u_long)((sc->sln_bufdata.sln_rx_buf + SL_RX_BUFLEN) - rx_bufpos);
if (pkt_size > rx_space) {
m = m_devget(rx_bufpos - 2, pkt_size + 2, 0, ifp);
if (m == NULL) {
IFNET_STAT_INC(ifp, ierrors, 1);
if_printf(ifp,
"out of mbufs, tried to copy %ld bytes\n",
rx_space);
} else {
m_adj(m, 2);
m_copyback(m, rx_space, pkt_size - rx_space,
sc->sln_bufdata.sln_rx_buf);
}
} else {
m = m_devget(rx_bufpos - 2, pkt_size + 2, 0, ifp);
if (m == NULL) {
u_long ierr;
IFNET_STAT_INC(ifp, ierrors, 1);
if_printf(ifp,
"out of mbufs, tried to copy %ld bytes\n",
pkt_size);
IFNET_STAT_GET(ifp, ierrors, ierr);
if_printf(ifp, "ierrors = %lu\n", ierr);
} else {
m_adj(m, 2);
}
}
IFNET_STAT_INC(ifp, ipackets, 1);
#ifdef SLN_DEBUG
IFNET_STAT_GET(ifp, ipackets, ipkts);
PDEBUG("ipackets = %lu\n", ipkts);
#endif
ifp->if_input(ifp, m, NULL, -1);
rx_offset = (rx_offset + rx_size + 4) & (u_long) (SL_RX_BUFLEN - 1);
}
sc->sln_bufdata.dirty_rx = cur_rx;
SLN_WRITE_4(sc, SL_RBR_PTR, cur_rx);
}
static void
sln_tx_intr(struct sln_softc *sc)
{
struct ifnet *ifp = &sc->arpcom.ac_if;
uint32_t txstat;
int entry;
do {
entry = sc->sln_bufdata.dirty_tx;
txstat = SLN_READ_4(sc, SL_TSD0 + entry * 4);
if (!(txstat & (SL_TXSD_TOK | SL_TXSD_TUN | SL_TXSD_TABT)))
break;
if (SL_DIRTY_TXBUF(sc) != NULL) {
m_freem(SL_DIRTY_TXBUF(sc));
SL_DIRTY_TXBUF(sc) = NULL;
}
if (txstat & SL_TXSD_TOK) {
#ifdef SLN_DEBUG
u_long opkts;
#endif
IFNET_STAT_INC(ifp, opackets, 1);
IFNET_STAT_INC(ifp, obytes, txstat & SL_TXSD_LENMASK);
#ifdef SLN_DEBUG
IFNET_STAT_GET(ifp, opackets, opkts);
PDEBUG("opackets = %lu\n", opkts);
#endif
IFNET_STAT_INC(ifp, collisions,
(txstat & SL_TXSD_NCC) >> 22);
} else {
IFNET_STAT_INC(ifp, oerrors, 1);
if ((txstat & (SL_TXSD_TABT | SL_TXSD_OWC))) {
sc->txcfg = TX_CFG_DEFAULT;
if (sc->txenablepad)
sc->txcfg |= 0x20000000;
SLN_WRITE_4(sc, SL_TX_CONFIG, sc->txcfg);
}
}
PDEBUG("tx done descriprtor %x\n", entry);
sc->sln_bufdata.dirty_tx = (entry + 1) % SL_TXD_CNT;
ifq_clr_oactive(&ifp->if_snd);
} while (sc->sln_bufdata.dirty_tx != sc->sln_bufdata.cur_tx);
if (sc->sln_bufdata.dirty_tx == sc->sln_bufdata.cur_tx)
ifp->if_timer = 0;
else
ifp->if_timer = 5;
}
static void
sln_media_intr(struct sln_softc *sc)
{
u_long phys[2];
struct ifnet *ifp = &sc->arpcom.ac_if;
phys[0] = SL_MII_STAT;
sln_mii_cmd(sc, SL_MII0_READ, phys);
PDEBUG("mii_stat:0x%lx\n", phys[1]);
if (0 == (phys[1] & SL_MIISTAT_LINK)) {
kprintf("media is unconnect,linked down,or uncompatible\n");
sc->connect = 0;
sln_mii_cmd(sc, SL_MII0_SCAN, phys);
sc->txcfg &= ~SL_TXCFG_EN;
sc->rxcfg &= ~SL_RXCFG_EN;
SLN_WRITE_4(sc, SL_TX_CONFIG, sc->txcfg);
SLN_WRITE_4(sc, SL_RX_CONFIG, sc->rxcfg);
return;
}
PDEBUG("media is connecting---> ");
sc->connect = 1;
phys[0] = SL_MII_STAT_OUTPUT;
sln_mii_cmd(sc, SL_MII0_READ, phys);
sc->media_duplex = ((phys[1] & 0x0004) == 0) ? IFM_HDX : IFM_FDX;
sc->media_speed = ((phys[1] & 0x0002) == 0) ? IFM_10_T : IFM_100_TX;
if_printf(ifp, "media option:%dM %s-duplex\n",
sc->media_speed == 0x6 ? 100 : 10,
sc->media_duplex == 0x100000 ? "full" : "half");
sln_mii_cmd(sc, SL_MII0_SCAN, phys);
sln_mac_cfg(sc);
sc->rxcfg |= SL_RXCFG_EN;
sc->txcfg |= SL_TXCFG_EN;
SLN_WRITE_4(sc, SL_TX_CONFIG, sc->txcfg);
SLN_WRITE_4(sc, SL_RX_CONFIG, sc->rxcfg);
}
static void
sln_interrupt(void *arg)
{
struct sln_softc *sc = arg;
struct ifnet *ifp = &sc->arpcom.ac_if;
uint32_t int_status;
ASSERT_SERIALIZED(ifp->if_serializer);
if (sc->suspended || (ifp->if_flags & IFF_RUNNING) == 0)
return;
SLN_WRITE_4(sc, SL_INT_MASK, 0);
int_status = SLN_READ_4(sc, SL_INT_STATUS);
if ((int_status == 0xffffffff) || (int_status & SL_INRTS) == 0)
goto back;
int_status = int_status & SL_INRTS;
PDEBUG("int_status = 0x%x\n", int_status);
while (0 != int_status) {
if (int_status & SL_INT_ROK)
sln_rx(sc);
if (int_status & SL_INT_TOK)
sln_tx_intr(sc);
if (int_status & SL_INT_RBO) {
IFNET_STAT_INC(ifp, ierrors, 1);
PDEBUG("rx buffer is overflow\n");
}
if (int_status & (SL_INT_LINKFAIL | SL_INT_LINKOK))
sln_media_intr(sc);
int_status = SLN_READ_4(sc, SL_INT_STATUS);
}
if (!ifq_is_empty(&ifp->if_snd))
if_devstart(ifp);
back:
SLN_WRITE_4(sc, SL_INT_MASK, SL_INRTS);
}
static void
sln_tick(void *x)
{
struct sln_softc *sc = x;
callout_reset(&sc->sln_state, hz, sln_tick, sc);
}
static int
sln_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data, struct ucred *cr)
{
struct sln_softc *sc = ifp->if_softc;
struct ifreq *ifr = (struct ifreq *)data;
int error = 0;
ASSERT_SERIALIZED(ifp->if_serializer);
switch (cmd) {
case SIOCSIFFLAGS:
if (ifp->if_flags & IFF_UP) {
if ((ifp->if_flags & IFF_RUNNING) == 0)
sln_init(sc);
} else {
if (ifp->if_flags & IFF_RUNNING)
sln_stop(sc);
}
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
sln_set_multi(sc);
break;
case SIOCGIFMEDIA:
case SIOCSIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &sc->ifmedia, cmd);
break;
default:
error = ether_ioctl(ifp, cmd, data);
break;
}
return error;
}
static void
sln_watchdog(struct ifnet *ifp)
{
struct sln_softc *sc = ifp->if_softc;
ASSERT_SERIALIZED(ifp->if_serializer);
if_printf(ifp, "watchdog timeout!\n");
IFNET_STAT_INC(ifp, oerrors, 1);
sln_tx_intr(sc);
sln_rx(sc);
sln_init(sc);
if (!ifq_is_empty(&ifp->if_snd))
if_devstart(ifp);
}
static int
sln_shutdown(device_t dev)
{
struct sln_softc *sc = device_get_softc(dev);
struct ifnet *ifp = &sc->arpcom.ac_if;
lwkt_serialize_enter(ifp->if_serializer);
sln_stop(sc);
lwkt_serialize_exit(ifp->if_serializer);
return 0;
}
static int
sln_suspend(device_t dev)
{
struct sln_softc *sc = device_get_softc(dev);
struct ifnet *ifp = &sc->arpcom.ac_if;
lwkt_serialize_enter(ifp->if_serializer);
sln_stop(sc);
sc->suspended = 1;
lwkt_serialize_exit(ifp->if_serializer);
return 0;
}
static int
sln_resume(device_t dev)
{
struct sln_softc *sc = device_get_softc(dev);
struct ifnet *ifp = &sc->arpcom.ac_if;
lwkt_serialize_enter(ifp->if_serializer);
if (ifp->if_flags & IFF_UP)
sln_init(sc);
sc->suspended = 0;
lwkt_serialize_exit(ifp->if_serializer);
return 0;
}