commit 455880dfe292a2bdd3b4ad6a107299fce610e64b upstream. In the multi-MSI case, hv_arch_irq_unmask() will only operate on the first MSI of the N allocated. This is because only the first msi_desc is cached and it is shared by all the MSIs of the multi-MSI block. This means that hv_arch_irq_unmask() gets the correct address, but the wrong data (always 0). This can break MSIs. Lets assume MSI0 is vector 34 on CPU0, and MSI1 is vector 33 on CPU0. hv_arch_irq_unmask() is called on MSI0. It uses a hypercall to configure the MSI address and data (0) to vector 34 of CPU0. This is correct. Then hv_arch_irq_unmask is called on MSI1. It uses another hypercall to configure the MSI address and data (0) to vector 33 of CPU0. This is wrong, and results in both MSI0 and MSI1 being routed to vector 33. Linux will observe extra instances of MSI1 and no instances of MSI0 despite the endpoint device behaving correctly. For the multi-MSI case, we need unique address and data info for each MSI, but the cached msi_desc does not provide that. However, that information can be gotten from the int_desc cached in the chip_data by compose_msi_msg(). Fix the multi-MSI case to use that cached information instead. Since hv_set_msi_entry_from_desc() is no longer applicable, remove it. 5.15 backport - no changes to code, but merge conflict due to refactor. Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/1651068453-29588-1-git-send-email-quic_jhugo@quicinc.com Signed-off-by: Wei Liu <wei.liu@kernel.org> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
207 lines
5.5 KiB
C
207 lines
5.5 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _ASM_X86_MSHYPER_H
|
|
#define _ASM_X86_MSHYPER_H
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/nmi.h>
|
|
#include <linux/msi.h>
|
|
#include <asm/io.h>
|
|
#include <asm/hyperv-tlfs.h>
|
|
#include <asm/nospec-branch.h>
|
|
#include <asm/paravirt.h>
|
|
#include <asm/mshyperv.h>
|
|
|
|
typedef int (*hyperv_fill_flush_list_func)(
|
|
struct hv_guest_mapping_flush_list *flush,
|
|
void *data);
|
|
|
|
static inline void hv_set_register(unsigned int reg, u64 value)
|
|
{
|
|
wrmsrl(reg, value);
|
|
}
|
|
|
|
static inline u64 hv_get_register(unsigned int reg)
|
|
{
|
|
u64 value;
|
|
|
|
rdmsrl(reg, value);
|
|
return value;
|
|
}
|
|
|
|
#define hv_get_raw_timer() rdtsc_ordered()
|
|
|
|
void hyperv_vector_handler(struct pt_regs *regs);
|
|
|
|
#if IS_ENABLED(CONFIG_HYPERV)
|
|
extern int hyperv_init_cpuhp;
|
|
|
|
extern void *hv_hypercall_pg;
|
|
|
|
extern u64 hv_current_partition_id;
|
|
|
|
int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages);
|
|
int hv_call_add_logical_proc(int node, u32 lp_index, u32 acpi_id);
|
|
int hv_call_create_vp(int node, u64 partition_id, u32 vp_index, u32 flags);
|
|
|
|
static inline u64 hv_do_hypercall(u64 control, void *input, void *output)
|
|
{
|
|
u64 input_address = input ? virt_to_phys(input) : 0;
|
|
u64 output_address = output ? virt_to_phys(output) : 0;
|
|
u64 hv_status;
|
|
|
|
#ifdef CONFIG_X86_64
|
|
if (!hv_hypercall_pg)
|
|
return U64_MAX;
|
|
|
|
__asm__ __volatile__("mov %4, %%r8\n"
|
|
CALL_NOSPEC
|
|
: "=a" (hv_status), ASM_CALL_CONSTRAINT,
|
|
"+c" (control), "+d" (input_address)
|
|
: "r" (output_address),
|
|
THUNK_TARGET(hv_hypercall_pg)
|
|
: "cc", "memory", "r8", "r9", "r10", "r11");
|
|
#else
|
|
u32 input_address_hi = upper_32_bits(input_address);
|
|
u32 input_address_lo = lower_32_bits(input_address);
|
|
u32 output_address_hi = upper_32_bits(output_address);
|
|
u32 output_address_lo = lower_32_bits(output_address);
|
|
|
|
if (!hv_hypercall_pg)
|
|
return U64_MAX;
|
|
|
|
__asm__ __volatile__(CALL_NOSPEC
|
|
: "=A" (hv_status),
|
|
"+c" (input_address_lo), ASM_CALL_CONSTRAINT
|
|
: "A" (control),
|
|
"b" (input_address_hi),
|
|
"D"(output_address_hi), "S"(output_address_lo),
|
|
THUNK_TARGET(hv_hypercall_pg)
|
|
: "cc", "memory");
|
|
#endif /* !x86_64 */
|
|
return hv_status;
|
|
}
|
|
|
|
/* Fast hypercall with 8 bytes of input and no output */
|
|
static inline u64 hv_do_fast_hypercall8(u16 code, u64 input1)
|
|
{
|
|
u64 hv_status, control = (u64)code | HV_HYPERCALL_FAST_BIT;
|
|
|
|
#ifdef CONFIG_X86_64
|
|
{
|
|
__asm__ __volatile__(CALL_NOSPEC
|
|
: "=a" (hv_status), ASM_CALL_CONSTRAINT,
|
|
"+c" (control), "+d" (input1)
|
|
: THUNK_TARGET(hv_hypercall_pg)
|
|
: "cc", "r8", "r9", "r10", "r11");
|
|
}
|
|
#else
|
|
{
|
|
u32 input1_hi = upper_32_bits(input1);
|
|
u32 input1_lo = lower_32_bits(input1);
|
|
|
|
__asm__ __volatile__ (CALL_NOSPEC
|
|
: "=A"(hv_status),
|
|
"+c"(input1_lo),
|
|
ASM_CALL_CONSTRAINT
|
|
: "A" (control),
|
|
"b" (input1_hi),
|
|
THUNK_TARGET(hv_hypercall_pg)
|
|
: "cc", "edi", "esi");
|
|
}
|
|
#endif
|
|
return hv_status;
|
|
}
|
|
|
|
/* Fast hypercall with 16 bytes of input */
|
|
static inline u64 hv_do_fast_hypercall16(u16 code, u64 input1, u64 input2)
|
|
{
|
|
u64 hv_status, control = (u64)code | HV_HYPERCALL_FAST_BIT;
|
|
|
|
#ifdef CONFIG_X86_64
|
|
{
|
|
__asm__ __volatile__("mov %4, %%r8\n"
|
|
CALL_NOSPEC
|
|
: "=a" (hv_status), ASM_CALL_CONSTRAINT,
|
|
"+c" (control), "+d" (input1)
|
|
: "r" (input2),
|
|
THUNK_TARGET(hv_hypercall_pg)
|
|
: "cc", "r8", "r9", "r10", "r11");
|
|
}
|
|
#else
|
|
{
|
|
u32 input1_hi = upper_32_bits(input1);
|
|
u32 input1_lo = lower_32_bits(input1);
|
|
u32 input2_hi = upper_32_bits(input2);
|
|
u32 input2_lo = lower_32_bits(input2);
|
|
|
|
__asm__ __volatile__ (CALL_NOSPEC
|
|
: "=A"(hv_status),
|
|
"+c"(input1_lo), ASM_CALL_CONSTRAINT
|
|
: "A" (control), "b" (input1_hi),
|
|
"D"(input2_hi), "S"(input2_lo),
|
|
THUNK_TARGET(hv_hypercall_pg)
|
|
: "cc");
|
|
}
|
|
#endif
|
|
return hv_status;
|
|
}
|
|
|
|
extern struct hv_vp_assist_page **hv_vp_assist_page;
|
|
|
|
static inline struct hv_vp_assist_page *hv_get_vp_assist_page(unsigned int cpu)
|
|
{
|
|
if (!hv_vp_assist_page)
|
|
return NULL;
|
|
|
|
return hv_vp_assist_page[cpu];
|
|
}
|
|
|
|
void __init hyperv_init(void);
|
|
void hyperv_setup_mmu_ops(void);
|
|
void set_hv_tscchange_cb(void (*cb)(void));
|
|
void clear_hv_tscchange_cb(void);
|
|
void hyperv_stop_tsc_emulation(void);
|
|
int hyperv_flush_guest_mapping(u64 as);
|
|
int hyperv_flush_guest_mapping_range(u64 as,
|
|
hyperv_fill_flush_list_func fill_func, void *data);
|
|
int hyperv_fill_flush_guest_mapping_list(
|
|
struct hv_guest_mapping_flush_list *flush,
|
|
u64 start_gfn, u64 end_gfn);
|
|
|
|
#ifdef CONFIG_X86_64
|
|
void hv_apic_init(void);
|
|
void __init hv_init_spinlocks(void);
|
|
bool hv_vcpu_is_preempted(int vcpu);
|
|
#else
|
|
static inline void hv_apic_init(void) {}
|
|
#endif
|
|
|
|
struct irq_domain *hv_create_pci_msi_domain(void);
|
|
|
|
int hv_map_ioapic_interrupt(int ioapic_id, bool level, int vcpu, int vector,
|
|
struct hv_interrupt_entry *entry);
|
|
int hv_unmap_ioapic_interrupt(int ioapic_id, struct hv_interrupt_entry *entry);
|
|
|
|
#else /* CONFIG_HYPERV */
|
|
static inline void hyperv_init(void) {}
|
|
static inline void hyperv_setup_mmu_ops(void) {}
|
|
static inline void set_hv_tscchange_cb(void (*cb)(void)) {}
|
|
static inline void clear_hv_tscchange_cb(void) {}
|
|
static inline void hyperv_stop_tsc_emulation(void) {};
|
|
static inline struct hv_vp_assist_page *hv_get_vp_assist_page(unsigned int cpu)
|
|
{
|
|
return NULL;
|
|
}
|
|
static inline int hyperv_flush_guest_mapping(u64 as) { return -1; }
|
|
static inline int hyperv_flush_guest_mapping_range(u64 as,
|
|
hyperv_fill_flush_list_func fill_func, void *data)
|
|
{
|
|
return -1;
|
|
}
|
|
#endif /* CONFIG_HYPERV */
|
|
|
|
|
|
#include <asm-generic/mshyperv.h>
|
|
|
|
#endif
|