From ff2e0f37e71f665db3f1f091d7ead5ccac0ec22a Mon Sep 17 00:00:00 2001 From: sephe Date: Thu, 29 Dec 2016 06:45:36 +0000 Subject: [PATCH] MFC 309030,309039,309080,309081,309083 309030 hyperv/vmbus: Set a mark on the revoked channel. This will be used to fix device detach DEVMETHOD for revoked primary channel. Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D8522 309039 hyperv/vmbus: Merge free/active locks. These functions are only used by management stuffs, so there are no needs to introduce extra complexity. Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D8524 309080 hyperv/vmbus: Implement orphan support for transaction API It will be used to fix the primary channel revocation support. Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D8525 309081 hyperv/vmbus: Fix the primary channel revoking on vmbus side. Drivers can now use vmbus_chan_{is_revoked,set_orphan,unset_orphan}() and vmbus_xact_ctx_orphan() to fix their attach/detach DEVMETHODs for revoked primary channels. Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D8545 309083 hyperv/vmbus: Fix the multi-channel revoking on vmbus side. - Reference count the sub-channel when channel offer message is processed, so that immediate rescind message on the same channel will not race sub-channel open on driver side. - Drop the above reference when sub-channel is closed, this closely mimics the hypervisor's reaction when primary channel is closed on the VM side. No drivers use sub-channel after primary channel is closed. Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D8546 git-svn-id: svn://svn.freebsd.org/base/stable/10@310740 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f --- sys/dev/hyperv/include/vmbus.h | 5 + sys/dev/hyperv/include/vmbus_xact.h | 2 + sys/dev/hyperv/vmbus/vmbus_chan.c | 106 ++++++++++++++++-- sys/dev/hyperv/vmbus/vmbus_chanvar.h | 10 +- sys/dev/hyperv/vmbus/vmbus_xact.c | 154 +++++++++++++++++++-------- 5 files changed, 227 insertions(+), 50 deletions(-) diff --git a/sys/dev/hyperv/include/vmbus.h b/sys/dev/hyperv/include/vmbus.h index b42902134..d023ff118 100644 --- a/sys/dev/hyperv/include/vmbus.h +++ b/sys/dev/hyperv/include/vmbus.h @@ -117,6 +117,7 @@ struct vmbus_chan_br { }; struct vmbus_channel; +struct vmbus_xact_ctx; struct hyperv_guid; struct task; struct taskqueue; @@ -139,6 +140,9 @@ void vmbus_chan_close(struct vmbus_channel *chan); void vmbus_chan_intr_drain(struct vmbus_channel *chan); void vmbus_chan_run_task(struct vmbus_channel *chan, struct task *task); +void vmbus_chan_set_orphan(struct vmbus_channel *chan, + struct vmbus_xact_ctx *); +void vmbus_chan_unset_orphan(struct vmbus_channel *chan); int vmbus_chan_gpadl_connect(struct vmbus_channel *chan, bus_addr_t paddr, int size, uint32_t *gpadl); @@ -173,6 +177,7 @@ int vmbus_chan_send_prplist(struct vmbus_channel *chan, uint32_t vmbus_chan_id(const struct vmbus_channel *chan); uint32_t vmbus_chan_subidx(const struct vmbus_channel *chan); bool vmbus_chan_is_primary(const struct vmbus_channel *chan); +bool vmbus_chan_is_revoked(const struct vmbus_channel *chan); const struct hyperv_guid * vmbus_chan_guid_inst(const struct vmbus_channel *chan); int vmbus_chan_prplist_nelem(int br_size, int prpcnt_max, diff --git a/sys/dev/hyperv/include/vmbus_xact.h b/sys/dev/hyperv/include/vmbus_xact.h index 3a549d169..f4d8e4d9f 100644 --- a/sys/dev/hyperv/include/vmbus_xact.h +++ b/sys/dev/hyperv/include/vmbus_xact.h @@ -40,6 +40,8 @@ struct vmbus_xact_ctx *vmbus_xact_ctx_create(bus_dma_tag_t dtag, size_t req_size, size_t resp_size, size_t priv_size); void vmbus_xact_ctx_destroy(struct vmbus_xact_ctx *ctx); +bool vmbus_xact_ctx_orphan(struct vmbus_xact_ctx *ctx); + struct vmbus_xact *vmbus_xact_get(struct vmbus_xact_ctx *ctx, size_t req_len); void vmbus_xact_put(struct vmbus_xact *xact); diff --git a/sys/dev/hyperv/vmbus/vmbus_chan.c b/sys/dev/hyperv/vmbus/vmbus_chan.c index 1cd5f2b0a..e3728db50 100644 --- a/sys/dev/hyperv/vmbus/vmbus_chan.c +++ b/sys/dev/hyperv/vmbus/vmbus_chan.c @@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include @@ -64,6 +65,7 @@ static void vmbus_chan_cpu_default(struct vmbus_channel *); static int vmbus_chan_release(struct vmbus_channel *); static void vmbus_chan_set_chmap(struct vmbus_channel *); static void vmbus_chan_clear_chmap(struct vmbus_channel *); +static void vmbus_chan_detach(struct vmbus_channel *); static void vmbus_chan_ins_prilist(struct vmbus_softc *, struct vmbus_channel *); @@ -626,6 +628,32 @@ vmbus_chan_gpadl_disconnect(struct vmbus_channel *chan, uint32_t gpadl) return 0; } +static void +vmbus_chan_detach(struct vmbus_channel *chan) +{ + int refs; + + KASSERT(chan->ch_refs > 0, ("chan%u: invalid refcnt %d", + chan->ch_id, chan->ch_refs)); + refs = atomic_fetchadd_int(&chan->ch_refs, -1); +#ifdef INVARIANTS + if (VMBUS_CHAN_ISPRIMARY(chan)) { + KASSERT(refs == 1, ("chan%u: invalid refcnt %d for prichan", + chan->ch_id, refs + 1)); + } +#endif + if (refs == 1) { + /* + * Detach the target channel. + */ + if (bootverbose) { + vmbus_chan_printf(chan, "chan%u detached\n", + chan->ch_id); + } + taskqueue_enqueue(chan->ch_mgmt_tq, &chan->ch_detach_task); + } +} + static void vmbus_chan_clrchmap_task(void *xchan, int pending __unused) { @@ -751,8 +779,15 @@ vmbus_chan_close(struct vmbus_channel *chan) int i; subchan = vmbus_subchan_get(chan, subchan_cnt); - for (i = 0; i < subchan_cnt; ++i) + for (i = 0; i < subchan_cnt; ++i) { vmbus_chan_close_internal(subchan[i]); + /* + * This sub-channel is referenced, when it is + * linked to the primary channel; drop that + * reference now. + */ + vmbus_chan_detach(subchan[i]); + } vmbus_subchan_rel(subchan, subchan_cnt); } @@ -1113,8 +1148,10 @@ vmbus_chan_alloc(struct vmbus_softc *sc) return NULL; } + chan->ch_refs = 1; chan->ch_vmbus = sc; mtx_init(&chan->ch_subchan_lock, "vmbus subchan", NULL, MTX_DEF); + sx_init(&chan->ch_orphan_lock, "vmbus chorphan"); TAILQ_INIT(&chan->ch_subchans); vmbus_rxbr_init(&chan->ch_rxbr); vmbus_txbr_init(&chan->ch_txbr); @@ -1133,8 +1170,14 @@ vmbus_chan_free(struct vmbus_channel *chan) VMBUS_CHAN_ST_ONPRIL | VMBUS_CHAN_ST_ONSUBL | VMBUS_CHAN_ST_ONLIST)) == 0, ("free busy channel")); + KASSERT(chan->ch_orphan_xact == NULL, + ("still has orphan xact installed")); + KASSERT(chan->ch_refs == 0, ("chan%u: invalid refcnt %d", + chan->ch_id, chan->ch_refs)); + hyperv_dmamem_free(&chan->ch_monprm_dma, chan->ch_monprm); mtx_destroy(&chan->ch_subchan_lock); + sx_destroy(&chan->ch_orphan_lock); vmbus_rxbr_deinit(&chan->ch_rxbr); vmbus_txbr_deinit(&chan->ch_txbr); free(chan, M_DEVBUF); @@ -1207,6 +1250,14 @@ vmbus_chan_add(struct vmbus_channel *newchan) ("new channel is not sub-channel")); KASSERT(prichan != NULL, ("no primary channel")); + /* + * Reference count this sub-channel; it will be dereferenced + * when this sub-channel is closed. + */ + KASSERT(newchan->ch_refs == 1, ("chan%u: invalid refcnt %d", + newchan->ch_id, newchan->ch_refs)); + atomic_add_int(&newchan->ch_refs, 1); + newchan->ch_prichan = prichan; newchan->ch_dev = prichan->ch_dev; @@ -1220,7 +1271,7 @@ vmbus_chan_add(struct vmbus_channel *newchan) wakeup(prichan); done: /* - * Hook this channel up for later rescind. + * Hook this channel up for later revocation. */ mtx_lock(&sc->vmbus_chan_lock); vmbus_chan_ins_list(sc, newchan); @@ -1353,6 +1404,7 @@ vmbus_chan_msgproc_choffer(struct vmbus_softc *sc, if (error) { device_printf(sc->vmbus_dev, "add chan%u failed: %d\n", chan->ch_id, error); + atomic_subtract_int(&chan->ch_refs, 1); vmbus_chan_free(chan); return; } @@ -1368,7 +1420,7 @@ vmbus_chan_msgproc_chrescind(struct vmbus_softc *sc, note = (const struct vmbus_chanmsg_chrescind *)msg->msg_data; if (note->chm_chanid > VMBUS_CHAN_MAX) { - device_printf(sc->vmbus_dev, "invalid rescinded chan%u\n", + device_printf(sc->vmbus_dev, "invalid revoked chan%u\n", note->chm_chanid); return; } @@ -1403,11 +1455,24 @@ vmbus_chan_msgproc_chrescind(struct vmbus_softc *sc, mtx_unlock(&sc->vmbus_prichan_lock); } - if (bootverbose) - vmbus_chan_printf(chan, "chan%u rescinded\n", note->chm_chanid); + /* + * NOTE: + * The following processing order is critical: + * Set the REVOKED state flag before orphaning the installed xact. + */ + + if (atomic_testandset_int(&chan->ch_stflags, + VMBUS_CHAN_ST_REVOKED_SHIFT)) + panic("channel has already been revoked"); - /* Detach the target channel. */ - taskqueue_enqueue(chan->ch_mgmt_tq, &chan->ch_detach_task); + sx_xlock(&chan->ch_orphan_lock); + if (chan->ch_orphan_xact != NULL) + vmbus_xact_ctx_orphan(chan->ch_orphan_xact); + sx_xunlock(&chan->ch_orphan_lock); + + if (bootverbose) + vmbus_chan_printf(chan, "chan%u revoked\n", note->chm_chanid); + vmbus_chan_detach(chan); } static int @@ -1695,3 +1760,30 @@ vmbus_chan_mgmt_tq(const struct vmbus_channel *chan) return (chan->ch_mgmt_tq); } + +bool +vmbus_chan_is_revoked(const struct vmbus_channel *chan) +{ + + if (chan->ch_stflags & VMBUS_CHAN_ST_REVOKED) + return (true); + return (false); +} + +void +vmbus_chan_set_orphan(struct vmbus_channel *chan, struct vmbus_xact_ctx *xact) +{ + + sx_xlock(&chan->ch_orphan_lock); + chan->ch_orphan_xact = xact; + sx_xunlock(&chan->ch_orphan_lock); +} + +void +vmbus_chan_unset_orphan(struct vmbus_channel *chan) +{ + + sx_xlock(&chan->ch_orphan_lock); + chan->ch_orphan_xact = NULL; + sx_xunlock(&chan->ch_orphan_lock); +} diff --git a/sys/dev/hyperv/vmbus/vmbus_chanvar.h b/sys/dev/hyperv/vmbus/vmbus_chanvar.h index b415d0079..de5c1329c 100644 --- a/sys/dev/hyperv/vmbus/vmbus_chanvar.h +++ b/sys/dev/hyperv/vmbus/vmbus_chanvar.h @@ -33,8 +33,9 @@ #include #include #include -#include #include +#include +#include #include #include @@ -138,6 +139,11 @@ struct vmbus_channel { struct hyperv_guid ch_guid_type; struct hyperv_guid ch_guid_inst; + struct sx ch_orphan_lock; + struct vmbus_xact_ctx *ch_orphan_xact; + + int ch_refs; + struct sysctl_ctx_list ch_sysctl_ctx; } __aligned(CACHE_LINE_SIZE); @@ -159,10 +165,12 @@ struct vmbus_channel { #define VMBUS_CHAN_ST_ONPRIL_SHIFT 1 #define VMBUS_CHAN_ST_ONSUBL_SHIFT 2 #define VMBUS_CHAN_ST_ONLIST_SHIFT 3 +#define VMBUS_CHAN_ST_REVOKED_SHIFT 4 /* sticky */ #define VMBUS_CHAN_ST_OPENED (1 << VMBUS_CHAN_ST_OPENED_SHIFT) #define VMBUS_CHAN_ST_ONPRIL (1 << VMBUS_CHAN_ST_ONPRIL_SHIFT) #define VMBUS_CHAN_ST_ONSUBL (1 << VMBUS_CHAN_ST_ONSUBL_SHIFT) #define VMBUS_CHAN_ST_ONLIST (1 << VMBUS_CHAN_ST_ONLIST_SHIFT) +#define VMBUS_CHAN_ST_REVOKED (1 << VMBUS_CHAN_ST_REVOKED_SHIFT) struct vmbus_softc; struct vmbus_message; diff --git a/sys/dev/hyperv/vmbus/vmbus_xact.c b/sys/dev/hyperv/vmbus/vmbus_xact.c index 2f9c2ea71..c0219e837 100644 --- a/sys/dev/hyperv/vmbus/vmbus_xact.c +++ b/sys/dev/hyperv/vmbus/vmbus_xact.c @@ -50,16 +50,18 @@ struct vmbus_xact { }; struct vmbus_xact_ctx { - uint32_t xc_flags; size_t xc_req_size; size_t xc_resp_size; size_t xc_priv_size; + struct mtx xc_lock; + /* + * Protected by xc_lock. + */ + uint32_t xc_flags; /* VMBUS_XACT_CTXF_ */ struct vmbus_xact *xc_free; - struct mtx xc_free_lock; - struct vmbus_xact *xc_active; - struct mtx xc_active_lock; + struct vmbus_xact *xc_orphan; }; #define VMBUS_XACT_CTXF_DESTROY 0x0001 @@ -71,6 +73,9 @@ static struct vmbus_xact *vmbus_xact_get1(struct vmbus_xact_ctx *, uint32_t); const void *vmbus_xact_wait1(struct vmbus_xact *, size_t *, bool); +static void vmbus_xact_save_resp(struct vmbus_xact *, + const void *, size_t); +static void vmbus_xact_ctx_free(struct vmbus_xact_ctx *); static struct vmbus_xact * vmbus_xact_alloc(struct vmbus_xact_ctx *ctx, bus_dma_tag_t parent_dtag) @@ -110,10 +115,10 @@ vmbus_xact_get1(struct vmbus_xact_ctx *ctx, uint32_t dtor_flag) { struct vmbus_xact *xact; - mtx_lock(&ctx->xc_free_lock); + mtx_lock(&ctx->xc_lock); while ((ctx->xc_flags & dtor_flag) == 0 && ctx->xc_free == NULL) - mtx_sleep(&ctx->xc_free, &ctx->xc_free_lock, 0, "gxact", 0); + mtx_sleep(&ctx->xc_free, &ctx->xc_lock, 0, "gxact", 0); if (ctx->xc_flags & dtor_flag) { /* Being destroyed */ xact = NULL; @@ -124,7 +129,7 @@ vmbus_xact_get1(struct vmbus_xact_ctx *ctx, uint32_t dtor_flag) ctx->xc_free = NULL; } - mtx_unlock(&ctx->xc_free_lock); + mtx_unlock(&ctx->xc_lock); return (xact); } @@ -135,6 +140,9 @@ vmbus_xact_ctx_create(bus_dma_tag_t dtag, size_t req_size, size_t resp_size, { struct vmbus_xact_ctx *ctx; + KASSERT(req_size > 0, ("request size is 0")); + KASSERT(resp_size > 0, ("response size is 0")); + ctx = malloc(sizeof(*ctx), M_DEVBUF, M_WAITOK | M_ZERO); ctx->xc_req_size = req_size; ctx->xc_resp_size = resp_size; @@ -146,32 +154,51 @@ vmbus_xact_ctx_create(bus_dma_tag_t dtag, size_t req_size, size_t resp_size, return (NULL); } - mtx_init(&ctx->xc_free_lock, "vmbus xact free", NULL, MTX_DEF); - mtx_init(&ctx->xc_active_lock, "vmbus xact active", NULL, MTX_DEF); + mtx_init(&ctx->xc_lock, "vmbus xact", NULL, MTX_DEF); return (ctx); } -void -vmbus_xact_ctx_destroy(struct vmbus_xact_ctx *ctx) +bool +vmbus_xact_ctx_orphan(struct vmbus_xact_ctx *ctx) { - struct vmbus_xact *xact; - - mtx_lock(&ctx->xc_free_lock); + mtx_lock(&ctx->xc_lock); + if (ctx->xc_flags & VMBUS_XACT_CTXF_DESTROY) { + mtx_unlock(&ctx->xc_lock); + return (false); + } ctx->xc_flags |= VMBUS_XACT_CTXF_DESTROY; - mtx_unlock(&ctx->xc_free_lock); + mtx_unlock(&ctx->xc_lock); + wakeup(&ctx->xc_free); + wakeup(&ctx->xc_active); - xact = vmbus_xact_get1(ctx, 0); - if (xact == NULL) + ctx->xc_orphan = vmbus_xact_get1(ctx, 0); + if (ctx->xc_orphan == NULL) panic("can't get xact"); + return (true); +} + +static void +vmbus_xact_ctx_free(struct vmbus_xact_ctx *ctx) +{ + KASSERT(ctx->xc_flags & VMBUS_XACT_CTXF_DESTROY, + ("xact ctx was not orphaned")); + KASSERT(ctx->xc_orphan != NULL, ("no orphaned xact")); - vmbus_xact_free(xact); - mtx_destroy(&ctx->xc_free_lock); - mtx_destroy(&ctx->xc_active_lock); + vmbus_xact_free(ctx->xc_orphan); + mtx_destroy(&ctx->xc_lock); free(ctx, M_DEVBUF); } +void +vmbus_xact_ctx_destroy(struct vmbus_xact_ctx *ctx) +{ + + vmbus_xact_ctx_orphan(ctx); + vmbus_xact_ctx_free(ctx); +} + struct vmbus_xact * vmbus_xact_get(struct vmbus_xact_ctx *ctx, size_t req_len) { @@ -196,10 +223,10 @@ vmbus_xact_put(struct vmbus_xact *xact) KASSERT(ctx->xc_active == NULL, ("pending active xact")); xact->x_resp = NULL; - mtx_lock(&ctx->xc_free_lock); + mtx_lock(&ctx->xc_lock); KASSERT(ctx->xc_free == NULL, ("has free xact")); ctx->xc_free = xact; - mtx_unlock(&ctx->xc_free_lock); + mtx_unlock(&ctx->xc_lock); wakeup(&ctx->xc_free); } @@ -233,10 +260,10 @@ vmbus_xact_activate(struct vmbus_xact *xact) KASSERT(xact->x_resp == NULL, ("xact has pending response")); - mtx_lock(&ctx->xc_active_lock); + mtx_lock(&ctx->xc_lock); KASSERT(ctx->xc_active == NULL, ("pending active xact")); ctx->xc_active = xact; - mtx_unlock(&ctx->xc_active_lock); + mtx_unlock(&ctx->xc_lock); } void @@ -244,10 +271,10 @@ vmbus_xact_deactivate(struct vmbus_xact *xact) { struct vmbus_xact_ctx *ctx = xact->x_ctx; - mtx_lock(&ctx->xc_active_lock); + mtx_lock(&ctx->xc_lock); KASSERT(ctx->xc_active == xact, ("xact mismatch")); ctx->xc_active = NULL; - mtx_unlock(&ctx->xc_active_lock); + mtx_unlock(&ctx->xc_lock); } const void * @@ -257,25 +284,40 @@ vmbus_xact_wait1(struct vmbus_xact *xact, size_t *resp_len, struct vmbus_xact_ctx *ctx = xact->x_ctx; const void *resp; - mtx_lock(&ctx->xc_active_lock); + mtx_lock(&ctx->xc_lock); KASSERT(ctx->xc_active == xact, ("xact mismatch")); - while (xact->x_resp == NULL) { + while (xact->x_resp == NULL && + (ctx->xc_flags & VMBUS_XACT_CTXF_DESTROY) == 0) { if (can_sleep) { - mtx_sleep(&ctx->xc_active, &ctx->xc_active_lock, 0, + mtx_sleep(&ctx->xc_active, &ctx->xc_lock, 0, "wxact", 0); } else { - mtx_unlock(&ctx->xc_active_lock); + mtx_unlock(&ctx->xc_lock); DELAY(1000); - mtx_lock(&ctx->xc_active_lock); + mtx_lock(&ctx->xc_lock); } } + KASSERT(ctx->xc_active == xact, ("xact trashed")); + + if ((ctx->xc_flags & VMBUS_XACT_CTXF_DESTROY) && xact->x_resp == NULL) { + uint8_t b = 0; + + /* + * Orphaned and no response was received yet; fake up + * an one byte response. + */ + printf("vmbus: xact ctx was orphaned w/ pending xact\n"); + vmbus_xact_save_resp(ctx->xc_active, &b, sizeof(b)); + } + KASSERT(xact->x_resp != NULL, ("no response")); + ctx->xc_active = NULL; resp = xact->x_resp; *resp_len = xact->x_resp_len; - mtx_unlock(&ctx->xc_active_lock); + mtx_unlock(&ctx->xc_lock); return (resp); } @@ -300,7 +342,7 @@ vmbus_xact_save_resp(struct vmbus_xact *xact, const void *data, size_t dlen) struct vmbus_xact_ctx *ctx = xact->x_ctx; size_t cplen = dlen; - mtx_assert(&ctx->xc_active_lock, MA_OWNED); + mtx_assert(&ctx->xc_lock, MA_OWNED); if (cplen > ctx->xc_resp_size) { printf("vmbus: xact response truncated %zu -> %zu\n", @@ -318,19 +360,47 @@ void vmbus_xact_wakeup(struct vmbus_xact *xact, const void *data, size_t dlen) { struct vmbus_xact_ctx *ctx = xact->x_ctx; + int do_wakeup = 0; + + mtx_lock(&ctx->xc_lock); + /* + * NOTE: + * xc_active could be NULL, if the ctx has been orphaned. + */ + if (ctx->xc_active != NULL) { + vmbus_xact_save_resp(xact, data, dlen); + do_wakeup = 1; + } else { + KASSERT(ctx->xc_flags & VMBUS_XACT_CTXF_DESTROY, + ("no active xact pending")); + printf("vmbus: drop xact response\n"); + } + mtx_unlock(&ctx->xc_lock); - mtx_lock(&ctx->xc_active_lock); - vmbus_xact_save_resp(xact, data, dlen); - mtx_unlock(&ctx->xc_active_lock); - wakeup(&ctx->xc_active); + if (do_wakeup) + wakeup(&ctx->xc_active); } void vmbus_xact_ctx_wakeup(struct vmbus_xact_ctx *ctx, const void *data, size_t dlen) { - mtx_lock(&ctx->xc_active_lock); - KASSERT(ctx->xc_active != NULL, ("no pending xact")); - vmbus_xact_save_resp(ctx->xc_active, data, dlen); - mtx_unlock(&ctx->xc_active_lock); - wakeup(&ctx->xc_active); + int do_wakeup = 0; + + mtx_lock(&ctx->xc_lock); + /* + * NOTE: + * xc_active could be NULL, if the ctx has been orphaned. + */ + if (ctx->xc_active != NULL) { + vmbus_xact_save_resp(ctx->xc_active, data, dlen); + do_wakeup = 1; + } else { + KASSERT(ctx->xc_flags & VMBUS_XACT_CTXF_DESTROY, + ("no active xact pending")); + printf("vmbus: drop xact response\n"); + } + mtx_unlock(&ctx->xc_lock); + + if (do_wakeup) + wakeup(&ctx->xc_active); } -- 2.45.0