[PATCH v2 07/14] hv: vmbus: distinguish host rescind from local channel unload
flat view
WARM1d
From: Emerson Busson <hidden>
Date: 2026-10-07 19:08:44
Also in:
linux-hyperv, lkml
Subsystem:
hyper-v/azure core and drivers, the rest · Maintainers:
"K. Y. Srinivasan", Haiyang Zhang, Wei Liu, Dexuan Cui, Long Li, Linus Torvalds
A channel can go away because the host revoked the offer, or because the guest is tearing the channel down itself. Both paths arrive at vmbus_onoffer_rescind() and set channel->rescind, so a later buffer consumer cannot tell whether the host has already taken the pages back or whether the guest still owns them and is about to free them. Carry the origin through the message layer. vmbus_onmessage() takes a host_generated flag: the DPC work item sets it for host messages and vmbus_force_channel_rescinded() clears it for the local unload path. A small table adapter keeps the dispatch signature unchanged, while the rescind handler itself records the origin in channel->rescind_from_host next to the existing rescind flag. Both flags are cleared when a channel is set up. The disconnected message path frees its work context instead of returning without a kfree(); it now owns that allocation from the moment container_of() runs. Nothing reads rescind_from_host yet. The buffer-ownership rework lands in the next patch and is what consumes the flag. Signed-off-by: Emerson Busson <redacted> --- drivers/hv/channel_mgmt.c | 31 +++++++++++++++++++++++++------ drivers/hv/vmbus_drv.c | 13 ++++++++----- include/linux/hyperv.h | 4 +++- 3 files changed, 36 insertions(+), 12 deletions(-)
diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c
index a044fd3b3c4e..93fc105cd179 100644
--- a/drivers/hv/channel_mgmt.c
+++ b/drivers/hv/channel_mgmt.c@@ -170,14 +170,17 @@ static const struct { * The rescinded channel may be blocked waiting for a response from the host; * take care of that. */ -static void vmbus_rescind_cleanup(struct vmbus_channel *channel) +static void vmbus_rescind_cleanup(struct vmbus_channel *channel, + bool host_generated) { struct vmbus_channel_msginfo *msginfo; unsigned long flags; spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); - channel->rescind = true; + if (host_generated) + WRITE_ONCE(channel->rescind_from_host, true); + WRITE_ONCE(channel->rescind, true); list_for_each_entry(msginfo, &vmbus_connection.chn_msg_list, msglistentry) {
@@ -955,6 +958,9 @@ EXPORT_SYMBOL_GPL(vmbus_initiate_unload); static void vmbus_setup_channel_state(struct vmbus_channel *channel, struct vmbus_channel_offer_channel *offer) { + WRITE_ONCE(channel->rescind, false); + WRITE_ONCE(channel->rescind_from_host, false); + /* * Setup state for signalling the host. */
@@ -1159,7 +1165,8 @@ static void check_ready_for_suspend_event(void) * * We queue a work item to process this offer synchronously */ -static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr) +static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr, + bool host_generated) { struct vmbus_channel_rescind_offer *rescind; struct vmbus_channel *channel;
@@ -1238,7 +1245,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr) /* * Now wait for offer handling to complete. */ - vmbus_rescind_cleanup(channel); + vmbus_rescind_cleanup(channel, host_generated); while (READ_ONCE(channel->probe_done) == false) { /* * We wait here until any channel offer is currently
@@ -1555,12 +1562,18 @@ static void vmbus_onversion_response( } /* Channel message dispatch table */ +static void +vmbus_onoffer_rescind_from_table(struct vmbus_channel_message_header *hdr) +{ + vmbus_onoffer_rescind(hdr, true); +} + const struct vmbus_channel_message_table_entry channel_message_table[CHANNELMSG_COUNT] = { { CHANNELMSG_INVALID, 0, NULL, 0}, { CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer, sizeof(struct vmbus_channel_offer_channel)}, - { CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind, + { CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind_from_table, sizeof(struct vmbus_channel_rescind_offer) }, { CHANNELMSG_REQUESTOFFERS, 0, NULL, 0}, { CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered, 0},
@@ -1596,7 +1609,8 @@ channel_message_table[CHANNELMSG_COUNT] = { * * This is invoked in the vmbus worker thread context. */ -void vmbus_onmessage(struct vmbus_channel_message_header *hdr) +void vmbus_onmessage(struct vmbus_channel_message_header *hdr, + bool host_generated) { trace_vmbus_on_message(hdr);
@@ -1604,6 +1618,11 @@ void vmbus_onmessage(struct vmbus_channel_message_header *hdr) * vmbus_on_msg_dpc() makes sure the hdr->msgtype here can not go * out of bound and the message_handler pointer can not be NULL. */ + if (hdr->msgtype == CHANNELMSG_RESCIND_CHANNELOFFER) { + vmbus_onoffer_rescind(hdr, host_generated); + return; + } + channel_message_table[hdr->msgtype].message_handler(hdr); }
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index 5ebdbe24b5a1..723252f1b551 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c@@ -1022,6 +1022,7 @@ static const struct bus_type hv_bus = { struct onmessage_work_context { struct work_struct work; + bool host_generated; struct { struct hv_message_header header; u8 payload[];
@@ -1032,14 +1033,14 @@ static void vmbus_onmessage_work(struct work_struct *work) { struct onmessage_work_context *ctx; + ctx = container_of(work, struct onmessage_work_context, work); /* Do not process messages if we're in DISCONNECTED state */ - if (vmbus_connection.conn_state == DISCONNECTED) + if (vmbus_connection.conn_state == DISCONNECTED) { + kfree(ctx); return; - - ctx = container_of(work, struct onmessage_work_context, - work); + } vmbus_onmessage((struct vmbus_channel_message_header *) - &ctx->msg.payload); + &ctx->msg.payload, ctx->host_generated); kfree(ctx); }
@@ -1109,6 +1110,7 @@ static void __vmbus_on_msg_dpc(void *message_page_addr) return; INIT_WORK(&ctx->work, vmbus_onmessage_work); + ctx->host_generated = true; ctx->msg.header = msg_copy.header; memcpy(&ctx->msg.payload, msg_copy.u.payload, payload_size);
@@ -1222,6 +1224,7 @@ static void vmbus_force_channel_rescinded(struct vmbus_channel *channel) rescind->child_relid = channel->offermsg.child_relid; INIT_WORK(&ctx->work, vmbus_onmessage_work); + ctx->host_generated = false; queue_work(vmbus_connection.work_queue, &ctx->work); }
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 2878aed14c45..096054fa07a3 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h@@ -809,6 +809,7 @@ struct vmbus_channel { u8 monitor_bit; bool rescind; /* got rescind msg */ + bool rescind_from_host; /* host revocation, not local channel removal */ bool rescind_ref; /* got rescind msg, got channel reference */ struct completion rescind_event;
@@ -1117,7 +1118,8 @@ static inline void set_channel_pending_send_size(struct vmbus_channel *c, c->outbound.ring_buffer->pending_send_sz = size; } -void vmbus_onmessage(struct vmbus_channel_message_header *hdr); +void vmbus_onmessage(struct vmbus_channel_message_header *hdr, + bool host_generated); int vmbus_request_offers(void);
--
2.43.0