linux-stable/drivers/pci/controller/pci-hyperv.c

4116 lines
114 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) Microsoft Corporation.
*
* Author:
* Jake Oshins <jakeo@microsoft.com>
*
* This driver acts as a paravirtual front-end for PCI Express root buses.
* When a PCI Express function (either an entire device or an SR-IOV
* Virtual Function) is being passed through to the VM, this driver exposes
* a new bus to the guest VM. This is modeled as a root PCI bus because
* no bridges are being exposed to the VM. In fact, with a "Generation 2"
* VM within Hyper-V, there may seem to be no PCI bus at all in the VM
* until a device as been exposed using this driver.
*
* Each root PCI bus has its own PCI domain, which is called "Segment" in
* the PCI Firmware Specifications. Thus while each device passed through
* to the VM using this front-end will appear at "device 0", the domain will
* be unique. Typically, each bus will have one PCI function on it, though
* this driver does support more than one.
*
* In order to map the interrupts from the device through to the guest VM,
* this driver also implements an IRQ Domain, which handles interrupts (either
* MSI or MSI-X) associated with the functions on the bus. As interrupts are
* set up, torn down, or reaffined, this driver communicates with the
* underlying hypervisor to adjust the mappings in the I/O MMU so that each
* interrupt will be delivered to the correct virtual processor at the right
* vector. This driver does not support level-triggered (line-based)
* interrupts, and will report that the Interrupt Line register in the
* function's configuration space is zero.
*
* The rest of this driver mostly maps PCI concepts onto underlying Hyper-V
* facilities. For instance, the configuration space of a function exposed
* by Hyper-V is mapped into a single page of memory space, and the
* read and write handlers for config space must be aware of this mechanism.
* Similarly, device setup and teardown involves messages sent to and from
* the PCI back-end driver in Hyper-V.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci-ecam.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
x86: Don't include linux/irq.h from asm/hardirq.h The next patch in this series will have to make the definition of irq_cpustat_t available to entering_irq(). Inclusion of asm/hardirq.h into asm/apic.h would cause circular header dependencies like asm/smp.h asm/apic.h asm/hardirq.h linux/irq.h linux/topology.h linux/smp.h asm/smp.h or linux/gfp.h linux/mmzone.h asm/mmzone.h asm/mmzone_64.h asm/smp.h asm/apic.h asm/hardirq.h linux/irq.h linux/irqdesc.h linux/kobject.h linux/sysfs.h linux/kernfs.h linux/idr.h linux/gfp.h and others. This causes compilation errors because of the header guards becoming effective in the second inclusion: symbols/macros that had been defined before wouldn't be available to intermediate headers in the #include chain anymore. A possible workaround would be to move the definition of irq_cpustat_t into its own header and include that from both, asm/hardirq.h and asm/apic.h. However, this wouldn't solve the real problem, namely asm/harirq.h unnecessarily pulling in all the linux/irq.h cruft: nothing in asm/hardirq.h itself requires it. Also, note that there are some other archs, like e.g. arm64, which don't have that #include in their asm/hardirq.h. Remove the linux/irq.h #include from x86' asm/hardirq.h. Fix resulting compilation errors by adding appropriate #includes to *.c files as needed. Note that some of these *.c files could be cleaned up a bit wrt. to their set of #includes, but that should better be done from separate patches, if at all. Signed-off-by: Nicolai Stange <nstange@suse.de> Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2018-07-29 10:15:33 +00:00
#include <linux/irq.h>
#include <linux/msi.h>
#include <linux/hyperv.h>
#include <linux/refcount.h>
#include <linux/irqdomain.h>
#include <linux/acpi.h>
#include <linux/sizes.h>
#include <asm/mshyperv.h>
/*
* Protocol versions. The low word is the minor version, the high word the
* major version.
*/
#define PCI_MAKE_VERSION(major, minor) ((u32)(((major) << 16) | (minor)))
#define PCI_MAJOR_VERSION(version) ((u32)(version) >> 16)
#define PCI_MINOR_VERSION(version) ((u32)(version) & 0xff)
enum pci_protocol_version_t {
PCI_PROTOCOL_VERSION_1_1 = PCI_MAKE_VERSION(1, 1), /* Win10 */
PCI_PROTOCOL_VERSION_1_2 = PCI_MAKE_VERSION(1, 2), /* RS1 */
PCI_PROTOCOL_VERSION_1_3 = PCI_MAKE_VERSION(1, 3), /* Vibranium */
PCI_PROTOCOL_VERSION_1_4 = PCI_MAKE_VERSION(1, 4), /* WS2022 */
};
#define CPU_AFFINITY_ALL -1ULL
/*
* Supported protocol versions in the order of probing - highest go
* first.
*/
static enum pci_protocol_version_t pci_protocol_versions[] = {
PCI_PROTOCOL_VERSION_1_4,
PCI_PROTOCOL_VERSION_1_3,
PCI_PROTOCOL_VERSION_1_2,
PCI_PROTOCOL_VERSION_1_1,
};
#define PCI_CONFIG_MMIO_LENGTH 0x2000
#define CFG_PAGE_OFFSET 0x1000
#define CFG_PAGE_SIZE (PCI_CONFIG_MMIO_LENGTH - CFG_PAGE_OFFSET)
#define MAX_SUPPORTED_MSI_MESSAGES 0x400
#define STATUS_REVISION_MISMATCH 0xC0000059
/* space for 32bit serial number as string */
#define SLOT_NAME_SIZE 11
/*
* Size of requestor for VMbus; the value is based on the observation
* that having more than one request outstanding is 'rare', and so 64
* should be generous in ensuring that we don't ever run out.
*/
#define HV_PCI_RQSTOR_SIZE 64
/*
* Message Types
*/
enum pci_message_type {
/*
* Version 1.1
*/
PCI_MESSAGE_BASE = 0x42490000,
PCI_BUS_RELATIONS = PCI_MESSAGE_BASE + 0,
PCI_QUERY_BUS_RELATIONS = PCI_MESSAGE_BASE + 1,
PCI_POWER_STATE_CHANGE = PCI_MESSAGE_BASE + 4,
PCI_QUERY_RESOURCE_REQUIREMENTS = PCI_MESSAGE_BASE + 5,
PCI_QUERY_RESOURCE_RESOURCES = PCI_MESSAGE_BASE + 6,
PCI_BUS_D0ENTRY = PCI_MESSAGE_BASE + 7,
PCI_BUS_D0EXIT = PCI_MESSAGE_BASE + 8,
PCI_READ_BLOCK = PCI_MESSAGE_BASE + 9,
PCI_WRITE_BLOCK = PCI_MESSAGE_BASE + 0xA,
PCI_EJECT = PCI_MESSAGE_BASE + 0xB,
PCI_QUERY_STOP = PCI_MESSAGE_BASE + 0xC,
PCI_REENABLE = PCI_MESSAGE_BASE + 0xD,
PCI_QUERY_STOP_FAILED = PCI_MESSAGE_BASE + 0xE,
PCI_EJECTION_COMPLETE = PCI_MESSAGE_BASE + 0xF,
PCI_RESOURCES_ASSIGNED = PCI_MESSAGE_BASE + 0x10,
PCI_RESOURCES_RELEASED = PCI_MESSAGE_BASE + 0x11,
PCI_INVALIDATE_BLOCK = PCI_MESSAGE_BASE + 0x12,
PCI_QUERY_PROTOCOL_VERSION = PCI_MESSAGE_BASE + 0x13,
PCI_CREATE_INTERRUPT_MESSAGE = PCI_MESSAGE_BASE + 0x14,
PCI_DELETE_INTERRUPT_MESSAGE = PCI_MESSAGE_BASE + 0x15,
PCI_RESOURCES_ASSIGNED2 = PCI_MESSAGE_BASE + 0x16,
PCI_CREATE_INTERRUPT_MESSAGE2 = PCI_MESSAGE_BASE + 0x17,
PCI_DELETE_INTERRUPT_MESSAGE2 = PCI_MESSAGE_BASE + 0x18, /* unused */
PCI_BUS_RELATIONS2 = PCI_MESSAGE_BASE + 0x19,
PCI_RESOURCES_ASSIGNED3 = PCI_MESSAGE_BASE + 0x1A,
PCI_CREATE_INTERRUPT_MESSAGE3 = PCI_MESSAGE_BASE + 0x1B,
PCI_MESSAGE_MAXIMUM
};
/*
* Structures defining the virtual PCI Express protocol.
*/
union pci_version {
struct {
u16 minor_version;
u16 major_version;
} parts;
u32 version;
} __packed;
/*
* Function numbers are 8-bits wide on Express, as interpreted through ARI,
* which is all this driver does. This representation is the one used in
* Windows, which is what is expected when sending this back and forth with
* the Hyper-V parent partition.
*/
union win_slot_encoding {
struct {
u32 dev:5;
u32 func:3;
u32 reserved:24;
} bits;
u32 slot;
} __packed;
/*
* Pretty much as defined in the PCI Specifications.
*/
struct pci_function_description {
u16 v_id; /* vendor ID */
u16 d_id; /* device ID */
u8 rev;
u8 prog_intf;
u8 subclass;
u8 base_class;
u32 subsystem_id;
union win_slot_encoding win_slot;
u32 ser; /* serial number */
} __packed;
enum pci_device_description_flags {
HV_PCI_DEVICE_FLAG_NONE = 0x0,
HV_PCI_DEVICE_FLAG_NUMA_AFFINITY = 0x1,
};
struct pci_function_description2 {
u16 v_id; /* vendor ID */
u16 d_id; /* device ID */
u8 rev;
u8 prog_intf;
u8 subclass;
u8 base_class;
u32 subsystem_id;
union win_slot_encoding win_slot;
u32 ser; /* serial number */
u32 flags;
u16 virtual_numa_node;
u16 reserved;
} __packed;
/**
* struct hv_msi_desc
* @vector: IDT entry
* @delivery_mode: As defined in Intel's Programmer's
* Reference Manual, Volume 3, Chapter 8.
* @vector_count: Number of contiguous entries in the
* Interrupt Descriptor Table that are
* occupied by this Message-Signaled
* Interrupt. For "MSI", as first defined
* in PCI 2.2, this can be between 1 and
* 32. For "MSI-X," as first defined in PCI
* 3.0, this must be 1, as each MSI-X table
* entry would have its own descriptor.
* @reserved: Empty space
* @cpu_mask: All the target virtual processors.
*/
struct hv_msi_desc {
u8 vector;
u8 delivery_mode;
u16 vector_count;
u32 reserved;
u64 cpu_mask;
} __packed;
/**
* struct hv_msi_desc2 - 1.2 version of hv_msi_desc
* @vector: IDT entry
* @delivery_mode: As defined in Intel's Programmer's
* Reference Manual, Volume 3, Chapter 8.
* @vector_count: Number of contiguous entries in the
* Interrupt Descriptor Table that are
* occupied by this Message-Signaled
* Interrupt. For "MSI", as first defined
* in PCI 2.2, this can be between 1 and
* 32. For "MSI-X," as first defined in PCI
* 3.0, this must be 1, as each MSI-X table
* entry would have its own descriptor.
* @processor_count: number of bits enabled in array.
* @processor_array: All the target virtual processors.
*/
struct hv_msi_desc2 {
u8 vector;
u8 delivery_mode;
u16 vector_count;
u16 processor_count;
u16 processor_array[32];
} __packed;
/*
* struct hv_msi_desc3 - 1.3 version of hv_msi_desc
* Everything is the same as in 'hv_msi_desc2' except that the size of the
* 'vector' field is larger to support bigger vector values. For ex: LPI
* vectors on ARM.
*/
struct hv_msi_desc3 {
u32 vector;
u8 delivery_mode;
u8 reserved;
u16 vector_count;
u16 processor_count;
u16 processor_array[32];
} __packed;
/**
* struct tran_int_desc
* @reserved: unused, padding
* @vector_count: same as in hv_msi_desc
* @data: This is the "data payload" value that is
* written by the device when it generates
* a message-signaled interrupt, either MSI
* or MSI-X.
* @address: This is the address to which the data
* payload is written on interrupt
* generation.
*/
struct tran_int_desc {
u16 reserved;
u16 vector_count;
u32 data;
u64 address;
} __packed;
/*
* A generic message format for virtual PCI.
* Specific message formats are defined later in the file.
*/
struct pci_message {
u32 type;
} __packed;
struct pci_child_message {
struct pci_message message_type;
union win_slot_encoding wslot;
} __packed;
struct pci_incoming_message {
struct vmpacket_descriptor hdr;
struct pci_message message_type;
} __packed;
struct pci_response {
struct vmpacket_descriptor hdr;
s32 status; /* negative values are failures */
} __packed;
struct pci_packet {
void (*completion_func)(void *context, struct pci_response *resp,
int resp_packet_size);
void *compl_ctxt;
struct pci_message message[];
};
/*
* Specific message types supporting the PCI protocol.
*/
/*
* Version negotiation message. Sent from the guest to the host.
* The guest is free to try different versions until the host
* accepts the version.
*
* pci_version: The protocol version requested.
* is_last_attempt: If TRUE, this is the last version guest will request.
* reservedz: Reserved field, set to zero.
*/
struct pci_version_request {
struct pci_message message_type;
u32 protocol_version;
} __packed;
/*
* Bus D0 Entry. This is sent from the guest to the host when the virtual
* bus (PCI Express port) is ready for action.
*/
struct pci_bus_d0_entry {
struct pci_message message_type;
u32 reserved;
u64 mmio_base;
} __packed;
struct pci_bus_relations {
struct pci_incoming_message incoming;
u32 device_count;
struct pci_function_description func[];
} __packed;
struct pci_bus_relations2 {
struct pci_incoming_message incoming;
u32 device_count;
struct pci_function_description2 func[];
} __packed;
struct pci_q_res_req_response {
struct vmpacket_descriptor hdr;
s32 status; /* negative values are failures */
u32 probed_bar[PCI_STD_NUM_BARS];
} __packed;
struct pci_set_power {
struct pci_message message_type;
union win_slot_encoding wslot;
u32 power_state; /* In Windows terms */
u32 reserved;
} __packed;
struct pci_set_power_response {
struct vmpacket_descriptor hdr;
s32 status; /* negative values are failures */
union win_slot_encoding wslot;
u32 resultant_state; /* In Windows terms */
u32 reserved;
} __packed;
struct pci_resources_assigned {
struct pci_message message_type;
union win_slot_encoding wslot;
u8 memory_range[0x14][6]; /* not used here */
u32 msi_descriptors;
u32 reserved[4];
} __packed;
struct pci_resources_assigned2 {
struct pci_message message_type;
union win_slot_encoding wslot;
u8 memory_range[0x14][6]; /* not used here */
u32 msi_descriptor_count;
u8 reserved[70];
} __packed;
struct pci_create_interrupt {
struct pci_message message_type;
union win_slot_encoding wslot;
struct hv_msi_desc int_desc;
} __packed;
struct pci_create_int_response {
struct pci_response response;
u32 reserved;
struct tran_int_desc int_desc;
} __packed;
struct pci_create_interrupt2 {
struct pci_message message_type;
union win_slot_encoding wslot;
struct hv_msi_desc2 int_desc;
} __packed;
struct pci_create_interrupt3 {
struct pci_message message_type;
union win_slot_encoding wslot;
struct hv_msi_desc3 int_desc;
} __packed;
struct pci_delete_interrupt {
struct pci_message message_type;
union win_slot_encoding wslot;
struct tran_int_desc int_desc;
} __packed;
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
/*
* Note: the VM must pass a valid block id, wslot and bytes_requested.
*/
struct pci_read_block {
struct pci_message message_type;
u32 block_id;
union win_slot_encoding wslot;
u32 bytes_requested;
} __packed;
struct pci_read_block_response {
struct vmpacket_descriptor hdr;
u32 status;
u8 bytes[HV_CONFIG_BLOCK_SIZE_MAX];
} __packed;
/*
* Note: the VM must pass a valid block id, wslot and byte_count.
*/
struct pci_write_block {
struct pci_message message_type;
u32 block_id;
union win_slot_encoding wslot;
u32 byte_count;
u8 bytes[HV_CONFIG_BLOCK_SIZE_MAX];
} __packed;
struct pci_dev_inval_block {
struct pci_incoming_message incoming;
union win_slot_encoding wslot;
u64 block_mask;
} __packed;
struct pci_dev_incoming {
struct pci_incoming_message incoming;
union win_slot_encoding wslot;
} __packed;
struct pci_eject_response {
struct pci_message message_type;
union win_slot_encoding wslot;
u32 status;
} __packed;
static int pci_ring_size = VMBUS_RING_SIZE(SZ_16K);
/*
* Driver specific state.
*/
enum hv_pcibus_state {
hv_pcibus_init = 0,
hv_pcibus_probed,
hv_pcibus_installed,
hv_pcibus_removing,
hv_pcibus_maximum
};
struct hv_pcibus_device {
#ifdef CONFIG_X86
struct pci_sysdata sysdata;
#elif defined(CONFIG_ARM64)
struct pci_config_window sysdata;
#endif
struct pci_host_bridge *bridge;
struct fwnode_handle *fwnode;
/* Protocol version negotiated with the host */
enum pci_protocol_version_t protocol_version;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
struct mutex state_lock;
enum hv_pcibus_state state;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
struct hv_device *hdev;
resource_size_t low_mmio_space;
resource_size_t high_mmio_space;
struct resource *mem_config;
struct resource *low_mmio_res;
struct resource *high_mmio_res;
struct completion *survey_event;
struct pci_bus *pci_bus;
spinlock_t config_lock; /* Avoid two threads writing index page */
spinlock_t device_list_lock; /* Protect lists below */
void __iomem *cfg_addr;
struct list_head children;
struct list_head dr_list;
struct msi_domain_info msi_info;
struct irq_domain *irq_domain;
struct workqueue_struct *wq;
/* Highest slot of child device with resources allocated */
int wslot_res_allocated;
bool use_calls; /* Use hypercalls to access mmio cfg space */
};
/*
* Tracks "Device Relations" messages from the host, which must be both
* processed in order and deferred so that they don't run in the context
* of the incoming packet callback.
*/
struct hv_dr_work {
struct work_struct wrk;
struct hv_pcibus_device *bus;
};
struct hv_pcidev_description {
u16 v_id; /* vendor ID */
u16 d_id; /* device ID */
u8 rev;
u8 prog_intf;
u8 subclass;
u8 base_class;
u32 subsystem_id;
union win_slot_encoding win_slot;
u32 ser; /* serial number */
u32 flags;
u16 virtual_numa_node;
};
struct hv_dr_state {
struct list_head list_entry;
u32 device_count;
struct hv_pcidev_description func[] __counted_by(device_count);
};
struct hv_pci_dev {
/* List protected by pci_rescan_remove_lock */
struct list_head list_entry;
refcount_t refs;
struct pci_slot *pci_slot;
struct hv_pcidev_description desc;
bool reported_missing;
struct hv_pcibus_device *hbus;
struct work_struct wrk;
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
void (*block_invalidate)(void *context, u64 block_mask);
void *invalidate_context;
/*
* What would be observed if one wrote 0xFFFFFFFF to a BAR and then
* read it back, for each of the BAR offsets within config space.
*/
u32 probed_bar[PCI_STD_NUM_BARS];
};
struct hv_pci_compl {
struct completion host_event;
s32 completion_status;
};
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
static void hv_pci_onchannelcallback(void *context);
#ifdef CONFIG_X86
#define DELIVERY_MODE APIC_DELIVERY_MODE_FIXED
#define FLOW_HANDLER handle_edge_irq
#define FLOW_NAME "edge"
static int hv_pci_irqchip_init(void)
{
return 0;
}
static struct irq_domain *hv_pci_get_root_domain(void)
{
return x86_vector_domain;
}
static unsigned int hv_msi_get_int_vector(struct irq_data *data)
{
struct irq_cfg *cfg = irqd_cfg(data);
return cfg->vector;
}
#define hv_msi_prepare pci_msi_prepare
/**
* hv_arch_irq_unmask() - "Unmask" the IRQ by setting its current
* affinity.
* @data: Describes the IRQ
*
* Build new a destination for the MSI and make a hypercall to
* update the Interrupt Redirection Table. "Device Logical ID"
* is built out of this PCI bus's instance GUID and the function
* number of the device.
*/
static void hv_arch_irq_unmask(struct irq_data *data)
{
struct msi_desc *msi_desc = irq_data_get_msi_desc(data);
struct hv_retarget_device_interrupt *params;
PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI 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. 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>
2022-04-27 14:07:33 +00:00
struct tran_int_desc *int_desc;
struct hv_pcibus_device *hbus;
const struct cpumask *dest;
cpumask_var_t tmp;
struct pci_bus *pbus;
struct pci_dev *pdev;
unsigned long flags;
u32 var_size = 0;
int cpu, nr_bank;
u64 res;
dest = irq_data_get_effective_affinity_mask(data);
pdev = msi_desc_to_pci_dev(msi_desc);
pbus = pdev->bus;
hbus = container_of(pbus->sysdata, struct hv_pcibus_device, sysdata);
PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI 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. 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>
2022-04-27 14:07:33 +00:00
int_desc = data->chip_data;
PCI: hv: Fix a race condition in hv_irq_unmask() that can cause panic When the host tries to remove a PCI device, the host first sends a PCI_EJECT message to the guest, and the guest is supposed to gracefully remove the PCI device and send a PCI_EJECTION_COMPLETE message to the host; the host then sends a VMBus message CHANNELMSG_RESCIND_CHANNELOFFER to the guest (when the guest receives this message, the device is already unassigned from the guest) and the guest can do some final cleanup work; if the guest fails to respond to the PCI_EJECT message within one minute, the host sends the VMBus message CHANNELMSG_RESCIND_CHANNELOFFER and removes the PCI device forcibly. In the case of fast device addition/removal, it's possible that the PCI device driver is still configuring MSI-X interrupts when the guest receives the PCI_EJECT message; the channel callback calls hv_pci_eject_device(), which sets hpdev->state to hv_pcichild_ejecting, and schedules a work hv_eject_device_work(); if the PCI device driver is calling pci_alloc_irq_vectors() -> ... -> hv_compose_msi_msg(), we can break the while loop in hv_compose_msi_msg() due to the updated hpdev->state, and leave data->chip_data with its default value of NULL; later, when the PCI device driver calls request_irq() -> ... -> hv_irq_unmask(), the guest crashes in hv_arch_irq_unmask() due to data->chip_data being NULL. Fix the issue by not testing hpdev->state in the while loop: when the guest receives PCI_EJECT, the device is still assigned to the guest, and the guest has one minute to finish the device removal gracefully. We don't really need to (and we should not) test hpdev->state in the loop. Fixes: de0aa7b2f97d ("PCI: hv: Fix 2 hang issues in hv_compose_msi_msg()") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-3-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:48 +00:00
if (!int_desc) {
dev_warn(&hbus->hdev->device, "%s() can not unmask irq %u\n",
__func__, data->irq);
return;
}
local_irq_save(flags);
params = *this_cpu_ptr(hyperv_pcpu_input_arg);
memset(params, 0, sizeof(*params));
params->partition_id = HV_PARTITION_ID_SELF;
params->int_entry.source = HV_INTERRUPT_SOURCE_MSI;
PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI 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. 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>
2022-04-27 14:07:33 +00:00
params->int_entry.msi_entry.address.as_uint32 = int_desc->address & 0xffffffff;
params->int_entry.msi_entry.data.as_uint32 = int_desc->data;
params->device_id = (hbus->hdev->dev_instance.b[5] << 24) |
(hbus->hdev->dev_instance.b[4] << 16) |
(hbus->hdev->dev_instance.b[7] << 8) |
(hbus->hdev->dev_instance.b[6] & 0xf8) |
PCI_FUNC(pdev->devfn);
params->int_target.vector = hv_msi_get_int_vector(data);
if (hbus->protocol_version >= PCI_PROTOCOL_VERSION_1_2) {
/*
* PCI_PROTOCOL_VERSION_1_2 supports the VP_SET version of the
* HVCALL_RETARGET_INTERRUPT hypercall, which also coincides
* with >64 VP support.
* ms_hyperv.hints & HV_X64_EX_PROCESSOR_MASKS_RECOMMENDED
* is not sufficient for this hypercall.
*/
params->int_target.flags |=
HV_DEVICE_INTERRUPT_TARGET_PROCESSOR_SET;
if (!alloc_cpumask_var(&tmp, GFP_ATOMIC)) {
res = 1;
goto out;
}
cpumask_and(tmp, dest, cpu_online_mask);
nr_bank = cpumask_to_vpset(&params->int_target.vp_set, tmp);
free_cpumask_var(tmp);
if (nr_bank <= 0) {
res = 1;
goto out;
}
/*
* var-sized hypercall, var-size starts after vp_mask (thus
* vp_set.format does not count, but vp_set.valid_bank_mask
* does).
*/
var_size = 1 + nr_bank;
} else {
for_each_cpu_and(cpu, dest, cpu_online_mask) {
params->int_target.vp_mask |=
(1ULL << hv_cpu_number_to_vp_number(cpu));
}
}
res = hv_do_hypercall(HVCALL_RETARGET_INTERRUPT | (var_size << 17),
params, NULL);
out:
local_irq_restore(flags);
/*
* During hibernation, when a CPU is offlined, the kernel tries
* to move the interrupt to the remaining CPUs that haven't
* been offlined yet. In this case, the below hv_do_hypercall()
* always fails since the vmbus channel has been closed:
* refer to cpu_disable_common() -> fixup_irqs() ->
* irq_migrate_all_off_this_cpu() -> migrate_one_irq().
*
* Suppress the error message for hibernation because the failure
* during hibernation does not matter (at this time all the devices
* have been frozen). Note: the correct affinity info is still updated
* into the irqdata data structure in migrate_one_irq() ->
* irq_do_set_affinity(), so later when the VM resumes,
* hv_pci_restore_msi_state() is able to correctly restore the
* interrupt with the correct affinity.
*/
if (!hv_result_success(res) && hbus->state != hv_pcibus_removing)
dev_err(&hbus->hdev->device,
"%s() failed: %#llx", __func__, res);
}
#elif defined(CONFIG_ARM64)
/*
* SPI vectors to use for vPCI; arch SPIs range is [32, 1019], but leaving a bit
* of room at the start to allow for SPIs to be specified through ACPI and
* starting with a power of two to satisfy power of 2 multi-MSI requirement.
*/
#define HV_PCI_MSI_SPI_START 64
#define HV_PCI_MSI_SPI_NR (1020 - HV_PCI_MSI_SPI_START)
#define DELIVERY_MODE 0
#define FLOW_HANDLER NULL
#define FLOW_NAME NULL
#define hv_msi_prepare NULL
struct hv_pci_chip_data {
DECLARE_BITMAP(spi_map, HV_PCI_MSI_SPI_NR);
struct mutex map_lock;
};
/* Hyper-V vPCI MSI GIC IRQ domain */
static struct irq_domain *hv_msi_gic_irq_domain;
/* Hyper-V PCI MSI IRQ chip */
static struct irq_chip hv_arm64_msi_irq_chip = {
.name = "MSI",
.irq_set_affinity = irq_chip_set_affinity_parent,
.irq_eoi = irq_chip_eoi_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent
};
static unsigned int hv_msi_get_int_vector(struct irq_data *irqd)
{
return irqd->parent_data->hwirq;
}
/*
* @nr_bm_irqs: Indicates the number of IRQs that were allocated from
* the bitmap.
* @nr_dom_irqs: Indicates the number of IRQs that were allocated from
* the parent domain.
*/
static void hv_pci_vec_irq_free(struct irq_domain *domain,
unsigned int virq,
unsigned int nr_bm_irqs,
unsigned int nr_dom_irqs)
{
struct hv_pci_chip_data *chip_data = domain->host_data;
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
int first = d->hwirq - HV_PCI_MSI_SPI_START;
int i;
mutex_lock(&chip_data->map_lock);
bitmap_release_region(chip_data->spi_map,
first,
get_count_order(nr_bm_irqs));
mutex_unlock(&chip_data->map_lock);
for (i = 0; i < nr_dom_irqs; i++) {
if (i)
d = irq_domain_get_irq_data(domain, virq + i);
irq_domain_reset_irq_data(d);
}
irq_domain_free_irqs_parent(domain, virq, nr_dom_irqs);
}
static void hv_pci_vec_irq_domain_free(struct irq_domain *domain,
unsigned int virq,
unsigned int nr_irqs)
{
hv_pci_vec_irq_free(domain, virq, nr_irqs, nr_irqs);
}
static int hv_pci_vec_alloc_device_irq(struct irq_domain *domain,
unsigned int nr_irqs,
irq_hw_number_t *hwirq)
{
struct hv_pci_chip_data *chip_data = domain->host_data;
int index;
/* Find and allocate region from the SPI bitmap */
mutex_lock(&chip_data->map_lock);
index = bitmap_find_free_region(chip_data->spi_map,
HV_PCI_MSI_SPI_NR,
get_count_order(nr_irqs));
mutex_unlock(&chip_data->map_lock);
if (index < 0)
return -ENOSPC;
*hwirq = index + HV_PCI_MSI_SPI_START;
return 0;
}
static int hv_pci_vec_irq_gic_domain_alloc(struct irq_domain *domain,
unsigned int virq,
irq_hw_number_t hwirq)
{
struct irq_fwspec fwspec;
struct irq_data *d;
int ret;
fwspec.fwnode = domain->parent->fwnode;
fwspec.param_count = 2;
fwspec.param[0] = hwirq;
fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
if (ret)
return ret;
/*
* Since the interrupt specifier is not coming from ACPI or DT, the
* trigger type will need to be set explicitly. Otherwise, it will be
* set to whatever is in the GIC configuration.
*/
d = irq_domain_get_irq_data(domain->parent, virq);
return d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING);
}
static int hv_pci_vec_irq_domain_alloc(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs,
void *args)
{
irq_hw_number_t hwirq;
unsigned int i;
int ret;
ret = hv_pci_vec_alloc_device_irq(domain, nr_irqs, &hwirq);
if (ret)
return ret;
for (i = 0; i < nr_irqs; i++) {
ret = hv_pci_vec_irq_gic_domain_alloc(domain, virq + i,
hwirq + i);
if (ret) {
hv_pci_vec_irq_free(domain, virq, nr_irqs, i);
return ret;
}
irq_domain_set_hwirq_and_chip(domain, virq + i,
hwirq + i,
&hv_arm64_msi_irq_chip,
domain->host_data);
pr_debug("pID:%d vID:%u\n", (int)(hwirq + i), virq + i);
}
return 0;
}
/*
* Pick the first cpu as the irq affinity that can be temporarily used for
* composing MSI from the hypervisor. GIC will eventually set the right
* affinity for the irq and the 'unmask' will retarget the interrupt to that
* cpu.
*/
static int hv_pci_vec_irq_domain_activate(struct irq_domain *domain,
struct irq_data *irqd, bool reserve)
{
int cpu = cpumask_first(cpu_present_mask);
irq_data_update_effective_affinity(irqd, cpumask_of(cpu));
return 0;
}
static const struct irq_domain_ops hv_pci_domain_ops = {
.alloc = hv_pci_vec_irq_domain_alloc,
.free = hv_pci_vec_irq_domain_free,
.activate = hv_pci_vec_irq_domain_activate,
};
static int hv_pci_irqchip_init(void)
{
static struct hv_pci_chip_data *chip_data;
struct fwnode_handle *fn = NULL;
int ret = -ENOMEM;
chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL);
if (!chip_data)
return ret;
mutex_init(&chip_data->map_lock);
fn = irq_domain_alloc_named_fwnode("hv_vpci_arm64");
if (!fn)
goto free_chip;
/*
* IRQ domain once enabled, should not be removed since there is no
* way to ensure that all the corresponding devices are also gone and
* no interrupts will be generated.
*/
hv_msi_gic_irq_domain = acpi_irq_create_hierarchy(0, HV_PCI_MSI_SPI_NR,
fn, &hv_pci_domain_ops,
chip_data);
if (!hv_msi_gic_irq_domain) {
pr_err("Failed to create Hyper-V arm64 vPCI MSI IRQ domain\n");
goto free_chip;
}
return 0;
free_chip:
kfree(chip_data);
if (fn)
irq_domain_free_fwnode(fn);
return ret;
}
static struct irq_domain *hv_pci_get_root_domain(void)
{
return hv_msi_gic_irq_domain;
}
/*
* SPIs are used for interrupts of PCI devices and SPIs is managed via GICD
* registers which Hyper-V already supports, so no hypercall needed.
*/
static void hv_arch_irq_unmask(struct irq_data *data) { }
#endif /* CONFIG_ARM64 */
/**
* hv_pci_generic_compl() - Invoked for a completion packet
* @context: Set up by the sender of the packet.
* @resp: The response packet
* @resp_packet_size: Size in bytes of the packet
*
* This function is used to trigger an event and report status
* for any message for which the completion packet contains a
* status and nothing else.
*/
static void hv_pci_generic_compl(void *context, struct pci_response *resp,
int resp_packet_size)
{
struct hv_pci_compl *comp_pkt = context;
comp_pkt->completion_status = resp->status;
complete(&comp_pkt->host_event);
}
static struct hv_pci_dev *get_pcichild_wslot(struct hv_pcibus_device *hbus,
u32 wslot);
static void get_pcichild(struct hv_pci_dev *hpdev)
{
refcount_inc(&hpdev->refs);
}
static void put_pcichild(struct hv_pci_dev *hpdev)
{
if (refcount_dec_and_test(&hpdev->refs))
kfree(hpdev);
}
/*
* There is no good way to get notified from vmbus_onoffer_rescind(),
* so let's use polling here, since this is not a hot path.
*/
static int wait_for_response(struct hv_device *hdev,
struct completion *comp)
{
while (true) {
if (hdev->channel->rescind) {
dev_warn_once(&hdev->device, "The device is gone.\n");
return -ENODEV;
}
if (wait_for_completion_timeout(comp, HZ / 10))
break;
}
return 0;
}
/**
* devfn_to_wslot() - Convert from Linux PCI slot to Windows
* @devfn: The Linux representation of PCI slot
*
* Windows uses a slightly different representation of PCI slot.
*
* Return: The Windows representation
*/
static u32 devfn_to_wslot(int devfn)
{
union win_slot_encoding wslot;
wslot.slot = 0;
wslot.bits.dev = PCI_SLOT(devfn);
wslot.bits.func = PCI_FUNC(devfn);
return wslot.slot;
}
/**
* wslot_to_devfn() - Convert from Windows PCI slot to Linux
* @wslot: The Windows representation of PCI slot
*
* Windows uses a slightly different representation of PCI slot.
*
* Return: The Linux representation
*/
static int wslot_to_devfn(u32 wslot)
{
union win_slot_encoding slot_no;
slot_no.slot = wslot;
return PCI_DEVFN(slot_no.bits.dev, slot_no.bits.func);
}
static void hv_pci_read_mmio(struct device *dev, phys_addr_t gpa, int size, u32 *val)
{
struct hv_mmio_read_input *in;
struct hv_mmio_read_output *out;
u64 ret;
/*
* Must be called with interrupts disabled so it is safe
* to use the per-cpu input argument page. Use it for
* both input and output.
*/
in = *this_cpu_ptr(hyperv_pcpu_input_arg);
out = *this_cpu_ptr(hyperv_pcpu_input_arg) + sizeof(*in);
in->gpa = gpa;
in->size = size;
ret = hv_do_hypercall(HVCALL_MMIO_READ, in, out);
if (hv_result_success(ret)) {
switch (size) {
case 1:
*val = *(u8 *)(out->data);
break;
case 2:
*val = *(u16 *)(out->data);
break;
default:
*val = *(u32 *)(out->data);
break;
}
} else
dev_err(dev, "MMIO read hypercall error %llx addr %llx size %d\n",
ret, gpa, size);
}
static void hv_pci_write_mmio(struct device *dev, phys_addr_t gpa, int size, u32 val)
{
struct hv_mmio_write_input *in;
u64 ret;
/*
* Must be called with interrupts disabled so it is safe
* to use the per-cpu input argument memory.
*/
in = *this_cpu_ptr(hyperv_pcpu_input_arg);
in->gpa = gpa;
in->size = size;
switch (size) {
case 1:
*(u8 *)(in->data) = val;
break;
case 2:
*(u16 *)(in->data) = val;
break;
default:
*(u32 *)(in->data) = val;
break;
}
ret = hv_do_hypercall(HVCALL_MMIO_WRITE, in, NULL);
if (!hv_result_success(ret))
dev_err(dev, "MMIO write hypercall error %llx addr %llx size %d\n",
ret, gpa, size);
}
/*
* PCI Configuration Space for these root PCI buses is implemented as a pair
* of pages in memory-mapped I/O space. Writing to the first page chooses
* the PCI function being written or read. Once the first page has been
* written to, the following page maps in the entire configuration space of
* the function.
*/
/**
* _hv_pcifront_read_config() - Internal PCI config read
* @hpdev: The PCI driver's representation of the device
* @where: Offset within config space
* @size: Size of the transfer
* @val: Pointer to the buffer receiving the data
*/
static void _hv_pcifront_read_config(struct hv_pci_dev *hpdev, int where,
int size, u32 *val)
{
struct hv_pcibus_device *hbus = hpdev->hbus;
struct device *dev = &hbus->hdev->device;
int offset = where + CFG_PAGE_OFFSET;
unsigned long flags;
/*
* If the attempt is to read the IDs or the ROM BAR, simulate that.
*/
if (where + size <= PCI_COMMAND) {
memcpy(val, ((u8 *)&hpdev->desc.v_id) + where, size);
} else if (where >= PCI_CLASS_REVISION && where + size <=
PCI_CACHE_LINE_SIZE) {
memcpy(val, ((u8 *)&hpdev->desc.rev) + where -
PCI_CLASS_REVISION, size);
} else if (where >= PCI_SUBSYSTEM_VENDOR_ID && where + size <=
PCI_ROM_ADDRESS) {
memcpy(val, (u8 *)&hpdev->desc.subsystem_id + where -
PCI_SUBSYSTEM_VENDOR_ID, size);
} else if (where >= PCI_ROM_ADDRESS && where + size <=
PCI_CAPABILITY_LIST) {
/* ROM BARs are unimplemented */
*val = 0;
} else if (where >= PCI_INTERRUPT_LINE && where + size <=
PCI_INTERRUPT_PIN) {
/*
* Interrupt Line and Interrupt PIN are hard-wired to zero
* because this front-end only supports message-signaled
* interrupts.
*/
*val = 0;
} else if (where + size <= CFG_PAGE_SIZE) {
spin_lock_irqsave(&hbus->config_lock, flags);
if (hbus->use_calls) {
phys_addr_t addr = hbus->mem_config->start + offset;
hv_pci_write_mmio(dev, hbus->mem_config->start, 4,
hpdev->desc.win_slot.slot);
hv_pci_read_mmio(dev, addr, size, val);
} else {
void __iomem *addr = hbus->cfg_addr + offset;
/* Choose the function to be read. (See comment above) */
writel(hpdev->desc.win_slot.slot, hbus->cfg_addr);
/* Make sure the function was chosen before reading. */
mb();
/* Read from that function's config space. */
switch (size) {
case 1:
*val = readb(addr);
break;
case 2:
*val = readw(addr);
break;
default:
*val = readl(addr);
break;
}
/*
* Make sure the read was done before we release the
* spinlock allowing consecutive reads/writes.
*/
mb();
}
spin_unlock_irqrestore(&hbus->config_lock, flags);
} else {
dev_err(dev, "Attempt to read beyond a function's config space.\n");
}
}
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
static u16 hv_pcifront_get_vendor_id(struct hv_pci_dev *hpdev)
{
struct hv_pcibus_device *hbus = hpdev->hbus;
struct device *dev = &hbus->hdev->device;
u32 val;
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
u16 ret;
unsigned long flags;
spin_lock_irqsave(&hbus->config_lock, flags);
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
if (hbus->use_calls) {
phys_addr_t addr = hbus->mem_config->start +
CFG_PAGE_OFFSET + PCI_VENDOR_ID;
hv_pci_write_mmio(dev, hbus->mem_config->start, 4,
hpdev->desc.win_slot.slot);
hv_pci_read_mmio(dev, addr, 2, &val);
ret = val; /* Truncates to 16 bits */
} else {
void __iomem *addr = hbus->cfg_addr + CFG_PAGE_OFFSET +
PCI_VENDOR_ID;
/* Choose the function to be read. (See comment above) */
writel(hpdev->desc.win_slot.slot, hbus->cfg_addr);
/* Make sure the function was chosen before we start reading. */
mb();
/* Read from that function's config space. */
ret = readw(addr);
/*
* mb() is not required here, because the
* spin_unlock_irqrestore() is a barrier.
*/
}
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
spin_unlock_irqrestore(&hbus->config_lock, flags);
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
return ret;
}
/**
* _hv_pcifront_write_config() - Internal PCI config write
* @hpdev: The PCI driver's representation of the device
* @where: Offset within config space
* @size: Size of the transfer
* @val: The data being transferred
*/
static void _hv_pcifront_write_config(struct hv_pci_dev *hpdev, int where,
int size, u32 val)
{
struct hv_pcibus_device *hbus = hpdev->hbus;
struct device *dev = &hbus->hdev->device;
int offset = where + CFG_PAGE_OFFSET;
unsigned long flags;
if (where >= PCI_SUBSYSTEM_VENDOR_ID &&
where + size <= PCI_CAPABILITY_LIST) {
/* SSIDs and ROM BARs are read-only */
} else if (where >= PCI_COMMAND && where + size <= CFG_PAGE_SIZE) {
spin_lock_irqsave(&hbus->config_lock, flags);
if (hbus->use_calls) {
phys_addr_t addr = hbus->mem_config->start + offset;
hv_pci_write_mmio(dev, hbus->mem_config->start, 4,
hpdev->desc.win_slot.slot);
hv_pci_write_mmio(dev, addr, size, val);
} else {
void __iomem *addr = hbus->cfg_addr + offset;
/* Choose the function to write. (See comment above) */
writel(hpdev->desc.win_slot.slot, hbus->cfg_addr);
/* Make sure the function was chosen before writing. */
wmb();
/* Write to that function's config space. */
switch (size) {
case 1:
writeb(val, addr);
break;
case 2:
writew(val, addr);
break;
default:
writel(val, addr);
break;
}
/*
* Make sure the write was done before we release the
* spinlock allowing consecutive reads/writes.
*/
mb();
}
spin_unlock_irqrestore(&hbus->config_lock, flags);
} else {
dev_err(dev, "Attempt to write beyond a function's config space.\n");
}
}
/**
* hv_pcifront_read_config() - Read configuration space
* @bus: PCI Bus structure
* @devfn: Device/function
* @where: Offset from base
* @size: Byte/word/dword
* @val: Value to be read
*
* Return: PCIBIOS_SUCCESSFUL on success
* PCIBIOS_DEVICE_NOT_FOUND on failure
*/
static int hv_pcifront_read_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *val)
{
struct hv_pcibus_device *hbus =
container_of(bus->sysdata, struct hv_pcibus_device, sysdata);
struct hv_pci_dev *hpdev;
hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(devfn));
if (!hpdev)
return PCIBIOS_DEVICE_NOT_FOUND;
_hv_pcifront_read_config(hpdev, where, size, val);
put_pcichild(hpdev);
return PCIBIOS_SUCCESSFUL;
}
/**
* hv_pcifront_write_config() - Write configuration space
* @bus: PCI Bus structure
* @devfn: Device/function
* @where: Offset from base
* @size: Byte/word/dword
* @val: Value to be written to device
*
* Return: PCIBIOS_SUCCESSFUL on success
* PCIBIOS_DEVICE_NOT_FOUND on failure
*/
static int hv_pcifront_write_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 val)
{
struct hv_pcibus_device *hbus =
container_of(bus->sysdata, struct hv_pcibus_device, sysdata);
struct hv_pci_dev *hpdev;
hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(devfn));
if (!hpdev)
return PCIBIOS_DEVICE_NOT_FOUND;
_hv_pcifront_write_config(hpdev, where, size, val);
put_pcichild(hpdev);
return PCIBIOS_SUCCESSFUL;
}
/* PCIe operations */
static struct pci_ops hv_pcifront_ops = {
.read = hv_pcifront_read_config,
.write = hv_pcifront_write_config,
};
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
/*
* Paravirtual backchannel
*
* Hyper-V SR-IOV provides a backchannel mechanism in software for
* communication between a VF driver and a PF driver. These
* "configuration blocks" are similar in concept to PCI configuration space,
* but instead of doing reads and writes in 32-bit chunks through a very slow
* path, packets of up to 128 bytes can be sent or received asynchronously.
*
* Nearly every SR-IOV device contains just such a communications channel in
* hardware, so using this one in software is usually optional. Using the
* software channel, however, allows driver implementers to leverage software
* tools that fuzz the communications channel looking for vulnerabilities.
*
* The usage model for these packets puts the responsibility for reading or
* writing on the VF driver. The VF driver sends a read or a write packet,
* indicating which "block" is being referred to by number.
*
* If the PF driver wishes to initiate communication, it can "invalidate" one or
* more of the first 64 blocks. This invalidation is delivered via a callback
* supplied by the VF driver by this driver.
*
* No protocol is implied, except that supplied by the PF and VF drivers.
*/
struct hv_read_config_compl {
struct hv_pci_compl comp_pkt;
void *buf;
unsigned int len;
unsigned int bytes_returned;
};
/**
* hv_pci_read_config_compl() - Invoked when a response packet
* for a read config block operation arrives.
* @context: Identifies the read config operation
* @resp: The response packet itself
* @resp_packet_size: Size in bytes of the response packet
*/
static void hv_pci_read_config_compl(void *context, struct pci_response *resp,
int resp_packet_size)
{
struct hv_read_config_compl *comp = context;
struct pci_read_block_response *read_resp =
(struct pci_read_block_response *)resp;
unsigned int data_len, hdr_len;
hdr_len = offsetof(struct pci_read_block_response, bytes);
if (resp_packet_size < hdr_len) {
comp->comp_pkt.completion_status = -1;
goto out;
}
data_len = resp_packet_size - hdr_len;
if (data_len > 0 && read_resp->status == 0) {
comp->bytes_returned = min(comp->len, data_len);
memcpy(comp->buf, read_resp->bytes, comp->bytes_returned);
} else {
comp->bytes_returned = 0;
}
comp->comp_pkt.completion_status = read_resp->status;
out:
complete(&comp->comp_pkt.host_event);
}
/**
* hv_read_config_block() - Sends a read config block request to
* the back-end driver running in the Hyper-V parent partition.
* @pdev: The PCI driver's representation for this device.
* @buf: Buffer into which the config block will be copied.
* @len: Size in bytes of buf.
* @block_id: Identifies the config block which has been requested.
* @bytes_returned: Size which came back from the back-end driver.
*
* Return: 0 on success, -errno on failure
*/
static int hv_read_config_block(struct pci_dev *pdev, void *buf,
unsigned int len, unsigned int block_id,
unsigned int *bytes_returned)
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
{
struct hv_pcibus_device *hbus =
container_of(pdev->bus->sysdata, struct hv_pcibus_device,
sysdata);
struct {
struct pci_packet pkt;
char buf[sizeof(struct pci_read_block)];
} pkt;
struct hv_read_config_compl comp_pkt;
struct pci_read_block *read_blk;
int ret;
if (len == 0 || len > HV_CONFIG_BLOCK_SIZE_MAX)
return -EINVAL;
init_completion(&comp_pkt.comp_pkt.host_event);
comp_pkt.buf = buf;
comp_pkt.len = len;
memset(&pkt, 0, sizeof(pkt));
pkt.pkt.completion_func = hv_pci_read_config_compl;
pkt.pkt.compl_ctxt = &comp_pkt;
read_blk = (struct pci_read_block *)&pkt.pkt.message;
read_blk->message_type.type = PCI_READ_BLOCK;
read_blk->wslot.slot = devfn_to_wslot(pdev->devfn);
read_blk->block_id = block_id;
read_blk->bytes_requested = len;
ret = vmbus_sendpacket(hbus->hdev->channel, read_blk,
sizeof(*read_blk), (unsigned long)&pkt.pkt,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (ret)
return ret;
ret = wait_for_response(hbus->hdev, &comp_pkt.comp_pkt.host_event);
if (ret)
return ret;
if (comp_pkt.comp_pkt.completion_status != 0 ||
comp_pkt.bytes_returned == 0) {
dev_err(&hbus->hdev->device,
"Read Config Block failed: 0x%x, bytes_returned=%d\n",
comp_pkt.comp_pkt.completion_status,
comp_pkt.bytes_returned);
return -EIO;
}
*bytes_returned = comp_pkt.bytes_returned;
return 0;
}
/**
* hv_pci_write_config_compl() - Invoked when a response packet for a write
* config block operation arrives.
* @context: Identifies the write config operation
* @resp: The response packet itself
* @resp_packet_size: Size in bytes of the response packet
*/
static void hv_pci_write_config_compl(void *context, struct pci_response *resp,
int resp_packet_size)
{
struct hv_pci_compl *comp_pkt = context;
comp_pkt->completion_status = resp->status;
complete(&comp_pkt->host_event);
}
/**
* hv_write_config_block() - Sends a write config block request to the
* back-end driver running in the Hyper-V parent partition.
* @pdev: The PCI driver's representation for this device.
* @buf: Buffer from which the config block will be copied.
* @len: Size in bytes of buf.
* @block_id: Identifies the config block which is being written.
*
* Return: 0 on success, -errno on failure
*/
static int hv_write_config_block(struct pci_dev *pdev, void *buf,
unsigned int len, unsigned int block_id)
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
{
struct hv_pcibus_device *hbus =
container_of(pdev->bus->sysdata, struct hv_pcibus_device,
sysdata);
struct {
struct pci_packet pkt;
char buf[sizeof(struct pci_write_block)];
u32 reserved;
} pkt;
struct hv_pci_compl comp_pkt;
struct pci_write_block *write_blk;
u32 pkt_size;
int ret;
if (len == 0 || len > HV_CONFIG_BLOCK_SIZE_MAX)
return -EINVAL;
init_completion(&comp_pkt.host_event);
memset(&pkt, 0, sizeof(pkt));
pkt.pkt.completion_func = hv_pci_write_config_compl;
pkt.pkt.compl_ctxt = &comp_pkt;
write_blk = (struct pci_write_block *)&pkt.pkt.message;
write_blk->message_type.type = PCI_WRITE_BLOCK;
write_blk->wslot.slot = devfn_to_wslot(pdev->devfn);
write_blk->block_id = block_id;
write_blk->byte_count = len;
memcpy(write_blk->bytes, buf, len);
pkt_size = offsetof(struct pci_write_block, bytes) + len;
/*
* This quirk is required on some hosts shipped around 2018, because
* these hosts don't check the pkt_size correctly (new hosts have been
* fixed since early 2019). The quirk is also safe on very old hosts
* and new hosts, because, on them, what really matters is the length
* specified in write_blk->byte_count.
*/
pkt_size += sizeof(pkt.reserved);
ret = vmbus_sendpacket(hbus->hdev->channel, write_blk, pkt_size,
(unsigned long)&pkt.pkt, VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (ret)
return ret;
ret = wait_for_response(hbus->hdev, &comp_pkt.host_event);
if (ret)
return ret;
if (comp_pkt.completion_status != 0) {
dev_err(&hbus->hdev->device,
"Write Config Block failed: 0x%x\n",
comp_pkt.completion_status);
return -EIO;
}
return 0;
}
/**
* hv_register_block_invalidate() - Invoked when a config block invalidation
* arrives from the back-end driver.
* @pdev: The PCI driver's representation for this device.
* @context: Identifies the device.
* @block_invalidate: Identifies all of the blocks being invalidated.
*
* Return: 0 on success, -errno on failure
*/
static int hv_register_block_invalidate(struct pci_dev *pdev, void *context,
void (*block_invalidate)(void *context,
u64 block_mask))
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
{
struct hv_pcibus_device *hbus =
container_of(pdev->bus->sysdata, struct hv_pcibus_device,
sysdata);
struct hv_pci_dev *hpdev;
hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(pdev->devfn));
if (!hpdev)
return -ENODEV;
hpdev->block_invalidate = block_invalidate;
hpdev->invalidate_context = context;
put_pcichild(hpdev);
return 0;
}
/* Interrupt management hooks */
static void hv_int_desc_free(struct hv_pci_dev *hpdev,
struct tran_int_desc *int_desc)
{
struct pci_delete_interrupt *int_pkt;
struct {
struct pci_packet pkt;
u8 buffer[sizeof(struct pci_delete_interrupt)];
} ctxt;
if (!int_desc->vector_count) {
kfree(int_desc);
return;
}
memset(&ctxt, 0, sizeof(ctxt));
int_pkt = (struct pci_delete_interrupt *)&ctxt.pkt.message;
int_pkt->message_type.type =
PCI_DELETE_INTERRUPT_MESSAGE;
int_pkt->wslot.slot = hpdev->desc.win_slot.slot;
int_pkt->int_desc = *int_desc;
vmbus_sendpacket(hpdev->hbus->hdev->channel, int_pkt, sizeof(*int_pkt),
0, VM_PKT_DATA_INBAND, 0);
kfree(int_desc);
}
/**
* hv_msi_free() - Free the MSI.
* @domain: The interrupt domain pointer
* @info: Extra MSI-related context
* @irq: Identifies the IRQ.
*
* The Hyper-V parent partition and hypervisor are tracking the
* messages that are in use, keeping the interrupt redirection
* table up to date. This callback sends a message that frees
* the IRT entry and related tracking nonsense.
*/
static void hv_msi_free(struct irq_domain *domain, struct msi_domain_info *info,
unsigned int irq)
{
struct hv_pcibus_device *hbus;
struct hv_pci_dev *hpdev;
struct pci_dev *pdev;
struct tran_int_desc *int_desc;
struct irq_data *irq_data = irq_domain_get_irq_data(domain, irq);
struct msi_desc *msi = irq_data_get_msi_desc(irq_data);
pdev = msi_desc_to_pci_dev(msi);
hbus = info->data;
int_desc = irq_data_get_irq_chip_data(irq_data);
if (!int_desc)
return;
irq_data->chip_data = NULL;
hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(pdev->devfn));
if (!hpdev) {
kfree(int_desc);
return;
}
hv_int_desc_free(hpdev, int_desc);
put_pcichild(hpdev);
}
static void hv_irq_mask(struct irq_data *data)
{
pci_msi_mask_irq(data);
if (data->parent_data->chip->irq_mask)
irq_chip_mask_parent(data);
}
static void hv_irq_unmask(struct irq_data *data)
{
hv_arch_irq_unmask(data);
if (data->parent_data->chip->irq_unmask)
irq_chip_unmask_parent(data);
pci_msi_unmask_irq(data);
}
struct compose_comp_ctxt {
struct hv_pci_compl comp_pkt;
struct tran_int_desc int_desc;
};
static void hv_pci_compose_compl(void *context, struct pci_response *resp,
int resp_packet_size)
{
struct compose_comp_ctxt *comp_pkt = context;
struct pci_create_int_response *int_resp =
(struct pci_create_int_response *)resp;
if (resp_packet_size < sizeof(*int_resp)) {
comp_pkt->comp_pkt.completion_status = -1;
goto out;
}
comp_pkt->comp_pkt.completion_status = resp->status;
comp_pkt->int_desc = int_resp->int_desc;
out:
complete(&comp_pkt->comp_pkt.host_event);
}
static u32 hv_compose_msi_req_v1(
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
struct pci_create_interrupt *int_pkt,
u32 slot, u8 vector, u16 vector_count)
{
int_pkt->message_type.type = PCI_CREATE_INTERRUPT_MESSAGE;
int_pkt->wslot.slot = slot;
int_pkt->int_desc.vector = vector;
int_pkt->int_desc.vector_count = vector_count;
int_pkt->int_desc.delivery_mode = DELIVERY_MODE;
/*
* Create MSI w/ dummy vCPU set, overwritten by subsequent retarget in
* hv_irq_unmask().
*/
int_pkt->int_desc.cpu_mask = CPU_AFFINITY_ALL;
return sizeof(*int_pkt);
}
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
/*
* The vCPU selected by hv_compose_multi_msi_req_get_cpu() and
* hv_compose_msi_req_get_cpu() is a "dummy" vCPU because the final vCPU to be
* interrupted is specified later in hv_irq_unmask() and communicated to Hyper-V
* via the HVCALL_RETARGET_INTERRUPT hypercall. But the choice of dummy vCPU is
* not irrelevant because Hyper-V chooses the physical CPU to handle the
* interrupts based on the vCPU specified in message sent to the vPCI VSP in
* hv_compose_msi_msg(). Hyper-V's choice of pCPU is not visible to the guest,
* but assigning too many vPCI device interrupts to the same pCPU can cause a
* performance bottleneck. So we spread out the dummy vCPUs to influence Hyper-V
* to spread out the pCPUs that it selects.
*
* For the single-MSI and MSI-X cases, it's OK for hv_compose_msi_req_get_cpu()
* to always return the same dummy vCPU, because a second call to
* hv_compose_msi_msg() contains the "real" vCPU, causing Hyper-V to choose a
* new pCPU for the interrupt. But for the multi-MSI case, the second call to
* hv_compose_msi_msg() exits without sending a message to the vPCI VSP, so the
* original dummy vCPU is used. This dummy vCPU must be round-robin'ed so that
* the pCPUs are spread out. All interrupts for a multi-MSI device end up using
* the same pCPU, even though the vCPUs will be spread out by later calls
* to hv_irq_unmask(), but that is the best we can do now.
*
* With Hyper-V in Nov 2022, the HVCALL_RETARGET_INTERRUPT hypercall does *not*
* cause Hyper-V to reselect the pCPU based on the specified vCPU. Such an
* enhancement is planned for a future version. With that enhancement, the
* dummy vCPU selection won't matter, and interrupts for the same multi-MSI
* device will be spread across multiple pCPUs.
*/
/*
* Create MSI w/ dummy vCPU set targeting just one vCPU, overwritten
* by subsequent retarget in hv_irq_unmask().
*/
static int hv_compose_msi_req_get_cpu(const struct cpumask *affinity)
{
return cpumask_first_and(affinity, cpu_online_mask);
}
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
/*
* Make sure the dummy vCPU values for multi-MSI don't all point to vCPU0.
*/
static int hv_compose_multi_msi_req_get_cpu(void)
{
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
static DEFINE_SPINLOCK(multi_msi_cpu_lock);
/* -1 means starting with CPU 0 */
static int cpu_next = -1;
unsigned long flags;
int cpu;
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
spin_lock_irqsave(&multi_msi_cpu_lock, flags);
cpu_next = cpumask_next_wrap(cpu_next, cpu_online_mask, nr_cpu_ids,
false);
cpu = cpu_next;
spin_unlock_irqrestore(&multi_msi_cpu_lock, flags);
return cpu;
}
static u32 hv_compose_msi_req_v2(
struct pci_create_interrupt2 *int_pkt, int cpu,
u32 slot, u8 vector, u16 vector_count)
{
int_pkt->message_type.type = PCI_CREATE_INTERRUPT_MESSAGE2;
int_pkt->wslot.slot = slot;
int_pkt->int_desc.vector = vector;
int_pkt->int_desc.vector_count = vector_count;
int_pkt->int_desc.delivery_mode = DELIVERY_MODE;
int_pkt->int_desc.processor_array[0] =
hv_cpu_number_to_vp_number(cpu);
int_pkt->int_desc.processor_count = 1;
return sizeof(*int_pkt);
}
static u32 hv_compose_msi_req_v3(
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
struct pci_create_interrupt3 *int_pkt, int cpu,
u32 slot, u32 vector, u16 vector_count)
{
int_pkt->message_type.type = PCI_CREATE_INTERRUPT_MESSAGE3;
int_pkt->wslot.slot = slot;
int_pkt->int_desc.vector = vector;
int_pkt->int_desc.reserved = 0;
int_pkt->int_desc.vector_count = vector_count;
int_pkt->int_desc.delivery_mode = DELIVERY_MODE;
int_pkt->int_desc.processor_array[0] =
hv_cpu_number_to_vp_number(cpu);
int_pkt->int_desc.processor_count = 1;
return sizeof(*int_pkt);
}
/**
* hv_compose_msi_msg() - Supplies a valid MSI address/data
* @data: Everything about this MSI
* @msg: Buffer that is filled in by this function
*
* This function unpacks the IRQ looking for target CPU set, IDT
* vector and mode and sends a message to the parent partition
* asking for a mapping for that tuple in this partition. The
* response supplies a data value and address to which that data
* should be written to trigger that interrupt.
*/
static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
{
struct hv_pcibus_device *hbus;
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
struct vmbus_channel *channel;
struct hv_pci_dev *hpdev;
struct pci_bus *pbus;
struct pci_dev *pdev;
const struct cpumask *dest;
struct compose_comp_ctxt comp;
struct tran_int_desc *int_desc;
struct msi_desc *msi_desc;
/*
* vector_count should be u16: see hv_msi_desc, hv_msi_desc2
* and hv_msi_desc3. vector must be u32: see hv_msi_desc3.
*/
u16 vector_count;
u32 vector;
struct {
struct pci_packet pci_pkt;
union {
struct pci_create_interrupt v1;
struct pci_create_interrupt2 v2;
struct pci_create_interrupt3 v3;
} int_pkts;
} __packed ctxt;
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
bool multi_msi;
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
u64 trans_id;
u32 size;
int ret;
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
int cpu;
msi_desc = irq_data_get_msi_desc(data);
multi_msi = !msi_desc->pci.msi_attrib.is_msix &&
msi_desc->nvec_used > 1;
/* Reuse the previous allocation */
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
if (data->chip_data && multi_msi) {
int_desc = data->chip_data;
msg->address_hi = int_desc->address >> 32;
msg->address_lo = int_desc->address & 0xffffffff;
msg->data = int_desc->data;
return;
}
pdev = msi_desc_to_pci_dev(msi_desc);
dest = irq_data_get_effective_affinity_mask(data);
pbus = pdev->bus;
hbus = container_of(pbus->sysdata, struct hv_pcibus_device, sysdata);
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
channel = hbus->hdev->channel;
hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(pdev->devfn));
if (!hpdev)
goto return_null_message;
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
/* Free any previous message that might have already been composed. */
if (data->chip_data && !multi_msi) {
int_desc = data->chip_data;
data->chip_data = NULL;
hv_int_desc_free(hpdev, int_desc);
}
int_desc = kzalloc(sizeof(*int_desc), GFP_ATOMIC);
if (!int_desc)
goto drop_reference;
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
if (multi_msi) {
/*
* If this is not the first MSI of Multi MSI, we already have
* a mapping. Can exit early.
*/
if (msi_desc->irq != data->irq) {
data->chip_data = int_desc;
int_desc->address = msi_desc->msg.address_lo |
(u64)msi_desc->msg.address_hi << 32;
int_desc->data = msi_desc->msg.data +
(data->irq - msi_desc->irq);
msg->address_hi = msi_desc->msg.address_hi;
msg->address_lo = msi_desc->msg.address_lo;
msg->data = int_desc->data;
put_pcichild(hpdev);
return;
}
/*
* The vector we select here is a dummy value. The correct
* value gets sent to the hypervisor in unmask(). This needs
* to be aligned with the count, and also not zero. Multi-msi
* is powers of 2 up to 32, so 32 will always work here.
*/
vector = 32;
vector_count = msi_desc->nvec_used;
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
cpu = hv_compose_multi_msi_req_get_cpu();
} else {
vector = hv_msi_get_int_vector(data);
vector_count = 1;
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
cpu = hv_compose_msi_req_get_cpu(dest);
}
/*
* hv_compose_msi_req_v1 and v2 are for x86 only, meaning 'vector'
* can't exceed u8. Cast 'vector' down to u8 for v1/v2 explicitly
* for better readability.
*/
memset(&ctxt, 0, sizeof(ctxt));
init_completion(&comp.comp_pkt.host_event);
ctxt.pci_pkt.completion_func = hv_pci_compose_compl;
ctxt.pci_pkt.compl_ctxt = &comp;
switch (hbus->protocol_version) {
case PCI_PROTOCOL_VERSION_1_1:
size = hv_compose_msi_req_v1(&ctxt.int_pkts.v1,
hpdev->desc.win_slot.slot,
(u8)vector,
vector_count);
break;
case PCI_PROTOCOL_VERSION_1_2:
case PCI_PROTOCOL_VERSION_1_3:
size = hv_compose_msi_req_v2(&ctxt.int_pkts.v2,
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
cpu,
hpdev->desc.win_slot.slot,
(u8)vector,
vector_count);
break;
case PCI_PROTOCOL_VERSION_1_4:
size = hv_compose_msi_req_v3(&ctxt.int_pkts.v3,
PCI: hv: Only reuse existing IRTE allocation for Multi-MSI Jeffrey added Multi-MSI support to the pci-hyperv driver by the 4 patches: 08e61e861a0e ("PCI: hv: Fix multi-MSI to allow more than one MSI vector") 455880dfe292 ("PCI: hv: Fix hv_arch_irq_unmask() for multi-MSI") b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") a2bad844a67b ("PCI: hv: Fix interrupt mapping for multi-MSI") It turns out that the third patch (b4b77778ecc5) causes a performance regression because all the interrupts now happen on 1 physical CPU (or two pCPUs, if one pCPU doesn't have enough vectors). When a guest has many PCI devices, it may suffer from soft lockups if the workload is heavy, e.g., see https://lwn.net/ml/linux-kernel/20220804025104.15673-1-decui@microsoft.com/ Commit b4b77778ecc5 itself is good. The real issue is that the hypercall in hv_irq_unmask() -> hv_arch_irq_unmask() -> hv_do_hypercall(HVCALL_RETARGET_INTERRUPT...) only changes the target virtual CPU rather than physical CPU; with b4b77778ecc5, the pCPU is determined only once in hv_compose_msi_msg() where only vCPU0 is specified; consequently the hypervisor only uses 1 target pCPU for all the interrupts. Note: before b4b77778ecc5, the pCPU is determined twice, and when the pCPU is determined the second time, the vCPU in the effective affinity mask is used (i.e., it isn't always vCPU0), so the hypervisor chooses different pCPU for each interrupt. The hypercall will be fixed in future to update the pCPU as well, but that will take quite a while, so let's restore the old behavior in hv_compose_msi_msg(), i.e., don't reuse the existing IRTE allocation for single-MSI and MSI-X; for multi-MSI, we choose the vCPU in a round-robin manner for each PCI device, so the interrupts of different devices can happen on different pCPUs, though the interrupts of each device happen on some single pCPU. The hypercall fix may not be backported to all old versions of Hyper-V, so we want to have this guest side change forever (or at least till we're sure the old affected versions of Hyper-V are no longer supported). Fixes: b4b77778ecc5 ("PCI: hv: Reuse existing IRTE allocation in compose_msi_msg()") Co-developed-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Signed-off-by: Jeffrey Hugo <quic_jhugo@quicinc.com> Co-developed-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Carl Vanderlip <quic_carlv@quicinc.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20221104222953.11356-1-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-11-04 22:29:53 +00:00
cpu,
hpdev->desc.win_slot.slot,
vector,
vector_count);
break;
default:
/* As we only negotiate protocol versions known to this driver,
* this path should never hit. However, this is it not a hot
* path so we print a message to aid future updates.
*/
dev_err(&hbus->hdev->device,
"Unexpected vPCI protocol, update driver.");
goto free_int_desc;
}
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
ret = vmbus_sendpacket_getid(hpdev->hbus->hdev->channel, &ctxt.int_pkts,
size, (unsigned long)&ctxt.pci_pkt,
&trans_id, VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (ret) {
dev_err(&hbus->hdev->device,
"Sending request for interrupt failed: 0x%x",
comp.comp_pkt.completion_status);
goto free_int_desc;
}
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
/*
* Prevents hv_pci_onchannelcallback() from running concurrently
* in the tasklet.
*/
tasklet_disable_in_atomic(&channel->callback_event);
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
/*
* Since this function is called with IRQ locks held, can't
* do normal wait for completion; instead poll.
*/
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
while (!try_wait_for_completion(&comp.comp_pkt.host_event)) {
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
unsigned long flags;
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
/* 0xFFFF means an invalid PCI VENDOR ID. */
if (hv_pcifront_get_vendor_id(hpdev) == 0xFFFF) {
dev_err_once(&hbus->hdev->device,
"the device has gone\n");
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
goto enable_tasklet;
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
}
/*
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
* Make sure that the ring buffer data structure doesn't get
* freed while we dereference the ring buffer pointer. Test
* for the channel's onchannel_callback being NULL within a
* sched_lock critical section. See also the inline comments
* in vmbus_reset_channel_cb().
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
*/
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
spin_lock_irqsave(&channel->sched_lock, flags);
if (unlikely(channel->onchannel_callback == NULL)) {
spin_unlock_irqrestore(&channel->sched_lock, flags);
goto enable_tasklet;
}
hv_pci_onchannelcallback(hbus);
spin_unlock_irqrestore(&channel->sched_lock, flags);
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
udelay(100);
PCI: hv: Fix 2 hang issues in hv_compose_msi_msg() 1. With the patch "x86/vector/msi: Switch to global reservation mode", the recent v4.15 and newer kernels always hang for 1-vCPU Hyper-V VM with SR-IOV. This is because when we reach hv_compose_msi_msg() by request_irq() -> request_threaded_irq() ->__setup_irq()->irq_startup() -> __irq_startup() -> irq_domain_activate_irq() -> ... -> msi_domain_activate() -> ... -> hv_compose_msi_msg(), local irq is disabled in __setup_irq(). Note: when we reach hv_compose_msi_msg() by another code path: pci_enable_msix_range() -> ... -> irq_domain_activate_irq() -> ... -> hv_compose_msi_msg(), local irq is not disabled. hv_compose_msi_msg() depends on an interrupt from the host. With interrupts disabled, a UP VM always hangs in the busy loop in the function, because the interrupt callback hv_pci_onchannelcallback() can not be called. We can do nothing but work it around by polling the channel. This is ugly, but we don't have any other choice. 2. If the host is ejecting the VF device before we reach hv_compose_msi_msg(), in a UP VM, we can hang in hv_compose_msi_msg() forever, because at this time the host doesn't respond to the CREATE_INTERRUPT request. This issue exists the first day the pci-hyperv driver appears in the kernel. Luckily, this can also by worked around by polling the channel for the PCI_EJECT message and hpdev->state, and by checking the PCI vendor ID. Note: actually the above 2 issues also happen to a SMP VM, if "hbus->hdev->channel->target_cpu == smp_processor_id()" is true. Fixes: 4900be83602b ("x86/vector/msi: Switch to global reservation mode") Tested-by: Adrian Suhov <v-adsuho@microsoft.com> Tested-by: Chris Valean <v-chvale@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: <stable@vger.kernel.org> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Vitaly Kuznetsov <vkuznets@redhat.com> Cc: Jack Morgenstein <jackm@mellanox.com>
2018-03-15 14:21:08 +00:00
}
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
tasklet_enable(&channel->callback_event);
if (comp.comp_pkt.completion_status < 0) {
dev_err(&hbus->hdev->device,
"Request for interrupt failed: 0x%x",
comp.comp_pkt.completion_status);
goto free_int_desc;
}
/*
* Record the assignment so that this can be unwound later. Using
* irq_set_chip_data() here would be appropriate, but the lock it takes
* is already held.
*/
*int_desc = comp.int_desc;
data->chip_data = int_desc;
/* Pass up the result. */
msg->address_hi = comp.int_desc.address >> 32;
msg->address_lo = comp.int_desc.address & 0xffffffff;
msg->data = comp.int_desc.data;
put_pcichild(hpdev);
return;
PCI: hv: Prepare hv_compose_msi_msg() for the VMBus-channel-interrupt-to-vCPU reassignment functionality The current implementation of hv_compose_msi_msg() is incompatible with the new functionality that allows changing the vCPU a VMBus channel will interrupt: if this function always calls hv_pci_onchannelcallback() in the polling loop, the interrupt going to a different CPU could cause hv_pci_onchannelcallback() to be running simultaneously in a tasklet, which will break. The current code also has a problem in that it is not synchronized with vmbus_reset_channel_cb(): hv_compose_msi_msg() could be accessing the ring buffer via the call of hv_pci_onchannelcallback() well after the time that vmbus_reset_channel_cb() has finished. Fix these issues as follows. Disable the channel tasklet before entering the polling loop in hv_compose_msi_msg() and re-enable it when done. This will prevent hv_pci_onchannelcallback() from running in a tasklet on a different CPU. Moreover, poll by always calling hv_pci_onchannelcallback(), but check the channel callback function for NULL and invoke the callback within a sched_lock critical section. This will prevent hv_compose_msi_msg() from accessing the ring buffer after vmbus_reset_channel_cb() has acquired the sched_lock spinlock. Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andrew Murray <amurray@thegoodpenguin.co.uk> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: <linux-pci@vger.kernel.org> Link: https://lore.kernel.org/r/20200406001514.19876-8-parri.andrea@gmail.com Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
2020-04-06 00:15:10 +00:00
enable_tasklet:
tasklet_enable(&channel->callback_event);
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
/*
* The completion packet on the stack becomes invalid after 'return';
* remove the ID from the VMbus requestor if the identifier is still
* mapped to/associated with the packet. (The identifier could have
* been 're-used', i.e., already removed and (re-)mapped.)
*
* Cf. hv_pci_onchannelcallback().
*/
vmbus_request_addr_match(channel, trans_id, (unsigned long)&ctxt.pci_pkt);
free_int_desc:
kfree(int_desc);
drop_reference:
put_pcichild(hpdev);
return_null_message:
msg->address_hi = 0;
msg->address_lo = 0;
msg->data = 0;
}
/* HW Interrupt Chip Descriptor */
static struct irq_chip hv_msi_irq_chip = {
.name = "Hyper-V PCIe MSI",
.irq_compose_msi_msg = hv_compose_msi_msg,
.irq_set_affinity = irq_chip_set_affinity_parent,
#ifdef CONFIG_X86
.irq_ack = irq_chip_ack_parent,
#elif defined(CONFIG_ARM64)
.irq_eoi = irq_chip_eoi_parent,
#endif
.irq_mask = hv_irq_mask,
.irq_unmask = hv_irq_unmask,
};
static struct msi_domain_ops hv_msi_ops = {
.msi_prepare = hv_msi_prepare,
.msi_free = hv_msi_free,
};
/**
* hv_pcie_init_irq_domain() - Initialize IRQ domain
* @hbus: The root PCI bus
*
* This function creates an IRQ domain which will be used for
* interrupts from devices that have been passed through. These
* devices only support MSI and MSI-X, not line-based interrupts
* or simulations of line-based interrupts through PCIe's
* fabric-layer messages. Because interrupts are remapped, we
* can support multi-message MSI here.
*
* Return: '0' on success and error value on failure
*/
static int hv_pcie_init_irq_domain(struct hv_pcibus_device *hbus)
{
hbus->msi_info.chip = &hv_msi_irq_chip;
hbus->msi_info.ops = &hv_msi_ops;
hbus->msi_info.flags = (MSI_FLAG_USE_DEF_DOM_OPS |
MSI_FLAG_USE_DEF_CHIP_OPS | MSI_FLAG_MULTI_PCI_MSI |
MSI_FLAG_PCI_MSIX);
hbus->msi_info.handler = FLOW_HANDLER;
hbus->msi_info.handler_name = FLOW_NAME;
hbus->msi_info.data = hbus;
hbus->irq_domain = pci_msi_create_irq_domain(hbus->fwnode,
&hbus->msi_info,
hv_pci_get_root_domain());
if (!hbus->irq_domain) {
dev_err(&hbus->hdev->device,
"Failed to build an MSI IRQ domain\n");
return -ENODEV;
}
dev_set_msi_domain(&hbus->bridge->dev, hbus->irq_domain);
return 0;
}
/**
* get_bar_size() - Get the address space consumed by a BAR
* @bar_val: Value that a BAR returned after -1 was written
* to it.
*
* This function returns the size of the BAR, rounded up to 1
* page. It has to be rounded up because the hypervisor's page
* table entry that maps the BAR into the VM can't specify an
* offset within a page. The invariant is that the hypervisor
* must place any BARs of smaller than page length at the
* beginning of a page.
*
* Return: Size in bytes of the consumed MMIO space.
*/
static u64 get_bar_size(u64 bar_val)
{
return round_up((1 + ~(bar_val & PCI_BASE_ADDRESS_MEM_MASK)),
PAGE_SIZE);
}
/**
* survey_child_resources() - Total all MMIO requirements
* @hbus: Root PCI bus, as understood by this driver
*/
static void survey_child_resources(struct hv_pcibus_device *hbus)
{
struct hv_pci_dev *hpdev;
resource_size_t bar_size = 0;
unsigned long flags;
struct completion *event;
u64 bar_val;
int i;
/* If nobody is waiting on the answer, don't compute it. */
event = xchg(&hbus->survey_event, NULL);
if (!event)
return;
/* If the answer has already been computed, go with it. */
if (hbus->low_mmio_space || hbus->high_mmio_space) {
complete(event);
return;
}
spin_lock_irqsave(&hbus->device_list_lock, flags);
/*
* Due to an interesting quirk of the PCI spec, all memory regions
* for a child device are a power of 2 in size and aligned in memory,
* so it's sufficient to just add them up without tracking alignment.
*/
list_for_each_entry(hpdev, &hbus->children, list_entry) {
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
if (hpdev->probed_bar[i] & PCI_BASE_ADDRESS_SPACE_IO)
dev_err(&hbus->hdev->device,
"There's an I/O BAR in this list!\n");
if (hpdev->probed_bar[i] != 0) {
/*
* A probed BAR has all the upper bits set that
* can be changed.
*/
bar_val = hpdev->probed_bar[i];
if (bar_val & PCI_BASE_ADDRESS_MEM_TYPE_64)
bar_val |=
((u64)hpdev->probed_bar[++i] << 32);
else
bar_val |= 0xffffffff00000000ULL;
bar_size = get_bar_size(bar_val);
if (bar_val & PCI_BASE_ADDRESS_MEM_TYPE_64)
hbus->high_mmio_space += bar_size;
else
hbus->low_mmio_space += bar_size;
}
}
}
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
complete(event);
}
/**
* prepopulate_bars() - Fill in BARs with defaults
* @hbus: Root PCI bus, as understood by this driver
*
* The core PCI driver code seems much, much happier if the BARs
* for a device have values upon first scan. So fill them in.
* The algorithm below works down from large sizes to small,
* attempting to pack the assignments optimally. The assumption,
* enforced in other parts of the code, is that the beginning of
* the memory-mapped I/O space will be aligned on the largest
* BAR size.
*/
static void prepopulate_bars(struct hv_pcibus_device *hbus)
{
resource_size_t high_size = 0;
resource_size_t low_size = 0;
resource_size_t high_base = 0;
resource_size_t low_base = 0;
resource_size_t bar_size;
struct hv_pci_dev *hpdev;
unsigned long flags;
u64 bar_val;
u32 command;
bool high;
int i;
if (hbus->low_mmio_space) {
low_size = 1ULL << (63 - __builtin_clzll(hbus->low_mmio_space));
low_base = hbus->low_mmio_res->start;
}
if (hbus->high_mmio_space) {
high_size = 1ULL <<
(63 - __builtin_clzll(hbus->high_mmio_space));
high_base = hbus->high_mmio_res->start;
}
spin_lock_irqsave(&hbus->device_list_lock, flags);
/*
* Clear the memory enable bit, in case it's already set. This occurs
* in the suspend path of hibernation, where the device is suspended,
* resumed and suspended again: see hibernation_snapshot() and
* hibernation_platform_enter().
*
* If the memory enable bit is already set, Hyper-V silently ignores
* the below BAR updates, and the related PCI device driver can not
* work, because reading from the device register(s) always returns
* 0xFFFFFFFF (PCI_ERROR_RESPONSE).
*/
list_for_each_entry(hpdev, &hbus->children, list_entry) {
_hv_pcifront_read_config(hpdev, PCI_COMMAND, 2, &command);
command &= ~PCI_COMMAND_MEMORY;
_hv_pcifront_write_config(hpdev, PCI_COMMAND, 2, command);
}
/* Pick addresses for the BARs. */
do {
list_for_each_entry(hpdev, &hbus->children, list_entry) {
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
bar_val = hpdev->probed_bar[i];
if (bar_val == 0)
continue;
high = bar_val & PCI_BASE_ADDRESS_MEM_TYPE_64;
if (high) {
bar_val |=
((u64)hpdev->probed_bar[i + 1]
<< 32);
} else {
bar_val |= 0xffffffffULL << 32;
}
bar_size = get_bar_size(bar_val);
if (high) {
if (high_size != bar_size) {
i++;
continue;
}
_hv_pcifront_write_config(hpdev,
PCI_BASE_ADDRESS_0 + (4 * i),
4,
(u32)(high_base & 0xffffff00));
i++;
_hv_pcifront_write_config(hpdev,
PCI_BASE_ADDRESS_0 + (4 * i),
4, (u32)(high_base >> 32));
high_base += bar_size;
} else {
if (low_size != bar_size)
continue;
_hv_pcifront_write_config(hpdev,
PCI_BASE_ADDRESS_0 + (4 * i),
4,
(u32)(low_base & 0xffffff00));
low_base += bar_size;
}
}
if (high_size <= 1 && low_size <= 1) {
/*
* No need to set the PCI_COMMAND_MEMORY bit as
* the core PCI driver doesn't require the bit
* to be pre-set. Actually here we intentionally
* keep the bit off so that the PCI BAR probing
* in the core PCI driver doesn't cause Hyper-V
* to unnecessarily unmap/map the virtual BARs
* from/to the physical BARs multiple times.
* This reduces the VM boot time significantly
* if the BAR sizes are huge.
*/
break;
}
}
high_size >>= 1;
low_size >>= 1;
} while (high_size || low_size);
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
}
/*
* Assign entries in sysfs pci slot directory.
*
* Note that this function does not need to lock the children list
* because it is called from pci_devices_present_work which
* is serialized with hv_eject_device_work because they are on the
* same ordered workqueue. Therefore hbus->children list will not change
* even when pci_create_slot sleeps.
*/
static void hv_pci_assign_slots(struct hv_pcibus_device *hbus)
{
struct hv_pci_dev *hpdev;
char name[SLOT_NAME_SIZE];
int slot_nr;
list_for_each_entry(hpdev, &hbus->children, list_entry) {
if (hpdev->pci_slot)
continue;
slot_nr = PCI_SLOT(wslot_to_devfn(hpdev->desc.win_slot.slot));
snprintf(name, SLOT_NAME_SIZE, "%u", hpdev->desc.ser);
hpdev->pci_slot = pci_create_slot(hbus->bridge->bus, slot_nr,
name, NULL);
if (IS_ERR(hpdev->pci_slot)) {
pr_warn("pci_create slot %s failed\n", name);
hpdev->pci_slot = NULL;
}
}
}
/*
* Remove entries in sysfs pci slot directory.
*/
static void hv_pci_remove_slots(struct hv_pcibus_device *hbus)
{
struct hv_pci_dev *hpdev;
list_for_each_entry(hpdev, &hbus->children, list_entry) {
if (!hpdev->pci_slot)
continue;
pci_destroy_slot(hpdev->pci_slot);
hpdev->pci_slot = NULL;
}
}
/*
* Set NUMA node for the devices on the bus
*/
static void hv_pci_assign_numa_node(struct hv_pcibus_device *hbus)
{
struct pci_dev *dev;
struct pci_bus *bus = hbus->bridge->bus;
struct hv_pci_dev *hv_dev;
list_for_each_entry(dev, &bus->devices, bus_list) {
hv_dev = get_pcichild_wslot(hbus, devfn_to_wslot(dev->devfn));
if (!hv_dev)
continue;
if (hv_dev->desc.flags & HV_PCI_DEVICE_FLAG_NUMA_AFFINITY &&
hv_dev->desc.virtual_numa_node < num_possible_nodes())
/*
* The kernel may boot with some NUMA nodes offline
* (e.g. in a KDUMP kernel) or with NUMA disabled via
* "numa=off". In those cases, adjust the host provided
* NUMA node to a valid NUMA node used by the kernel.
*/
set_dev_node(&dev->dev,
numa_map_to_online_node(
hv_dev->desc.virtual_numa_node));
put_pcichild(hv_dev);
}
}
/**
* create_root_hv_pci_bus() - Expose a new root PCI bus
* @hbus: Root PCI bus, as understood by this driver
*
* Return: 0 on success, -errno on failure
*/
static int create_root_hv_pci_bus(struct hv_pcibus_device *hbus)
{
int error;
struct pci_host_bridge *bridge = hbus->bridge;
bridge->dev.parent = &hbus->hdev->device;
bridge->sysdata = &hbus->sysdata;
bridge->ops = &hv_pcifront_ops;
error = pci_scan_root_bus_bridge(bridge);
if (error)
return error;
pci_lock_rescan_remove();
hv_pci_assign_numa_node(hbus);
pci_bus_assign_resources(bridge->bus);
hv_pci_assign_slots(hbus);
pci_bus_add_devices(bridge->bus);
pci_unlock_rescan_remove();
hbus->state = hv_pcibus_installed;
return 0;
}
struct q_res_req_compl {
struct completion host_event;
struct hv_pci_dev *hpdev;
};
/**
* q_resource_requirements() - Query Resource Requirements
* @context: The completion context.
* @resp: The response that came from the host.
* @resp_packet_size: The size in bytes of resp.
*
* This function is invoked on completion of a Query Resource
* Requirements packet.
*/
static void q_resource_requirements(void *context, struct pci_response *resp,
int resp_packet_size)
{
struct q_res_req_compl *completion = context;
struct pci_q_res_req_response *q_res_req =
(struct pci_q_res_req_response *)resp;
s32 status;
int i;
status = (resp_packet_size < sizeof(*q_res_req)) ? -1 : resp->status;
if (status < 0) {
dev_err(&completion->hpdev->hbus->hdev->device,
"query resource requirements failed: %x\n",
status);
} else {
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
completion->hpdev->probed_bar[i] =
q_res_req->probed_bar[i];
}
}
complete(&completion->host_event);
}
/**
* new_pcichild_device() - Create a new child device
* @hbus: The internal struct tracking this root PCI bus.
* @desc: The information supplied so far from the host
* about the device.
*
* This function creates the tracking structure for a new child
* device and kicks off the process of figuring out what it is.
*
* Return: Pointer to the new tracking struct
*/
static struct hv_pci_dev *new_pcichild_device(struct hv_pcibus_device *hbus,
struct hv_pcidev_description *desc)
{
struct hv_pci_dev *hpdev;
struct pci_child_message *res_req;
struct q_res_req_compl comp_pkt;
struct {
struct pci_packet init_packet;
u8 buffer[sizeof(struct pci_child_message)];
} pkt;
unsigned long flags;
int ret;
hpdev = kzalloc(sizeof(*hpdev), GFP_KERNEL);
if (!hpdev)
return NULL;
hpdev->hbus = hbus;
memset(&pkt, 0, sizeof(pkt));
init_completion(&comp_pkt.host_event);
comp_pkt.hpdev = hpdev;
pkt.init_packet.compl_ctxt = &comp_pkt;
pkt.init_packet.completion_func = q_resource_requirements;
res_req = (struct pci_child_message *)&pkt.init_packet.message;
res_req->message_type.type = PCI_QUERY_RESOURCE_REQUIREMENTS;
res_req->wslot.slot = desc->win_slot.slot;
ret = vmbus_sendpacket(hbus->hdev->channel, res_req,
sizeof(struct pci_child_message),
(unsigned long)&pkt.init_packet,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (ret)
goto error;
if (wait_for_response(hbus->hdev, &comp_pkt.host_event))
goto error;
hpdev->desc = *desc;
refcount_set(&hpdev->refs, 1);
get_pcichild(hpdev);
spin_lock_irqsave(&hbus->device_list_lock, flags);
list_add_tail(&hpdev->list_entry, &hbus->children);
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
return hpdev;
error:
kfree(hpdev);
return NULL;
}
/**
* get_pcichild_wslot() - Find device from slot
* @hbus: Root PCI bus, as understood by this driver
* @wslot: Location on the bus
*
* This function looks up a PCI device and returns the internal
* representation of it. It acquires a reference on it, so that
* the device won't be deleted while somebody is using it. The
* caller is responsible for calling put_pcichild() to release
* this reference.
*
* Return: Internal representation of a PCI device
*/
static struct hv_pci_dev *get_pcichild_wslot(struct hv_pcibus_device *hbus,
u32 wslot)
{
unsigned long flags;
struct hv_pci_dev *iter, *hpdev = NULL;
spin_lock_irqsave(&hbus->device_list_lock, flags);
list_for_each_entry(iter, &hbus->children, list_entry) {
if (iter->desc.win_slot.slot == wslot) {
hpdev = iter;
get_pcichild(hpdev);
break;
}
}
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
return hpdev;
}
/**
* pci_devices_present_work() - Handle new list of child devices
* @work: Work struct embedded in struct hv_dr_work
*
* "Bus Relations" is the Windows term for "children of this
* bus." The terminology is preserved here for people trying to
* debug the interaction between Hyper-V and Linux. This
* function is called when the parent partition reports a list
* of functions that should be observed under this PCI Express
* port (bus).
*
* This function updates the list, and must tolerate being
* called multiple times with the same information. The typical
* number of child devices is one, with very atypical cases
* involving three or four, so the algorithms used here can be
* simple and inefficient.
*
* It must also treat the omission of a previously observed device as
* notification that the device no longer exists.
*
* Note that this function is serialized with hv_eject_device_work(),
* because both are pushed to the ordered workqueue hbus->wq.
*/
static void pci_devices_present_work(struct work_struct *work)
{
u32 child_no;
bool found;
struct hv_pcidev_description *new_desc;
struct hv_pci_dev *hpdev;
struct hv_pcibus_device *hbus;
struct list_head removed;
struct hv_dr_work *dr_wrk;
struct hv_dr_state *dr = NULL;
unsigned long flags;
dr_wrk = container_of(work, struct hv_dr_work, wrk);
hbus = dr_wrk->bus;
kfree(dr_wrk);
INIT_LIST_HEAD(&removed);
/* Pull this off the queue and process it if it was the last one. */
spin_lock_irqsave(&hbus->device_list_lock, flags);
while (!list_empty(&hbus->dr_list)) {
dr = list_first_entry(&hbus->dr_list, struct hv_dr_state,
list_entry);
list_del(&dr->list_entry);
/* Throw this away if the list still has stuff in it. */
if (!list_empty(&hbus->dr_list)) {
kfree(dr);
continue;
}
}
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
if (!dr)
return;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_lock(&hbus->state_lock);
/* First, mark all existing children as reported missing. */
spin_lock_irqsave(&hbus->device_list_lock, flags);
list_for_each_entry(hpdev, &hbus->children, list_entry) {
hpdev->reported_missing = true;
}
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
/* Next, add back any reported devices. */
for (child_no = 0; child_no < dr->device_count; child_no++) {
found = false;
new_desc = &dr->func[child_no];
spin_lock_irqsave(&hbus->device_list_lock, flags);
list_for_each_entry(hpdev, &hbus->children, list_entry) {
if ((hpdev->desc.win_slot.slot == new_desc->win_slot.slot) &&
(hpdev->desc.v_id == new_desc->v_id) &&
(hpdev->desc.d_id == new_desc->d_id) &&
(hpdev->desc.ser == new_desc->ser)) {
hpdev->reported_missing = false;
found = true;
}
}
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
if (!found) {
hpdev = new_pcichild_device(hbus, new_desc);
if (!hpdev)
dev_err(&hbus->hdev->device,
"couldn't record a child device.\n");
}
}
/* Move missing children to a list on the stack. */
spin_lock_irqsave(&hbus->device_list_lock, flags);
do {
found = false;
list_for_each_entry(hpdev, &hbus->children, list_entry) {
if (hpdev->reported_missing) {
found = true;
put_pcichild(hpdev);
list_move_tail(&hpdev->list_entry, &removed);
break;
}
}
} while (found);
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
/* Delete everything that should no longer exist. */
while (!list_empty(&removed)) {
hpdev = list_first_entry(&removed, struct hv_pci_dev,
list_entry);
list_del(&hpdev->list_entry);
PCI: hv: Add pci_destroy_slot() in pci_devices_present_work(), if necessary When we hot-remove a device, usually the host sends us a PCI_EJECT message, and a PCI_BUS_RELATIONS message with bus_rel->device_count == 0. When we execute the quick hot-add/hot-remove test, the host may not send us the PCI_EJECT message if the guest has not fully finished the initialization by sending the PCI_RESOURCES_ASSIGNED* message to the host, so it's potentially unsafe to only depend on the pci_destroy_slot() in hv_eject_device_work() because the code path create_root_hv_pci_bus() -> hv_pci_assign_slots() is not called in this case. Note: in this case, the host still sends the guest a PCI_BUS_RELATIONS message with bus_rel->device_count == 0. In the quick hot-add/hot-remove test, we can have such a race before the code path pci_devices_present_work() -> new_pcichild_device() adds the new device into the hbus->children list, we may have already received the PCI_EJECT message, and since the tasklet handler hv_pci_onchannelcallback() may fail to find the "hpdev" by calling get_pcichild_wslot(hbus, dev_message->wslot.slot) hv_pci_eject_device() is not called; Later, by continuing execution create_root_hv_pci_bus() -> hv_pci_assign_slots() creates the slot and the PCI_BUS_RELATIONS message with bus_rel->device_count == 0 removes the device from hbus->children, and we end up being unable to remove the slot in hv_pci_remove() -> hv_pci_remove_slots() Remove the slot in pci_devices_present_work() when the device is removed to address this race. pci_devices_present_work() and hv_eject_device_work() run in the singled-threaded hbus->wq, so there is not a double-remove issue for the slot. We cannot offload hv_pci_eject_device() from hv_pci_onchannelcallback() to the workqueue, because we need the hv_pci_onchannelcallback() synchronously call hv_pci_eject_device() to poll the channel ringbuffer to work around the "hangs in hv_compose_msi_msg()" issue fixed in commit de0aa7b2f97d ("PCI: hv: Fix 2 hang issues in hv_compose_msi_msg()") Fixes: a15f2c08c708 ("PCI: hv: support reporting serial number as slot information") Signed-off-by: Dexuan Cui <decui@microsoft.com> [lorenzo.pieralisi@arm.com: rewritten commit log] Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Stephen Hemminger <stephen@networkplumber.org> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Cc: stable@vger.kernel.org
2019-03-04 21:34:49 +00:00
if (hpdev->pci_slot)
pci_destroy_slot(hpdev->pci_slot);
put_pcichild(hpdev);
}
switch (hbus->state) {
case hv_pcibus_installed:
/*
* Tell the core to rescan bus
* because there may have been changes.
*/
pci_lock_rescan_remove();
pci_scan_child_bus(hbus->bridge->bus);
hv_pci_assign_numa_node(hbus);
hv_pci_assign_slots(hbus);
pci_unlock_rescan_remove();
break;
case hv_pcibus_init:
case hv_pcibus_probed:
survey_child_resources(hbus);
break;
default:
break;
}
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_unlock(&hbus->state_lock);
kfree(dr);
}
/**
* hv_pci_start_relations_work() - Queue work to start device discovery
* @hbus: Root PCI bus, as understood by this driver
* @dr: The list of children returned from host
*
* Return: 0 on success, -errno on failure
*/
static int hv_pci_start_relations_work(struct hv_pcibus_device *hbus,
struct hv_dr_state *dr)
{
struct hv_dr_work *dr_wrk;
unsigned long flags;
bool pending_dr;
if (hbus->state == hv_pcibus_removing) {
dev_info(&hbus->hdev->device,
"PCI VMBus BUS_RELATIONS: ignored\n");
return -ENOENT;
}
dr_wrk = kzalloc(sizeof(*dr_wrk), GFP_NOWAIT);
if (!dr_wrk)
return -ENOMEM;
INIT_WORK(&dr_wrk->wrk, pci_devices_present_work);
dr_wrk->bus = hbus;
spin_lock_irqsave(&hbus->device_list_lock, flags);
/*
* If pending_dr is true, we have already queued a work,
* which will see the new dr. Otherwise, we need to
* queue a new work.
*/
pending_dr = !list_empty(&hbus->dr_list);
list_add_tail(&dr->list_entry, &hbus->dr_list);
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
if (pending_dr)
kfree(dr_wrk);
else
queue_work(hbus->wq, &dr_wrk->wrk);
return 0;
}
/**
* hv_pci_devices_present() - Handle list of new children
* @hbus: Root PCI bus, as understood by this driver
* @relations: Packet from host listing children
*
* Process a new list of devices on the bus. The list of devices is
* discovered by VSP and sent to us via VSP message PCI_BUS_RELATIONS,
* whenever a new list of devices for this bus appears.
*/
static void hv_pci_devices_present(struct hv_pcibus_device *hbus,
struct pci_bus_relations *relations)
{
struct hv_dr_state *dr;
int i;
dr = kzalloc(struct_size(dr, func, relations->device_count),
GFP_NOWAIT);
if (!dr)
return;
dr->device_count = relations->device_count;
for (i = 0; i < dr->device_count; i++) {
dr->func[i].v_id = relations->func[i].v_id;
dr->func[i].d_id = relations->func[i].d_id;
dr->func[i].rev = relations->func[i].rev;
dr->func[i].prog_intf = relations->func[i].prog_intf;
dr->func[i].subclass = relations->func[i].subclass;
dr->func[i].base_class = relations->func[i].base_class;
dr->func[i].subsystem_id = relations->func[i].subsystem_id;
dr->func[i].win_slot = relations->func[i].win_slot;
dr->func[i].ser = relations->func[i].ser;
}
if (hv_pci_start_relations_work(hbus, dr))
kfree(dr);
}
/**
* hv_pci_devices_present2() - Handle list of new children
* @hbus: Root PCI bus, as understood by this driver
* @relations: Packet from host listing children
*
* This function is the v2 version of hv_pci_devices_present()
*/
static void hv_pci_devices_present2(struct hv_pcibus_device *hbus,
struct pci_bus_relations2 *relations)
{
struct hv_dr_state *dr;
int i;
dr = kzalloc(struct_size(dr, func, relations->device_count),
GFP_NOWAIT);
if (!dr)
return;
dr->device_count = relations->device_count;
for (i = 0; i < dr->device_count; i++) {
dr->func[i].v_id = relations->func[i].v_id;
dr->func[i].d_id = relations->func[i].d_id;
dr->func[i].rev = relations->func[i].rev;
dr->func[i].prog_intf = relations->func[i].prog_intf;
dr->func[i].subclass = relations->func[i].subclass;
dr->func[i].base_class = relations->func[i].base_class;
dr->func[i].subsystem_id = relations->func[i].subsystem_id;
dr->func[i].win_slot = relations->func[i].win_slot;
dr->func[i].ser = relations->func[i].ser;
dr->func[i].flags = relations->func[i].flags;
dr->func[i].virtual_numa_node =
relations->func[i].virtual_numa_node;
}
if (hv_pci_start_relations_work(hbus, dr))
kfree(dr);
}
/**
* hv_eject_device_work() - Asynchronously handles ejection
* @work: Work struct embedded in internal device struct
*
* This function handles ejecting a device. Windows will
* attempt to gracefully eject a device, waiting 60 seconds to
* hear back from the guest OS that this completed successfully.
* If this timer expires, the device will be forcibly removed.
*/
static void hv_eject_device_work(struct work_struct *work)
{
struct pci_eject_response *ejct_pkt;
struct hv_pcibus_device *hbus;
struct hv_pci_dev *hpdev;
struct pci_dev *pdev;
unsigned long flags;
int wslot;
struct {
struct pci_packet pkt;
u8 buffer[sizeof(struct pci_eject_response)];
} ctxt;
hpdev = container_of(work, struct hv_pci_dev, wrk);
hbus = hpdev->hbus;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_lock(&hbus->state_lock);
/*
* Ejection can come before or after the PCI bus has been set up, so
* attempt to find it and tear down the bus state, if it exists. This
* must be done without constructs like pci_domain_nr(hbus->bridge->bus)
* because hbus->bridge->bus may not exist yet.
*/
wslot = wslot_to_devfn(hpdev->desc.win_slot.slot);
pdev = pci_get_domain_bus_and_slot(hbus->bridge->domain_nr, 0, wslot);
if (pdev) {
pci_lock_rescan_remove();
pci_stop_and_remove_bus_device(pdev);
pci_dev_put(pdev);
pci_unlock_rescan_remove();
}
spin_lock_irqsave(&hbus->device_list_lock, flags);
list_del(&hpdev->list_entry);
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
if (hpdev->pci_slot)
pci_destroy_slot(hpdev->pci_slot);
memset(&ctxt, 0, sizeof(ctxt));
ejct_pkt = (struct pci_eject_response *)&ctxt.pkt.message;
ejct_pkt->message_type.type = PCI_EJECTION_COMPLETE;
ejct_pkt->wslot.slot = hpdev->desc.win_slot.slot;
vmbus_sendpacket(hbus->hdev->channel, ejct_pkt,
sizeof(*ejct_pkt), 0,
VM_PKT_DATA_INBAND, 0);
/* For the get_pcichild() in hv_pci_eject_device() */
put_pcichild(hpdev);
/* For the two refs got in new_pcichild_device() */
put_pcichild(hpdev);
put_pcichild(hpdev);
/* hpdev has been freed. Do not use it any more. */
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_unlock(&hbus->state_lock);
}
/**
* hv_pci_eject_device() - Handles device ejection
* @hpdev: Internal device tracking struct
*
* This function is invoked when an ejection packet arrives. It
* just schedules work so that we don't re-enter the packet
* delivery code handling the ejection.
*/
static void hv_pci_eject_device(struct hv_pci_dev *hpdev)
{
struct hv_pcibus_device *hbus = hpdev->hbus;
struct hv_device *hdev = hbus->hdev;
if (hbus->state == hv_pcibus_removing) {
dev_info(&hdev->device, "PCI VMBus EJECT: ignored\n");
return;
}
get_pcichild(hpdev);
INIT_WORK(&hpdev->wrk, hv_eject_device_work);
queue_work(hbus->wq, &hpdev->wrk);
}
/**
* hv_pci_onchannelcallback() - Handles incoming packets
* @context: Internal bus tracking struct
*
* This function is invoked whenever the host sends a packet to
* this channel (which is private to this root PCI bus).
*/
static void hv_pci_onchannelcallback(void *context)
{
const int packet_size = 0x100;
int ret;
struct hv_pcibus_device *hbus = context;
struct vmbus_channel *chan = hbus->hdev->channel;
u32 bytes_recvd;
u64 req_id, req_addr;
struct vmpacket_descriptor *desc;
unsigned char *buffer;
int bufferlen = packet_size;
struct pci_packet *comp_packet;
struct pci_response *response;
struct pci_incoming_message *new_message;
struct pci_bus_relations *bus_rel;
struct pci_bus_relations2 *bus_rel2;
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
struct pci_dev_inval_block *inval;
struct pci_dev_incoming *dev_message;
struct hv_pci_dev *hpdev;
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
unsigned long flags;
buffer = kmalloc(bufferlen, GFP_ATOMIC);
if (!buffer)
return;
while (1) {
ret = vmbus_recvpacket_raw(chan, buffer, bufferlen,
&bytes_recvd, &req_id);
if (ret == -ENOBUFS) {
kfree(buffer);
/* Handle large packet */
bufferlen = bytes_recvd;
buffer = kmalloc(bytes_recvd, GFP_ATOMIC);
if (!buffer)
return;
continue;
}
/* Zero length indicates there are no more packets. */
if (ret || !bytes_recvd)
break;
/*
* All incoming packets must be at least as large as a
* response.
*/
if (bytes_recvd <= sizeof(struct pci_response))
continue;
desc = (struct vmpacket_descriptor *)buffer;
switch (desc->type) {
case VM_PKT_COMP:
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
lock_requestor(chan, flags);
req_addr = __vmbus_request_addr_match(chan, req_id,
VMBUS_RQST_ADDR_ANY);
if (req_addr == VMBUS_RQST_ERROR) {
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
unlock_requestor(chan, flags);
dev_err(&hbus->hdev->device,
"Invalid transaction ID %llx\n",
req_id);
break;
}
comp_packet = (struct pci_packet *)req_addr;
response = (struct pci_response *)buffer;
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
/*
* Call ->completion_func() within the critical section to make
* sure that the packet pointer is still valid during the call:
* here 'valid' means that there's a task still waiting for the
* completion, and that the packet data is still on the waiting
* task's stack. Cf. hv_compose_msi_msg().
*/
comp_packet->completion_func(comp_packet->compl_ctxt,
response,
bytes_recvd);
PCI: hv: Fix synchronization between channel callback and hv_compose_msi_msg() Dexuan wrote: "[...] when we disable AccelNet, the host PCI VSP driver sends a PCI_EJECT message first, and the channel callback may set hpdev->state to hv_pcichild_ejecting on a different CPU. This can cause hv_compose_msi_msg() to exit from the loop and 'return', and the on-stack variable 'ctxt' is invalid. Now, if the response message from the host arrives, the channel callback will try to access the invalid 'ctxt' variable, and this may cause a crash." Schematically: Hyper-V sends PCI_EJECT msg hv_pci_onchannelcallback() state = hv_pcichild_ejecting hv_compose_msi_msg() alloc and init comp_pkt state == hv_pcichild_ejecting Hyper-V sends VM_PKT_COMP msg hv_pci_onchannelcallback() retrieve address of comp_pkt 'free' comp_pkt and return comp_pkt->completion_func() Dexuan also showed how the crash can be triggered after introducing suitable delays in the driver code, thus validating the 'assumption' that the host can still normally respond to the guest's compose_msi request after the host has started to eject the PCI device. Fix the synchronization by leveraging the requestor lock as follows: - Before 'return'-ing in hv_compose_msi_msg(), remove the ID (while holding the requestor lock) associated to the completion packet. - Retrieve the address *and call ->completion_func() within a same (requestor) critical section in hv_pci_onchannelcallback(). Reported-by: Wei Hu <weh@microsoft.com> Reported-by: Dexuan Cui <decui@microsoft.com> Suggested-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Link: https://lore.kernel.org/r/20220419122325.10078-7-parri.andrea@gmail.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2022-04-19 12:23:25 +00:00
unlock_requestor(chan, flags);
break;
case VM_PKT_DATA_INBAND:
new_message = (struct pci_incoming_message *)buffer;
switch (new_message->message_type.type) {
case PCI_BUS_RELATIONS:
bus_rel = (struct pci_bus_relations *)buffer;
if (bytes_recvd < sizeof(*bus_rel) ||
bytes_recvd <
struct_size(bus_rel, func,
bus_rel->device_count)) {
dev_err(&hbus->hdev->device,
"bus relations too small\n");
break;
}
hv_pci_devices_present(hbus, bus_rel);
break;
case PCI_BUS_RELATIONS2:
bus_rel2 = (struct pci_bus_relations2 *)buffer;
if (bytes_recvd < sizeof(*bus_rel2) ||
bytes_recvd <
struct_size(bus_rel2, func,
bus_rel2->device_count)) {
dev_err(&hbus->hdev->device,
"bus relations v2 too small\n");
break;
}
hv_pci_devices_present2(hbus, bus_rel2);
break;
case PCI_EJECT:
dev_message = (struct pci_dev_incoming *)buffer;
if (bytes_recvd < sizeof(*dev_message)) {
dev_err(&hbus->hdev->device,
"eject message too small\n");
break;
}
hpdev = get_pcichild_wslot(hbus,
dev_message->wslot.slot);
if (hpdev) {
hv_pci_eject_device(hpdev);
put_pcichild(hpdev);
}
break;
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
case PCI_INVALIDATE_BLOCK:
inval = (struct pci_dev_inval_block *)buffer;
if (bytes_recvd < sizeof(*inval)) {
dev_err(&hbus->hdev->device,
"invalidate message too small\n");
break;
}
PCI: hv: Add a paravirtual backchannel in software Windows SR-IOV provides a backchannel mechanism in software for communication between a VF driver and a PF driver. These "configuration blocks" are similar in concept to PCI configuration space, but instead of doing reads and writes in 32-bit chunks through a very slow path, packets of up to 128 bytes can be sent or received asynchronously. Nearly every SR-IOV device contains just such a communications channel in hardware, so using this one in software is usually optional. Using the software channel, however, allows driver implementers to leverage software tools that fuzz the communications channel looking for vulnerabilities. The usage model for these packets puts the responsibility for reading or writing on the VF driver. The VF driver sends a read or a write packet, indicating which "block" is being referred to by number. If the PF driver wishes to initiate communication, it can "invalidate" one or more of the first 64 blocks. This invalidation is delivered via a callback supplied by the VF driver by this driver. No protocol is implied, except that supplied by the PF and VF drivers. Signed-off-by: Jake Oshins <jakeo@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: Saeed Mahameed <saeedm@mellanox.com> Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-22 05:05:37 +00:00
hpdev = get_pcichild_wslot(hbus,
inval->wslot.slot);
if (hpdev) {
if (hpdev->block_invalidate) {
hpdev->block_invalidate(
hpdev->invalidate_context,
inval->block_mask);
}
put_pcichild(hpdev);
}
break;
default:
dev_warn(&hbus->hdev->device,
"Unimplemented protocol message %x\n",
new_message->message_type.type);
break;
}
break;
default:
dev_err(&hbus->hdev->device,
"unhandled packet type %d, tid %llx len %d\n",
desc->type, req_id, bytes_recvd);
break;
}
}
kfree(buffer);
}
/**
* hv_pci_protocol_negotiation() - Set up protocol
* @hdev: VMBus's tracking struct for this root PCI bus.
* @version: Array of supported channel protocol versions in
* the order of probing - highest go first.
* @num_version: Number of elements in the version array.
*
* This driver is intended to support running on Windows 10
* (server) and later versions. It will not run on earlier
* versions, as they assume that many of the operations which
* Linux needs accomplished with a spinlock held were done via
* asynchronous messaging via VMBus. Windows 10 increases the
* surface area of PCI emulation so that these actions can take
* place by suspending a virtual processor for their duration.
*
* This function negotiates the channel protocol version,
* failing if the host doesn't support the necessary protocol
* level.
*/
static int hv_pci_protocol_negotiation(struct hv_device *hdev,
enum pci_protocol_version_t version[],
int num_version)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct pci_version_request *version_req;
struct hv_pci_compl comp_pkt;
struct pci_packet *pkt;
int ret;
int i;
/*
* Initiate the handshake with the host and negotiate
* a version that the host can support. We start with the
* highest version number and go down if the host cannot
* support it.
*/
pkt = kzalloc(sizeof(*pkt) + sizeof(*version_req), GFP_KERNEL);
if (!pkt)
return -ENOMEM;
init_completion(&comp_pkt.host_event);
pkt->completion_func = hv_pci_generic_compl;
pkt->compl_ctxt = &comp_pkt;
version_req = (struct pci_version_request *)&pkt->message;
version_req->message_type.type = PCI_QUERY_PROTOCOL_VERSION;
for (i = 0; i < num_version; i++) {
version_req->protocol_version = version[i];
ret = vmbus_sendpacket(hdev->channel, version_req,
sizeof(struct pci_version_request),
(unsigned long)pkt, VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (!ret)
ret = wait_for_response(hdev, &comp_pkt.host_event);
if (ret) {
dev_err(&hdev->device,
"PCI Pass-through VSP failed to request version: %d",
ret);
goto exit;
}
if (comp_pkt.completion_status >= 0) {
hbus->protocol_version = version[i];
dev_info(&hdev->device,
"PCI VMBus probing: Using version %#x\n",
hbus->protocol_version);
goto exit;
}
if (comp_pkt.completion_status != STATUS_REVISION_MISMATCH) {
dev_err(&hdev->device,
"PCI Pass-through VSP failed version request: %#x",
comp_pkt.completion_status);
ret = -EPROTO;
goto exit;
}
reinit_completion(&comp_pkt.host_event);
}
dev_err(&hdev->device,
"PCI pass-through VSP failed to find supported version");
ret = -EPROTO;
exit:
kfree(pkt);
return ret;
}
/**
* hv_pci_free_bridge_windows() - Release memory regions for the
* bus
* @hbus: Root PCI bus, as understood by this driver
*/
static void hv_pci_free_bridge_windows(struct hv_pcibus_device *hbus)
{
/*
* Set the resources back to the way they looked when they
* were allocated by setting IORESOURCE_BUSY again.
*/
if (hbus->low_mmio_space && hbus->low_mmio_res) {
hbus->low_mmio_res->flags |= IORESOURCE_BUSY;
vmbus_free_mmio(hbus->low_mmio_res->start,
resource_size(hbus->low_mmio_res));
}
if (hbus->high_mmio_space && hbus->high_mmio_res) {
hbus->high_mmio_res->flags |= IORESOURCE_BUSY;
vmbus_free_mmio(hbus->high_mmio_res->start,
resource_size(hbus->high_mmio_res));
}
}
/**
* hv_pci_allocate_bridge_windows() - Allocate memory regions
* for the bus
* @hbus: Root PCI bus, as understood by this driver
*
* This function calls vmbus_allocate_mmio(), which is itself a
* bit of a compromise. Ideally, we might change the pnp layer
* in the kernel such that it comprehends either PCI devices
* which are "grandchildren of ACPI," with some intermediate bus
* node (in this case, VMBus) or change it such that it
* understands VMBus. The pnp layer, however, has been declared
* deprecated, and not subject to change.
*
* The workaround, implemented here, is to ask VMBus to allocate
* MMIO space for this bus. VMBus itself knows which ranges are
* appropriate by looking at its own ACPI objects. Then, after
* these ranges are claimed, they're modified to look like they
* would have looked if the ACPI and pnp code had allocated
* bridge windows. These descriptors have to exist in this form
* in order to satisfy the code which will get invoked when the
* endpoint PCI function driver calls request_mem_region() or
* request_mem_region_exclusive().
*
* Return: 0 on success, -errno on failure
*/
static int hv_pci_allocate_bridge_windows(struct hv_pcibus_device *hbus)
{
resource_size_t align;
int ret;
if (hbus->low_mmio_space) {
align = 1ULL << (63 - __builtin_clzll(hbus->low_mmio_space));
ret = vmbus_allocate_mmio(&hbus->low_mmio_res, hbus->hdev, 0,
(u64)(u32)0xffffffff,
hbus->low_mmio_space,
align, false);
if (ret) {
dev_err(&hbus->hdev->device,
"Need %#llx of low MMIO space. Consider reconfiguring the VM.\n",
hbus->low_mmio_space);
return ret;
}
/* Modify this resource to become a bridge window. */
hbus->low_mmio_res->flags |= IORESOURCE_WINDOW;
hbus->low_mmio_res->flags &= ~IORESOURCE_BUSY;
pci_add_resource(&hbus->bridge->windows, hbus->low_mmio_res);
}
if (hbus->high_mmio_space) {
align = 1ULL << (63 - __builtin_clzll(hbus->high_mmio_space));
ret = vmbus_allocate_mmio(&hbus->high_mmio_res, hbus->hdev,
0x100000000, -1,
hbus->high_mmio_space, align,
false);
if (ret) {
dev_err(&hbus->hdev->device,
"Need %#llx of high MMIO space. Consider reconfiguring the VM.\n",
hbus->high_mmio_space);
goto release_low_mmio;
}
/* Modify this resource to become a bridge window. */
hbus->high_mmio_res->flags |= IORESOURCE_WINDOW;
hbus->high_mmio_res->flags &= ~IORESOURCE_BUSY;
pci_add_resource(&hbus->bridge->windows, hbus->high_mmio_res);
}
return 0;
release_low_mmio:
if (hbus->low_mmio_res) {
vmbus_free_mmio(hbus->low_mmio_res->start,
resource_size(hbus->low_mmio_res));
}
return ret;
}
/**
* hv_allocate_config_window() - Find MMIO space for PCI Config
* @hbus: Root PCI bus, as understood by this driver
*
* This function claims memory-mapped I/O space for accessing
* configuration space for the functions on this bus.
*
* Return: 0 on success, -errno on failure
*/
static int hv_allocate_config_window(struct hv_pcibus_device *hbus)
{
int ret;
/*
* Set up a region of MMIO space to use for accessing configuration
* space.
*/
ret = vmbus_allocate_mmio(&hbus->mem_config, hbus->hdev, 0, -1,
PCI_CONFIG_MMIO_LENGTH, 0x1000, false);
if (ret)
return ret;
/*
* vmbus_allocate_mmio() gets used for allocating both device endpoint
* resource claims (those which cannot be overlapped) and the ranges
* which are valid for the children of this bus, which are intended
* to be overlapped by those children. Set the flag on this claim
* meaning that this region can't be overlapped.
*/
hbus->mem_config->flags |= IORESOURCE_BUSY;
return 0;
}
static void hv_free_config_window(struct hv_pcibus_device *hbus)
{
vmbus_free_mmio(hbus->mem_config->start, PCI_CONFIG_MMIO_LENGTH);
}
static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs);
/**
* hv_pci_enter_d0() - Bring the "bus" into the D0 power state
* @hdev: VMBus's tracking struct for this root PCI bus
*
* Return: 0 on success, -errno on failure
*/
static int hv_pci_enter_d0(struct hv_device *hdev)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct pci_bus_d0_entry *d0_entry;
struct hv_pci_compl comp_pkt;
struct pci_packet *pkt;
bool retry = true;
int ret;
enter_d0_retry:
/*
* Tell the host that the bus is ready to use, and moved into the
* powered-on state. This includes telling the host which region
* of memory-mapped I/O space has been chosen for configuration space
* access.
*/
pkt = kzalloc(sizeof(*pkt) + sizeof(*d0_entry), GFP_KERNEL);
if (!pkt)
return -ENOMEM;
init_completion(&comp_pkt.host_event);
pkt->completion_func = hv_pci_generic_compl;
pkt->compl_ctxt = &comp_pkt;
d0_entry = (struct pci_bus_d0_entry *)&pkt->message;
d0_entry->message_type.type = PCI_BUS_D0ENTRY;
d0_entry->mmio_base = hbus->mem_config->start;
ret = vmbus_sendpacket(hdev->channel, d0_entry, sizeof(*d0_entry),
(unsigned long)pkt, VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (!ret)
ret = wait_for_response(hdev, &comp_pkt.host_event);
if (ret)
goto exit;
/*
* In certain case (Kdump) the pci device of interest was
* not cleanly shut down and resource is still held on host
* side, the host could return invalid device status.
* We need to explicitly request host to release the resource
* and try to enter D0 again.
*/
if (comp_pkt.completion_status < 0 && retry) {
retry = false;
dev_err(&hdev->device, "Retrying D0 Entry\n");
/*
* Hv_pci_bus_exit() calls hv_send_resource_released()
* to free up resources of its child devices.
* In the kdump kernel we need to set the
* wslot_res_allocated to 255 so it scans all child
* devices to release resources allocated in the
* normal kernel before panic happened.
*/
hbus->wslot_res_allocated = 255;
ret = hv_pci_bus_exit(hdev, true);
if (ret == 0) {
kfree(pkt);
goto enter_d0_retry;
}
dev_err(&hdev->device,
"Retrying D0 failed with ret %d\n", ret);
}
if (comp_pkt.completion_status < 0) {
dev_err(&hdev->device,
"PCI Pass-through VSP failed D0 Entry with status %x\n",
comp_pkt.completion_status);
ret = -EPROTO;
goto exit;
}
ret = 0;
exit:
kfree(pkt);
return ret;
}
/**
* hv_pci_query_relations() - Ask host to send list of child
* devices
* @hdev: VMBus's tracking struct for this root PCI bus
*
* Return: 0 on success, -errno on failure
*/
static int hv_pci_query_relations(struct hv_device *hdev)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct pci_message message;
struct completion comp;
int ret;
/* Ask the host to send along the list of child devices */
init_completion(&comp);
if (cmpxchg(&hbus->survey_event, NULL, &comp))
return -ENOTEMPTY;
memset(&message, 0, sizeof(message));
message.type = PCI_QUERY_BUS_RELATIONS;
ret = vmbus_sendpacket(hdev->channel, &message, sizeof(message),
0, VM_PKT_DATA_INBAND, 0);
if (!ret)
ret = wait_for_response(hdev, &comp);
/*
* In the case of fast device addition/removal, it's possible that
* vmbus_sendpacket() or wait_for_response() returns -ENODEV but we
* already got a PCI_BUS_RELATIONS* message from the host and the
* channel callback already scheduled a work to hbus->wq, which can be
* running pci_devices_present_work() -> survey_child_resources() ->
* complete(&hbus->survey_event), even after hv_pci_query_relations()
* exits and the stack variable 'comp' is no longer valid; as a result,
* a hang or a page fault may happen when the complete() calls
* raw_spin_lock_irqsave(). Flush hbus->wq before we exit from
* hv_pci_query_relations() to avoid the issues. Note: if 'ret' is
* -ENODEV, there can't be any more work item scheduled to hbus->wq
* after the flush_workqueue(): see vmbus_onoffer_rescind() ->
* vmbus_reset_channel_cb(), vmbus_rescind_cleanup() ->
* channel->rescind = true.
*/
flush_workqueue(hbus->wq);
return ret;
}
/**
* hv_send_resources_allocated() - Report local resource choices
* @hdev: VMBus's tracking struct for this root PCI bus
*
* The host OS is expecting to be sent a request as a message
* which contains all the resources that the device will use.
* The response contains those same resources, "translated"
* which is to say, the values which should be used by the
* hardware, when it delivers an interrupt. (MMIO resources are
* used in local terms.) This is nice for Windows, and lines up
* with the FDO/PDO split, which doesn't exist in Linux. Linux
* is deeply expecting to scan an emulated PCI configuration
* space. So this message is sent here only to drive the state
* machine on the host forward.
*
* Return: 0 on success, -errno on failure
*/
static int hv_send_resources_allocated(struct hv_device *hdev)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct pci_resources_assigned *res_assigned;
struct pci_resources_assigned2 *res_assigned2;
struct hv_pci_compl comp_pkt;
struct hv_pci_dev *hpdev;
struct pci_packet *pkt;
size_t size_res;
int wslot;
int ret;
size_res = (hbus->protocol_version < PCI_PROTOCOL_VERSION_1_2)
? sizeof(*res_assigned) : sizeof(*res_assigned2);
pkt = kmalloc(sizeof(*pkt) + size_res, GFP_KERNEL);
if (!pkt)
return -ENOMEM;
ret = 0;
for (wslot = 0; wslot < 256; wslot++) {
hpdev = get_pcichild_wslot(hbus, wslot);
if (!hpdev)
continue;
memset(pkt, 0, sizeof(*pkt) + size_res);
init_completion(&comp_pkt.host_event);
pkt->completion_func = hv_pci_generic_compl;
pkt->compl_ctxt = &comp_pkt;
if (hbus->protocol_version < PCI_PROTOCOL_VERSION_1_2) {
res_assigned =
(struct pci_resources_assigned *)&pkt->message;
res_assigned->message_type.type =
PCI_RESOURCES_ASSIGNED;
res_assigned->wslot.slot = hpdev->desc.win_slot.slot;
} else {
res_assigned2 =
(struct pci_resources_assigned2 *)&pkt->message;
res_assigned2->message_type.type =
PCI_RESOURCES_ASSIGNED2;
res_assigned2->wslot.slot = hpdev->desc.win_slot.slot;
}
put_pcichild(hpdev);
ret = vmbus_sendpacket(hdev->channel, &pkt->message,
size_res, (unsigned long)pkt,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (!ret)
ret = wait_for_response(hdev, &comp_pkt.host_event);
if (ret)
break;
if (comp_pkt.completion_status < 0) {
ret = -EPROTO;
dev_err(&hdev->device,
"resource allocated returned 0x%x",
comp_pkt.completion_status);
break;
}
hbus->wslot_res_allocated = wslot;
}
kfree(pkt);
return ret;
}
/**
* hv_send_resources_released() - Report local resources
* released
* @hdev: VMBus's tracking struct for this root PCI bus
*
* Return: 0 on success, -errno on failure
*/
static int hv_send_resources_released(struct hv_device *hdev)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct pci_child_message pkt;
struct hv_pci_dev *hpdev;
int wslot;
int ret;
for (wslot = hbus->wslot_res_allocated; wslot >= 0; wslot--) {
hpdev = get_pcichild_wslot(hbus, wslot);
if (!hpdev)
continue;
memset(&pkt, 0, sizeof(pkt));
pkt.message_type.type = PCI_RESOURCES_RELEASED;
pkt.wslot.slot = hpdev->desc.win_slot.slot;
put_pcichild(hpdev);
ret = vmbus_sendpacket(hdev->channel, &pkt, sizeof(pkt), 0,
VM_PKT_DATA_INBAND, 0);
if (ret)
return ret;
hbus->wslot_res_allocated = wslot - 1;
}
hbus->wslot_res_allocated = -1;
return 0;
}
#define HVPCI_DOM_MAP_SIZE (64 * 1024)
static DECLARE_BITMAP(hvpci_dom_map, HVPCI_DOM_MAP_SIZE);
/*
* PCI domain number 0 is used by emulated devices on Gen1 VMs, so define 0
* as invalid for passthrough PCI devices of this driver.
*/
#define HVPCI_DOM_INVALID 0
/**
* hv_get_dom_num() - Get a valid PCI domain number
* Check if the PCI domain number is in use, and return another number if
* it is in use.
*
* @dom: Requested domain number
*
* return: domain number on success, HVPCI_DOM_INVALID on failure
*/
static u16 hv_get_dom_num(u16 dom)
{
unsigned int i;
if (test_and_set_bit(dom, hvpci_dom_map) == 0)
return dom;
for_each_clear_bit(i, hvpci_dom_map, HVPCI_DOM_MAP_SIZE) {
if (test_and_set_bit(i, hvpci_dom_map) == 0)
return i;
}
return HVPCI_DOM_INVALID;
}
/**
* hv_put_dom_num() - Mark the PCI domain number as free
* @dom: Domain number to be freed
*/
static void hv_put_dom_num(u16 dom)
{
clear_bit(dom, hvpci_dom_map);
}
/**
* hv_pci_probe() - New VMBus channel probe, for a root PCI bus
* @hdev: VMBus's tracking struct for this root PCI bus
* @dev_id: Identifies the device itself
*
* Return: 0 on success, -errno on failure
*/
static int hv_pci_probe(struct hv_device *hdev,
const struct hv_vmbus_device_id *dev_id)
{
struct pci_host_bridge *bridge;
struct hv_pcibus_device *hbus;
u16 dom_req, dom;
char *name;
int ret;
bridge = devm_pci_alloc_host_bridge(&hdev->device, 0);
if (!bridge)
return -ENOMEM;
hbus = kzalloc(sizeof(*hbus), GFP_KERNEL);
if (!hbus)
return -ENOMEM;
hbus->bridge = bridge;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_init(&hbus->state_lock);
hbus->state = hv_pcibus_init;
hbus->wslot_res_allocated = -1;
/*
* The PCI bus "domain" is what is called "segment" in ACPI and other
* specs. Pull it from the instance ID, to get something usually
* unique. In rare cases of collision, we will find out another number
* not in use.
*
* Note that, since this code only runs in a Hyper-V VM, Hyper-V
* together with this guest driver can guarantee that (1) The only
* domain used by Gen1 VMs for something that looks like a physical
* PCI bus (which is actually emulated by the hypervisor) is domain 0.
* (2) There will be no overlap between domains (after fixing possible
* collisions) in the same VM.
*/
dom_req = hdev->dev_instance.b[5] << 8 | hdev->dev_instance.b[4];
dom = hv_get_dom_num(dom_req);
if (dom == HVPCI_DOM_INVALID) {
dev_err(&hdev->device,
"Unable to use dom# 0x%x or other numbers", dom_req);
ret = -EINVAL;
goto free_bus;
}
if (dom != dom_req)
dev_info(&hdev->device,
"PCI dom# 0x%x has collision, using 0x%x",
dom_req, dom);
hbus->bridge->domain_nr = dom;
#ifdef CONFIG_X86
hbus->sysdata.domain = dom;
hbus->use_calls = !!(ms_hyperv.hints & HV_X64_USE_MMIO_HYPERCALLS);
#elif defined(CONFIG_ARM64)
/*
* Set the PCI bus parent to be the corresponding VMbus
* device. Then the VMbus device will be assigned as the
* ACPI companion in pcibios_root_bridge_prepare() and
* pci_dma_configure() will propagate device coherence
* information to devices created on the bus.
*/
hbus->sysdata.parent = hdev->device.parent;
hbus->use_calls = false;
#endif
hbus->hdev = hdev;
INIT_LIST_HEAD(&hbus->children);
INIT_LIST_HEAD(&hbus->dr_list);
spin_lock_init(&hbus->config_lock);
spin_lock_init(&hbus->device_list_lock);
hbus->wq = alloc_ordered_workqueue("hv_pci_%x", 0,
hbus->bridge->domain_nr);
if (!hbus->wq) {
ret = -ENOMEM;
goto free_dom;
}
hdev->channel->next_request_id_callback = vmbus_next_request_id;
hdev->channel->request_addr_callback = vmbus_request_addr;
hdev->channel->rqstor_size = HV_PCI_RQSTOR_SIZE;
ret = vmbus_open(hdev->channel, pci_ring_size, pci_ring_size, NULL, 0,
hv_pci_onchannelcallback, hbus);
if (ret)
goto destroy_wq;
hv_set_drvdata(hdev, hbus);
ret = hv_pci_protocol_negotiation(hdev, pci_protocol_versions,
ARRAY_SIZE(pci_protocol_versions));
if (ret)
goto close;
ret = hv_allocate_config_window(hbus);
if (ret)
goto close;
hbus->cfg_addr = ioremap(hbus->mem_config->start,
PCI_CONFIG_MMIO_LENGTH);
if (!hbus->cfg_addr) {
dev_err(&hdev->device,
"Unable to map a virtual address for config space\n");
ret = -ENOMEM;
goto free_config;
}
name = kasprintf(GFP_KERNEL, "%pUL", &hdev->dev_instance);
if (!name) {
ret = -ENOMEM;
goto unmap;
}
hbus->fwnode = irq_domain_alloc_named_fwnode(name);
kfree(name);
if (!hbus->fwnode) {
ret = -ENOMEM;
goto unmap;
}
ret = hv_pcie_init_irq_domain(hbus);
if (ret)
goto free_fwnode;
ret = hv_pci_query_relations(hdev);
if (ret)
goto free_irq_domain;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_lock(&hbus->state_lock);
ret = hv_pci_enter_d0(hdev);
if (ret)
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
goto release_state_lock;
ret = hv_pci_allocate_bridge_windows(hbus);
if (ret)
goto exit_d0;
ret = hv_send_resources_allocated(hdev);
if (ret)
goto free_windows;
prepopulate_bars(hbus);
hbus->state = hv_pcibus_probed;
ret = create_root_hv_pci_bus(hbus);
if (ret)
goto free_windows;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_unlock(&hbus->state_lock);
return 0;
free_windows:
hv_pci_free_bridge_windows(hbus);
exit_d0:
(void) hv_pci_bus_exit(hdev, true);
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
release_state_lock:
mutex_unlock(&hbus->state_lock);
free_irq_domain:
irq_domain_remove(hbus->irq_domain);
free_fwnode:
irq_domain_free_fwnode(hbus->fwnode);
unmap:
iounmap(hbus->cfg_addr);
free_config:
hv_free_config_window(hbus);
close:
vmbus_close(hdev->channel);
destroy_wq:
destroy_workqueue(hbus->wq);
free_dom:
hv_put_dom_num(hbus->bridge->domain_nr);
free_bus:
kfree(hbus);
return ret;
}
static int hv_pci_bus_exit(struct hv_device *hdev, bool keep_devs)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
struct vmbus_channel *chan = hdev->channel;
struct {
struct pci_packet teardown_packet;
u8 buffer[sizeof(struct pci_message)];
} pkt;
struct hv_pci_compl comp_pkt;
struct hv_pci_dev *hpdev, *tmp;
unsigned long flags;
u64 trans_id;
int ret;
/*
* After the host sends the RESCIND_CHANNEL message, it doesn't
* access the per-channel ringbuffer any longer.
*/
if (chan->rescind)
return 0;
if (!keep_devs) {
struct list_head removed;
/* Move all present children to the list on stack */
INIT_LIST_HEAD(&removed);
spin_lock_irqsave(&hbus->device_list_lock, flags);
list_for_each_entry_safe(hpdev, tmp, &hbus->children, list_entry)
list_move_tail(&hpdev->list_entry, &removed);
spin_unlock_irqrestore(&hbus->device_list_lock, flags);
/* Remove all children in the list */
list_for_each_entry_safe(hpdev, tmp, &removed, list_entry) {
list_del(&hpdev->list_entry);
if (hpdev->pci_slot)
pci_destroy_slot(hpdev->pci_slot);
/* For the two refs got in new_pcichild_device() */
put_pcichild(hpdev);
put_pcichild(hpdev);
}
}
ret = hv_send_resources_released(hdev);
if (ret) {
dev_err(&hdev->device,
"Couldn't send resources released packet(s)\n");
return ret;
}
memset(&pkt.teardown_packet, 0, sizeof(pkt.teardown_packet));
init_completion(&comp_pkt.host_event);
pkt.teardown_packet.completion_func = hv_pci_generic_compl;
pkt.teardown_packet.compl_ctxt = &comp_pkt;
pkt.teardown_packet.message[0].type = PCI_BUS_D0EXIT;
ret = vmbus_sendpacket_getid(chan, &pkt.teardown_packet.message,
sizeof(struct pci_message),
(unsigned long)&pkt.teardown_packet,
&trans_id, VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (ret)
return ret;
if (wait_for_completion_timeout(&comp_pkt.host_event, 10 * HZ) == 0) {
/*
* The completion packet on the stack becomes invalid after
* 'return'; remove the ID from the VMbus requestor if the
* identifier is still mapped to/associated with the packet.
*
* Cf. hv_pci_onchannelcallback().
*/
vmbus_request_addr_match(chan, trans_id,
(unsigned long)&pkt.teardown_packet);
return -ETIMEDOUT;
}
return 0;
}
/**
* hv_pci_remove() - Remove routine for this VMBus channel
* @hdev: VMBus's tracking struct for this root PCI bus
*/
static void hv_pci_remove(struct hv_device *hdev)
{
struct hv_pcibus_device *hbus;
hbus = hv_get_drvdata(hdev);
if (hbus->state == hv_pcibus_installed) {
tasklet_disable(&hdev->channel->callback_event);
hbus->state = hv_pcibus_removing;
tasklet_enable(&hdev->channel->callback_event);
destroy_workqueue(hbus->wq);
hbus->wq = NULL;
/*
* At this point, no work is running or can be scheduled
* on hbus-wq. We can't race with hv_pci_devices_present()
* or hv_pci_eject_device(), it's safe to proceed.
*/
/* Remove the bus from PCI's point of view. */
pci_lock_rescan_remove();
pci_stop_root_bus(hbus->bridge->bus);
hv_pci_remove_slots(hbus);
pci_remove_root_bus(hbus->bridge->bus);
pci_unlock_rescan_remove();
}
hv_pci_bus_exit(hdev, false);
vmbus_close(hdev->channel);
iounmap(hbus->cfg_addr);
hv_free_config_window(hbus);
hv_pci_free_bridge_windows(hbus);
irq_domain_remove(hbus->irq_domain);
irq_domain_free_fwnode(hbus->fwnode);
hv_put_dom_num(hbus->bridge->domain_nr);
kfree(hbus);
}
static int hv_pci_suspend(struct hv_device *hdev)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
enum hv_pcibus_state old_state;
int ret;
/*
* hv_pci_suspend() must make sure there are no pending work items
* before calling vmbus_close(), since it runs in a process context
* as a callback in dpm_suspend(). When it starts to run, the channel
* callback hv_pci_onchannelcallback(), which runs in a tasklet
* context, can be still running concurrently and scheduling new work
* items onto hbus->wq in hv_pci_devices_present() and
* hv_pci_eject_device(), and the work item handlers can access the
* vmbus channel, which can be being closed by hv_pci_suspend(), e.g.
* the work item handler pci_devices_present_work() ->
* new_pcichild_device() writes to the vmbus channel.
*
* To eliminate the race, hv_pci_suspend() disables the channel
* callback tasklet, sets hbus->state to hv_pcibus_removing, and
* re-enables the tasklet. This way, when hv_pci_suspend() proceeds,
* it knows that no new work item can be scheduled, and then it flushes
* hbus->wq and safely closes the vmbus channel.
*/
tasklet_disable(&hdev->channel->callback_event);
/* Change the hbus state to prevent new work items. */
old_state = hbus->state;
if (hbus->state == hv_pcibus_installed)
hbus->state = hv_pcibus_removing;
tasklet_enable(&hdev->channel->callback_event);
if (old_state != hv_pcibus_installed)
return -EINVAL;
flush_workqueue(hbus->wq);
ret = hv_pci_bus_exit(hdev, true);
if (ret)
return ret;
vmbus_close(hdev->channel);
return 0;
}
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
static int hv_pci_restore_msi_msg(struct pci_dev *pdev, void *arg)
{
struct irq_data *irq_data;
struct msi_desc *entry;
int ret = 0;
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
if (!pdev->msi_enabled && !pdev->msix_enabled)
return 0;
msi_lock_descs(&pdev->dev);
msi_for_each_desc(entry, &pdev->dev, MSI_DESC_ASSOCIATED) {
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
irq_data = irq_get_irq_data(entry->irq);
if (WARN_ON_ONCE(!irq_data)) {
ret = -EINVAL;
break;
}
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
hv_compose_msi_msg(irq_data, &entry->msg);
}
msi_unlock_descs(&pdev->dev);
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
return ret;
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
}
/*
* Upon resume, pci_restore_msi_state() -> ... -> __pci_write_msi_msg()
* directly writes the MSI/MSI-X registers via MMIO, but since Hyper-V
* doesn't trap and emulate the MMIO accesses, here hv_compose_msi_msg()
* must be used to ask Hyper-V to re-create the IOMMU Interrupt Remapping
* Table entries.
*/
static void hv_pci_restore_msi_state(struct hv_pcibus_device *hbus)
{
pci_walk_bus(hbus->bridge->bus, hv_pci_restore_msi_msg, NULL);
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
}
static int hv_pci_resume(struct hv_device *hdev)
{
struct hv_pcibus_device *hbus = hv_get_drvdata(hdev);
enum pci_protocol_version_t version[1];
int ret;
hbus->state = hv_pcibus_init;
hdev->channel->next_request_id_callback = vmbus_next_request_id;
hdev->channel->request_addr_callback = vmbus_request_addr;
hdev->channel->rqstor_size = HV_PCI_RQSTOR_SIZE;
ret = vmbus_open(hdev->channel, pci_ring_size, pci_ring_size, NULL, 0,
hv_pci_onchannelcallback, hbus);
if (ret)
return ret;
/* Only use the version that was in use before hibernation. */
version[0] = hbus->protocol_version;
ret = hv_pci_protocol_negotiation(hdev, version, 1);
if (ret)
goto out;
ret = hv_pci_query_relations(hdev);
if (ret)
goto out;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_lock(&hbus->state_lock);
ret = hv_pci_enter_d0(hdev);
if (ret)
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
goto release_state_lock;
ret = hv_send_resources_allocated(hdev);
if (ret)
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
goto release_state_lock;
prepopulate_bars(hbus);
PCI: hv: Fix hibernation in case interrupts are not re-created pci_restore_msi_state() directly writes the MSI/MSI-X related registers via MMIO. On a physical machine, this works perfectly; for a Linux VM running on a hypervisor, which typically enables IOMMU interrupt remapping, the hypervisor usually should trap and emulate the MMIO accesses in order to re-create the necessary interrupt remapping table entries in the IOMMU, otherwise the interrupts can not work in the VM after hibernation. Hyper-V is different from other hypervisors in that it does not trap and emulate the MMIO accesses, and instead it uses a para-virtualized method, which requires the VM to call hv_compose_msi_msg() to notify the hypervisor of the info that would be passed to the hypervisor in the case of the trap-and-emulate method. This is not an issue to a lot of PCI device drivers, which destroy and re-create the interrupts across hibernation, so hv_compose_msi_msg() is called automatically. However, some PCI device drivers (e.g. the in-tree GPU driver nouveau and the out-of-tree Nvidia proprietary GPU driver) do not destroy and re-create MSI/MSI-X interrupts across hibernation, so hv_pci_resume() has to call hv_compose_msi_msg(), otherwise the PCI device drivers can no longer receive interrupts after the VM resumes from hibernation. Hyper-V is also different in that chip->irq_unmask() may fail in a Linux VM running on Hyper-V (on a physical machine, chip->irq_unmask() can not fail because unmasking an MSI/MSI-X register just means an MMIO write): during hibernation, when a CPU is offlined, the kernel tries to move the interrupt to the remaining CPUs that haven't been offlined yet. In this case, hv_irq_unmask() -> hv_do_hypercall() always fails because the vmbus channel has been closed: here the early "return" in hv_irq_unmask() means the pci_msi_unmask_irq() is not called, i.e. the desc->masked remains "true", so later after hibernation, the MSI interrupt always remains masked, which is incorrect. Refer to cpu_disable_common() -> fixup_irqs() -> irq_migrate_all_off_this_cpu() -> migrate_one_irq(): static bool migrate_one_irq(struct irq_desc *desc) { ... if (maskchip && chip->irq_mask) chip->irq_mask(d); ... err = irq_do_set_affinity(d, affinity, false); ... if (maskchip && chip->irq_unmask) chip->irq_unmask(d); Fix the issue by calling pci_msi_unmask_irq() unconditionally in hv_irq_unmask(). Also suppress the error message for hibernation because the hypercall failure during hibernation does not matter (at this time all the devices have been frozen). Note: the correct affinity info is still updated into the irqdata data structure in migrate_one_irq() -> irq_do_set_affinity() -> hv_set_affinity(), so later when the VM resumes, hv_pci_restore_msi_state() is able to correctly restore the interrupt with the correct affinity. Link: https://lore.kernel.org/r/20201002085158.9168-1-decui@microsoft.com Fixes: ac82fc832708 ("PCI: hv: Add hibernation support") Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Jake Oshins <jakeo@microsoft.com>
2020-10-02 08:51:58 +00:00
hv_pci_restore_msi_state(hbus);
hbus->state = hv_pcibus_installed;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
mutex_unlock(&hbus->state_lock);
return 0;
PCI: hv: Add a per-bus mutex state_lock In the case of fast device addition/removal, it's possible that hv_eject_device_work() can start to run before create_root_hv_pci_bus() starts to run; as a result, the pci_get_domain_bus_and_slot() in hv_eject_device_work() can return a 'pdev' of NULL, and hv_eject_device_work() can remove the 'hpdev', and immediately send a message PCI_EJECTION_COMPLETE to the host, and the host immediately unassigns the PCI device from the guest; meanwhile, create_root_hv_pci_bus() and the PCI device driver can be probing the dead PCI device and reporting timeout errors. Fix the issue by adding a per-bus mutex 'state_lock' and grabbing the mutex before powering on the PCI bus in hv_pci_enter_d0(): when hv_eject_device_work() starts to run, it's able to find the 'pdev' and call pci_stop_and_remove_bus_device(pdev): if the PCI device driver has loaded, the PCI device driver's probe() function is already called in create_root_hv_pci_bus() -> pci_bus_add_devices(), and now hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to call the PCI device driver's remove() function and remove the device reliably; if the PCI device driver hasn't loaded yet, the function call hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to remove the PCI device reliably and the PCI device driver's probe() function won't be called; if the PCI device driver's probe() is already running (e.g., systemd-udev is loading the PCI device driver), it must be holding the per-device lock, and after the probe() finishes and releases the lock, hv_eject_device_work() -> pci_stop_and_remove_bus_device() is able to proceed to remove the device reliably. Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") Signed-off-by: Dexuan Cui <decui@microsoft.com> Reviewed-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230615044451.5580-6-decui@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
2023-06-15 04:44:51 +00:00
release_state_lock:
mutex_unlock(&hbus->state_lock);
out:
vmbus_close(hdev->channel);
return ret;
}
static const struct hv_vmbus_device_id hv_pci_id_table[] = {
/* PCI Pass-through Class ID */
/* 44C4F61D-4444-4400-9D52-802E27EDE19F */
{ HV_PCIE_GUID, },
{ },
};
MODULE_DEVICE_TABLE(vmbus, hv_pci_id_table);
static struct hv_driver hv_pci_drv = {
.name = "hv_pci",
.id_table = hv_pci_id_table,
.probe = hv_pci_probe,
.remove = hv_pci_remove,
.suspend = hv_pci_suspend,
.resume = hv_pci_resume,
};
static void __exit exit_hv_pci_drv(void)
{
vmbus_driver_unregister(&hv_pci_drv);
hvpci_block_ops.read_block = NULL;
hvpci_block_ops.write_block = NULL;
hvpci_block_ops.reg_blk_invalidate = NULL;
}
static int __init init_hv_pci_drv(void)
{
int ret;
if (!hv_is_hyperv_initialized())
return -ENODEV;
ret = hv_pci_irqchip_init();
if (ret)
return ret;
/* Set the invalid domain number's bit, so it will not be used */
set_bit(HVPCI_DOM_INVALID, hvpci_dom_map);
/* Initialize PCI block r/w interface */
hvpci_block_ops.read_block = hv_read_config_block;
hvpci_block_ops.write_block = hv_write_config_block;
hvpci_block_ops.reg_blk_invalidate = hv_register_block_invalidate;
return vmbus_driver_register(&hv_pci_drv);
}
module_init(init_hv_pci_drv);
module_exit(exit_hv_pci_drv);
MODULE_DESCRIPTION("Hyper-V PCI");
MODULE_LICENSE("GPL v2");