summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/hv/hv_common.c10
-rw-r--r--drivers/hv/hv_proc.c246
-rw-r--r--drivers/hv/mshv_root_hv_call.c10
-rw-r--r--drivers/hv/mshv_vtl_main.c55
-rw-r--r--drivers/hv/ring_buffer.c126
-rw-r--r--drivers/hv/vmbus_drv.c30
6 files changed, 259 insertions, 218 deletions
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 4f3bb9b4e87c..27b9b79d4f4c 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -711,16 +711,6 @@ void __weak hv_setup_mshv_handler(void (*handler)(void))
}
EXPORT_SYMBOL_GPL(hv_setup_mshv_handler);
-void __weak hv_setup_kexec_handler(void (*handler)(void))
-{
-}
-EXPORT_SYMBOL_GPL(hv_setup_kexec_handler);
-
-void __weak hv_remove_kexec_handler(void)
-{
-}
-EXPORT_SYMBOL_GPL(hv_remove_kexec_handler);
-
void __weak hv_setup_crash_handler(void (*handler)(struct pt_regs *regs))
{
}
diff --git a/drivers/hv/hv_proc.c b/drivers/hv/hv_proc.c
index 57b2c64197cb..f811bb5c42a1 100644
--- a/drivers/hv/hv_proc.c
+++ b/drivers/hv/hv_proc.c
@@ -9,135 +9,177 @@
#include <linux/export.h>
#include <asm/mshyperv.h>
+#define HV_DEPOSIT_MAX 512
+#define HV_DEPOSIT_INP_MAX ((HV_HYP_PAGE_SIZE - \
+ offsetof(struct hv_deposit_memory, gpa_page_list)) / sizeof(u64))
+
/*
- * See struct hv_deposit_memory. The first u64 is partition ID, the rest
- * are GPAs.
+ * Allocate free pages for deposit to hypervisor. pfna[] must be large enough
+ * to hold HV_DEPOSIT_INP_MAX (511) pages. If num_pages is 512, return last
+ * pfn in lastpfn. If @single, then it must be a single allocation (not split
+ * over multiple contiguous ranges).
+ *
+ * Returns: number of pages allocated or -ENOMEM
*/
-#define HV_DEPOSIT_MAX (HV_HYP_PAGE_SIZE / sizeof(u64) - 1)
-
-/* Deposits exact number of pages. Must be called with interrupts enabled. */
-int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages)
+static int hv_alloc_dep_pages(int node, u64 *pfna, u64 *lastpfnp, int num_pages,
+ bool single)
{
- struct page **pages, *page;
- int *counts;
- int num_allocations;
- int i, j, page_count;
- int order;
- u64 status;
- int ret;
- u64 base_pfn;
- struct hv_deposit_memory *input_page;
- unsigned long flags;
-
- if (num_pages > HV_DEPOSIT_MAX)
- return -E2BIG;
- if (!num_pages)
- return 0;
-
- /* One buffer for page pointers and counts */
- page = alloc_page(GFP_KERNEL);
- if (!page)
- return -ENOMEM;
- pages = page_address(page);
+ struct page *page;
+ int num_allocd, count = 0;
- counts = kzalloc_objs(int, HV_DEPOSIT_MAX);
- if (!counts) {
- free_page((unsigned long)pages);
- return -ENOMEM;
- }
+ /* Published ABI, enforce its immutability. */
+ BUILD_BUG_ON(HV_DEPOSIT_INP_MAX != 511);
- /* Allocate all the pages before disabling interrupts */
- i = 0;
+ if (num_pages > HV_DEPOSIT_MAX ||
+ (num_pages == HV_DEPOSIT_MAX && lastpfnp == NULL))
+ return -EINVAL;
while (num_pages) {
/* Find highest order we can actually allocate */
- order = 31 - __builtin_clz(num_pages);
+ int order = 31 - __builtin_clz(num_pages);
+ gfp_t gfp_flags = GFP_KERNEL;
+
+ if (!single)
+ gfp_flags |= __GFP_NOWARN;
while (1) {
- pages[i] = alloc_pages_node(node, GFP_KERNEL, order);
- if (pages[i])
+ page = alloc_pages_node(node, gfp_flags, order);
+ if (page || order == 0 || single)
break;
- if (!order) {
- ret = -ENOMEM;
- num_allocations = i;
- goto err_free_allocations;
- }
- --order;
+
+ order--;
}
- split_page(pages[i], order);
- counts[i] = 1 << order;
- num_pages -= counts[i];
- i++;
- }
- num_allocations = i;
+ if (page == NULL)
+ break;
- local_irq_save(flags);
+ split_page(page, order);
+ num_allocd = 1 << order;
+ num_pages -= num_allocd;
- input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ while (num_allocd && count < HV_DEPOSIT_INP_MAX) {
+ pfna[count++] = page_to_pfn(page++);
+ num_allocd--;
+ }
- input_page->partition_id = partition_id;
+ if (num_allocd-- && count == HV_DEPOSIT_INP_MAX) {
+ *lastpfnp = page_to_pfn(page);
+ count++;
+ break;
+ }
+ }
+
+ return count ? count : -ENOMEM;
+}
- /* Populate gpa_page_list - these will fit on the input page */
- for (i = 0, page_count = 0; i < num_allocations; ++i) {
- base_pfn = page_to_pfn(pages[i]);
- for (j = 0; j < counts[i]; ++j, ++page_count)
- input_page->gpa_page_list[page_count] = base_pfn + j;
+/*
+ * Deposit memory in the hypervisor. Even if @contiguous is false, a contiguous
+ * 2M worth of pfns is utmost desired for performance reasons. But short of
+ * that, we deposit whatever contiguous chunks we can get. If @contiguous is
+ * true, then the entire range has to be physically contiguous. Note, in that
+ * case, upon withdrawl, hypervisor could return any page in between the range,
+ * so we must split that also. Lastly, HV_MAX_CONTIGUOUS_ALLOCATION_PAGES is
+ * not guaranteed to always be power of 2.
+ */
+static int hv_call_deposit_pages(int node, u64 partition_id, bool contiguous)
+{
+ struct hv_deposit_memory *hc_input;
+ int i, rc, num_pages;
+ u64 status, *pfna, lastpfn = 0;
+ bool trunc_extra = false;
+
+ BUILD_BUG_ON(HV_MAX_CONTIGUOUS_ALLOCATION_PAGES > HV_DEPOSIT_MAX);
+
+ if (contiguous) {
+ num_pages = roundup_pow_of_two(
+ HV_MAX_CONTIGUOUS_ALLOCATION_PAGES);
+ trunc_extra = num_pages != HV_MAX_CONTIGUOUS_ALLOCATION_PAGES;
+ } else {
+ num_pages = HV_DEPOSIT_MAX;
}
- status = hv_do_rep_hypercall(HVCALL_DEPOSIT_MEMORY,
- page_count, 0, input_page, NULL);
- local_irq_restore(flags);
- if (!hv_result_success(status)) {
- hv_status_err(status, "\n");
- ret = hv_result_to_errno(status);
- goto err_free_allocations;
+
+ hc_input = (struct hv_deposit_memory *)get_zeroed_page(GFP_KERNEL);
+ if (hc_input == NULL)
+ return -ENOMEM;
+
+ hc_input->partition_id = partition_id;
+ pfna = hc_input->gpa_page_list;
+
+ rc = hv_alloc_dep_pages(node, pfna, &lastpfn, num_pages, contiguous);
+ if (rc < 0)
+ goto out_free;
+
+ num_pages = rc;
+ if (num_pages > HV_DEPOSIT_INP_MAX)
+ num_pages = HV_DEPOSIT_INP_MAX;
+
+ if (contiguous && trunc_extra) {
+ for (i = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES; i < num_pages; i++)
+ __free_page(pfn_to_page(pfna[i]));
+
+ if (lastpfn) {
+ __free_page(pfn_to_page(lastpfn));
+ lastpfn = 0;
+ }
+
+ num_pages = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES;
}
- ret = 0;
- goto free_buf;
+ /* Not using hyperv_pcpu_input_arg, so no need to disable interrupts */
+
+ status = hv_do_rep_hypercall(HVCALL_DEPOSIT_MEMORY, num_pages, 0,
+ hc_input, NULL);
+ if (!hv_result_success(status))
+ goto err_free_dep_pages;
-err_free_allocations:
- for (i = 0; i < num_allocations; ++i) {
- base_pfn = page_to_pfn(pages[i]);
- for (j = 0; j < counts[i]; ++j)
- __free_page(pfn_to_page(base_pfn + j));
+ if (lastpfn) {
+ hc_input->gpa_page_list[0] = lastpfn;
+ status = hv_do_rep_hypercall(HVCALL_DEPOSIT_MEMORY, 1, 0,
+ hc_input, NULL);
+
+ if (!hv_result_success(status) && hv_repcomp(status) == 0)
+ /* We deposited lot earlier, so give it a go */
+ __free_page(pfn_to_page(lastpfn));
}
-free_buf:
- free_page((unsigned long)pages);
- kfree(counts);
- return ret;
+ free_page((unsigned long)hc_input);
+ return 0;
+
+err_free_dep_pages:
+ hv_status_err(status, "\n");
+ rc = hv_result_to_errno(status);
+
+ for (i = hv_repcomp(status); i < num_pages; i++)
+ __free_page(pfn_to_page(pfna[i]));
+ if (lastpfn)
+ __free_page(pfn_to_page(lastpfn));
+
+out_free:
+ free_page((unsigned long)hc_input);
+ return rc;
}
-EXPORT_SYMBOL_GPL(hv_call_deposit_pages);
-int hv_deposit_memory_node(int node, u64 partition_id,
- u64 hv_status)
+int hv_deposit_memory_node(int node, u64 pt_id, u64 hv_status)
{
- u32 num_pages = 1;
+ int result = hv_result(hv_status);
+ bool contiguous = false;
- switch (hv_result(hv_status)) {
- case HV_STATUS_INSUFFICIENT_MEMORY:
- break;
- case HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY:
- num_pages = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES;
- break;
-
- case HV_STATUS_INSUFFICIENT_CONTIGUOUS_ROOT_MEMORY:
- num_pages = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES;
- fallthrough;
- case HV_STATUS_INSUFFICIENT_ROOT_MEMORY:
+ if (result == HV_STATUS_INSUFFICIENT_ROOT_MEMORY ||
+ result == HV_STATUS_INSUFFICIENT_CONTIGUOUS_ROOT_MEMORY) {
if (!hv_root_partition()) {
- hv_status_err(hv_status, "Unexpected root memory deposit\n");
- return -ENOMEM;
+ hv_status_err(hv_status,
+ "Unexpected root memory deposit\n");
+ return -EINVAL;
}
- partition_id = HV_PARTITION_ID_SELF;
- break;
- default:
- hv_status_err(hv_status, "Unexpected!\n");
- return -ENOMEM;
+ pt_id = HV_PARTITION_ID_SELF;
}
- return hv_call_deposit_pages(node, partition_id, num_pages);
+
+ if (result == HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY ||
+ result == HV_STATUS_INSUFFICIENT_CONTIGUOUS_ROOT_MEMORY)
+ contiguous = true;
+
+ return hv_call_deposit_pages(node, pt_id, contiguous);
}
EXPORT_SYMBOL_GPL(hv_deposit_memory_node);
@@ -203,14 +245,6 @@ int hv_call_create_vp(int node, u64 partition_id, u32 vp_index, u32 flags)
unsigned long irq_flags;
int ret = 0;
- /* Root VPs don't seem to need pages deposited */
- if (partition_id != hv_current_partition_id) {
- /* The value 90 is empirically determined. It may change. */
- ret = hv_call_deposit_pages(node, partition_id, 90);
- if (ret)
- return ret;
- }
-
do {
local_irq_save(irq_flags);
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index cb55d4d4be2e..b8d199f95299 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -15,8 +15,6 @@
#include "mshv_root.h"
/* Determined empirically */
-#define HV_INIT_PARTITION_DEPOSIT_PAGES 208
-#define HV_MAP_GPA_DEPOSIT_PAGES 256
#define HV_UMAP_GPA_PAGES 512
#define HV_PAGE_COUNT_2M_ALIGNED(pg_count) (!((pg_count) & (0x200 - 1)))
@@ -140,11 +138,6 @@ int hv_call_initialize_partition(u64 partition_id)
input.partition_id = partition_id;
- ret = hv_call_deposit_pages(NUMA_NO_NODE, partition_id,
- HV_INIT_PARTITION_DEPOSIT_PAGES);
- if (ret)
- return ret;
-
do {
status = hv_do_fast_hypercall8(HVCALL_INITIALIZE_PARTITION,
*(u64 *)&input);
@@ -248,8 +241,7 @@ static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
completed = hv_repcomp(status);
if (hv_result_needs_memory(status)) {
- ret = hv_call_deposit_pages(NUMA_NO_NODE, partition_id,
- HV_MAP_GPA_DEPOSIT_PAGES);
+ ret = hv_deposit_memory(partition_id, status);
if (ret)
break;
diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c
index d8225242c73f..2d4fe24cecbf 100644
--- a/drivers/hv/mshv_vtl_main.c
+++ b/drivers/hv/mshv_vtl_main.c
@@ -17,6 +17,7 @@
#include <linux/entry-virt.h>
#include <linux/eventfd.h>
#include <linux/poll.h>
+#include <linux/rcupdate.h>
#include <linux/file.h>
#include <linux/vmalloc.h>
#include <asm/debugreg.h>
@@ -72,6 +73,7 @@ struct mshv_vtl_poll_file {
wait_queue_entry_t wait;
wait_queue_head_t *wqh;
poll_table pt;
+ int error;
int cpu;
};
@@ -95,7 +97,7 @@ union hv_synic_overlay_page_msr {
} __packed;
};
-static struct mutex mshv_vtl_poll_file_lock;
+static DEFINE_MUTEX(mshv_vtl_poll_file_lock);
static union hv_register_vsm_page_offsets mshv_vsm_page_offsets;
static union hv_register_vsm_capabilities mshv_vsm_capabilities;
@@ -448,17 +450,51 @@ static void mshv_vtl_cancel(int cpu)
static int mshv_vtl_poll_file_wake(wait_queue_entry_t *wait, unsigned int mode, int sync, void *key)
{
struct mshv_vtl_poll_file *poll_file = container_of(wait, struct mshv_vtl_poll_file, wait);
+ __poll_t pollflags = key_to_poll(key);
+
+ if (pollflags & POLLFREE)
+ list_del_init(&wait->entry);
mshv_vtl_cancel(poll_file->cpu);
+ if (pollflags & POLLFREE) {
+ /* Publish teardown last; pair with acquire in the removal path. */
+ smp_store_release(&poll_file->wqh, NULL);
+ }
+
return 0;
}
+static void mshv_vtl_poll_file_remove_wait_queue(struct mshv_vtl_poll_file *poll_file)
+{
+ wait_queue_head_t *wqh;
+
+ /* POLLFREE providers RCU-delay the waitqueue free. */
+ rcu_read_lock();
+ /* Pair with the release store in the POLLFREE callback. */
+ wqh = smp_load_acquire(&poll_file->wqh);
+ if (wqh)
+ remove_wait_queue(wqh, &poll_file->wait);
+ rcu_read_unlock();
+ WRITE_ONCE(poll_file->wqh, NULL);
+}
+
static void mshv_vtl_ptable_queue_proc(struct file *file, wait_queue_head_t *wqh, poll_table *pt)
{
struct mshv_vtl_poll_file *poll_file = container_of(pt, struct mshv_vtl_poll_file, pt);
- WARN_ON(poll_file->wqh);
+ if (poll_file->error)
+ return;
+
+ if (poll_file->wqh) {
+ if (poll_file->wqh == wqh)
+ return;
+
+ mshv_vtl_poll_file_remove_wait_queue(poll_file);
+ poll_file->error = -EINVAL;
+ return;
+ }
+
poll_file->wqh = wqh;
add_wait_queue(wqh, &poll_file->wait);
}
@@ -468,6 +504,7 @@ static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *us
struct file *file, *old_file;
struct mshv_vtl_poll_file *poll_file;
struct mshv_vtl_set_poll_file input;
+ int ret;
if (copy_from_user(&input, user_input, sizeof(input)))
return -EFAULT;
@@ -490,12 +527,11 @@ static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *us
mutex_lock(&mshv_vtl_poll_file_lock);
- if (poll_file->wqh)
- remove_wait_queue(poll_file->wqh, &poll_file->wait);
- poll_file->wqh = NULL;
+ mshv_vtl_poll_file_remove_wait_queue(poll_file);
old_file = poll_file->file;
poll_file->file = file;
+ poll_file->error = 0;
poll_file->cpu = input.cpu;
if (file) {
@@ -503,13 +539,18 @@ static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *us
init_poll_funcptr(&poll_file->pt, mshv_vtl_ptable_queue_proc);
vfs_poll(file, &poll_file->pt);
}
+ ret = poll_file->error;
+ if (ret)
+ poll_file->file = NULL;
mutex_unlock(&mshv_vtl_poll_file_lock);
if (old_file)
fput(old_file);
+ if (ret)
+ fput(file);
- return 0;
+ return ret;
}
/* Static table mapping register names to their corresponding actions */
@@ -1370,8 +1411,6 @@ static int __init mshv_vtl_init(void)
goto free_low;
}
- mutex_init(&mshv_vtl_poll_file_lock);
-
device_initialize(mem_dev);
dev_set_name(mem_dev, "mshv vtl mem dev");
ret = device_add(mem_dev);
diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c
index 592a9601faaa..d8b085dfdb46 100644
--- a/drivers/hv/ring_buffer.c
+++ b/drivers/hv/ring_buffer.c
@@ -70,21 +70,12 @@ static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel)
}
}
-/* Get the next write location for the specified ring buffer. */
-static inline u32
-hv_get_next_write_location(struct hv_ring_buffer_info *ring_info)
-{
- u32 next = ring_info->ring_buffer->write_index;
-
- return next;
-}
-
/* Set the next write location for the specified ring buffer. */
static inline void
hv_set_next_write_location(struct hv_ring_buffer_info *ring_info,
u32 next_write_location)
{
- ring_info->ring_buffer->write_index = next_write_location;
+ WRITE_ONCE(ring_info->ring_buffer->write_index, next_write_location);
}
/* Get the size of the ring buffer. */
@@ -94,13 +85,6 @@ hv_get_ring_buffersize(const struct hv_ring_buffer_info *ring_info)
return ring_info->ring_datasize;
}
-/* Get the read and write indices as u64 of the specified ring buffer. */
-static inline u64
-hv_get_ring_bufferindices(struct hv_ring_buffer_info *ring_info)
-{
- return (u64)ring_info->ring_buffer->write_index << 32;
-}
-
/*
* Helper routine to copy from source to ring buffer.
* Assume there is enough room. Handles wrap-around in dest case only!!
@@ -123,35 +107,12 @@ static u32 hv_copyto_ringbuffer(
return start_write_offset;
}
-/*
- *
- * hv_get_ringbuffer_availbytes()
- *
- * Get number of bytes available to read and to write to
- * for the specified ring buffer
- */
-static void
-hv_get_ringbuffer_availbytes(const struct hv_ring_buffer_info *rbi,
- u32 *read, u32 *write)
-{
- u32 read_loc, write_loc, dsize;
-
- /* Capture the read/write indices before they changed */
- read_loc = READ_ONCE(rbi->ring_buffer->read_index);
- write_loc = READ_ONCE(rbi->ring_buffer->write_index);
- dsize = rbi->ring_datasize;
-
- *write = write_loc >= read_loc ? dsize - (write_loc - read_loc) :
- read_loc - write_loc;
- *read = dsize - *write;
-}
-
/* Get various debug metrics for the specified ring buffer. */
int hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info,
struct hv_ring_buffer_debug_info *debug_info)
{
- u32 bytes_avail_towrite;
- u32 bytes_avail_toread;
+ u32 read_index;
+ u32 write_index;
mutex_lock(&ring_info->ring_buffer_mutex);
@@ -160,13 +121,14 @@ int hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info,
return -EINVAL;
}
- hv_get_ringbuffer_availbytes(ring_info,
- &bytes_avail_toread,
- &bytes_avail_towrite);
- debug_info->bytes_avail_toread = bytes_avail_toread;
- debug_info->bytes_avail_towrite = bytes_avail_towrite;
- debug_info->current_read_index = ring_info->ring_buffer->read_index;
- debug_info->current_write_index = ring_info->ring_buffer->write_index;
+ read_index = READ_ONCE(ring_info->ring_buffer->read_index);
+ write_index = READ_ONCE(ring_info->ring_buffer->write_index);
+ debug_info->bytes_avail_toread =
+ hv_ringbuffer_avail_read(ring_info, read_index, write_index);
+ debug_info->bytes_avail_towrite =
+ hv_ringbuffer_avail_write(ring_info, read_index, write_index);
+ debug_info->current_read_index = read_index;
+ debug_info->current_write_index = write_index;
debug_info->current_interrupt_mask
= ring_info->ring_buffer->interrupt_mask;
mutex_unlock(&ring_info->ring_buffer_mutex);
@@ -281,6 +243,7 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
u32 totalbytes_towrite = sizeof(u64);
u32 next_write_location;
u32 old_write;
+ u32 read_index;
u64 prev_indices;
unsigned long flags;
struct hv_ring_buffer_info *outring_info = &channel->outbound;
@@ -295,7 +258,19 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
spin_lock_irqsave(&outring_info->ring_lock, flags);
- bytes_avail_towrite = hv_get_bytes_to_write(outring_info);
+ read_index = READ_ONCE(outring_info->ring_buffer->read_index);
+ old_write = READ_ONCE(outring_info->ring_buffer->write_index);
+ if (unlikely(hv_ringbuffer_indices_invalid(outring_info, read_index,
+ old_write))) {
+ spin_unlock_irqrestore(&outring_info->ring_lock, flags);
+ pr_err_ratelimited("outbound ring indices out of range: relid %u read %u write %u size %u\n",
+ channel->offermsg.child_relid, read_index,
+ old_write, outring_info->ring_datasize);
+ return -EIO;
+ }
+
+ bytes_avail_towrite = hv_ringbuffer_avail_write(outring_info, read_index,
+ old_write);
/*
* If there is only room for the packet, assume it is full.
@@ -317,9 +292,7 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
channel->out_full_flag = false;
/* Write to the ring buffer */
- next_write_location = hv_get_next_write_location(outring_info);
-
- old_write = next_write_location;
+ next_write_location = old_write;
for (i = 0; i < kv_count; i++) {
next_write_location = hv_copyto_ringbuffer(outring_info,
@@ -355,7 +328,7 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
*trans_id = __trans_id;
/* Set previous packet start */
- prev_indices = hv_get_ring_bufferindices(outring_info);
+ prev_indices = (u64)old_write << 32;
next_write_location = hv_copyto_ringbuffer(outring_info,
next_write_location,
@@ -435,8 +408,9 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
* This is similar to hv_get_bytes_to_read but with private
* read index instead.
*/
-static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi)
+static u32 hv_pkt_iter_avail(const struct vmbus_channel *channel)
{
+ const struct hv_ring_buffer_info *rbi = &channel->inbound;
u32 priv_read_loc = rbi->priv_read_index;
u32 write_loc;
@@ -448,11 +422,15 @@ static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi)
* stale data.
*/
write_loc = virt_load_acquire(&rbi->ring_buffer->write_index);
+ if (unlikely(hv_ringbuffer_indices_invalid(rbi, priv_read_loc,
+ write_loc))) {
+ pr_err_ratelimited("inbound write index out of range: relid %u write %u size %u\n",
+ channel->offermsg.child_relid, write_loc,
+ rbi->ring_datasize);
+ return 0;
+ }
- if (write_loc >= priv_read_loc)
- return write_loc - priv_read_loc;
- else
- return (rbi->ring_datasize - priv_read_loc) + write_loc;
+ return hv_ringbuffer_avail_read(rbi, priv_read_loc, write_loc);
}
/*
@@ -468,7 +446,7 @@ struct vmpacket_descriptor *hv_pkt_iter_first(struct vmbus_channel *channel)
hv_debug_delay_test(channel, MESSAGE_DELAY);
- bytes_avail = hv_pkt_iter_avail(rbi);
+ bytes_avail = hv_pkt_iter_avail(channel);
if (bytes_avail < sizeof(struct vmpacket_descriptor))
return NULL;
bytes_avail = min(rbi->pkt_buffer_size, bytes_avail);
@@ -572,6 +550,7 @@ void hv_pkt_iter_close(struct vmbus_channel *channel)
{
struct hv_ring_buffer_info *rbi = &channel->inbound;
u32 curr_write_sz, pending_sz, bytes_read, start_read_index;
+ u32 write_index;
/*
* Make sure all reads are done before we update the read index since
@@ -579,16 +558,8 @@ void hv_pkt_iter_close(struct vmbus_channel *channel)
* is updated.
*/
virt_rmb();
- start_read_index = rbi->ring_buffer->read_index;
- rbi->ring_buffer->read_index = rbi->priv_read_index;
-
- /*
- * Older versions of Hyper-V (before WS2102 and Win8) do not
- * implement pending_send_sz and simply poll if the host->guest
- * ring buffer is full. No signaling is needed or expected.
- */
- if (!rbi->ring_buffer->feature_bits.feat_pending_send_sz)
- return;
+ start_read_index = READ_ONCE(rbi->ring_buffer->read_index);
+ WRITE_ONCE(rbi->ring_buffer->read_index, rbi->priv_read_index);
/*
* Issue a full memory barrier before making the signaling decision.
@@ -611,11 +582,17 @@ void hv_pkt_iter_close(struct vmbus_channel *channel)
return;
/*
- * Ensure the read of write_index in hv_get_bytes_to_write()
- * happens after the read of pending_send_sz.
+ * Ensure the read of write_index happens after the read of
+ * pending_send_sz.
*/
virt_rmb();
- curr_write_sz = hv_get_bytes_to_write(rbi);
+ write_index = READ_ONCE(rbi->ring_buffer->write_index);
+ if (unlikely(hv_ringbuffer_indices_invalid(rbi, start_read_index,
+ write_index)))
+ return;
+
+ curr_write_sz = hv_ringbuffer_avail_write(rbi, rbi->priv_read_index,
+ write_index);
bytes_read = hv_pkt_iter_bytes_read(rbi, start_read_index);
/*
@@ -631,8 +608,7 @@ void hv_pkt_iter_close(struct vmbus_channel *channel)
* Exactly filling the ring buffer is treated as "not enough
* space". The ring buffer always must have at least one byte
* empty so the empty and full conditions are distinguishable.
- * hv_get_bytes_to_write() doesn't fully tell the truth in
- * this regard.
+ * curr_write_sz doesn't fully tell the truth in this regard.
*
* So first check if we were in the "enough free space" state
* before we began the iteration. If so, the host was not
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index 4692687cdbf3..c8330fffb138 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -33,6 +33,7 @@
#include <linux/sysfb.h>
#include <linux/efi.h>
#include <linux/kernel.h>
+#include <linux/kexec.h>
#include <linux/syscore_ops.h>
#include <linux/dma-map-ops.h>
#include <linux/pci.h>
@@ -2571,10 +2572,14 @@ EXPORT_SYMBOL_GPL(vmbus_free_mmio);
#ifdef CONFIG_ACPI
static int vmbus_acpi_add(struct platform_device *pdev)
{
+ struct acpi_device *ancestor;
+ struct acpi_device *device;
acpi_status result;
int ret_val = -ENODEV;
- struct acpi_device *ancestor;
- struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
+
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
vmbus_root_device = &device->dev;
@@ -2904,6 +2909,12 @@ static void hv_kexec_handler(void)
cpuhp_remove_state(hyperv_cpuhp_online);
};
+static void hv_vmbus_syscore_shutdown(void *data)
+{
+ if (kexec_is_in_progress())
+ hv_kexec_handler();
+}
+
static void hv_crash_handler(struct pt_regs *regs)
{
int cpu;
@@ -2954,14 +2965,15 @@ static void hv_synic_resume(void *data)
*/
}
-/* The callbacks run only on CPU0, with irqs_disabled. */
-static const struct syscore_ops hv_synic_syscore_ops = {
+/* The suspend and resume callbacks run only on CPU0, with irqs_disabled. */
+static const struct syscore_ops hv_vmbus_syscore_ops = {
.suspend = hv_synic_suspend,
.resume = hv_synic_resume,
+ .shutdown = hv_vmbus_syscore_shutdown,
};
-static struct syscore hv_synic_syscore = {
- .ops = &hv_synic_syscore_ops,
+static struct syscore hv_vmbus_syscore = {
+ .ops = &hv_vmbus_syscore_ops,
};
static int __init hv_acpi_init(void)
@@ -3010,10 +3022,9 @@ static int __init hv_acpi_init(void)
if (ret)
goto cleanup;
- hv_setup_kexec_handler(hv_kexec_handler);
hv_setup_crash_handler(hv_crash_handler);
- register_syscore(&hv_synic_syscore);
+ register_syscore(&hv_vmbus_syscore);
return 0;
@@ -3035,9 +3046,8 @@ static void __exit vmbus_exit(void)
*/
return;
- unregister_syscore(&hv_synic_syscore);
+ unregister_syscore(&hv_vmbus_syscore);
- hv_remove_kexec_handler();
hv_remove_crash_handler();
hv_stimer_global_cleanup();
vmbus_disconnect();