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:
parent
0768a69095
commit
3672ee2e9a
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user