linux/drivers/hv/hv.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2009, Microsoft Corporation.
   3 *
   4 * This program is free software; you can redistribute it and/or modify it
   5 * under the terms and conditions of the GNU General Public License,
   6 * version 2, as published by the Free Software Foundation.
   7 *
   8 * This program is distributed in the hope it will be useful, but WITHOUT
   9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  11 * more details.
  12 *
  13 * You should have received a copy of the GNU General Public License along with
  14 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  15 * Place - Suite 330, Boston, MA 02111-1307 USA.
  16 *
  17 * Authors:
  18 *   Haiyang Zhang <haiyangz@microsoft.com>
  19 *   Hank Janssen  <hjanssen@microsoft.com>
  20 *
  21 */
  22#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  23
  24#include <linux/kernel.h>
  25#include <linux/mm.h>
  26#include <linux/slab.h>
  27#include <linux/vmalloc.h>
  28#include <linux/hyperv.h>
  29#include <linux/version.h>
  30#include <linux/interrupt.h>
  31#include <asm/hyperv.h>
  32#include "hyperv_vmbus.h"
  33
  34/* The one and only */
  35struct hv_context hv_context = {
  36        .synic_initialized      = false,
  37        .hypercall_page         = NULL,
  38};
  39
  40/*
  41 * query_hypervisor_info - Get version info of the windows hypervisor
  42 */
  43unsigned int host_info_eax;
  44unsigned int host_info_ebx;
  45unsigned int host_info_ecx;
  46unsigned int host_info_edx;
  47
  48static int query_hypervisor_info(void)
  49{
  50        unsigned int eax;
  51        unsigned int ebx;
  52        unsigned int ecx;
  53        unsigned int edx;
  54        unsigned int max_leaf;
  55        unsigned int op;
  56
  57        /*
  58        * Its assumed that this is called after confirming that Viridian
  59        * is present. Query id and revision.
  60        */
  61        eax = 0;
  62        ebx = 0;
  63        ecx = 0;
  64        edx = 0;
  65        op = HVCPUID_VENDOR_MAXFUNCTION;
  66        cpuid(op, &eax, &ebx, &ecx, &edx);
  67
  68        max_leaf = eax;
  69
  70        if (max_leaf >= HVCPUID_VERSION) {
  71                eax = 0;
  72                ebx = 0;
  73                ecx = 0;
  74                edx = 0;
  75                op = HVCPUID_VERSION;
  76                cpuid(op, &eax, &ebx, &ecx, &edx);
  77                host_info_eax = eax;
  78                host_info_ebx = ebx;
  79                host_info_ecx = ecx;
  80                host_info_edx = edx;
  81        }
  82        return max_leaf;
  83}
  84
  85/*
  86 * do_hypercall- Invoke the specified hypercall
  87 */
  88static u64 do_hypercall(u64 control, void *input, void *output)
  89{
  90#ifdef CONFIG_X86_64
  91        u64 hv_status = 0;
  92        u64 input_address = (input) ? virt_to_phys(input) : 0;
  93        u64 output_address = (output) ? virt_to_phys(output) : 0;
  94        void *hypercall_page = hv_context.hypercall_page;
  95
  96        __asm__ __volatile__("mov %0, %%r8" : : "r" (output_address) : "r8");
  97        __asm__ __volatile__("call *%3" : "=a" (hv_status) :
  98                             "c" (control), "d" (input_address),
  99                             "m" (hypercall_page));
 100
 101        return hv_status;
 102
 103#else
 104
 105        u32 control_hi = control >> 32;
 106        u32 control_lo = control & 0xFFFFFFFF;
 107        u32 hv_status_hi = 1;
 108        u32 hv_status_lo = 1;
 109        u64 input_address = (input) ? virt_to_phys(input) : 0;
 110        u32 input_address_hi = input_address >> 32;
 111        u32 input_address_lo = input_address & 0xFFFFFFFF;
 112        u64 output_address = (output) ? virt_to_phys(output) : 0;
 113        u32 output_address_hi = output_address >> 32;
 114        u32 output_address_lo = output_address & 0xFFFFFFFF;
 115        void *hypercall_page = hv_context.hypercall_page;
 116
 117        __asm__ __volatile__ ("call *%8" : "=d"(hv_status_hi),
 118                              "=a"(hv_status_lo) : "d" (control_hi),
 119                              "a" (control_lo), "b" (input_address_hi),
 120                              "c" (input_address_lo), "D"(output_address_hi),
 121                              "S"(output_address_lo), "m" (hypercall_page));
 122
 123        return hv_status_lo | ((u64)hv_status_hi << 32);
 124#endif /* !x86_64 */
 125}
 126
 127/*
 128 * hv_init - Main initialization routine.
 129 *
 130 * This routine must be called before any other routines in here are called
 131 */
 132int hv_init(void)
 133{
 134        int max_leaf;
 135        union hv_x64_msr_hypercall_contents hypercall_msr;
 136        void *virtaddr = NULL;
 137
 138        memset(hv_context.synic_event_page, 0, sizeof(void *) * NR_CPUS);
 139        memset(hv_context.synic_message_page, 0,
 140               sizeof(void *) * NR_CPUS);
 141        memset(hv_context.vp_index, 0,
 142               sizeof(int) * NR_CPUS);
 143        memset(hv_context.event_dpc, 0,
 144               sizeof(void *) * NR_CPUS);
 145
 146        max_leaf = query_hypervisor_info();
 147
 148        /*
 149         * Write our OS ID.
 150         */
 151        hv_context.guestid = generate_guest_id(0, LINUX_VERSION_CODE, 0);
 152        wrmsrl(HV_X64_MSR_GUEST_OS_ID, hv_context.guestid);
 153
 154        /* See if the hypercall page is already set */
 155        rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 156
 157        virtaddr = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL_EXEC);
 158
 159        if (!virtaddr)
 160                goto cleanup;
 161
 162        hypercall_msr.enable = 1;
 163
 164        hypercall_msr.guest_physical_address = vmalloc_to_pfn(virtaddr);
 165        wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 166
 167        /* Confirm that hypercall page did get setup. */
 168        hypercall_msr.as_uint64 = 0;
 169        rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 170
 171        if (!hypercall_msr.enable)
 172                goto cleanup;
 173
 174        hv_context.hypercall_page = virtaddr;
 175
 176        return 0;
 177
 178cleanup:
 179        if (virtaddr) {
 180                if (hypercall_msr.enable) {
 181                        hypercall_msr.as_uint64 = 0;
 182                        wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 183                }
 184
 185                vfree(virtaddr);
 186        }
 187
 188        return -ENOTSUPP;
 189}
 190
 191/*
 192 * hv_cleanup - Cleanup routine.
 193 *
 194 * This routine is called normally during driver unloading or exiting.
 195 */
 196void hv_cleanup(void)
 197{
 198        union hv_x64_msr_hypercall_contents hypercall_msr;
 199
 200        /* Reset our OS id */
 201        wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0);
 202
 203        if (hv_context.hypercall_page) {
 204                hypercall_msr.as_uint64 = 0;
 205                wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 206                vfree(hv_context.hypercall_page);
 207                hv_context.hypercall_page = NULL;
 208        }
 209}
 210
 211/*
 212 * hv_post_message - Post a message using the hypervisor message IPC.
 213 *
 214 * This involves a hypercall.
 215 */
 216int hv_post_message(union hv_connection_id connection_id,
 217                  enum hv_message_type message_type,
 218                  void *payload, size_t payload_size)
 219{
 220        struct aligned_input {
 221                u64 alignment8;
 222                struct hv_input_post_message msg;
 223        };
 224
 225        struct hv_input_post_message *aligned_msg;
 226        u16 status;
 227        unsigned long addr;
 228
 229        if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
 230                return -EMSGSIZE;
 231
 232        addr = (unsigned long)kmalloc(sizeof(struct aligned_input), GFP_ATOMIC);
 233        if (!addr)
 234                return -ENOMEM;
 235
 236        aligned_msg = (struct hv_input_post_message *)
 237                        (ALIGN(addr, HV_HYPERCALL_PARAM_ALIGN));
 238
 239        aligned_msg->connectionid = connection_id;
 240        aligned_msg->message_type = message_type;
 241        aligned_msg->payload_size = payload_size;
 242        memcpy((void *)aligned_msg->payload, payload, payload_size);
 243
 244        status = do_hypercall(HVCALL_POST_MESSAGE, aligned_msg, NULL)
 245                & 0xFFFF;
 246
 247        kfree((void *)addr);
 248
 249        return status;
 250}
 251
 252
 253/*
 254 * hv_signal_event -
 255 * Signal an event on the specified connection using the hypervisor event IPC.
 256 *
 257 * This involves a hypercall.
 258 */
 259u16 hv_signal_event(void *con_id)
 260{
 261        u16 status;
 262
 263        status = (do_hypercall(HVCALL_SIGNAL_EVENT, con_id, NULL) & 0xFFFF);
 264
 265        return status;
 266}
 267
 268/*
 269 * hv_synic_init - Initialize the Synthethic Interrupt Controller.
 270 *
 271 * If it is already initialized by another entity (ie x2v shim), we need to
 272 * retrieve the initialized message and event pages.  Otherwise, we create and
 273 * initialize the message and event pages.
 274 */
 275void hv_synic_init(void *arg)
 276{
 277        u64 version;
 278        union hv_synic_simp simp;
 279        union hv_synic_siefp siefp;
 280        union hv_synic_sint shared_sint;
 281        union hv_synic_scontrol sctrl;
 282        u64 vp_index;
 283
 284        int cpu = smp_processor_id();
 285
 286        if (!hv_context.hypercall_page)
 287                return;
 288
 289        /* Check the version */
 290        rdmsrl(HV_X64_MSR_SVERSION, version);
 291
 292        hv_context.event_dpc[cpu] = (struct tasklet_struct *)
 293                                        kmalloc(sizeof(struct tasklet_struct),
 294                                                GFP_ATOMIC);
 295        if (hv_context.event_dpc[cpu] == NULL) {
 296                pr_err("Unable to allocate event dpc\n");
 297                goto cleanup;
 298        }
 299        tasklet_init(hv_context.event_dpc[cpu], vmbus_on_event, cpu);
 300
 301        hv_context.synic_message_page[cpu] =
 302                (void *)get_zeroed_page(GFP_ATOMIC);
 303
 304        if (hv_context.synic_message_page[cpu] == NULL) {
 305                pr_err("Unable to allocate SYNIC message page\n");
 306                goto cleanup;
 307        }
 308
 309        hv_context.synic_event_page[cpu] =
 310                (void *)get_zeroed_page(GFP_ATOMIC);
 311
 312        if (hv_context.synic_event_page[cpu] == NULL) {
 313                pr_err("Unable to allocate SYNIC event page\n");
 314                goto cleanup;
 315        }
 316
 317        /* Setup the Synic's message page */
 318        rdmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
 319        simp.simp_enabled = 1;
 320        simp.base_simp_gpa = virt_to_phys(hv_context.synic_message_page[cpu])
 321                >> PAGE_SHIFT;
 322
 323        wrmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
 324
 325        /* Setup the Synic's event page */
 326        rdmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
 327        siefp.siefp_enabled = 1;
 328        siefp.base_siefp_gpa = virt_to_phys(hv_context.synic_event_page[cpu])
 329                >> PAGE_SHIFT;
 330
 331        wrmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
 332
 333        /* Setup the shared SINT. */
 334        rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
 335
 336        shared_sint.as_uint64 = 0;
 337        shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR;
 338        shared_sint.masked = false;
 339        shared_sint.auto_eoi = true;
 340
 341        wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
 342
 343        /* Enable the global synic bit */
 344        rdmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64);
 345        sctrl.enable = 1;
 346
 347        wrmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64);
 348
 349        hv_context.synic_initialized = true;
 350
 351        /*
 352         * Setup the mapping between Hyper-V's notion
 353         * of cpuid and Linux' notion of cpuid.
 354         * This array will be indexed using Linux cpuid.
 355         */
 356        rdmsrl(HV_X64_MSR_VP_INDEX, vp_index);
 357        hv_context.vp_index[cpu] = (u32)vp_index;
 358        return;
 359
 360cleanup:
 361        if (hv_context.synic_event_page[cpu])
 362                free_page((unsigned long)hv_context.synic_event_page[cpu]);
 363
 364        if (hv_context.synic_message_page[cpu])
 365                free_page((unsigned long)hv_context.synic_message_page[cpu]);
 366        return;
 367}
 368
 369/*
 370 * hv_synic_cleanup - Cleanup routine for hv_synic_init().
 371 */
 372void hv_synic_cleanup(void *arg)
 373{
 374        union hv_synic_sint shared_sint;
 375        union hv_synic_simp simp;
 376        union hv_synic_siefp siefp;
 377        int cpu = smp_processor_id();
 378
 379        if (!hv_context.synic_initialized)
 380                return;
 381
 382        rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
 383
 384        shared_sint.masked = 1;
 385
 386        /* Need to correctly cleanup in the case of SMP!!! */
 387        /* Disable the interrupt */
 388        wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
 389
 390        rdmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
 391        simp.simp_enabled = 0;
 392        simp.base_simp_gpa = 0;
 393
 394        wrmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
 395
 396        rdmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
 397        siefp.siefp_enabled = 0;
 398        siefp.base_siefp_gpa = 0;
 399
 400        wrmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
 401
 402        free_page((unsigned long)hv_context.synic_message_page[cpu]);
 403        free_page((unsigned long)hv_context.synic_event_page[cpu]);
 404}
 405
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.