Implement interrupt moderation scheme supported by VT61xx

controllers. TX/RX interrupt mitigation is controlled by
VGE_TXSUPPTHR and VGE_RXSUPPTHR register. These registers suppress
generation of interrupts until the programmed frames counter equals
to the registers. VT61xx also supports interrupt hold off timer
register. If this interrupt hold off timer is active all interrupts
would be disabled until the timer reaches to 0. The timer value is
reloaded whenever VGE_ISR register written. The timer resolution is
about 20us.

Previously vge(4) used single shot timer to reduce Tx completion
interrupts. This required VGE_CRS1 register access in Tx
start/completion handler to rearm new timeout value and it did not
show satisfactory result(more than 50k interrupts under load). Rx
interrupts was not moderated at all such that vge(4) used to
generate too many interrupts which in turn made polling(4) better
approach under high network load.

This change activates all interrupt moderation mechanism and
initial values were tuned to generate interrupt less than 8k per
second. That number of interrupts wouldn't add additional packet
latencies compared to polling(4). These interrupt parameters could
be changed with sysctl.
dev.vge.%d.int_holdoff
dev.vge.%d.rx_coal_pkt
dev.vge.%d.tx_coal_pkt
Interface has be brought down and up again before change take
effect.

With interrupt moderation there is no more need to loop in
interrupt handler. This loop always added one more register access.
While I'm here remove dead code which tried to implement subset of
interrupt moderation.
This commit is contained in:
yongari 2009-12-17 18:00:25 +00:00
parent 0768a69095
commit 3672ee2e9a
3 changed files with 103 additions and 80 deletions

View File

@ -175,6 +175,7 @@ static int vge_ifmedia_upd(struct ifnet *);
static void vge_init(void *);
static void vge_init_locked(struct vge_softc *);
static void vge_intr(void *);
static void vge_intr_holdoff(struct vge_softc *);
static int vge_ioctl(struct ifnet *, u_long, caddr_t);
static void vge_link_statchg(void *);
static int vge_miibus_readreg(device_t, int, int);
@ -1631,15 +1632,6 @@ vge_txeof(struct vge_softc *sc)
sc->vge_cdata.vge_tx_considx = cons;
if (sc->vge_cdata.vge_tx_cnt == 0)
sc->vge_timer = 0;
else {
/*
* If not all descriptors have been released reaped yet,
* reload the timer so that we will eventually get another
* interrupt that will cause us to re-enter this routine.
* This is done in case the transmitter has gone idle.
*/
CSR_WRITE_1(sc, VGE_CRS1, VGE_CR1_TIMER0_ENABLE);
}
}
static void
@ -1746,30 +1738,21 @@ vge_intr(void *arg)
/* Disable interrupts */
CSR_WRITE_1(sc, VGE_CRC3, VGE_CR3_INT_GMSK);
for (;;) {
status = CSR_READ_4(sc, VGE_ISR);
/* If the card has gone away the read returns 0xffff. */
if (status == 0xFFFFFFFF)
break;
if (status)
CSR_WRITE_4(sc, VGE_ISR, status);
if ((status & VGE_INTRS) == 0)
break;
status = CSR_READ_4(sc, VGE_ISR);
CSR_WRITE_4(sc, VGE_ISR, status | VGE_ISR_HOLDOFF_RELOAD);
/* If the card has gone away the read returns 0xffff. */
if (status == 0xFFFFFFFF || (status & VGE_INTRS) == 0)
goto done;
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
if (status & (VGE_ISR_RXOK|VGE_ISR_RXOK_HIPRIO))
vge_rxeof(sc, VGE_RX_DESC_CNT);
if (status & (VGE_ISR_RXOFLOW|VGE_ISR_RXNODESC)) {
vge_rxeof(sc, VGE_RX_DESC_CNT);
CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_RUN);
CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_WAK);
}
if (status & (VGE_ISR_TXOK0|VGE_ISR_TIMER0))
if (status & (VGE_ISR_TXOK0|VGE_ISR_TXOK_HIPRIO))
vge_txeof(sc);
if (status & (VGE_ISR_TXDMA_STALL|VGE_ISR_RXDMA_STALL)) {
@ -1780,13 +1763,14 @@ vge_intr(void *arg)
if (status & VGE_ISR_LINKSTS)
vge_link_statchg(sc);
}
done:
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
/* Re-enable interrupts */
CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_GMSK);
/* Re-enable interrupts */
CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_GMSK);
if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd))
vge_start_locked(ifp);
if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd))
vge_start_locked(ifp);
}
VGE_UNLOCK(sc);
}
@ -1984,17 +1968,6 @@ vge_start_locked(struct ifnet *ifp)
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
/* Issue a transmit command. */
CSR_WRITE_2(sc, VGE_TXQCSRS, VGE_TXQCSR_WAK0);
/*
* Use the countdown timer for interrupt moderation.
* 'TX done' interrupts are disabled. Instead, we reset the
* countdown timer, which will begin counting until it hits
* the value in the SSTIMER register, and then trigger an
* interrupt. Each time we set the TIMER0_ENABLE bit, the
* the timer count is reloaded. Only when the transmitter
* is idle will the timer hit 0 and an interrupt fire.
*/
CSR_WRITE_1(sc, VGE_CRS1, VGE_CR1_TIMER0_ENABLE);
/*
* Set a timeout in case the chip goes out to lunch.
*/
@ -2084,6 +2057,9 @@ vge_init_locked(struct vge_softc *sc)
CSR_WRITE_2(sc, VGE_RXDESCNUM, VGE_RX_DESC_CNT - 1);
CSR_WRITE_2(sc, VGE_RXDESC_RESIDUECNT, VGE_RX_DESC_CNT);
/* Configure interrupt moderation. */
vge_intr_holdoff(sc);
/* Enable and wake up the RX descriptor queue */
CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_RUN);
CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_WAK);
@ -2110,42 +2086,6 @@ vge_init_locked(struct vge_softc *sc)
CSR_WRITE_1(sc, VGE_CRS0,
VGE_CR0_TX_ENABLE|VGE_CR0_RX_ENABLE|VGE_CR0_START);
/*
* Configure one-shot timer for microsecond
* resolution and load it for 500 usecs.
*/
CSR_SETBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_TIMER0_RES);
CSR_WRITE_2(sc, VGE_SSTIMER, 400);
/*
* Configure interrupt moderation for receive. Enable
* the holdoff counter and load it, and set the RX
* suppression count to the number of descriptors we
* want to allow before triggering an interrupt.
* The holdoff timer is in units of 20 usecs.
*/
#ifdef notyet
CSR_WRITE_1(sc, VGE_INTCTL1, VGE_INTCTL_TXINTSUP_DISABLE);
/* Select the interrupt holdoff timer page. */
CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL);
CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_INTHLDOFF);
CSR_WRITE_1(sc, VGE_INTHOLDOFF, 10); /* ~200 usecs */
/* Enable use of the holdoff timer. */
CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_HOLDOFF);
CSR_WRITE_1(sc, VGE_INTCTL1, VGE_INTCTL_SC_RELOAD);
/* Select the RX suppression threshold page. */
CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL);
CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_RXSUPPTHR);
CSR_WRITE_1(sc, VGE_RXSUPPTHR, 64); /* interrupt after 64 packets */
/* Restore the page select bits. */
CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL);
CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_MAR);
#endif
#ifdef DEVICE_POLLING
/*
* Disable interrupts if we are polling.
@ -2494,6 +2434,25 @@ vge_sysctl_node(struct vge_softc *sc)
stats = &sc->vge_stats;
ctx = device_get_sysctl_ctx(sc->vge_dev);
child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->vge_dev));
SYSCTL_ADD_INT(ctx, child, OID_AUTO, "int_holdoff",
CTLFLAG_RW, &sc->vge_int_holdoff, 0, "interrupt holdoff");
SYSCTL_ADD_INT(ctx, child, OID_AUTO, "rx_coal_pkt",
CTLFLAG_RW, &sc->vge_rx_coal_pkt, 0, "rx coalescing packet");
SYSCTL_ADD_INT(ctx, child, OID_AUTO, "tx_coal_pkt",
CTLFLAG_RW, &sc->vge_tx_coal_pkt, 0, "tx coalescing packet");
/* Pull in device tunables. */
sc->vge_int_holdoff = VGE_INT_HOLDOFF_DEFAULT;
resource_int_value(device_get_name(sc->vge_dev),
device_get_unit(sc->vge_dev), "int_holdoff", &sc->vge_int_holdoff);
sc->vge_rx_coal_pkt = VGE_RX_COAL_PKT_DEFAULT;
resource_int_value(device_get_name(sc->vge_dev),
device_get_unit(sc->vge_dev), "rx_coal_pkt", &sc->vge_rx_coal_pkt);
sc->vge_tx_coal_pkt = VGE_TX_COAL_PKT_DEFAULT;
resource_int_value(device_get_name(sc->vge_dev),
device_get_unit(sc->vge_dev), "tx_coal_pkt", &sc->vge_tx_coal_pkt);
tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD,
NULL, "VGE statistics");
parent = SYSCTL_CHILDREN(tree);
@ -2699,3 +2658,51 @@ vge_stats_update(struct vge_softc *sc)
mib[VGE_MIB_RX_SYMERRS] +
mib[VGE_MIB_RX_LENERRS];
}
static void
vge_intr_holdoff(struct vge_softc *sc)
{
uint8_t intctl;
VGE_LOCK_ASSERT(sc);
/*
* Set Tx interrupt supression threshold.
* It's possible to use single-shot timer in VGE_CRS1 register
* in Tx path such that driver can remove most of Tx completion
* interrupts. However this requires additional access to
* VGE_CRS1 register to reload the timer in addintion to
* activating Tx kick command. Another downside is we don't know
* what single-shot timer value should be used in advance so
* reclaiming transmitted mbufs could be delayed a lot which in
* turn slows down Tx operation.
*/
CSR_WRITE_1(sc, VGE_CAMCTL, VGE_PAGESEL_TXSUPPTHR);
CSR_WRITE_1(sc, VGE_TXSUPPTHR, sc->vge_tx_coal_pkt);
/* Set Rx interrupt suppresion threshold. */
CSR_WRITE_1(sc, VGE_CAMCTL, VGE_PAGESEL_RXSUPPTHR);
CSR_WRITE_1(sc, VGE_RXSUPPTHR, sc->vge_rx_coal_pkt);
intctl = CSR_READ_1(sc, VGE_INTCTL1);
intctl &= ~VGE_INTCTL_SC_RELOAD;
intctl |= VGE_INTCTL_HC_RELOAD;
if (sc->vge_tx_coal_pkt <= 0)
intctl |= VGE_INTCTL_TXINTSUP_DISABLE;
else
intctl &= ~VGE_INTCTL_TXINTSUP_DISABLE;
if (sc->vge_rx_coal_pkt <= 0)
intctl |= VGE_INTCTL_RXINTSUP_DISABLE;
else
intctl &= ~VGE_INTCTL_RXINTSUP_DISABLE;
CSR_WRITE_1(sc, VGE_INTCTL1, intctl);
CSR_WRITE_1(sc, VGE_CRC3, VGE_CR3_INT_HOLDOFF);
if (sc->vge_int_holdoff > 0) {
/* Set interrupt holdoff timer. */
CSR_WRITE_1(sc, VGE_CAMCTL, VGE_PAGESEL_INTHLDOFF);
CSR_WRITE_1(sc, VGE_INTHOLDOFF,
VGE_INT_HOLDOFF_USEC(sc->vge_int_holdoff));
/* Enable holdoff timer. */
CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_HOLDOFF);
}
}

View File

@ -300,8 +300,7 @@
#define VGE_INTRS (VGE_ISR_TXOK0|VGE_ISR_RXOK|VGE_ISR_STOPPED| \
VGE_ISR_RXOFLOW|VGE_ISR_PHYINT| \
VGE_ISR_LINKSTS|VGE_ISR_RXNODESC| \
VGE_ISR_RXDMA_STALL|VGE_ISR_TXDMA_STALL| \
VGE_ISR_TIMER0)
VGE_ISR_RXDMA_STALL|VGE_ISR_TXDMA_STALL)
/* Interrupt mask register */

View File

@ -65,6 +65,20 @@
#define VGE_RXBYTES(x) (((x) & VGE_RDSTS_BUFSIZ) >> 16)
#define VGE_MIN_FRAMELEN 60
#define VGE_INT_HOLDOFF_TICK 20
#define VGE_INT_HOLDOFF_USEC(x) ((x) / VGE_INT_HOLDOFF_TICK)
#define VGE_INT_HOLDOFF_MIN 0
#define VGE_INT_HOLDOFF_MAX (255 * VGE_INT_HOLDOFF_TICK)
#define VGE_INT_HOLDOFF_DEFAULT 150
#define VGE_RX_COAL_PKT_MIN 1
#define VGE_RX_COAL_PKT_MAX VGE_RX_DESC_CNT
#define VGE_RX_COAL_PKT_DEFAULT 64
#define VGE_TX_COAL_PKT_MIN 1
#define VGE_TX_COAL_PKT_MAX VGE_TX_DESC_CNT
#define VGE_TX_COAL_PKT_DEFAULT 128
struct vge_type {
uint16_t vge_vid;
uint16_t vge_did;
@ -177,6 +191,9 @@ struct vge_softc {
#define VGE_FLAG_LINK 0x8000
int vge_expcap;
int vge_camidx;
int vge_int_holdoff;
int vge_rx_coal_pkt;
int vge_tx_coal_pkt;
struct mtx vge_mtx;
struct callout vge_watchdog;
int vge_timer;