root/drivers/hv/mshv_synic.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2023, Microsoft Corporation.
 *
 * mshv_root module's main interrupt handler and associated functionality.
 *
 * Authors: Microsoft Linux virtualization team
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/random.h>
#include <linux/cpuhotplug.h>
#include <linux/reboot.h>
#include <asm/mshyperv.h>
#include <linux/acpi.h>

#include "mshv_eventfd.h"
#include "mshv.h"

static int synic_cpuhp_online;
static struct hv_synic_pages __percpu *synic_pages;
static int mshv_sint_vector = -1; /* hwirq for the SynIC SINTs */
static int mshv_sint_irq = -1; /* Linux IRQ for mshv_sint_vector */

static u32 synic_event_ring_get_queued_port(u32 sint_index)
{
        struct hv_synic_event_ring_page **event_ring_page;
        volatile struct hv_synic_event_ring *ring;
        struct hv_synic_pages *spages;
        u8 **synic_eventring_tail;
        u32 message;
        u8 tail;

        spages = this_cpu_ptr(synic_pages);
        event_ring_page = &spages->synic_event_ring_page;
        synic_eventring_tail = (u8 **)this_cpu_ptr(hv_synic_eventring_tail);

        if (unlikely(!*synic_eventring_tail)) {
                pr_debug("Missing synic event ring tail!\n");
                return 0;
        }
        tail = (*synic_eventring_tail)[sint_index];

        if (unlikely(!*event_ring_page)) {
                pr_debug("Missing synic event ring page!\n");
                return 0;
        }

        ring = &(*event_ring_page)->sint_event_ring[sint_index];

        /*
         * Get the message.
         */
        message = ring->data[tail];

        if (!message) {
                if (ring->ring_full) {
                        /*
                         * Ring is marked full, but we would have consumed all
                         * the messages. Notify the hypervisor that ring is now
                         * empty and check again.
                         */
                        ring->ring_full = 0;
                        hv_call_notify_port_ring_empty(sint_index);
                        message = ring->data[tail];
                }

                if (!message) {
                        ring->signal_masked = 0;
                        /*
                         * Unmask the signal and sync with hypervisor
                         * before one last check for any message.
                         */
                        mb();
                        message = ring->data[tail];

                        /*
                         * Ok, lets bail out.
                         */
                        if (!message)
                                return 0;
                }

                ring->signal_masked = 1;
        }

        /*
         * Clear the message in the ring buffer.
         */
        ring->data[tail] = 0;

        if (++tail == HV_SYNIC_EVENT_RING_MESSAGE_COUNT)
                tail = 0;

        (*synic_eventring_tail)[sint_index] = tail;

        return message;
}

static bool
mshv_doorbell_isr(struct hv_message *msg)
{
        struct hv_notification_message_payload *notification;
        u32 port;

        if (msg->header.message_type != HVMSG_SYNIC_SINT_INTERCEPT)
                return false;

        notification = (struct hv_notification_message_payload *)msg->u.payload;
        if (notification->sint_index != HV_SYNIC_DOORBELL_SINT_INDEX)
                return false;

        while ((port = synic_event_ring_get_queued_port(HV_SYNIC_DOORBELL_SINT_INDEX))) {
                struct port_table_info ptinfo = { 0 };

                if (mshv_portid_lookup(port, &ptinfo)) {
                        pr_debug("Failed to get port info from port_table!\n");
                        continue;
                }

                if (ptinfo.hv_port_type != HV_PORT_TYPE_DOORBELL) {
                        pr_debug("Not a doorbell port!, port: %d, port_type: %d\n",
                                 port, ptinfo.hv_port_type);
                        continue;
                }

                /* Invoke the callback */
                ptinfo.hv_port_doorbell.doorbell_cb(port,
                                                 ptinfo.hv_port_doorbell.data);
        }

        return true;
}

static bool mshv_async_call_completion_isr(struct hv_message *msg)
{
        bool handled = false;
        struct hv_async_completion_message_payload *async_msg;
        struct mshv_partition *partition;
        u64 partition_id;

        if (msg->header.message_type != HVMSG_ASYNC_CALL_COMPLETION)
                goto out;

        async_msg =
                (struct hv_async_completion_message_payload *)msg->u.payload;

        partition_id = async_msg->partition_id;

        /*
         * Hold this lock for the rest of the isr, because the partition could
         * be released anytime.
         * e.g. the MSHV_RUN_VP thread could wake on another cpu; it could
         * release the partition unless we hold this!
         */
        rcu_read_lock();

        partition = mshv_partition_find(partition_id);

        if (unlikely(!partition)) {
                pr_debug("failed to find partition %llu\n", partition_id);
                goto unlock_out;
        }

        partition->async_hypercall_status = async_msg->status;
        complete(&partition->async_hypercall);

        handled = true;

unlock_out:
        rcu_read_unlock();
out:
        return handled;
}

static void kick_vp(struct mshv_vp *vp)
{
        atomic64_inc(&vp->run.vp_signaled_count);
        vp->run.kicked_by_hv = 1;
        wake_up(&vp->run.vp_suspend_queue);
}

static void
handle_bitset_message(const struct hv_vp_signal_bitset_scheduler_message *msg)
{
        int bank_idx, vps_signaled = 0, bank_mask_size;
        struct mshv_partition *partition;
        const struct hv_vpset *vpset;
        const u64 *bank_contents;
        u64 partition_id = msg->partition_id;

        if (msg->vp_bitset.bitset.format != HV_GENERIC_SET_SPARSE_4K) {
                pr_debug("scheduler message format is not HV_GENERIC_SET_SPARSE_4K");
                return;
        }

        if (msg->vp_count == 0) {
                pr_debug("scheduler message with no VP specified");
                return;
        }

        rcu_read_lock();

        partition = mshv_partition_find(partition_id);
        if (unlikely(!partition)) {
                pr_debug("failed to find partition %llu\n", partition_id);
                goto unlock_out;
        }

        vpset = &msg->vp_bitset.bitset;

        bank_idx = -1;
        bank_contents = vpset->bank_contents;
        bank_mask_size = sizeof(vpset->valid_bank_mask) * BITS_PER_BYTE;

        while (true) {
                int vp_bank_idx = -1;
                int vp_bank_size = sizeof(*bank_contents) * BITS_PER_BYTE;
                int vp_index;

                bank_idx = find_next_bit((unsigned long *)&vpset->valid_bank_mask,
                                         bank_mask_size, bank_idx + 1);
                if (bank_idx == bank_mask_size)
                        break;

                while (true) {
                        struct mshv_vp *vp;

                        vp_bank_idx = find_next_bit((unsigned long *)bank_contents,
                                                    vp_bank_size, vp_bank_idx + 1);
                        if (vp_bank_idx == vp_bank_size)
                                break;

                        vp_index = (bank_idx * vp_bank_size) + vp_bank_idx;

                        /* This shouldn't happen, but just in case. */
                        if (unlikely(vp_index >= MSHV_MAX_VPS)) {
                                pr_debug("VP index %u out of bounds\n",
                                         vp_index);
                                goto unlock_out;
                        }

                        vp = partition->pt_vp_array[vp_index];
                        if (unlikely(!vp)) {
                                pr_debug("failed to find VP %u\n", vp_index);
                                goto unlock_out;
                        }

                        kick_vp(vp);
                        vps_signaled++;
                }

                bank_contents++;
        }

unlock_out:
        rcu_read_unlock();

        if (vps_signaled != msg->vp_count)
                pr_debug("asked to signal %u VPs but only did %u\n",
                         msg->vp_count, vps_signaled);
}

static void
handle_pair_message(const struct hv_vp_signal_pair_scheduler_message *msg)
{
        struct mshv_partition *partition = NULL;
        struct mshv_vp *vp;
        int idx;

        rcu_read_lock();

        for (idx = 0; idx < msg->vp_count; idx++) {
                u64 partition_id = msg->partition_ids[idx];
                u32 vp_index = msg->vp_indexes[idx];

                if (idx == 0 || partition->pt_id != partition_id) {
                        partition = mshv_partition_find(partition_id);
                        if (unlikely(!partition)) {
                                pr_debug("failed to find partition %llu\n",
                                         partition_id);
                                break;
                        }
                }

                /* This shouldn't happen, but just in case. */
                if (unlikely(vp_index >= MSHV_MAX_VPS)) {
                        pr_debug("VP index %u out of bounds\n", vp_index);
                        break;
                }

                vp = partition->pt_vp_array[vp_index];
                if (!vp) {
                        pr_debug("failed to find VP %u\n", vp_index);
                        break;
                }

                kick_vp(vp);
        }

        rcu_read_unlock();
}

static bool
mshv_scheduler_isr(struct hv_message *msg)
{
        if (msg->header.message_type != HVMSG_SCHEDULER_VP_SIGNAL_BITSET &&
            msg->header.message_type != HVMSG_SCHEDULER_VP_SIGNAL_PAIR)
                return false;

        if (msg->header.message_type == HVMSG_SCHEDULER_VP_SIGNAL_BITSET)
                handle_bitset_message((struct hv_vp_signal_bitset_scheduler_message *)
                                      msg->u.payload);
        else
                handle_pair_message((struct hv_vp_signal_pair_scheduler_message *)
                                    msg->u.payload);

        return true;
}

static bool
mshv_intercept_isr(struct hv_message *msg)
{
        struct mshv_partition *partition;
        bool handled = false;
        struct mshv_vp *vp;
        u64 partition_id;
        u32 vp_index;

        partition_id = msg->header.sender;

        rcu_read_lock();

        partition = mshv_partition_find(partition_id);
        if (unlikely(!partition)) {
                pr_debug("failed to find partition %llu\n",
                         partition_id);
                goto unlock_out;
        }

        if (msg->header.message_type == HVMSG_X64_APIC_EOI) {
                /*
                 * Check if this gsi is registered in the
                 * ack_notifier list and invoke the callback
                 * if registered.
                 */

                /*
                 * If there is a notifier, the ack callback is supposed
                 * to handle the VMEXIT. So we need not pass this message
                 * to vcpu thread.
                 */
                struct hv_x64_apic_eoi_message *eoi_msg =
                        (struct hv_x64_apic_eoi_message *)&msg->u.payload[0];

                if (mshv_notify_acked_gsi(partition, eoi_msg->interrupt_vector)) {
                        handled = true;
                        goto unlock_out;
                }
        }

        /*
         * We should get an opaque intercept message here for all intercept
         * messages, since we're using the mapped VP intercept message page.
         *
         * The intercept message will have been placed in intercept message
         * page at this point.
         *
         * Make sure the message type matches our expectation.
         */
        if (msg->header.message_type != HVMSG_OPAQUE_INTERCEPT) {
                pr_debug("wrong message type %d", msg->header.message_type);
                goto unlock_out;
        }

        /*
         * Since we directly index the vp, and it has to exist for us to be here
         * (because the vp is only deleted when the partition is), no additional
         * locking is needed here
         */
        vp_index =
               ((struct hv_opaque_intercept_message *)msg->u.payload)->vp_index;
        vp = partition->pt_vp_array[vp_index];
        if (unlikely(!vp)) {
                pr_debug("failed to find VP %u\n", vp_index);
                goto unlock_out;
        }

        kick_vp(vp);

        handled = true;

unlock_out:
        rcu_read_unlock();

        return handled;
}

void mshv_isr(void)
{
        struct hv_synic_pages *spages = this_cpu_ptr(synic_pages);
        struct hv_message_page **msg_page = &spages->hyp_synic_message_page;
        struct hv_message *msg;
        bool handled;

        if (unlikely(!(*msg_page))) {
                pr_debug("Missing synic page!\n");
                return;
        }

        msg = &((*msg_page)->sint_message[HV_SYNIC_INTERCEPTION_SINT_INDEX]);

        /*
         * If the type isn't set, there isn't really a message;
         * it may be some other hyperv interrupt
         */
        if (msg->header.message_type == HVMSG_NONE)
                return;

        handled = mshv_doorbell_isr(msg);

        if (!handled)
                handled = mshv_scheduler_isr(msg);

        if (!handled)
                handled = mshv_async_call_completion_isr(msg);

        if (!handled)
                handled = mshv_intercept_isr(msg);

        if (handled) {
                /*
                 * Acknowledge message with hypervisor if another message is
                 * pending.
                 */
                msg->header.message_type = HVMSG_NONE;
                /*
                 * Ensure the write is complete so the hypervisor will deliver
                 * the next message if available.
                 */
                mb();
                if (msg->header.message_flags.msg_pending)
                        hv_set_non_nested_msr(HV_MSR_EOM, 0);

                add_interrupt_randomness(mshv_sint_vector);
        } else {
                pr_warn_once("%s: unknown message type 0x%x\n", __func__,
                             msg->header.message_type);
        }
}

static int mshv_synic_cpu_init(unsigned int cpu)
{
        union hv_synic_simp simp;
        union hv_synic_siefp siefp;
        union hv_synic_sirbp sirbp;
        union hv_synic_sint sint;
        union hv_synic_scontrol sctrl;
        struct hv_synic_pages *spages = this_cpu_ptr(synic_pages);
        struct hv_message_page **msg_page = &spages->hyp_synic_message_page;
        struct hv_synic_event_flags_page **event_flags_page =
                        &spages->synic_event_flags_page;
        struct hv_synic_event_ring_page **event_ring_page =
                        &spages->synic_event_ring_page;

        /* Setup the Synic's message page */
        simp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIMP);
        simp.simp_enabled = true;
        *msg_page = memremap(simp.base_simp_gpa << HV_HYP_PAGE_SHIFT,
                             HV_HYP_PAGE_SIZE,
                             MEMREMAP_WB);

        if (!(*msg_page))
                return -EFAULT;

        hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);

        /* Setup the Synic's event flags page */
        siefp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIEFP);
        siefp.siefp_enabled = true;
        *event_flags_page = memremap(siefp.base_siefp_gpa << PAGE_SHIFT,
                                     PAGE_SIZE, MEMREMAP_WB);

        if (!(*event_flags_page))
                goto cleanup;

        hv_set_non_nested_msr(HV_MSR_SIEFP, siefp.as_uint64);

        /* Setup the Synic's event ring page */
        sirbp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIRBP);
        sirbp.sirbp_enabled = true;
        *event_ring_page = memremap(sirbp.base_sirbp_gpa << PAGE_SHIFT,
                                    PAGE_SIZE, MEMREMAP_WB);

        if (!(*event_ring_page))
                goto cleanup;

        hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64);

        if (mshv_sint_irq != -1)
                enable_percpu_irq(mshv_sint_irq, 0);

        /* Enable intercepts */
        sint.as_uint64 = 0;
        sint.vector = mshv_sint_vector;
        sint.masked = false;
        sint.auto_eoi = hv_recommend_using_aeoi();
        hv_set_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX,
                              sint.as_uint64);

        /* Doorbell SINT */
        sint.as_uint64 = 0;
        sint.vector = mshv_sint_vector;
        sint.masked = false;
        sint.as_intercept = 1;
        sint.auto_eoi = hv_recommend_using_aeoi();
        hv_set_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_DOORBELL_SINT_INDEX,
                              sint.as_uint64);

        /* Enable global synic bit */
        sctrl.as_uint64 = hv_get_non_nested_msr(HV_MSR_SCONTROL);
        sctrl.enable = 1;
        hv_set_non_nested_msr(HV_MSR_SCONTROL, sctrl.as_uint64);

        return 0;

cleanup:
        if (*event_ring_page) {
                sirbp.sirbp_enabled = false;
                hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64);
                memunmap(*event_ring_page);
        }
        if (*event_flags_page) {
                siefp.siefp_enabled = false;
                hv_set_non_nested_msr(HV_MSR_SIEFP, siefp.as_uint64);
                memunmap(*event_flags_page);
        }
        if (*msg_page) {
                simp.simp_enabled = false;
                hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);
                memunmap(*msg_page);
        }

        return -EFAULT;
}

static int mshv_synic_cpu_exit(unsigned int cpu)
{
        union hv_synic_sint sint;
        union hv_synic_simp simp;
        union hv_synic_siefp siefp;
        union hv_synic_sirbp sirbp;
        union hv_synic_scontrol sctrl;
        struct hv_synic_pages *spages = this_cpu_ptr(synic_pages);
        struct hv_message_page **msg_page = &spages->hyp_synic_message_page;
        struct hv_synic_event_flags_page **event_flags_page =
                &spages->synic_event_flags_page;
        struct hv_synic_event_ring_page **event_ring_page =
                &spages->synic_event_ring_page;

        /* Disable the interrupt */
        sint.as_uint64 = hv_get_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX);
        sint.masked = true;
        hv_set_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX,
                              sint.as_uint64);

        /* Disable Doorbell SINT */
        sint.as_uint64 = hv_get_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_DOORBELL_SINT_INDEX);
        sint.masked = true;
        hv_set_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_DOORBELL_SINT_INDEX,
                              sint.as_uint64);

        if (mshv_sint_irq != -1)
                disable_percpu_irq(mshv_sint_irq);

        /* Disable Synic's event ring page */
        sirbp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIRBP);
        sirbp.sirbp_enabled = false;
        hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64);
        memunmap(*event_ring_page);

        /* Disable Synic's event flags page */
        siefp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIEFP);
        siefp.siefp_enabled = false;
        hv_set_non_nested_msr(HV_MSR_SIEFP, siefp.as_uint64);
        memunmap(*event_flags_page);

        /* Disable Synic's message page */
        simp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIMP);
        simp.simp_enabled = false;
        hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);
        memunmap(*msg_page);

        /* Disable global synic bit */
        sctrl.as_uint64 = hv_get_non_nested_msr(HV_MSR_SCONTROL);
        sctrl.enable = 0;
        hv_set_non_nested_msr(HV_MSR_SCONTROL, sctrl.as_uint64);

        return 0;
}

int
mshv_register_doorbell(u64 partition_id, doorbell_cb_t doorbell_cb, void *data,
                       u64 gpa, u64 val, u64 flags)
{
        struct hv_connection_info connection_info = { 0 };
        union hv_connection_id connection_id = { 0 };
        struct port_table_info *port_table_info;
        struct hv_port_info port_info = { 0 };
        union hv_port_id port_id = { 0 };
        int ret;

        port_table_info = kmalloc_obj(*port_table_info);
        if (!port_table_info)
                return -ENOMEM;

        port_table_info->hv_port_type = HV_PORT_TYPE_DOORBELL;
        port_table_info->hv_port_doorbell.doorbell_cb = doorbell_cb;
        port_table_info->hv_port_doorbell.data = data;
        ret = mshv_portid_alloc(port_table_info);
        if (ret < 0) {
                kfree(port_table_info);
                return ret;
        }

        port_id.u.id = ret;
        port_info.port_type = HV_PORT_TYPE_DOORBELL;
        port_info.doorbell_port_info.target_sint = HV_SYNIC_DOORBELL_SINT_INDEX;
        port_info.doorbell_port_info.target_vp = HV_ANY_VP;
        ret = hv_call_create_port(hv_current_partition_id, port_id, partition_id,
                                  &port_info,
                                  0, 0, NUMA_NO_NODE);

        if (ret < 0) {
                mshv_portid_free(port_id.u.id);
                return ret;
        }

        connection_id.u.id = port_id.u.id;
        connection_info.port_type = HV_PORT_TYPE_DOORBELL;
        connection_info.doorbell_connection_info.gpa = gpa;
        connection_info.doorbell_connection_info.trigger_value = val;
        connection_info.doorbell_connection_info.flags = flags;

        ret = hv_call_connect_port(hv_current_partition_id, port_id, partition_id,
                                   connection_id, &connection_info, 0, NUMA_NO_NODE);
        if (ret < 0) {
                hv_call_delete_port(hv_current_partition_id, port_id);
                mshv_portid_free(port_id.u.id);
                return ret;
        }

        // lets use the port_id as the doorbell_id
        return port_id.u.id;
}

void
mshv_unregister_doorbell(u64 partition_id, int doorbell_portid)
{
        union hv_port_id port_id = { 0 };
        union hv_connection_id connection_id = { 0 };

        connection_id.u.id = doorbell_portid;
        hv_call_disconnect_port(partition_id, connection_id);

        port_id.u.id = doorbell_portid;
        hv_call_delete_port(hv_current_partition_id, port_id);

        mshv_portid_free(doorbell_portid);
}

static int mshv_synic_reboot_notify(struct notifier_block *nb,
                              unsigned long code, void *unused)
{
        if (!hv_root_partition())
                return 0;

        cpuhp_remove_state(synic_cpuhp_online);
        return 0;
}

static struct notifier_block mshv_synic_reboot_nb = {
        .notifier_call = mshv_synic_reboot_notify,
};

#ifndef HYPERVISOR_CALLBACK_VECTOR
static DEFINE_PER_CPU(long, mshv_evt);

static irqreturn_t mshv_percpu_isr(int irq, void *dev_id)
{
        mshv_isr();
        return IRQ_HANDLED;
}

#ifdef CONFIG_ACPI
static int __init mshv_acpi_setup_sint_irq(void)
{
        return acpi_register_gsi(NULL, mshv_sint_vector, ACPI_EDGE_SENSITIVE,
                                        ACPI_ACTIVE_HIGH);
}

static void mshv_acpi_cleanup_sint_irq(void)
{
        acpi_unregister_gsi(mshv_sint_vector);
}
#else
static int __init mshv_acpi_setup_sint_irq(void)
{
        return -ENODEV;
}

static void mshv_acpi_cleanup_sint_irq(void)
{
}
#endif

static int __init mshv_sint_vector_setup(void)
{
        int ret;
        struct hv_register_assoc reg = {
                .name = HV_ARM64_REGISTER_SINT_RESERVED_INTERRUPT_ID,
        };
        union hv_input_vtl input_vtl = { 0 };

        if (acpi_disabled)
                return -ENODEV;

        ret = hv_call_get_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF,
                                1, input_vtl, &reg);
        if (ret || !reg.value.reg64)
                return -ENODEV;

        mshv_sint_vector = reg.value.reg64;
        ret = mshv_acpi_setup_sint_irq();
        if (ret < 0) {
                pr_err("Failed to setup IRQ for MSHV SINT vector %d: %d\n",
                        mshv_sint_vector, ret);
                goto out_fail;
        }

        mshv_sint_irq = ret;

        ret = request_percpu_irq(mshv_sint_irq, mshv_percpu_isr, "MSHV",
                &mshv_evt);
        if (ret)
                goto out_unregister;

        return 0;

out_unregister:
        mshv_acpi_cleanup_sint_irq();
out_fail:
        return ret;
}

static void mshv_sint_vector_cleanup(void)
{
        free_percpu_irq(mshv_sint_irq, &mshv_evt);
        mshv_acpi_cleanup_sint_irq();
}
#else /* !HYPERVISOR_CALLBACK_VECTOR */
static int __init mshv_sint_vector_setup(void)
{
        mshv_sint_vector = HYPERVISOR_CALLBACK_VECTOR;
        return 0;
}

static void mshv_sint_vector_cleanup(void)
{
}
#endif /* HYPERVISOR_CALLBACK_VECTOR */

int __init mshv_synic_init(struct device *dev)
{
        int ret = 0;

        ret = mshv_sint_vector_setup();
        if (ret)
                return ret;

        synic_pages = alloc_percpu(struct hv_synic_pages);
        if (!synic_pages) {
                dev_err(dev, "Failed to allocate percpu synic page\n");
                ret = -ENOMEM;
                goto sint_vector_cleanup;
        }

        ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "mshv_synic",
                                mshv_synic_cpu_init,
                                mshv_synic_cpu_exit);
        if (ret < 0) {
                dev_err(dev, "Failed to setup cpu hotplug state: %i\n", ret);
                goto free_synic_pages;
        }

        synic_cpuhp_online = ret;

        ret = register_reboot_notifier(&mshv_synic_reboot_nb);
        if (ret)
                goto remove_cpuhp_state;

        return 0;

remove_cpuhp_state:
        cpuhp_remove_state(synic_cpuhp_online);
free_synic_pages:
        free_percpu(synic_pages);
sint_vector_cleanup:
        mshv_sint_vector_cleanup();
        return ret;
}

void mshv_synic_exit(void)
{
        unregister_reboot_notifier(&mshv_synic_reboot_nb);
        cpuhp_remove_state(synic_cpuhp_online);
        free_percpu(synic_pages);
        mshv_sint_vector_cleanup();
}