linux/drivers/staging/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#include <linux/kernel.h>
  23#include <linux/mm.h>
  24#include <linux/vmalloc.h>
  25#include "osd.h"
  26#include "logging.h"
  27#include "VmbusPrivate.h"
  28
  29/* The one and only */
  30struct hv_context gHvContext = {
  31        .SynICInitialized       = false,
  32        .HypercallPage          = NULL,
  33        .SignalEventParam       = NULL,
  34        .SignalEventBuffer      = NULL,
  35};
  36
  37/**
  38 * HvQueryHypervisorPresence - Query the cpuid for presense of windows hypervisor
  39 */
  40static int HvQueryHypervisorPresence(void)
  41{
  42        unsigned int eax;
  43        unsigned int ebx;
  44        unsigned int ecx;
  45        unsigned int edx;
  46        unsigned int op;
  47
  48        eax = 0;
  49        ebx = 0;
  50        ecx = 0;
  51        edx = 0;
  52        op = HvCpuIdFunctionVersionAndFeatures;
  53        cpuid(op, &eax, &ebx, &ecx, &edx);
  54
  55        return ecx & HV_PRESENT_BIT;
  56}
  57
  58/**
  59 * HvQueryHypervisorInfo - Get version info of the windows hypervisor
  60 */
  61static int HvQueryHypervisorInfo(void)
  62{
  63        unsigned int eax;
  64        unsigned int ebx;
  65        unsigned int ecx;
  66        unsigned int edx;
  67        unsigned int maxLeaf;
  68        unsigned int op;
  69
  70        /*
  71        * Its assumed that this is called after confirming that Viridian
  72        * is present. Query id and revision.
  73        */
  74        eax = 0;
  75        ebx = 0;
  76        ecx = 0;
  77        edx = 0;
  78        op = HvCpuIdFunctionHvVendorAndMaxFunction;
  79        cpuid(op, &eax, &ebx, &ecx, &edx);
  80
  81        DPRINT_INFO(VMBUS, "Vendor ID: %c%c%c%c%c%c%c%c%c%c%c%c",
  82                    (ebx & 0xFF),
  83                    ((ebx >> 8) & 0xFF),
  84                    ((ebx >> 16) & 0xFF),
  85                    ((ebx >> 24) & 0xFF),
  86                    (ecx & 0xFF),
  87                    ((ecx >> 8) & 0xFF),
  88                    ((ecx >> 16) & 0xFF),
  89                    ((ecx >> 24) & 0xFF),
  90                    (edx & 0xFF),
  91                    ((edx >> 8) & 0xFF),
  92                    ((edx >> 16) & 0xFF),
  93                    ((edx >> 24) & 0xFF));
  94
  95        maxLeaf = eax;
  96        eax = 0;
  97        ebx = 0;
  98        ecx = 0;
  99        edx = 0;
 100        op = HvCpuIdFunctionHvInterface;
 101        cpuid(op, &eax, &ebx, &ecx, &edx);
 102
 103        DPRINT_INFO(VMBUS, "Interface ID: %c%c%c%c",
 104                    (eax & 0xFF),
 105                    ((eax >> 8) & 0xFF),
 106                    ((eax >> 16) & 0xFF),
 107                    ((eax >> 24) & 0xFF));
 108
 109        if (maxLeaf >= HvCpuIdFunctionMsHvVersion) {
 110                eax = 0;
 111                ebx = 0;
 112                ecx = 0;
 113                edx = 0;
 114                op = HvCpuIdFunctionMsHvVersion;
 115                cpuid(op, &eax, &ebx, &ecx, &edx);
 116                DPRINT_INFO(VMBUS, "OS Build:%d-%d.%d-%d-%d.%d",\
 117                            eax,
 118                            ebx >> 16,
 119                            ebx & 0xFFFF,
 120                            ecx,
 121                            edx >> 24,
 122                            edx & 0xFFFFFF);
 123        }
 124        return maxLeaf;
 125}
 126
 127/**
 128 * HvDoHypercall - Invoke the specified hypercall
 129 */
 130static u64 HvDoHypercall(u64 Control, void *Input, void *Output)
 131{
 132#ifdef CONFIG_X86_64
 133        u64 hvStatus = 0;
 134        u64 inputAddress = (Input) ? virt_to_phys(Input) : 0;
 135        u64 outputAddress = (Output) ? virt_to_phys(Output) : 0;
 136        volatile void *hypercallPage = gHvContext.HypercallPage;
 137
 138        DPRINT_DBG(VMBUS, "Hypercall <control %llx input phys %llx virt %p "
 139                   "output phys %llx virt %p hypercall %p>",
 140                   Control, inputAddress, Input,
 141                   outputAddress, Output, hypercallPage);
 142
 143        __asm__ __volatile__("mov %0, %%r8" : : "r" (outputAddress) : "r8");
 144        __asm__ __volatile__("call *%3" : "=a" (hvStatus) :
 145                             "c" (Control), "d" (inputAddress),
 146                             "m" (hypercallPage));
 147
 148        DPRINT_DBG(VMBUS, "Hypercall <return %llx>",  hvStatus);
 149
 150        return hvStatus;
 151
 152#else
 153
 154        u32 controlHi = Control >> 32;
 155        u32 controlLo = Control & 0xFFFFFFFF;
 156        u32 hvStatusHi = 1;
 157        u32 hvStatusLo = 1;
 158        u64 inputAddress = (Input) ? virt_to_phys(Input) : 0;
 159        u32 inputAddressHi = inputAddress >> 32;
 160        u32 inputAddressLo = inputAddress & 0xFFFFFFFF;
 161        u64 outputAddress = (Output) ? virt_to_phys(Output) : 0;
 162        u32 outputAddressHi = outputAddress >> 32;
 163        u32 outputAddressLo = outputAddress & 0xFFFFFFFF;
 164        volatile void *hypercallPage = gHvContext.HypercallPage;
 165
 166        DPRINT_DBG(VMBUS, "Hypercall <control %llx input %p output %p>",
 167                   Control, Input, Output);
 168
 169        __asm__ __volatile__ ("call *%8" : "=d"(hvStatusHi),
 170                              "=a"(hvStatusLo) : "d" (controlHi),
 171                              "a" (controlLo), "b" (inputAddressHi),
 172                              "c" (inputAddressLo), "D"(outputAddressHi),
 173                              "S"(outputAddressLo), "m" (hypercallPage));
 174
 175        DPRINT_DBG(VMBUS, "Hypercall <return %llx>",
 176                   hvStatusLo | ((u64)hvStatusHi << 32));
 177
 178        return hvStatusLo | ((u64)hvStatusHi << 32);
 179#endif /* !x86_64 */
 180}
 181
 182/**
 183 * HvInit - Main initialization routine.
 184 *
 185 * This routine must be called before any other routines in here are called
 186 */
 187int HvInit(void)
 188{
 189        int ret = 0;
 190        int maxLeaf;
 191        union hv_x64_msr_hypercall_contents hypercallMsr;
 192        void *virtAddr = NULL;
 193
 194        DPRINT_ENTER(VMBUS);
 195
 196        memset(gHvContext.synICEventPage, 0, sizeof(void *) * MAX_NUM_CPUS);
 197        memset(gHvContext.synICMessagePage, 0, sizeof(void *) * MAX_NUM_CPUS);
 198
 199        if (!HvQueryHypervisorPresence()) {
 200                DPRINT_ERR(VMBUS, "No Windows hypervisor detected!!");
 201                goto Cleanup;
 202        }
 203
 204        DPRINT_INFO(VMBUS,
 205                    "Windows hypervisor detected! Retrieving more info...");
 206
 207        maxLeaf = HvQueryHypervisorInfo();
 208        /* HvQueryHypervisorFeatures(maxLeaf); */
 209
 210        /*
 211         * Determine if we are running on xenlinux (ie x2v shim) or native
 212         * linux
 213         */
 214        rdmsrl(HV_X64_MSR_GUEST_OS_ID, gHvContext.GuestId);
 215        if (gHvContext.GuestId == 0) {
 216                /* Write our OS info */
 217                wrmsrl(HV_X64_MSR_GUEST_OS_ID, HV_LINUX_GUEST_ID);
 218                gHvContext.GuestId = HV_LINUX_GUEST_ID;
 219        }
 220
 221        /* See if the hypercall page is already set */
 222        rdmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64);
 223        if (gHvContext.GuestId == HV_LINUX_GUEST_ID) {
 224                /* Allocate the hypercall page memory */
 225                /* virtAddr = osd_PageAlloc(1); */
 226                virtAddr = osd_VirtualAllocExec(PAGE_SIZE);
 227
 228                if (!virtAddr) {
 229                        DPRINT_ERR(VMBUS,
 230                                   "unable to allocate hypercall page!!");
 231                        goto Cleanup;
 232                }
 233
 234                hypercallMsr.Enable = 1;
 235                /* hypercallMsr.GuestPhysicalAddress =
 236                 *              virt_to_phys(virtAddr) >> PAGE_SHIFT; */
 237                hypercallMsr.GuestPhysicalAddress = vmalloc_to_pfn(virtAddr);
 238                wrmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64);
 239
 240                /* Confirm that hypercall page did get setup. */
 241                hypercallMsr.AsUINT64 = 0;
 242                rdmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64);
 243                if (!hypercallMsr.Enable) {
 244                        DPRINT_ERR(VMBUS, "unable to set hypercall page!!");
 245                        goto Cleanup;
 246                }
 247
 248                gHvContext.HypercallPage = virtAddr;
 249        } else {
 250                DPRINT_ERR(VMBUS, "Unknown guest id (0x%llx)!!",
 251                                gHvContext.GuestId);
 252                goto Cleanup;
 253        }
 254
 255        DPRINT_INFO(VMBUS, "Hypercall page VA=%p, PA=0x%0llx",
 256                    gHvContext.HypercallPage,
 257                    (u64)hypercallMsr.GuestPhysicalAddress << PAGE_SHIFT);
 258
 259        /* Setup the global signal event param for the signal event hypercall */
 260        gHvContext.SignalEventBuffer =
 261                        kmalloc(sizeof(struct hv_input_signal_event_buffer),
 262                                GFP_KERNEL);
 263        if (!gHvContext.SignalEventBuffer)
 264                goto Cleanup;
 265
 266        gHvContext.SignalEventParam =
 267                (struct hv_input_signal_event *)
 268                        (ALIGN_UP((unsigned long)gHvContext.SignalEventBuffer,
 269                                  HV_HYPERCALL_PARAM_ALIGN));
 270        gHvContext.SignalEventParam->ConnectionId.Asu32 = 0;
 271        gHvContext.SignalEventParam->ConnectionId.u.Id =
 272                                                VMBUS_EVENT_CONNECTION_ID;
 273        gHvContext.SignalEventParam->FlagNumber = 0;
 274        gHvContext.SignalEventParam->RsvdZ = 0;
 275
 276        /* DPRINT_DBG(VMBUS, "My id %llu", HvGetCurrentPartitionId()); */
 277
 278        DPRINT_EXIT(VMBUS);
 279
 280        return ret;
 281
 282Cleanup:
 283        if (virtAddr) {
 284                if (hypercallMsr.Enable) {
 285                        hypercallMsr.AsUINT64 = 0;
 286                        wrmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64);
 287                }
 288
 289                vfree(virtAddr);
 290        }
 291        ret = -1;
 292        DPRINT_EXIT(VMBUS);
 293
 294        return ret;
 295}
 296
 297/**
 298 * HvCleanup - Cleanup routine.
 299 *
 300 * This routine is called normally during driver unloading or exiting.
 301 */
 302void HvCleanup(void)
 303{
 304        union hv_x64_msr_hypercall_contents hypercallMsr;
 305
 306        DPRINT_ENTER(VMBUS);
 307
 308        if (gHvContext.SignalEventBuffer) {
 309                gHvContext.SignalEventBuffer = NULL;
 310                gHvContext.SignalEventParam = NULL;
 311                kfree(gHvContext.SignalEventBuffer);
 312        }
 313
 314        if (gHvContext.GuestId == HV_LINUX_GUEST_ID) {
 315                if (gHvContext.HypercallPage) {
 316                        hypercallMsr.AsUINT64 = 0;
 317                        wrmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64);
 318                        vfree(gHvContext.HypercallPage);
 319                        gHvContext.HypercallPage = NULL;
 320                }
 321        }
 322
 323        DPRINT_EXIT(VMBUS);
 324
 325}
 326
 327/**
 328 * HvPostMessage - Post a message using the hypervisor message IPC.
 329 *
 330 * This involves a hypercall.
 331 */
 332u16 HvPostMessage(union hv_connection_id connectionId,
 333                  enum hv_message_type messageType,
 334                  void *payload, size_t payloadSize)
 335{
 336        struct alignedInput {
 337                u64 alignment8;
 338                struct hv_input_post_message msg;
 339        };
 340
 341        struct hv_input_post_message *alignedMsg;
 342        u16 status;
 343        unsigned long addr;
 344
 345        if (payloadSize > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
 346                return -1;
 347
 348        addr = (unsigned long)kmalloc(sizeof(struct alignedInput), GFP_ATOMIC);
 349        if (!addr)
 350                return -1;
 351
 352        alignedMsg = (struct hv_input_post_message *)
 353                        (ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
 354
 355        alignedMsg->ConnectionId = connectionId;
 356        alignedMsg->MessageType = messageType;
 357        alignedMsg->PayloadSize = payloadSize;
 358        memcpy((void *)alignedMsg->Payload, payload, payloadSize);
 359
 360        status = HvDoHypercall(HvCallPostMessage, alignedMsg, NULL) & 0xFFFF;
 361
 362        kfree((void *)addr);
 363
 364        return status;
 365}
 366
 367
 368/**
 369 * HvSignalEvent - Signal an event on the specified connection using the hypervisor event IPC.
 370 *
 371 * This involves a hypercall.
 372 */
 373u16 HvSignalEvent(void)
 374{
 375        u16 status;
 376
 377        status = HvDoHypercall(HvCallSignalEvent, gHvContext.SignalEventParam,
 378                               NULL) & 0xFFFF;
 379        return status;
 380}
 381
 382/**
 383 * HvSynicInit - Initialize the Synthethic Interrupt Controller.
 384 *
 385 * If it is already initialized by another entity (ie x2v shim), we need to
 386 * retrieve the initialized message and event pages.  Otherwise, we create and
 387 * initialize the message and event pages.
 388 */
 389void HvSynicInit(void *irqarg)
 390{
 391        u64 version;
 392        union hv_synic_simp simp;
 393        union hv_synic_siefp siefp;
 394        union hv_synic_sint sharedSint;
 395        union hv_synic_scontrol sctrl;
 396        u64 guestID;
 397        u32 irqVector = *((u32 *)(irqarg));
 398        int cpu = smp_processor_id();
 399
 400        DPRINT_ENTER(VMBUS);
 401
 402        if (!gHvContext.HypercallPage) {
 403                DPRINT_EXIT(VMBUS);
 404                return;
 405        }
 406
 407        /* Check the version */
 408        rdmsrl(HV_X64_MSR_SVERSION, version);
 409
 410        DPRINT_INFO(VMBUS, "SynIC version: %llx", version);
 411
 412        /* TODO: Handle SMP */
 413        if (gHvContext.GuestId == HV_XENLINUX_GUEST_ID) {
 414                DPRINT_INFO(VMBUS, "Skipping SIMP and SIEFP setup since "
 415                                "it is already set.");
 416
 417                rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 418                rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 419
 420                DPRINT_DBG(VMBUS, "Simp: %llx, Sifep: %llx",
 421                           simp.AsUINT64, siefp.AsUINT64);
 422
 423                /*
 424                 * Determine if we are running on xenlinux (ie x2v shim) or
 425                 * native linux
 426                 */
 427                rdmsrl(HV_X64_MSR_GUEST_OS_ID, guestID);
 428                if (guestID == HV_LINUX_GUEST_ID) {
 429                        gHvContext.synICMessagePage[cpu] =
 430                                phys_to_virt(simp.BaseSimpGpa << PAGE_SHIFT);
 431                        gHvContext.synICEventPage[cpu] =
 432                                phys_to_virt(siefp.BaseSiefpGpa << PAGE_SHIFT);
 433                } else {
 434                        DPRINT_ERR(VMBUS, "unknown guest id!!");
 435                        goto Cleanup;
 436                }
 437                DPRINT_DBG(VMBUS, "MAPPED: Simp: %p, Sifep: %p",
 438                           gHvContext.synICMessagePage[cpu],
 439                           gHvContext.synICEventPage[cpu]);
 440        } else {
 441                gHvContext.synICMessagePage[cpu] = (void *)get_zeroed_page(GFP_ATOMIC);
 442                if (gHvContext.synICMessagePage[cpu] == NULL) {
 443                        DPRINT_ERR(VMBUS,
 444                                   "unable to allocate SYNIC message page!!");
 445                        goto Cleanup;
 446                }
 447
 448                gHvContext.synICEventPage[cpu] = (void *)get_zeroed_page(GFP_ATOMIC);
 449                if (gHvContext.synICEventPage[cpu] == NULL) {
 450                        DPRINT_ERR(VMBUS,
 451                                   "unable to allocate SYNIC event page!!");
 452                        goto Cleanup;
 453                }
 454
 455                /* Setup the Synic's message page */
 456                rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 457                simp.SimpEnabled = 1;
 458                simp.BaseSimpGpa = virt_to_phys(gHvContext.synICMessagePage[cpu])
 459                                        >> PAGE_SHIFT;
 460
 461                DPRINT_DBG(VMBUS, "HV_X64_MSR_SIMP msr set to: %llx",
 462                                simp.AsUINT64);
 463
 464                wrmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 465
 466                /* Setup the Synic's event page */
 467                rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 468                siefp.SiefpEnabled = 1;
 469                siefp.BaseSiefpGpa = virt_to_phys(gHvContext.synICEventPage[cpu])
 470                                        >> PAGE_SHIFT;
 471
 472                DPRINT_DBG(VMBUS, "HV_X64_MSR_SIEFP msr set to: %llx",
 473                                siefp.AsUINT64);
 474
 475                wrmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 476        }
 477
 478        /* Setup the interception SINT. */
 479        /* wrmsrl((HV_X64_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX), */
 480        /*        interceptionSint.AsUINT64); */
 481
 482        /* Setup the shared SINT. */
 483        rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64);
 484
 485        sharedSint.AsUINT64 = 0;
 486        sharedSint.Vector = irqVector; /* HV_SHARED_SINT_IDT_VECTOR + 0x20; */
 487        sharedSint.Masked = false;
 488        sharedSint.AutoEoi = true;
 489
 490        DPRINT_DBG(VMBUS, "HV_X64_MSR_SINT1 msr set to: %llx",
 491                   sharedSint.AsUINT64);
 492
 493        wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64);
 494
 495        /* Enable the global synic bit */
 496        rdmsrl(HV_X64_MSR_SCONTROL, sctrl.AsUINT64);
 497        sctrl.Enable = 1;
 498
 499        wrmsrl(HV_X64_MSR_SCONTROL, sctrl.AsUINT64);
 500
 501        gHvContext.SynICInitialized = true;
 502
 503        DPRINT_EXIT(VMBUS);
 504
 505        return;
 506
 507Cleanup:
 508        if (gHvContext.GuestId == HV_LINUX_GUEST_ID) {
 509                if (gHvContext.synICEventPage[cpu])
 510                        osd_PageFree(gHvContext.synICEventPage[cpu], 1);
 511
 512                if (gHvContext.synICMessagePage[cpu])
 513                        osd_PageFree(gHvContext.synICMessagePage[cpu], 1);
 514        }
 515
 516        DPRINT_EXIT(VMBUS);
 517        return;
 518}
 519
 520/**
 521 * HvSynicCleanup - Cleanup routine for HvSynicInit().
 522 */
 523void HvSynicCleanup(void *arg)
 524{
 525        union hv_synic_sint sharedSint;
 526        union hv_synic_simp simp;
 527        union hv_synic_siefp siefp;
 528        int cpu = smp_processor_id();
 529
 530        DPRINT_ENTER(VMBUS);
 531
 532        if (!gHvContext.SynICInitialized) {
 533                DPRINT_EXIT(VMBUS);
 534                return;
 535        }
 536
 537        rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64);
 538
 539        sharedSint.Masked = 1;
 540
 541        /* Need to correctly cleanup in the case of SMP!!! */
 542        /* Disable the interrupt */
 543        wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64);
 544
 545        /*
 546         * Disable and free the resources only if we are running as
 547         * native linux since in xenlinux, we are sharing the
 548         * resources with the x2v shim
 549         */
 550        if (gHvContext.GuestId == HV_LINUX_GUEST_ID) {
 551                rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 552                simp.SimpEnabled = 0;
 553                simp.BaseSimpGpa = 0;
 554
 555                wrmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 556
 557                rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 558                siefp.SiefpEnabled = 0;
 559                siefp.BaseSiefpGpa = 0;
 560
 561                wrmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 562
 563                osd_PageFree(gHvContext.synICMessagePage[cpu], 1);
 564                osd_PageFree(gHvContext.synICEventPage[cpu], 1);
 565        }
 566
 567        DPRINT_EXIT(VMBUS);
 568}
 569
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.