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 */
 389int HvSynicInit(u32 irqVector)
 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        int ret = 0;
 398
 399        DPRINT_ENTER(VMBUS);
 400
 401        if (!gHvContext.HypercallPage) {
 402                DPRINT_EXIT(VMBUS);
 403                return ret;
 404        }
 405
 406        /* Check the version */
 407        rdmsrl(HV_X64_MSR_SVERSION, version);
 408
 409        DPRINT_INFO(VMBUS, "SynIC version: %llx", version);
 410
 411        /* TODO: Handle SMP */
 412        if (gHvContext.GuestId == HV_XENLINUX_GUEST_ID) {
 413                DPRINT_INFO(VMBUS, "Skipping SIMP and SIEFP setup since "
 414                                "it is already set.");
 415
 416                rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 417                rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 418
 419                DPRINT_DBG(VMBUS, "Simp: %llx, Sifep: %llx",
 420                           simp.AsUINT64, siefp.AsUINT64);
 421
 422                /*
 423                 * Determine if we are running on xenlinux (ie x2v shim) or
 424                 * native linux
 425                 */
 426                rdmsrl(HV_X64_MSR_GUEST_OS_ID, guestID);
 427                if (guestID == HV_LINUX_GUEST_ID) {
 428                        gHvContext.synICMessagePage[0] =
 429                                phys_to_virt(simp.BaseSimpGpa << PAGE_SHIFT);
 430                        gHvContext.synICEventPage[0] =
 431                                phys_to_virt(siefp.BaseSiefpGpa << PAGE_SHIFT);
 432                } else {
 433                        DPRINT_ERR(VMBUS, "unknown guest id!!");
 434                        goto Cleanup;
 435                }
 436                DPRINT_DBG(VMBUS, "MAPPED: Simp: %p, Sifep: %p",
 437                           gHvContext.synICMessagePage[0],
 438                           gHvContext.synICEventPage[0]);
 439        } else {
 440                gHvContext.synICMessagePage[0] = osd_PageAlloc(1);
 441                if (gHvContext.synICMessagePage[0] == NULL) {
 442                        DPRINT_ERR(VMBUS,
 443                                   "unable to allocate SYNIC message page!!");
 444                        goto Cleanup;
 445                }
 446
 447                gHvContext.synICEventPage[0] = osd_PageAlloc(1);
 448                if (gHvContext.synICEventPage[0] == NULL) {
 449                        DPRINT_ERR(VMBUS,
 450                                   "unable to allocate SYNIC event page!!");
 451                        goto Cleanup;
 452                }
 453
 454                /* Setup the Synic's message page */
 455                rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 456                simp.SimpEnabled = 1;
 457                simp.BaseSimpGpa = virt_to_phys(gHvContext.synICMessagePage[0])
 458                                        >> PAGE_SHIFT;
 459
 460                DPRINT_DBG(VMBUS, "HV_X64_MSR_SIMP msr set to: %llx",
 461                                simp.AsUINT64);
 462
 463                wrmsrl(HV_X64_MSR_SIMP, simp.AsUINT64);
 464
 465                /* Setup the Synic's event page */
 466                rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 467                siefp.SiefpEnabled = 1;
 468                siefp.BaseSiefpGpa = virt_to_phys(gHvContext.synICEventPage[0])
 469                                        >> PAGE_SHIFT;
 470
 471                DPRINT_DBG(VMBUS, "HV_X64_MSR_SIEFP msr set to: %llx",
 472                                siefp.AsUINT64);
 473
 474                wrmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64);
 475        }
 476
 477        /* Setup the interception SINT. */
 478        /* wrmsrl((HV_X64_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX), */
 479        /*        interceptionSint.AsUINT64); */
 480
 481        /* Setup the shared SINT. */
 482        rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64);
 483
 484        sharedSint.AsUINT64 = 0;
 485        sharedSint.Vector = irqVector; /* HV_SHARED_SINT_IDT_VECTOR + 0x20; */
 486        sharedSint.Masked = false;
 487        sharedSint.AutoEoi = true;
 488
 489        DPRINT_DBG(VMBUS, "HV_X64_MSR_SINT1 msr set to: %llx",
 490                   sharedSint.AsUINT64);
 491
 492        wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64);
 493
 494        /* Enable the global synic bit */
 495        rdmsrl(HV_X64_MSR_SCONTROL, sctrl.AsUINT64);
 496        sctrl.Enable = 1;
 497
 498        wrmsrl(HV_X64_MSR_SCONTROL, sctrl.AsUINT64);
 499
 500        gHvContext.SynICInitialized = true;
 501
 502        DPRINT_EXIT(VMBUS);
 503
 504        return ret;
 505
 506Cleanup:
 507        ret = -1;
 508
 509        if (gHvContext.GuestId == HV_LINUX_GUEST_ID) {
 510                if (gHvContext.synICEventPage[0])
 511                        osd_PageFree(gHvContext.synICEventPage[0], 1);
 512
 513                if (gHvContext.synICMessagePage[0])
 514                        osd_PageFree(gHvContext.synICMessagePage[0], 1);
 515        }
 516
 517        DPRINT_EXIT(VMBUS);
 518
 519        return ret;
 520}
 521
 522/**
 523 * HvSynicCleanup - Cleanup routine for HvSynicInit().
 524 */
 525void HvSynicCleanup(void)
 526{
 527        union hv_synic_sint sharedSint;
 528        union hv_synic_simp simp;
 529        union hv_synic_siefp siefp;
 530
 531        DPRINT_ENTER(VMBUS);
 532
 533        if (!gHvContext.SynICInitialized) {
 534                DPRINT_EXIT(VMBUS);
 535                return;
 536        }
 537
 538        rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64);
 539
 540        sharedSint.Masked = 1;
 541
 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[0], 1);
 564                osd_PageFree(gHvContext.synICEventPage[0], 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.