From 39150ad6fb258c15eebaf30f35c4161e5dc0bb86 Mon Sep 17 00:00:00 2001 From: sephe Date: Mon, 12 Dec 2016 05:04:55 +0000 Subject: [PATCH] hyperv/vmbus: Add channel polling support. MFC after: 1 week Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D8738 --- sys/dev/hyperv/include/vmbus.h | 7 + sys/dev/hyperv/vmbus/vmbus_chan.c | 208 +++++++++++++++++++++++++++ sys/dev/hyperv/vmbus/vmbus_chanvar.h | 5 + 3 files changed, 220 insertions(+) diff --git a/sys/dev/hyperv/include/vmbus.h b/sys/dev/hyperv/include/vmbus.h index a6dfe5b6a59e..4b0060e9241d 100644 --- a/sys/dev/hyperv/include/vmbus.h +++ b/sys/dev/hyperv/include/vmbus.h @@ -49,6 +49,9 @@ #define VMBUS_VERSION_MAJOR(ver) (((uint32_t)(ver)) >> 16) #define VMBUS_VERSION_MINOR(ver) (((uint32_t)(ver)) & 0xffff) +#define VMBUS_CHAN_POLLHZ_MIN 100 /* 10ms interval */ +#define VMBUS_CHAN_POLLHZ_MAX 1000000 /* 1us interval */ + /* * GPA stuffs. */ @@ -220,4 +223,8 @@ bool vmbus_chan_tx_empty(const struct vmbus_channel *chan); struct taskqueue * vmbus_chan_mgmt_tq(const struct vmbus_channel *chan); +void vmbus_chan_poll_enable(struct vmbus_channel *chan, + u_int pollhz); +void vmbus_chan_poll_disable(struct vmbus_channel *chan); + #endif /* !_VMBUS_H_ */ diff --git a/sys/dev/hyperv/vmbus/vmbus_chan.c b/sys/dev/hyperv/vmbus/vmbus_chan.c index 0f26fc059cf9..fe3746416ad2 100644 --- a/sys/dev/hyperv/vmbus/vmbus_chan.c +++ b/sys/dev/hyperv/vmbus/vmbus_chan.c @@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include @@ -50,6 +51,11 @@ __FBSDID("$FreeBSD$"); #include #include +struct vmbus_chan_pollarg { + struct vmbus_channel *poll_chan; + u_int poll_hz; +}; + static void vmbus_chan_update_evtflagcnt( struct vmbus_softc *, const struct vmbus_channel *); @@ -68,6 +74,10 @@ static void vmbus_chan_clear_chmap(struct vmbus_channel *); static void vmbus_chan_detach(struct vmbus_channel *); static bool vmbus_chan_wait_revoke( const struct vmbus_channel *, bool); +static void vmbus_chan_poll_timeout(void *); +static bool vmbus_chan_poll_cancel_intq( + struct vmbus_channel *); +static void vmbus_chan_poll_cancel(struct vmbus_channel *); static void vmbus_chan_ins_prilist(struct vmbus_softc *, struct vmbus_channel *); @@ -84,7 +94,11 @@ static void vmbus_chan_rem_sublist(struct vmbus_channel *, static void vmbus_chan_task(void *, int); static void vmbus_chan_task_nobatch(void *, int); +static void vmbus_chan_poll_task(void *, int); static void vmbus_chan_clrchmap_task(void *, int); +static void vmbus_chan_pollcfg_task(void *, int); +static void vmbus_chan_polldis_task(void *, int); +static void vmbus_chan_poll_cancel_task(void *, int); static void vmbus_prichan_attach_task(void *, int); static void vmbus_subchan_attach_task(void *, int); static void vmbus_prichan_detach_task(void *, int); @@ -782,6 +796,22 @@ vmbus_chan_set_chmap(struct vmbus_channel *chan) chan->ch_vmbus->vmbus_chmap[chan->ch_id] = chan; } +static void +vmbus_chan_poll_cancel_task(void *xchan, int pending __unused) +{ + + vmbus_chan_poll_cancel_intq(xchan); +} + +static void +vmbus_chan_poll_cancel(struct vmbus_channel *chan) +{ + struct task poll_cancel; + + TASK_INIT(&poll_cancel, 0, vmbus_chan_poll_cancel_task, chan); + vmbus_chan_run_task(chan, &poll_cancel); +} + static int vmbus_chan_close_internal(struct vmbus_channel *chan) { @@ -817,6 +847,11 @@ vmbus_chan_close_internal(struct vmbus_channel *chan) */ sysctl_ctx_free(&chan->ch_sysctl_ctx); + /* + * Cancel polling, if it is enabled. + */ + vmbus_chan_poll_cancel(chan); + /* * NOTE: * Order is critical. This channel _must_ be uninstalled first, @@ -1185,6 +1220,9 @@ vmbus_chan_task(void *xchan, int pending __unused) vmbus_chan_callback_t cb = chan->ch_cb; void *cbarg = chan->ch_cbarg; + KASSERT(chan->ch_poll_intvl == 0, + ("chan%u: interrupted in polling mode", chan->ch_id)); + /* * Optimize host to guest signaling by ensuring: * 1. While reading the channel, we disable interrupts from @@ -1216,9 +1254,145 @@ vmbus_chan_task_nobatch(void *xchan, int pending __unused) { struct vmbus_channel *chan = xchan; + KASSERT(chan->ch_poll_intvl == 0, + ("chan%u: interrupted in polling mode", chan->ch_id)); chan->ch_cb(chan, chan->ch_cbarg); } +static void +vmbus_chan_poll_timeout(void *xchan) +{ + struct vmbus_channel *chan = xchan; + + KASSERT(chan->ch_poll_intvl != 0, + ("chan%u: polling timeout in interrupt mode", chan->ch_id)); + taskqueue_enqueue(chan->ch_tq, &chan->ch_poll_task); +} + +static void +vmbus_chan_poll_task(void *xchan, int pending __unused) +{ + struct vmbus_channel *chan = xchan; + + KASSERT(chan->ch_poll_intvl != 0, + ("chan%u: polling in interrupt mode", chan->ch_id)); + callout_reset_sbt_curcpu(&chan->ch_poll_timeo, chan->ch_poll_intvl, 0, + vmbus_chan_poll_timeout, chan, chan->ch_poll_flags); + chan->ch_cb(chan, chan->ch_cbarg); +} + +static void +vmbus_chan_pollcfg_task(void *xarg, int pending __unused) +{ + const struct vmbus_chan_pollarg *arg = xarg; + struct vmbus_channel *chan = arg->poll_chan; + sbintime_t intvl; + int poll_flags; + + /* + * Save polling interval. + */ + intvl = SBT_1S / arg->poll_hz; + if (intvl == 0) + intvl = 1; + if (intvl == chan->ch_poll_intvl) { + /* Nothing changes; done */ + return; + } + chan->ch_poll_intvl = intvl; + + /* Adjust callout flags. */ + poll_flags = C_DIRECT_EXEC; + if (arg->poll_hz <= hz) + poll_flags |= C_HARDCLOCK; + chan->ch_poll_flags = poll_flags; + + /* + * Disable interrupt from the RX bufring (TX bufring does not + * generate interrupt to VM), and disconnect this channel from + * the channel map to make sure that ISR can not enqueue this + * channel task anymore. + */ + critical_enter(); + vmbus_rxbr_intr_mask(&chan->ch_rxbr); + chan->ch_vmbus->vmbus_chmap[chan->ch_id] = NULL; + critical_exit(); + + /* + * NOTE: + * At this point, this channel task will not be enqueued by + * the ISR anymore, time to cancel the pending one. + */ + taskqueue_cancel(chan->ch_tq, &chan->ch_task, NULL); + + /* Kick start! */ + taskqueue_enqueue(chan->ch_tq, &chan->ch_poll_task); +} + +static bool +vmbus_chan_poll_cancel_intq(struct vmbus_channel *chan) +{ + + if (chan->ch_poll_intvl == 0) { + /* Not enabled. */ + return (false); + } + + /* + * Stop polling callout, so that channel polling task + * will not be enqueued anymore. + */ + callout_drain(&chan->ch_poll_timeo); + + /* + * Disable polling by resetting polling interval. + * + * NOTE: + * The polling interval resetting MUST be conducted + * after the callout is drained; mainly to keep the + * proper assertion in place. + */ + chan->ch_poll_intvl = 0; + + /* + * NOTE: + * At this point, this channel polling task will not be + * enqueued by the callout anymore, time to cancel the + * pending one. + */ + taskqueue_cancel(chan->ch_tq, &chan->ch_poll_task, NULL); + + /* Polling was enabled. */ + return (true); +} + +static void +vmbus_chan_polldis_task(void *xchan, int pending __unused) +{ + struct vmbus_channel *chan = xchan; + + if (!vmbus_chan_poll_cancel_intq(chan)) { + /* Already disabled; done. */ + return; + } + + /* + * Plug this channel back to the channel map and unmask + * the RX bufring interrupt. + */ + critical_enter(); + chan->ch_vmbus->vmbus_chmap[chan->ch_id] = chan; + __compiler_membar(); + vmbus_rxbr_intr_unmask(&chan->ch_rxbr); + critical_exit(); + + /* + * Kick start the interrupt task, just in case unmasking + * interrupt races ISR. + */ + taskqueue_enqueue(chan->ch_tq, &chan->ch_task); +} + static __inline void vmbus_event_flags_proc(struct vmbus_softc *sc, volatile u_long *event_flags, int flag_cnt) @@ -1333,6 +1507,9 @@ vmbus_chan_alloc(struct vmbus_softc *sc) vmbus_rxbr_init(&chan->ch_rxbr); vmbus_txbr_init(&chan->ch_txbr); + TASK_INIT(&chan->ch_poll_task, 0, vmbus_chan_poll_task, chan); + callout_init(&chan->ch_poll_timeo, 1); + return chan; } @@ -1351,6 +1528,8 @@ vmbus_chan_free(struct vmbus_channel *chan) ("still has orphan xact installed")); KASSERT(chan->ch_refs == 0, ("chan%u: invalid refcnt %d", chan->ch_id, chan->ch_refs)); + KASSERT(chan->ch_poll_intvl == 0, ("chan%u: polling is activated", + chan->ch_id)); hyperv_dmamem_free(&chan->ch_monprm_dma, chan->ch_monprm); mtx_destroy(&chan->ch_subchan_lock); @@ -1998,3 +2177,32 @@ vmbus_chan_xact_wait(const struct vmbus_channel *chan, } return (ret); } + +void +vmbus_chan_poll_enable(struct vmbus_channel *chan, u_int pollhz) +{ + struct vmbus_chan_pollarg arg; + struct task poll_cfg; + + KASSERT(chan->ch_flags & VMBUS_CHAN_FLAG_BATCHREAD, + ("enable polling on non-batch chan%u", chan->ch_id)); + KASSERT(pollhz >= VMBUS_CHAN_POLLHZ_MIN && + pollhz <= VMBUS_CHAN_POLLHZ_MAX, ("invalid pollhz %u", pollhz)); + + arg.poll_chan = chan; + arg.poll_hz = pollhz; + TASK_INIT(&poll_cfg, 0, vmbus_chan_pollcfg_task, &arg); + vmbus_chan_run_task(chan, &poll_cfg); +} + +void +vmbus_chan_poll_disable(struct vmbus_channel *chan) +{ + struct task poll_dis; + + KASSERT(chan->ch_flags & VMBUS_CHAN_FLAG_BATCHREAD, + ("disable polling on non-batch chan%u", chan->ch_id)); + + TASK_INIT(&poll_dis, 0, vmbus_chan_polldis_task, chan); + vmbus_chan_run_task(chan, &poll_dis); +} diff --git a/sys/dev/hyperv/vmbus/vmbus_chanvar.h b/sys/dev/hyperv/vmbus/vmbus_chanvar.h index de5c1329cd3f..ec78c47db444 100644 --- a/sys/dev/hyperv/vmbus/vmbus_chanvar.h +++ b/sys/dev/hyperv/vmbus/vmbus_chanvar.h @@ -30,6 +30,7 @@ #define _VMBUS_CHANVAR_H_ #include +#include #include #include #include @@ -49,6 +50,7 @@ struct vmbus_channel { * target CPU. */ uint32_t ch_flags; /* VMBUS_CHAN_FLAG_ */ + int ch_poll_flags; /* callout flags */ /* * RX bufring; immediately following ch_txbr. @@ -57,6 +59,9 @@ struct vmbus_channel { struct taskqueue *ch_tq; struct task ch_task; + struct task ch_poll_task; + sbintime_t ch_poll_intvl; + struct callout ch_poll_timeo; vmbus_chan_callback_t ch_cb; void *ch_cbarg;