#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rtl8169.c,v 1.183 2026/05/21 10:19:28 skrll Exp $");
#include <sys/param.h>
#include <sys/cprng.h>
#include <sys/endian.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/device.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <net/if_vlanvar.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <net/bpf.h>
#include <sys/rndsource.h>
#include <sys/bus.h>
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/ic/rtl81x9reg.h>
#include <dev/ic/rtl81x9var.h>
#include <dev/ic/rtl8169var.h>
#define RE_LOCK(sc) mutex_enter(&(sc)->sc_lock)
#define RE_UNLOCK(sc) mutex_exit(&(sc)->sc_lock)
#define RE_ASSERT_LOCKED(sc) KASSERT(mutex_owned(&(sc)->sc_lock))
#define ETHER_IS_ZERO(addr) \
(!(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]))
static inline void re_set_bufaddr(struct re_desc *, bus_addr_t);
static int re_newbuf(struct rtk_softc *, int, struct mbuf *);
static int re_rx_list_init(struct rtk_softc *);
static int re_tx_list_init(struct rtk_softc *);
static void re_rxeof(struct rtk_softc *);
static void re_txeof(struct rtk_softc *);
static void re_tick(void *);
static void re_tick_locked(struct rtk_softc *);
static void re_start(struct ifnet *);
static void re_start_locked(struct rtk_softc *);
static int re_ioctl(struct ifnet *, u_long, void *);
static int re_ifflags_cb(struct ethercom *);
static int re_init(struct ifnet *);
static int re_init_locked(struct rtk_softc *);
static void re_stop(struct ifnet *, int);
static void re_stop_locked(struct rtk_softc *, int);
static void re_watchdog(struct ifnet *);
static int re_enable(struct rtk_softc *);
static void re_disable(struct rtk_softc *);
static int re_gmii_readreg(device_t, int, int, uint16_t *);
static int re_gmii_writereg(device_t, int, int, uint16_t);
static int re_miibus_readreg(device_t, int, int, uint16_t *);
static int re_miibus_writereg(device_t, int, int, uint16_t);
static void re_miibus_statchg(struct ifnet *);
static void re_reset(struct rtk_softc *);
static const struct re_revision {
uint32_t re_chipid;
const char *re_name;
} re_revisions[] = {
{ RTK_HWREV_8100, "RTL8100" },
{ RTK_HWREV_8100E, "RTL8100E" },
{ RTK_HWREV_8100E_SPIN2, "RTL8100E 2" },
{ RTK_HWREV_8101, "RTL8101" },
{ RTK_HWREV_8101E, "RTL8101E" },
{ RTK_HWREV_8102E, "RTL8102E" },
{ RTK_HWREV_8106E, "RTL8106E" },
{ RTK_HWREV_8401E, "RTL8401E" },
{ RTK_HWREV_8402, "RTL8402" },
{ RTK_HWREV_8411, "RTL8411" },
{ RTK_HWREV_8411B, "RTL8411B" },
{ RTK_HWREV_8102EL, "RTL8102EL" },
{ RTK_HWREV_8102EL_SPIN1, "RTL8102EL 1" },
{ RTK_HWREV_8103E, "RTL8103E" },
{ RTK_HWREV_8110S, "RTL8110S" },
{ RTK_HWREV_8139CPLUS, "RTL8139C+" },
{ RTK_HWREV_8168B_SPIN1, "RTL8168 1" },
{ RTK_HWREV_8168B_SPIN2, "RTL8168 2" },
{ RTK_HWREV_8168B_SPIN3, "RTL8168 3" },
{ RTK_HWREV_8168C, "RTL8168C/8111C" },
{ RTK_HWREV_8168C_SPIN2, "RTL8168C/8111C" },
{ RTK_HWREV_8168CP, "RTL8168CP/8111CP" },
{ RTK_HWREV_8168F, "RTL8168F/8111F" },
{ RTK_HWREV_8168G, "RTL8168G/8111G" },
{ RTK_HWREV_8168GU, "RTL8168GU/8111GU" },
{ RTK_HWREV_8168H, "RTL8168H/8111H" },
{ RTK_HWREV_8105E, "RTL8105E" },
{ RTK_HWREV_8105E_SPIN1, "RTL8105E" },
{ RTK_HWREV_8168D, "RTL8168D/8111D" },
{ RTK_HWREV_8168DP, "RTL8168DP/8111DP" },
{ RTK_HWREV_8168E, "RTL8168E/8111E" },
{ RTK_HWREV_8168E_VL, "RTL8168E/8111E-VL" },
{ RTK_HWREV_8168EP, "RTL8168EP/8111EP" },
{ RTK_HWREV_8168FP, "RTL8168FP/8117" },
{ RTK_HWREV_8169, "RTL8169" },
{ RTK_HWREV_8169_8110SB, "RTL8169/8110SB" },
{ RTK_HWREV_8169_8110SBL, "RTL8169SBL" },
{ RTK_HWREV_8169_8110SC, "RTL8169/8110SCd" },
{ RTK_HWREV_8169_8110SCE, "RTL8169/8110SCe" },
{ RTK_HWREV_8169S, "RTL8169S" },
{ 0, NULL }
};
static inline void
re_set_bufaddr(struct re_desc *d, bus_addr_t addr)
{
d->re_bufaddr_lo = htole32(RE_ADDR_LO(addr));
d->re_bufaddr_hi = htole32(RE_ADDR_HI(addr));
}
static int
re_gmii_readreg(device_t dev, int phy, int reg, uint16_t *val)
{
struct rtk_softc *sc = device_private(dev);
uint32_t data;
int i;
if (phy != 7)
return -1;
if (reg == RTK_GMEDIASTAT) {
*val = CSR_READ_1(sc, RTK_GMEDIASTAT);
return 0;
}
CSR_WRITE_4(sc, RTK_PHYAR, reg << 16);
DELAY(1000);
for (i = 0; i < RTK_TIMEOUT; i++) {
data = CSR_READ_4(sc, RTK_PHYAR);
if (data & RTK_PHYAR_BUSY)
break;
DELAY(100);
}
if (i == RTK_TIMEOUT) {
printf("%s: PHY read failed\n", device_xname(sc->sc_dev));
return ETIMEDOUT;
}
*val = data & RTK_PHYAR_PHYDATA;
return 0;
}
static int
re_gmii_writereg(device_t dev, int phy, int reg, uint16_t val)
{
struct rtk_softc *sc = device_private(dev);
uint32_t data;
int i;
CSR_WRITE_4(sc, RTK_PHYAR, (reg << 16) |
(val & RTK_PHYAR_PHYDATA) | RTK_PHYAR_BUSY);
DELAY(1000);
for (i = 0; i < RTK_TIMEOUT; i++) {
data = CSR_READ_4(sc, RTK_PHYAR);
if (!(data & RTK_PHYAR_BUSY))
break;
DELAY(100);
}
if (i == RTK_TIMEOUT) {
printf("%s: PHY write reg %x <- %hx failed\n",
device_xname(sc->sc_dev), reg, val);
return ETIMEDOUT;
}
return 0;
}
static int
re_miibus_readreg(device_t dev, int phy, int reg, uint16_t *val)
{
struct rtk_softc *sc = device_private(dev);
uint16_t re8139_reg = 0;
int rv = 0;
if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
rv = re_gmii_readreg(dev, phy, reg, val);
return rv;
}
if (phy) {
return -1;
}
switch (reg) {
case MII_BMCR:
re8139_reg = RTK_BMCR;
break;
case MII_BMSR:
re8139_reg = RTK_BMSR;
break;
case MII_ANAR:
re8139_reg = RTK_ANAR;
break;
case MII_ANER:
re8139_reg = RTK_ANER;
break;
case MII_ANLPAR:
re8139_reg = RTK_LPAR;
break;
case MII_PHYIDR1:
case MII_PHYIDR2:
*val = 0;
return 0;
case RTK_MEDIASTAT:
*val = CSR_READ_1(sc, RTK_MEDIASTAT);
return 0;
default:
printf("%s: bad phy register\n", device_xname(sc->sc_dev));
return -1;
}
*val = CSR_READ_2(sc, re8139_reg);
if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0 && re8139_reg == RTK_BMCR) {
*val &= ~(BMCR_LOOP | BMCR_ISO);
}
return 0;
}
static int
re_miibus_writereg(device_t dev, int phy, int reg, uint16_t val)
{
struct rtk_softc *sc = device_private(dev);
uint16_t re8139_reg = 0;
int rv;
if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
rv = re_gmii_writereg(dev, phy, reg, val);
return rv;
}
if (phy) {
return -1;
}
switch (reg) {
case MII_BMCR:
re8139_reg = RTK_BMCR;
if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0) {
val &= ~(BMCR_LOOP | BMCR_ISO);
}
break;
case MII_BMSR:
re8139_reg = RTK_BMSR;
break;
case MII_ANAR:
re8139_reg = RTK_ANAR;
break;
case MII_ANER:
re8139_reg = RTK_ANER;
break;
case MII_ANLPAR:
re8139_reg = RTK_LPAR;
break;
case MII_PHYIDR1:
case MII_PHYIDR2:
return 0;
break;
default:
printf("%s: bad phy register\n", device_xname(sc->sc_dev));
return -1;
}
CSR_WRITE_2(sc, re8139_reg, val);
return 0;
}
static void
re_miibus_statchg(struct ifnet *ifp)
{
return;
}
static void
re_reset(struct rtk_softc *sc)
{
int i;
CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_RESET);
for (i = 0; i < RTK_TIMEOUT; i++) {
DELAY(10);
if ((CSR_READ_1(sc, RTK_COMMAND) & RTK_CMD_RESET) == 0)
break;
}
if (i == RTK_TIMEOUT)
printf("%s: reset never completed!\n",
device_xname(sc->sc_dev));
if ((sc->sc_quirk & RTKQ_MACLDPS) != 0)
CSR_WRITE_1(sc, RTK_LDPS, 1);
}
int
re_diag(struct rtk_softc *sc)
{
struct ifnet *ifp = &sc->ethercom.ec_if;
struct mbuf *m0;
struct ether_header *eh;
struct re_rxsoft *rxs;
struct re_desc *cur_rx;
bus_dmamap_t dmamap;
uint16_t status;
uint32_t rxstat;
int total_len, i, error = 0;
static const uint8_t dst[] = { 0x00, 'h', 'e', 'l', 'l', 'o' };
static const uint8_t src[] = { 0x00, 'w', 'o', 'r', 'l', 'd' };
RE_LOCK(sc);
MGETHDR(m0, M_DONTWAIT, MT_DATA);
if (m0 == NULL) {
RE_UNLOCK(sc);
return ENOBUFS;
}
ifp->if_flags |= IFF_PROMISC;
sc->re_testmode = 1;
re_init_locked(sc);
re_stop_locked(sc, 0);
DELAY(100000);
re_init_locked(sc);
eh = mtod(m0, struct ether_header *);
memcpy(eh->ether_dhost, &dst, ETHER_ADDR_LEN);
memcpy(eh->ether_shost, &src, ETHER_ADDR_LEN);
eh->ether_type = htons(ETHERTYPE_IP);
m0->m_pkthdr.len = m0->m_len = ETHER_MIN_LEN - ETHER_CRC_LEN;
CSR_WRITE_2(sc, RTK_ISR, 0xFFFF);
IF_ENQUEUE(&ifp->if_snd, m0);
re_start_locked(sc);
m0 = NULL;
DELAY(100000);
for (i = 0; i < RTK_TIMEOUT; i++) {
status = CSR_READ_2(sc, RTK_ISR);
if ((status & (RTK_ISR_TIMEOUT_EXPIRED | RTK_ISR_RX_OK)) ==
(RTK_ISR_TIMEOUT_EXPIRED | RTK_ISR_RX_OK))
break;
DELAY(10);
}
if (i == RTK_TIMEOUT) {
aprint_error_dev(sc->sc_dev,
"diagnostic failed, failed to receive packet "
"in loopback mode\n");
error = EIO;
goto done;
}
rxs = &sc->re_ldata.re_rxsoft[0];
dmamap = rxs->rxs_dmamap;
bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize,
BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, dmamap);
m0 = rxs->rxs_mbuf;
rxs->rxs_mbuf = NULL;
eh = mtod(m0, struct ether_header *);
RE_RXDESCSYNC(sc, 0, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
cur_rx = &sc->re_ldata.re_rx_list[0];
rxstat = le32toh(cur_rx->re_cmdstat);
total_len = rxstat & sc->re_rxlenmask;
if (total_len != ETHER_MIN_LEN) {
aprint_error_dev(sc->sc_dev,
"diagnostic failed, received short packet\n");
error = EIO;
goto done;
}
if (memcmp(&eh->ether_dhost, &dst, ETHER_ADDR_LEN) ||
memcmp(&eh->ether_shost, &src, ETHER_ADDR_LEN) ||
ntohs(eh->ether_type) != ETHERTYPE_IP) {
aprint_error_dev(sc->sc_dev, "WARNING, DMA FAILURE!\n"
"expected TX data: %s/%s/0x%x\n"
"received RX data: %s/%s/0x%x\n"
"You may have a defective 32-bit NIC plugged "
"into a 64-bit PCI slot.\n"
"Please re-install the NIC in a 32-bit slot "
"for proper operation.\n"
"Read the re(4) man page for more details.\n" ,
ether_sprintf(dst), ether_sprintf(src), ETHERTYPE_IP,
ether_sprintf(eh->ether_dhost),
ether_sprintf(eh->ether_shost), ntohs(eh->ether_type));
error = EIO;
}
done:
sc->re_testmode = 0;
ifp->if_flags &= ~IFF_PROMISC;
re_stop_locked(sc, 0);
m_freem(m0);
RE_UNLOCK(sc);
return error;
}
void
re_attach(struct rtk_softc *sc)
{
uint8_t eaddr[ETHER_ADDR_LEN];
struct ifnet *ifp;
struct mii_data *mii = &sc->mii;
int error = 0, i;
const struct re_revision *rr;
const char *re_name = NULL;
uint32_t maclo, machi;
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NET);
if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
sc->sc_hwrev = CSR_READ_4(sc, RTK_TXCFG) & RTK_TXCFG_HWREV;
for (rr = re_revisions; rr->re_name != NULL; rr++) {
if (rr->re_chipid == sc->sc_hwrev)
re_name = rr->re_name;
}
if (re_name == NULL)
aprint_normal_dev(sc->sc_dev,
"unknown ASIC (0x%04x)\n", sc->sc_hwrev >> 16);
else
aprint_normal_dev(sc->sc_dev,
"%s (0x%04x)\n", re_name, sc->sc_hwrev >> 16);
switch (sc->sc_hwrev) {
case RTK_HWREV_8169:
sc->sc_quirk |= RTKQ_8169NONS;
break;
case RTK_HWREV_8169S:
case RTK_HWREV_8110S:
case RTK_HWREV_8169_8110SB:
case RTK_HWREV_8169_8110SBL:
case RTK_HWREV_8169_8110SC:
sc->sc_quirk |= RTKQ_MACLDPS;
break;
case RTK_HWREV_8168B_SPIN1:
case RTK_HWREV_8168B_SPIN2:
case RTK_HWREV_8168B_SPIN3:
sc->sc_quirk |= RTKQ_MACSTAT;
break;
case RTK_HWREV_8168C:
case RTK_HWREV_8168C_SPIN2:
case RTK_HWREV_8168CP:
case RTK_HWREV_8168D:
case RTK_HWREV_8168DP:
sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
RTKQ_MACSTAT | RTKQ_CMDSTOP;
sc->sc_quirk |= RTKQ_NOJUMBO;
break;
case RTK_HWREV_8168E:
sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_PHYWAKE_PM |
RTKQ_NOJUMBO;
break;
case RTK_HWREV_8168E_VL:
case RTK_HWREV_8168F:
sc->sc_quirk |= RTKQ_EARLYOFF;
case RTK_HWREV_8411:
sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_NOJUMBO;
break;
case RTK_HWREV_8168EP:
case RTK_HWREV_8168FP:
case RTK_HWREV_8168G:
case RTK_HWREV_8168GU:
case RTK_HWREV_8168H:
case RTK_HWREV_8411B:
sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_NOJUMBO |
RTKQ_RXDV_GATED | RTKQ_TXRXEN_LATER;
break;
case RTK_HWREV_8100E:
case RTK_HWREV_8100E_SPIN2:
case RTK_HWREV_8101E:
sc->sc_quirk |= RTKQ_NOJUMBO;
break;
case RTK_HWREV_8102E:
case RTK_HWREV_8102EL:
case RTK_HWREV_8102EL_SPIN1:
sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_NOJUMBO;
break;
case RTK_HWREV_8103E:
sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
RTKQ_MACSTAT | RTKQ_CMDSTOP;
break;
case RTK_HWREV_8401E:
case RTK_HWREV_8105E:
case RTK_HWREV_8105E_SPIN1:
case RTK_HWREV_8106E:
sc->sc_quirk |= RTKQ_PHYWAKE_PM |
RTKQ_DESCV2 | RTKQ_NOEECMD | RTKQ_MACSTAT |
RTKQ_CMDSTOP;
break;
case RTK_HWREV_8402:
sc->sc_quirk |= RTKQ_PHYWAKE_PM |
RTKQ_DESCV2 | RTKQ_NOEECMD | RTKQ_MACSTAT |
RTKQ_CMDSTOP;
break;
default:
sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD;
sc->sc_quirk |= RTKQ_NOJUMBO;
}
sc->re_rxlenmask = RE_RDESC_STAT_GFRAGLEN;
sc->re_ldata.re_tx_desc_cnt = RE_TX_DESC_CNT_8169;
sc->re_ldata.re_rx_desc_cnt = RE_RX_DESC_CNT_8169;
} else {
sc->sc_quirk |= RTKQ_NOJUMBO;
sc->re_rxlenmask = RE_RDESC_STAT_FRAGLEN;
sc->re_ldata.re_tx_desc_cnt = RE_TX_DESC_CNT_8139;
sc->re_ldata.re_rx_desc_cnt = RE_RX_DESC_CNT_8139;
}
re_reset(sc);
#ifdef RE_USE_EECMD
if ((sc->sc_quirk & RTKQ_NOEECMD) != 0) {
for (i = 0; i < ETHER_ADDR_LEN; i++)
eaddr[i] = CSR_READ_1(sc, RTK_IDR0 + i);
} else {
uint16_t val;
int addr_len;
if (rtk_read_eeprom(sc, RTK_EE_ID, RTK_EEADDR_LEN1) == 0x8129)
addr_len = RTK_EEADDR_LEN1;
else
addr_len = RTK_EEADDR_LEN0;
for (i = 0; i < ETHER_ADDR_LEN / 2; i++) {
val = rtk_read_eeprom(sc, RTK_EE_EADDR0 + i, addr_len);
eaddr[(i * 2) + 0] = val & 0xff;
eaddr[(i * 2) + 1] = val >> 8;
}
}
#else
for (i = 0; i < ETHER_ADDR_LEN; i++)
eaddr[i] = CSR_READ_1(sc, RTK_IDR0 + i);
#endif
if (ETHER_IS_ZERO(eaddr)) {
maclo = 0x00f2 | (cprng_strong32() & 0xffff0000);
machi = cprng_strong32() & 0xffff;
eaddr[0] = maclo & 0xff;
eaddr[1] = (maclo >> 8) & 0xff;
eaddr[2] = (maclo >> 16) & 0xff;
eaddr[3] = (maclo >> 24) & 0xff;
eaddr[4] = machi & 0xff;
eaddr[5] = (machi >> 8) & 0xff;
}
if ((sc->sc_quirk & RTKQ_PHYWAKE_PM) != 0)
CSR_WRITE_1(sc, RTK_PMCH, CSR_READ_1(sc, RTK_PMCH) | 0x80);
aprint_normal_dev(sc->sc_dev, "Ethernet address %s\n",
ether_sprintf(eaddr));
#if 0
if (sc->re_ldata.re_tx_desc_cnt >
PAGE_SIZE / sizeof(struct re_desc)) {
sc->re_ldata.re_tx_desc_cnt =
PAGE_SIZE / sizeof(struct re_desc);
}
#endif
aprint_verbose_dev(sc->sc_dev, "using %d tx descriptors\n",
sc->re_ldata.re_tx_desc_cnt);
KASSERT(RE_NEXT_TX_DESC(sc, RE_TX_DESC_CNT(sc) - 1) == 0);
if ((error = bus_dmamem_alloc(sc->sc_dmat, RE_TX_LIST_SZ(sc),
RE_RING_ALIGN, 0, &sc->re_ldata.re_tx_listseg, 1,
&sc->re_ldata.re_tx_listnseg, BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't allocate tx listseg, error = %d\n", error);
goto fail_0;
}
if ((error = bus_dmamem_map(sc->sc_dmat, &sc->re_ldata.re_tx_listseg,
sc->re_ldata.re_tx_listnseg, RE_TX_LIST_SZ(sc),
(void **)&sc->re_ldata.re_tx_list,
BUS_DMA_COHERENT | BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't map tx list, error = %d\n", error);
goto fail_1;
}
memset(sc->re_ldata.re_tx_list, 0, RE_TX_LIST_SZ(sc));
if ((error = bus_dmamap_create(sc->sc_dmat, RE_TX_LIST_SZ(sc), 1,
RE_TX_LIST_SZ(sc), 0, 0,
&sc->re_ldata.re_tx_list_map)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't create tx list map, error = %d\n", error);
goto fail_2;
}
if ((error = bus_dmamap_load(sc->sc_dmat,
sc->re_ldata.re_tx_list_map, sc->re_ldata.re_tx_list,
RE_TX_LIST_SZ(sc), NULL, BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't load tx list, error = %d\n", error);
goto fail_3;
}
for (i = 0; i < RE_TX_QLEN(sc); i++) {
error = bus_dmamap_create(sc->sc_dmat,
round_page(IP_MAXPACKET),
RE_TX_DESC_CNT(sc), RE_TDESC_CMD_FRAGLEN,
0, 0, &sc->re_ldata.re_txq[i].txq_dmamap);
if (error) {
aprint_error_dev(sc->sc_dev,
"can't create DMA map for TX\n");
goto fail_4;
}
}
if ((error = bus_dmamem_alloc(sc->sc_dmat,
RE_RX_DMAMEM_SZ, RE_RING_ALIGN, 0, &sc->re_ldata.re_rx_listseg, 1,
&sc->re_ldata.re_rx_listnseg, BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't allocate rx listseg, error = %d\n", error);
goto fail_4;
}
if ((error = bus_dmamem_map(sc->sc_dmat, &sc->re_ldata.re_rx_listseg,
sc->re_ldata.re_rx_listnseg, RE_RX_DMAMEM_SZ,
(void **)&sc->re_ldata.re_rx_list,
BUS_DMA_COHERENT | BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't map rx list, error = %d\n", error);
goto fail_5;
}
memset(sc->re_ldata.re_rx_list, 0, RE_RX_DMAMEM_SZ);
if ((error = bus_dmamap_create(sc->sc_dmat,
RE_RX_DMAMEM_SZ, 1, RE_RX_DMAMEM_SZ, 0, 0,
&sc->re_ldata.re_rx_list_map)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't create rx list map, error = %d\n", error);
goto fail_6;
}
if ((error = bus_dmamap_load(sc->sc_dmat,
sc->re_ldata.re_rx_list_map, sc->re_ldata.re_rx_list,
RE_RX_DMAMEM_SZ, NULL, BUS_DMA_NOWAIT)) != 0) {
aprint_error_dev(sc->sc_dev,
"can't load rx list, error = %d\n", error);
goto fail_7;
}
for (i = 0; i < RE_RX_DESC_CNT(sc); i++) {
error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES,
0, 0, &sc->re_ldata.re_rxsoft[i].rxs_dmamap);
if (error) {
aprint_error_dev(sc->sc_dev,
"can't create DMA map for RX\n");
goto fail_8;
}
}
sc->sc_flags |= RTK_ATTACHED;
ifp = &sc->ethercom.ec_if;
ifp->if_softc = sc;
strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
ifp->if_mtu = ETHERMTU;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_extflags = IFEF_MPSAFE;
ifp->if_ioctl = re_ioctl;
sc->ethercom.ec_capabilities |=
ETHERCAP_VLAN_MTU | ETHERCAP_VLAN_HWTAGGING;
ifp->if_start = re_start;
ifp->if_stop = re_stop;
sc->sc_if_flags = ifp->if_flags;
ifp->if_capabilities |=
IFCAP_CSUM_IPv4_Tx | IFCAP_CSUM_IPv4_Rx |
IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv4_Rx |
IFCAP_CSUM_UDPv4_Tx | IFCAP_CSUM_UDPv4_Rx |
IFCAP_TSOv4;
ifp->if_watchdog = re_watchdog;
ifp->if_init = re_init;
ifp->if_capenable = ifp->if_capabilities;
IFQ_SET_MAXLEN(&ifp->if_snd, RE_IFQ_MAXLEN);
IFQ_SET_READY(&ifp->if_snd);
callout_init(&sc->rtk_tick_ch, CALLOUT_MPSAFE);
callout_setfunc(&sc->rtk_tick_ch, re_tick, sc);
mii->mii_ifp = ifp;
mii->mii_readreg = re_miibus_readreg;
mii->mii_writereg = re_miibus_writereg;
mii->mii_statchg = re_miibus_statchg;
sc->ethercom.ec_mii = mii;
ifmedia_init(&mii->mii_media, IFM_IMASK, ether_mediachange,
ether_mediastatus);
mii_attach(sc->sc_dev, mii, 0xffffffff, MII_PHY_ANY,
MII_OFFSET_ANY, 0);
ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, eaddr);
ether_set_ifflags_cb(&sc->ethercom, re_ifflags_cb);
rnd_attach_source(&sc->rnd_source, device_xname(sc->sc_dev),
RND_TYPE_NET, RND_FLAG_DEFAULT);
if (pmf_device_register(sc->sc_dev, NULL, NULL))
pmf_class_network_register(sc->sc_dev, ifp);
else
aprint_error_dev(sc->sc_dev,
"couldn't establish power handler\n");
return;
fail_8:
for (i = 0; i < RE_RX_DESC_CNT(sc); i++)
if (sc->re_ldata.re_rxsoft[i].rxs_dmamap != NULL)
bus_dmamap_destroy(sc->sc_dmat,
sc->re_ldata.re_rxsoft[i].rxs_dmamap);
bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
fail_7:
bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
fail_6:
bus_dmamem_unmap(sc->sc_dmat,
(void *)sc->re_ldata.re_rx_list, RE_RX_DMAMEM_SZ);
fail_5:
bus_dmamem_free(sc->sc_dmat,
&sc->re_ldata.re_rx_listseg, sc->re_ldata.re_rx_listnseg);
fail_4:
for (i = 0; i < RE_TX_QLEN(sc); i++)
if (sc->re_ldata.re_txq[i].txq_dmamap != NULL)
bus_dmamap_destroy(sc->sc_dmat,
sc->re_ldata.re_txq[i].txq_dmamap);
bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
fail_3:
bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
fail_2:
bus_dmamem_unmap(sc->sc_dmat,
(void *)sc->re_ldata.re_tx_list, RE_TX_LIST_SZ(sc));
fail_1:
bus_dmamem_free(sc->sc_dmat,
&sc->re_ldata.re_tx_listseg, sc->re_ldata.re_tx_listnseg);
fail_0:
mutex_destroy(&sc->sc_lock);
return;
}
int
re_activate(device_t self, enum devact act)
{
struct rtk_softc *sc = device_private(self);
switch (act) {
case DVACT_DEACTIVATE:
if_deactivate(&sc->ethercom.ec_if);
return 0;
default:
return EOPNOTSUPP;
}
}
int
re_detach(struct rtk_softc *sc)
{
struct ifnet *ifp = &sc->ethercom.ec_if;
int i;
if ((sc->sc_flags & RTK_ATTACHED) == 0)
return 0;
re_stop(ifp, 1);
mii_detach(&sc->mii, MII_PHY_ANY, MII_OFFSET_ANY);
rnd_detach_source(&sc->rnd_source);
ether_ifdetach(ifp);
if_detach(ifp);
ifmedia_fini(&sc->mii.mii_media);
for (i = 0; i < RE_RX_DESC_CNT(sc); i++)
if (sc->re_ldata.re_rxsoft[i].rxs_dmamap != NULL)
bus_dmamap_destroy(sc->sc_dmat,
sc->re_ldata.re_rxsoft[i].rxs_dmamap);
bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
bus_dmamem_unmap(sc->sc_dmat,
(void *)sc->re_ldata.re_rx_list, RE_RX_DMAMEM_SZ);
bus_dmamem_free(sc->sc_dmat,
&sc->re_ldata.re_rx_listseg, sc->re_ldata.re_rx_listnseg);
for (i = 0; i < RE_TX_QLEN(sc); i++)
if (sc->re_ldata.re_txq[i].txq_dmamap != NULL)
bus_dmamap_destroy(sc->sc_dmat,
sc->re_ldata.re_txq[i].txq_dmamap);
bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
bus_dmamem_unmap(sc->sc_dmat,
(void *)sc->re_ldata.re_tx_list, RE_TX_LIST_SZ(sc));
bus_dmamem_free(sc->sc_dmat,
&sc->re_ldata.re_tx_listseg, sc->re_ldata.re_tx_listnseg);
pmf_device_deregister(sc->sc_dev);
mutex_destroy(&sc->sc_lock);
sc->sc_flags &= ~RTK_ATTACHED;
return 0;
}
static int
re_enable(struct rtk_softc *sc)
{
if (RTK_IS_ENABLED(sc) == 0 && sc->sc_enable != NULL) {
if ((*sc->sc_enable)(sc) != 0) {
printf("%s: device enable failed\n",
device_xname(sc->sc_dev));
return EIO;
}
sc->sc_flags |= RTK_ENABLED;
}
return 0;
}
static void
re_disable(struct rtk_softc *sc)
{
if (RTK_IS_ENABLED(sc) && sc->sc_disable != NULL) {
(*sc->sc_disable)(sc);
sc->sc_flags &= ~RTK_ENABLED;
}
}
static int
re_newbuf(struct rtk_softc *sc, int idx, struct mbuf *m)
{
struct mbuf *n = NULL;
bus_dmamap_t map;
struct re_desc *d;
struct re_rxsoft *rxs;
uint32_t cmdstat;
int error;
if (m == NULL) {
MGETHDR(n, M_DONTWAIT, MT_DATA);
if (n == NULL)
return ENOBUFS;
MCLAIM(n, &sc->ethercom.ec_rx_mowner);
MCLGET(n, M_DONTWAIT);
if ((n->m_flags & M_EXT) == 0) {
m_freem(n);
return ENOBUFS;
}
m = n;
} else
m->m_data = m->m_ext.ext_buf;
m->m_len = m->m_pkthdr.len = MCLBYTES - RE_ETHER_ALIGN;
m->m_data += RE_ETHER_ALIGN;
rxs = &sc->re_ldata.re_rxsoft[idx];
map = rxs->rxs_dmamap;
error = bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
BUS_DMA_READ|BUS_DMA_NOWAIT);
if (error)
goto out;
bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize,
BUS_DMASYNC_PREREAD);
d = &sc->re_ldata.re_rx_list[idx];
#ifdef DIAGNOSTIC
RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
cmdstat = le32toh(d->re_cmdstat);
RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_PREREAD);
if (cmdstat & RE_RDESC_STAT_OWN) {
panic("%s: tried to map busy RX descriptor",
device_xname(sc->sc_dev));
}
#endif
rxs->rxs_mbuf = m;
d->re_vlanctl = 0;
cmdstat = map->dm_segs[0].ds_len;
if (idx == (RE_RX_DESC_CNT(sc) - 1))
cmdstat |= RE_RDESC_CMD_EOR;
re_set_bufaddr(d, map->dm_segs[0].ds_addr);
d->re_cmdstat = htole32(cmdstat);
RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
cmdstat |= RE_RDESC_CMD_OWN;
d->re_cmdstat = htole32(cmdstat);
RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
return 0;
out:
m_freem(n);
return ENOMEM;
}
static int
re_tx_list_init(struct rtk_softc *sc)
{
int i;
memset(sc->re_ldata.re_tx_list, 0, RE_TX_LIST_SZ(sc));
for (i = 0; i < RE_TX_QLEN(sc); i++) {
sc->re_ldata.re_txq[i].txq_mbuf = NULL;
}
bus_dmamap_sync(sc->sc_dmat,
sc->re_ldata.re_tx_list_map, 0,
sc->re_ldata.re_tx_list_map->dm_mapsize,
BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
sc->re_ldata.re_txq_prodidx = 0;
sc->re_ldata.re_txq_considx = 0;
sc->re_ldata.re_txq_free = RE_TX_QLEN(sc);
sc->re_ldata.re_tx_free = RE_TX_DESC_CNT(sc);
sc->re_ldata.re_tx_nextfree = 0;
return 0;
}
static int
re_rx_list_init(struct rtk_softc *sc)
{
int i;
memset(sc->re_ldata.re_rx_list, 0, RE_RX_LIST_SZ);
for (i = 0; i < RE_RX_DESC_CNT(sc); i++) {
if (re_newbuf(sc, i, NULL) == ENOBUFS)
return ENOBUFS;
}
sc->re_ldata.re_rx_prodidx = 0;
sc->re_head = sc->re_tail = NULL;
return 0;
}
static void
re_rxeof(struct rtk_softc *sc)
{
struct mbuf *m;
struct ifnet *ifp;
int i, total_len;
struct re_desc *cur_rx;
struct re_rxsoft *rxs;
uint32_t rxstat, rxvlan;
RE_ASSERT_LOCKED(sc);
ifp = &sc->ethercom.ec_if;
for (i = sc->re_ldata.re_rx_prodidx;; i = RE_NEXT_RX_DESC(sc, i)) {
cur_rx = &sc->re_ldata.re_rx_list[i];
RE_RXDESCSYNC(sc, i,
BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
rxstat = le32toh(cur_rx->re_cmdstat);
rxvlan = le32toh(cur_rx->re_vlanctl);
RE_RXDESCSYNC(sc, i, BUS_DMASYNC_PREREAD);
if ((rxstat & RE_RDESC_STAT_OWN) != 0) {
break;
}
total_len = rxstat & sc->re_rxlenmask;
rxs = &sc->re_ldata.re_rxsoft[i];
m = rxs->rxs_mbuf;
bus_dmamap_sync(sc->sc_dmat,
rxs->rxs_dmamap, 0, rxs->rxs_dmamap->dm_mapsize,
BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, rxs->rxs_dmamap);
if ((rxstat & RE_RDESC_STAT_EOF) == 0) {
if (re_newbuf(sc, i, NULL) != 0) {
if_statinc(ifp, if_ierrors);
if (sc->re_head != NULL) {
m_freem(sc->re_head);
sc->re_head = sc->re_tail = NULL;
}
re_newbuf(sc, i, m);
continue;
}
m->m_len = MCLBYTES - RE_ETHER_ALIGN;
if (sc->re_head == NULL)
sc->re_head = sc->re_tail = m;
else {
m_remove_pkthdr(m);
sc->re_tail->m_next = m;
sc->re_tail = m;
}
continue;
}
if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0)
rxstat >>= 1;
if (__predict_false((rxstat & RE_RDESC_STAT_RXERRSUM) != 0)) {
#ifdef RE_DEBUG
printf("%s: RX error (rxstat = 0x%08x)",
device_xname(sc->sc_dev), rxstat);
if (rxstat & RE_RDESC_STAT_FRALIGN)
printf(", frame alignment error");
if (rxstat & RE_RDESC_STAT_BUFOFLOW)
printf(", out of buffer space");
if (rxstat & RE_RDESC_STAT_FIFOOFLOW)
printf(", FIFO overrun");
if (rxstat & RE_RDESC_STAT_GIANT)
printf(", giant packet");
if (rxstat & RE_RDESC_STAT_RUNT)
printf(", runt packet");
if (rxstat & RE_RDESC_STAT_CRCERR)
printf(", CRC error");
printf("\n");
#endif
if_statinc(ifp, if_ierrors);
if (sc->re_head != NULL) {
m_freem(sc->re_head);
sc->re_head = sc->re_tail = NULL;
}
re_newbuf(sc, i, m);
continue;
}
if (__predict_false(re_newbuf(sc, i, NULL) != 0)) {
if_statinc(ifp, if_ierrors);
if (sc->re_head != NULL) {
m_freem(sc->re_head);
sc->re_head = sc->re_tail = NULL;
}
re_newbuf(sc, i, m);
continue;
}
if (sc->re_head != NULL) {
m->m_len = total_len % (MCLBYTES - RE_ETHER_ALIGN);
if (m->m_len <= ETHER_CRC_LEN) {
sc->re_tail->m_len -=
(ETHER_CRC_LEN - m->m_len);
m_freem(m);
} else {
m->m_len -= ETHER_CRC_LEN;
m_remove_pkthdr(m);
sc->re_tail->m_next = m;
}
m = sc->re_head;
sc->re_head = sc->re_tail = NULL;
m->m_pkthdr.len = total_len - ETHER_CRC_LEN;
} else
m->m_pkthdr.len = m->m_len =
(total_len - ETHER_CRC_LEN);
m_set_rcvif(m, ifp);
if ((sc->sc_quirk & RTKQ_DESCV2) == 0) {
if ((rxstat & RE_RDESC_STAT_PROTOID) != 0) {
m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
if (rxstat & RE_RDESC_STAT_IPSUMBAD)
m->m_pkthdr.csum_flags |=
M_CSUM_IPv4_BAD;
if (RE_TCPPKT(rxstat)) {
m->m_pkthdr.csum_flags |= M_CSUM_TCPv4;
if (rxstat & RE_RDESC_STAT_TCPSUMBAD)
m->m_pkthdr.csum_flags |=
M_CSUM_TCP_UDP_BAD;
} else if (RE_UDPPKT(rxstat)) {
m->m_pkthdr.csum_flags |= M_CSUM_UDPv4;
if (rxstat & RE_RDESC_STAT_UDPSUMBAD) {
if (sc->sc_quirk & RTKQ_8139CPLUS)
m->m_pkthdr.csum_flags
&= ~M_CSUM_UDPv4;
else
m->m_pkthdr.csum_flags
|= M_CSUM_TCP_UDP_BAD;
}
}
}
} else {
if ((rxvlan & RE_RDESC_VLANCTL_IPV4) != 0) {
m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
if (rxstat & RE_RDESC_STAT_IPSUMBAD)
m->m_pkthdr.csum_flags |=
M_CSUM_IPv4_BAD;
if (RE_TCPPKT(rxstat)) {
m->m_pkthdr.csum_flags |= M_CSUM_TCPv4;
if (rxstat & RE_RDESC_STAT_TCPSUMBAD)
m->m_pkthdr.csum_flags |=
M_CSUM_TCP_UDP_BAD;
} else if (RE_UDPPKT(rxstat)) {
m->m_pkthdr.csum_flags |= M_CSUM_UDPv4;
if (rxstat & RE_RDESC_STAT_UDPSUMBAD)
m->m_pkthdr.csum_flags |=
M_CSUM_TCP_UDP_BAD;
}
}
}
if (rxvlan & RE_RDESC_VLANCTL_TAG) {
vlan_set_tag(m,
bswap16(rxvlan & RE_RDESC_VLANCTL_DATA));
}
if_percpuq_enqueue(ifp->if_percpuq, m);
}
sc->re_ldata.re_rx_prodidx = i;
}
static void
re_txeof(struct rtk_softc *sc)
{
struct ifnet *ifp;
struct re_txq *txq;
uint32_t txstat;
int idx, descidx;
RE_ASSERT_LOCKED(sc);
ifp = &sc->ethercom.ec_if;
for (idx = sc->re_ldata.re_txq_considx;
sc->re_ldata.re_txq_free < RE_TX_QLEN(sc);
idx = RE_NEXT_TXQ(sc, idx), sc->re_ldata.re_txq_free++) {
txq = &sc->re_ldata.re_txq[idx];
KASSERT(txq->txq_mbuf != NULL);
descidx = txq->txq_descidx;
RE_TXDESCSYNC(sc, descidx,
BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
txstat =
le32toh(sc->re_ldata.re_tx_list[descidx].re_cmdstat);
RE_TXDESCSYNC(sc, descidx, BUS_DMASYNC_PREREAD);
KASSERT((txstat & RE_TDESC_CMD_EOF) != 0);
if (txstat & RE_TDESC_CMD_OWN) {
break;
}
sc->re_ldata.re_tx_free += txq->txq_nsegs;
KASSERT(sc->re_ldata.re_tx_free <= RE_TX_DESC_CNT(sc));
bus_dmamap_sync(sc->sc_dmat, txq->txq_dmamap,
0, txq->txq_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, txq->txq_dmamap);
m_freem(txq->txq_mbuf);
txq->txq_mbuf = NULL;
txq->txq_nsegs = -42;
net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
if (txstat & (RE_TDESC_STAT_EXCESSCOL | RE_TDESC_STAT_COLCNT))
if_statinc_ref(ifp, nsr, if_collisions);
if (txstat & RE_TDESC_STAT_TXERRSUM)
if_statinc_ref(ifp, nsr, if_oerrors);
else
if_statinc_ref(ifp, nsr, if_opackets);
IF_STAT_PUTREF(ifp);
}
sc->re_ldata.re_txq_considx = idx;
if ((sc->sc_if_flags & IFF_OACTIVE) != 0 &&
sc->re_ldata.re_txq_free > 0 &&
sc->re_ldata.re_tx_free >= RE_NTXDESC_RSVD) {
ifp->if_flags &= ~IFF_OACTIVE;
sc->sc_if_flags = ifp->if_flags;
}
if (sc->re_ldata.re_txq_free < RE_TX_QLEN(sc)) {
if ((sc->sc_quirk & RTKQ_IM_HW) == 0)
CSR_WRITE_4(sc, RTK_TIMERCNT, 1);
} else
ifp->if_timer = 0;
}
static void
re_tick(void *arg)
{
struct rtk_softc *sc = arg;
RE_LOCK(sc);
re_tick_locked(sc);
RE_UNLOCK(sc);
}
static void
re_tick_locked(struct rtk_softc *sc)
{
mii_tick(&sc->mii);
callout_schedule(&sc->rtk_tick_ch, hz);
}
int
re_intr(void *arg)
{
struct rtk_softc *sc = arg;
struct ifnet *ifp;
uint16_t status, rndstatus = 0;
int handled = 0;
if (!device_has_power(sc->sc_dev))
return 0;
ifp = &sc->ethercom.ec_if;
if ((ifp->if_flags & IFF_UP) == 0)
return 0;
const uint16_t status_mask = (sc->sc_quirk & RTKQ_IM_HW) ?
RTK_INTRS_IM_HW : RTK_INTRS_CPLUS;
RE_LOCK(sc);
for (;;) {
status = CSR_READ_2(sc, RTK_ISR);
if (status == 0xffff)
break;
if (status != 0) {
handled = 1;
CSR_WRITE_2(sc, RTK_ISR, status);
rndstatus = status;
}
if ((status & status_mask) == 0)
break;
if (status & (RTK_ISR_RX_OK | RTK_ISR_RX_ERR))
re_rxeof(sc);
if ((status & (RTK_ISR_TX_OK | RTK_ISR_TX_DESC_UNAVAIL)) &&
(sc->sc_quirk & RTKQ_PCIE) != 0) {
if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0)
CSR_WRITE_1(sc, RTK_TXSTART, RTK_TXSTART_START);
else
CSR_WRITE_1(sc, RTK_GTXSTART, RTK_TXSTART_START);
}
if (status & (RTK_ISR_TIMEOUT_EXPIRED | RTK_ISR_TX_ERR |
RTK_ISR_TX_DESC_UNAVAIL | RTK_ISR_TX_OK))
re_txeof(sc);
if (status & RTK_ISR_SYSTEM_ERR)
re_init_locked(sc);
if (status & RTK_ISR_LINKCHG) {
callout_stop(&sc->rtk_tick_ch);
re_tick_locked(sc);
}
}
if (handled)
if_schedule_deferred_start(ifp);
rnd_add_uint32(&sc->rnd_source, rndstatus);
RE_UNLOCK(sc);
return handled;
}
static void
re_start(struct ifnet *ifp)
{
struct rtk_softc *sc = ifp->if_softc;
RE_LOCK(sc);
re_start_locked(sc);
RE_UNLOCK(sc);
}
static void
re_start_locked(struct rtk_softc *sc)
{
struct ifnet *ifp = &sc->ethercom.ec_if;
struct mbuf *m;
bus_dmamap_t map;
struct re_txq *txq;
struct re_desc *d;
uint32_t cmdstat, re_flags, vlanctl;
int ofree, idx, error, nsegs, seg;
int startdesc, curdesc, lastdesc;
bool pad;
RE_ASSERT_LOCKED(sc);
ofree = sc->re_ldata.re_txq_free;
for (idx = sc->re_ldata.re_txq_prodidx;; idx = RE_NEXT_TXQ(sc, idx)) {
if (sc->re_ldata.re_txq_free == 0 ||
sc->re_ldata.re_tx_free == 0) {
ifp->if_flags |= IFF_OACTIVE;
sc->sc_if_flags = ifp->if_flags;
break;
}
IFQ_POLL(&ifp->if_snd, m);
if (m == NULL)
break;
vlanctl = 0;
if ((m->m_pkthdr.csum_flags & M_CSUM_TSOv4) != 0) {
uint32_t segsz = m->m_pkthdr.segsz;
if ((sc->sc_quirk & RTKQ_DESCV2) == 0) {
re_flags = RE_TDESC_CMD_LGSEND |
(segsz << RE_TDESC_CMD_MSSVAL_SHIFT);
} else {
re_flags = RE_TDESC_CMD_LGSEND_V4;
vlanctl |=
(segsz << RE_TDESC_VLANCTL_MSSVAL_SHIFT);
}
} else {
re_flags = 0;
if ((m->m_pkthdr.csum_flags &
(M_CSUM_IPv4 | M_CSUM_TCPv4 | M_CSUM_UDPv4))
!= 0) {
if ((sc->sc_quirk & RTKQ_DESCV2) == 0) {
re_flags |= RE_TDESC_CMD_IPCSUM;
if (m->m_pkthdr.csum_flags &
M_CSUM_TCPv4) {
re_flags |=
RE_TDESC_CMD_TCPCSUM;
} else if (m->m_pkthdr.csum_flags &
M_CSUM_UDPv4) {
re_flags |=
RE_TDESC_CMD_UDPCSUM;
}
} else {
vlanctl |= RE_TDESC_VLANCTL_IPCSUM;
if (m->m_pkthdr.csum_flags &
M_CSUM_TCPv4) {
vlanctl |=
RE_TDESC_VLANCTL_TCPCSUM;
} else if (m->m_pkthdr.csum_flags &
M_CSUM_UDPv4) {
vlanctl |=
RE_TDESC_VLANCTL_UDPCSUM;
}
}
}
}
txq = &sc->re_ldata.re_txq[idx];
map = txq->txq_dmamap;
error = bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
BUS_DMA_WRITE|BUS_DMA_NOWAIT);
if (__predict_false(error)) {
printf("%s: can't map mbuf (error %d)\n",
device_xname(sc->sc_dev), error);
IFQ_DEQUEUE(&ifp->if_snd, m);
m_freem(m);
if_statinc(ifp, if_oerrors);
continue;
}
nsegs = map->dm_nsegs;
pad = false;
if (__predict_false(m->m_pkthdr.len <= RE_IP4CSUMTX_PADLEN &&
(re_flags & RE_TDESC_CMD_IPCSUM) != 0 &&
(sc->sc_quirk & RTKQ_DESCV2) == 0)) {
pad = true;
nsegs++;
}
if (nsegs > sc->re_ldata.re_tx_free) {
ifp->if_flags |= IFF_OACTIVE;
sc->sc_if_flags = ifp->if_flags;
bus_dmamap_unload(sc->sc_dmat, map);
break;
}
IFQ_DEQUEUE(&ifp->if_snd, m);
bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize,
BUS_DMASYNC_PREWRITE);
if (vlan_has_tag(m))
vlanctl |= bswap16(vlan_get_tag(m)) |
RE_TDESC_VLANCTL_TAG;
curdesc = startdesc = sc->re_ldata.re_tx_nextfree;
lastdesc = -1;
for (seg = 0; seg < map->dm_nsegs;
seg++, curdesc = RE_NEXT_TX_DESC(sc, curdesc)) {
d = &sc->re_ldata.re_tx_list[curdesc];
#ifdef DIAGNOSTIC
RE_TXDESCSYNC(sc, curdesc,
BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
cmdstat = le32toh(d->re_cmdstat);
RE_TXDESCSYNC(sc, curdesc, BUS_DMASYNC_PREREAD);
if (cmdstat & RE_TDESC_STAT_OWN) {
panic("%s: tried to map busy TX descriptor",
device_xname(sc->sc_dev));
}
#endif
d->re_vlanctl = htole32(vlanctl);
re_set_bufaddr(d, map->dm_segs[seg].ds_addr);
cmdstat = re_flags | map->dm_segs[seg].ds_len;
if (seg == 0)
cmdstat |= RE_TDESC_CMD_SOF;
else
cmdstat |= RE_TDESC_CMD_OWN;
if (curdesc == (RE_TX_DESC_CNT(sc) - 1))
cmdstat |= RE_TDESC_CMD_EOR;
if (seg == nsegs - 1) {
cmdstat |= RE_TDESC_CMD_EOF;
lastdesc = curdesc;
}
d->re_cmdstat = htole32(cmdstat);
RE_TXDESCSYNC(sc, curdesc,
BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
}
if (__predict_false(pad)) {
d = &sc->re_ldata.re_tx_list[curdesc];
d->re_vlanctl = htole32(vlanctl);
re_set_bufaddr(d, RE_TXPADDADDR(sc));
cmdstat = re_flags |
RE_TDESC_CMD_OWN | RE_TDESC_CMD_EOF |
(RE_IP4CSUMTX_PADLEN + 1 - m->m_pkthdr.len);
if (curdesc == (RE_TX_DESC_CNT(sc) - 1))
cmdstat |= RE_TDESC_CMD_EOR;
d->re_cmdstat = htole32(cmdstat);
RE_TXDESCSYNC(sc, curdesc,
BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
lastdesc = curdesc;
curdesc = RE_NEXT_TX_DESC(sc, curdesc);
}
KASSERT(lastdesc != -1);
sc->re_ldata.re_tx_list[startdesc].re_cmdstat |=
htole32(RE_TDESC_CMD_OWN);
RE_TXDESCSYNC(sc, startdesc,
BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
txq->txq_mbuf = m;
txq->txq_descidx = lastdesc;
txq->txq_nsegs = nsegs;
sc->re_ldata.re_txq_free--;
sc->re_ldata.re_tx_free -= nsegs;
sc->re_ldata.re_tx_nextfree = curdesc;
bpf_mtap(ifp, m, BPF_D_OUT);
}
if (sc->re_ldata.re_txq_free < ofree) {
sc->re_ldata.re_txq_prodidx = idx;
if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0)
CSR_WRITE_1(sc, RTK_TXSTART, RTK_TXSTART_START);
else
CSR_WRITE_1(sc, RTK_GTXSTART, RTK_TXSTART_START);
if ((sc->sc_quirk & RTKQ_IM_HW) == 0) {
CSR_WRITE_4(sc, RTK_TIMERCNT, 1);
}
ifp->if_timer = 5;
}
}
static int
re_init(struct ifnet *ifp)
{
struct rtk_softc *sc = ifp->if_softc;
int error;
RE_LOCK(sc);
error = re_init_locked(sc);
RE_UNLOCK(sc);
return error;
}
static int
re_init_locked(struct rtk_softc *sc)
{
struct ifnet *ifp = &sc->ethercom.ec_if;
uint32_t rxcfg = 0;
uint16_t cfg;
int error;
#ifdef RE_USE_EECMD
const uint8_t *enaddr;
uint32_t reg;
#endif
sc->sc_if_flags = ifp->if_flags;
if ((error = re_enable(sc)) != 0)
goto out;
re_stop_locked(sc, 0);
re_reset(sc);
cfg = RE_CPLUSCMD_PCI_MRW;
if ((sc->sc_quirk & RTKQ_8169NONS) != 0)
cfg |= (0x1 << 14);
if ((sc->ethercom.ec_capenable & ETHERCAP_VLAN_HWTAGGING) != 0)
cfg |= RE_CPLUSCMD_VLANSTRIP;
if ((ifp->if_capenable & (IFCAP_CSUM_IPv4_Rx |
IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_UDPv4_Rx)) != 0)
cfg |= RE_CPLUSCMD_RXCSUM_ENB;
if ((sc->sc_quirk & RTKQ_MACSTAT) != 0) {
cfg |= RE_CPLUSCMD_MACSTAT_DIS;
cfg |= RE_CPLUSCMD_TXENB;
} else
cfg |= RE_CPLUSCMD_RXENB | RE_CPLUSCMD_TXENB;
CSR_WRITE_2(sc, RTK_CPLUS_CMD, cfg);
if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
if ((sc->sc_quirk & RTKQ_IM_HW) == 0) {
CSR_WRITE_2(sc, RTK_IM, 0x0000);
} else {
CSR_WRITE_2(sc, RTK_IM, 0x5151);
}
}
DELAY(10000);
#ifdef RE_USE_EECMD
CSR_WRITE_1(sc, RTK_EECMD, RTK_EEMODE_WRITECFG);
enaddr = CLLADDR(ifp->if_sadl);
reg = enaddr[0] | (enaddr[1] << 8) |
(enaddr[2] << 16) | (enaddr[3] << 24);
CSR_WRITE_4(sc, RTK_IDR0, reg);
reg = enaddr[4] | (enaddr[5] << 8);
CSR_WRITE_4(sc, RTK_IDR4, reg);
CSR_WRITE_1(sc, RTK_EECMD, RTK_EEMODE_OFF);
#endif
re_rx_list_init(sc);
re_tx_list_init(sc);
CSR_WRITE_4(sc, RTK_RXLIST_ADDR_HI,
RE_ADDR_HI(sc->re_ldata.re_rx_list_map->dm_segs[0].ds_addr));
CSR_WRITE_4(sc, RTK_RXLIST_ADDR_LO,
RE_ADDR_LO(sc->re_ldata.re_rx_list_map->dm_segs[0].ds_addr));
CSR_WRITE_4(sc, RTK_TXLIST_ADDR_HI,
RE_ADDR_HI(sc->re_ldata.re_tx_list_map->dm_segs[0].ds_addr));
CSR_WRITE_4(sc, RTK_TXLIST_ADDR_LO,
RE_ADDR_LO(sc->re_ldata.re_tx_list_map->dm_segs[0].ds_addr));
if (sc->sc_quirk & RTKQ_RXDV_GATED) {
CSR_WRITE_4(sc, RTK_MISC,
CSR_READ_4(sc, RTK_MISC) & ~RTK_MISC_RXDV_GATED_EN);
}
if ((sc->sc_quirk & RTKQ_TXRXEN_LATER) == 0)
CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_TX_ENB | RTK_CMD_RX_ENB);
if (sc->re_testmode && (sc->sc_quirk & RTKQ_8169NONS) != 0) {
CSR_WRITE_4(sc, RTK_TXCFG,
RE_TXCFG_CONFIG | RTK_LOOPTEST_ON);
} else
CSR_WRITE_4(sc, RTK_TXCFG, RE_TXCFG_CONFIG);
CSR_WRITE_1(sc, RTK_EARLY_TX_THRESH, 16);
CSR_WRITE_4(sc, RTK_RXCFG, RE_RXCFG_CONFIG);
rxcfg = CSR_READ_4(sc, RTK_RXCFG);
rxcfg |= RTK_RXCFG_RX_INDIV;
if (sc->sc_quirk & RTKQ_EARLYOFF)
rxcfg |= RTK_RXCFG_EARLYOFF;
else if (sc->sc_quirk & RTKQ_RXDV_GATED)
rxcfg |= RTK_RXCFG_EARLYOFFV2;
if (ifp->if_flags & IFF_PROMISC)
rxcfg |= RTK_RXCFG_RX_ALLPHYS;
else
rxcfg &= ~RTK_RXCFG_RX_ALLPHYS;
CSR_WRITE_4(sc, RTK_RXCFG, rxcfg);
if (ifp->if_flags & IFF_BROADCAST)
rxcfg |= RTK_RXCFG_RX_BROAD;
else
rxcfg &= ~RTK_RXCFG_RX_BROAD;
CSR_WRITE_4(sc, RTK_RXCFG, rxcfg);
rtk_setmulti(sc);
if ((sc->sc_quirk & RTKQ_TXRXEN_LATER) != 0)
CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_TX_ENB | RTK_CMD_RX_ENB);
if (sc->re_testmode)
CSR_WRITE_2(sc, RTK_IMR, 0);
else if ((sc->sc_quirk & RTKQ_IM_HW) != 0)
CSR_WRITE_2(sc, RTK_IMR, RTK_INTRS_IM_HW);
else
CSR_WRITE_2(sc, RTK_IMR, RTK_INTRS_CPLUS);
CSR_WRITE_4(sc, RTK_MISSEDPKT, 0);
#ifdef notdef
CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_TX_ENB | RTK_CMD_RX_ENB);
#endif
unsigned defer;
unsigned period;
defer = 128000;
if ((sc->sc_quirk & RTKQ_IM_HW) != 0) {
period = 1;
defer = 0;
} else if ((sc->sc_quirk & RTKQ_PCIE) != 0) {
period = 8;
} else {
switch (CSR_READ_1(sc, RTK_CFG2_BUSFREQ) & 0x7) {
case RTK_BUSFREQ_33MHZ:
period = 30;
break;
case RTK_BUSFREQ_66MHZ:
period = 15;
break;
default:
period = 60;
break;
}
}
uint16_t re8139_reg;
if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0)
re8139_reg = RTK_TIMERINT;
else
re8139_reg = RTK_TIMERINT_8169;
CSR_WRITE_4(sc, re8139_reg, defer / period);
if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
CSR_WRITE_2(sc, RTK_MAXRXPKTLEN, 16383);
}
if (sc->re_testmode)
return 0;
CSR_WRITE_1(sc, RTK_CFG1, RTK_CFG1_DRVLOAD);
ifp->if_flags |= IFF_RUNNING;
ifp->if_flags &= ~IFF_OACTIVE;
sc->sc_if_flags = ifp->if_flags;
callout_schedule(&sc->rtk_tick_ch, hz);
out:
if (error) {
ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
sc->sc_if_flags = ifp->if_flags;
ifp->if_timer = 0;
printf("%s: interface not running\n",
device_xname(sc->sc_dev));
}
return error;
}
static int
re_ioctl(struct ifnet *ifp, u_long command, void *data)
{
struct rtk_softc *sc = ifp->if_softc;
struct ifreq *ifr = data;
int error = 0, s;
switch (command) {
case SIOCSIFMTU:
if ((sc->sc_quirk & RTKQ_NOJUMBO) != 0 &&
ifr->ifr_mtu > ETHERMTU) {
error = EINVAL;
break;
}
if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > ETHERMTU_JUMBO)
error = EINVAL;
else if ((error = ifioctl_common(ifp, command, data)) ==
ENETRESET)
error = 0;
break;
default:
s = splnet();
error = ether_ioctl(ifp, command, data);
splx(s);
if (error != ENETRESET)
break;
error = 0;
if (command == SIOCSIFCAP)
error = (*ifp->if_init)(ifp);
else if (command == SIOCADDMULTI || command == SIOCADDMULTI) {
RE_LOCK(sc);
if (sc->sc_if_flags & IFF_RUNNING)
rtk_setmulti(sc);
RE_UNLOCK(sc);
}
break;
}
return error;
}
static int
re_ifflags_cb(struct ethercom *ec)
{
struct ifnet * const ifp = &ec->ec_if;
struct rtk_softc * const sc = ifp->if_softc;
int ret = 0;
u_short change;
KASSERT(IFNET_LOCKED(ifp));
RE_LOCK(sc);
change = ifp->if_flags ^ sc->sc_if_flags;
sc->sc_if_flags = ifp->if_flags;
if ((change & ~(IFF_CANTCHANGE | IFF_DEBUG)) != 0) {
ret = ENETRESET;
} else if ((change & IFF_PROMISC) != 0) {
if ((sc->sc_if_flags & IFF_RUNNING) != 0)
rtk_setmulti(sc);
}
RE_UNLOCK(sc);
return ret;
}
static void
re_watchdog(struct ifnet *ifp)
{
struct rtk_softc *sc = ifp->if_softc;
RE_LOCK(sc);
printf("%s: watchdog timeout\n", device_xname(sc->sc_dev));
if_statinc(ifp, if_oerrors);
re_txeof(sc);
re_rxeof(sc);
re_init_locked(sc);
RE_UNLOCK(sc);
}
static void
re_stop(struct ifnet *ifp, int disable)
{
struct rtk_softc *sc = ifp->if_softc;
RE_LOCK(sc);
re_stop_locked(sc, disable);
RE_UNLOCK(sc);
}
static void
re_stop_locked(struct rtk_softc *sc, int disable)
{
struct ifnet *ifp = &sc->ethercom.ec_if;
int i;
callout_stop(&sc->rtk_tick_ch);
mii_down(&sc->mii);
CSR_WRITE_4(sc, RTK_RXCFG, CSR_READ_4(sc, RTK_RXCFG) &
~(RTK_RXCFG_RX_ALLPHYS | RTK_RXCFG_RX_INDIV | RTK_RXCFG_RX_MULTI |
RTK_RXCFG_RX_BROAD));
if (sc->sc_quirk & RTKQ_RXDV_GATED) {
CSR_WRITE_4(sc, RTK_MISC,
CSR_READ_4(sc, RTK_MISC) | RTK_MISC_RXDV_GATED_EN);
}
if ((sc->sc_quirk & RTKQ_CMDSTOP) != 0)
CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_STOPREQ | RTK_CMD_TX_ENB |
RTK_CMD_RX_ENB);
else
CSR_WRITE_1(sc, RTK_COMMAND, 0x00);
DELAY(1000);
CSR_WRITE_2(sc, RTK_IMR, 0x0000);
CSR_WRITE_2(sc, RTK_ISR, 0xFFFF);
if (sc->re_head != NULL) {
m_freem(sc->re_head);
sc->re_head = sc->re_tail = NULL;
}
for (i = 0; i < RE_TX_QLEN(sc); i++) {
if (sc->re_ldata.re_txq[i].txq_mbuf != NULL) {
bus_dmamap_unload(sc->sc_dmat,
sc->re_ldata.re_txq[i].txq_dmamap);
m_freem(sc->re_ldata.re_txq[i].txq_mbuf);
sc->re_ldata.re_txq[i].txq_mbuf = NULL;
sc->re_ldata.re_txq[i].txq_nsegs = 0;
}
}
for (i = 0; i < RE_RX_DESC_CNT(sc); i++) {
if (sc->re_ldata.re_rxsoft[i].rxs_mbuf != NULL) {
bus_dmamap_unload(sc->sc_dmat,
sc->re_ldata.re_rxsoft[i].rxs_dmamap);
m_freem(sc->re_ldata.re_rxsoft[i].rxs_mbuf);
sc->re_ldata.re_rxsoft[i].rxs_mbuf = NULL;
}
}
if (disable)
re_disable(sc);
ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
ifp->if_timer = 0;
sc->sc_if_flags = ifp->if_flags;
}