linux/arch/x86/hyperv/hv_init.c
<<
>>
Prefs
   1/*
   2 * X86 specific Hyper-V initialization code.
   3 *
   4 * Copyright (C) 2016, Microsoft, Inc.
   5 *
   6 * Author : K. Y. Srinivasan <kys@microsoft.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify it
   9 * under the terms of the GNU General Public License version 2 as published
  10 * by the Free Software Foundation.
  11 *
  12 * This program is distributed in the hope that it will be useful, but
  13 * WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
  15 * NON INFRINGEMENT.  See the GNU General Public License for more
  16 * details.
  17 *
  18 */
  19
  20#include <linux/types.h>
  21#include <asm/hypervisor.h>
  22#include <asm/hyperv.h>
  23#include <asm/mshyperv.h>
  24#include <linux/version.h>
  25#include <linux/vmalloc.h>
  26#include <linux/mm.h>
  27#include <linux/clockchips.h>
  28
  29
  30#ifdef CONFIG_X86_64
  31
  32static struct ms_hyperv_tsc_page *tsc_pg;
  33
  34static u64 read_hv_clock_tsc(struct clocksource *arg)
  35{
  36        u64 current_tick;
  37
  38        if (tsc_pg->tsc_sequence != 0) {
  39                /*
  40                 * Use the tsc page to compute the value.
  41                 */
  42
  43                while (1) {
  44                        u64 tmp;
  45                        u32 sequence = tsc_pg->tsc_sequence;
  46                        u64 cur_tsc;
  47                        u64 scale = tsc_pg->tsc_scale;
  48                        s64 offset = tsc_pg->tsc_offset;
  49
  50                        rdtscll(cur_tsc);
  51                        /* current_tick = ((cur_tsc *scale) >> 64) + offset */
  52                        asm("mulq %3"
  53                                : "=d" (current_tick), "=a" (tmp)
  54                                : "a" (cur_tsc), "r" (scale));
  55
  56                        current_tick += offset;
  57                        if (tsc_pg->tsc_sequence == sequence)
  58                                return current_tick;
  59
  60                        if (tsc_pg->tsc_sequence != 0)
  61                                continue;
  62                        /*
  63                         * Fallback using MSR method.
  64                         */
  65                        break;
  66                }
  67        }
  68        rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
  69        return current_tick;
  70}
  71
  72static struct clocksource hyperv_cs_tsc = {
  73                .name           = "hyperv_clocksource_tsc_page",
  74                .rating         = 400,
  75                .read           = read_hv_clock_tsc,
  76                .mask           = CLOCKSOURCE_MASK(64),
  77                .flags          = CLOCK_SOURCE_IS_CONTINUOUS,
  78};
  79#endif
  80
  81static u64 read_hv_clock_msr(struct clocksource *arg)
  82{
  83        u64 current_tick;
  84        /*
  85         * Read the partition counter to get the current tick count. This count
  86         * is set to 0 when the partition is created and is incremented in
  87         * 100 nanosecond units.
  88         */
  89        rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
  90        return current_tick;
  91}
  92
  93static struct clocksource hyperv_cs_msr = {
  94        .name           = "hyperv_clocksource_msr",
  95        .rating         = 400,
  96        .read           = read_hv_clock_msr,
  97        .mask           = CLOCKSOURCE_MASK(64),
  98        .flags          = CLOCK_SOURCE_IS_CONTINUOUS,
  99};
 100
 101static void *hypercall_pg;
 102struct clocksource *hyperv_cs;
 103EXPORT_SYMBOL_GPL(hyperv_cs);
 104
 105/*
 106 * This function is to be invoked early in the boot sequence after the
 107 * hypervisor has been detected.
 108 *
 109 * 1. Setup the hypercall page.
 110 * 2. Register Hyper-V specific clocksource.
 111 */
 112void hyperv_init(void)
 113{
 114        u64 guest_id;
 115        union hv_x64_msr_hypercall_contents hypercall_msr;
 116
 117        if (x86_hyper != &x86_hyper_ms_hyperv)
 118                return;
 119
 120        /*
 121         * Setup the hypercall page and enable hypercalls.
 122         * 1. Register the guest ID
 123         * 2. Enable the hypercall and register the hypercall page
 124         */
 125        guest_id = generate_guest_id(0, LINUX_VERSION_CODE, 0);
 126        wrmsrl(HV_X64_MSR_GUEST_OS_ID, guest_id);
 127
 128        hypercall_pg  = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL_RX);
 129        if (hypercall_pg == NULL) {
 130                wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0);
 131                return;
 132        }
 133
 134        rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 135        hypercall_msr.enable = 1;
 136        hypercall_msr.guest_physical_address = vmalloc_to_pfn(hypercall_pg);
 137        wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 138
 139        /*
 140         * Register Hyper-V specific clocksource.
 141         */
 142#ifdef CONFIG_X86_64
 143        if (ms_hyperv.features & HV_X64_MSR_REFERENCE_TSC_AVAILABLE) {
 144                union hv_x64_msr_hypercall_contents tsc_msr;
 145
 146                tsc_pg = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL);
 147                if (!tsc_pg)
 148                        goto register_msr_cs;
 149
 150                hyperv_cs = &hyperv_cs_tsc;
 151
 152                rdmsrl(HV_X64_MSR_REFERENCE_TSC, tsc_msr.as_uint64);
 153
 154                tsc_msr.enable = 1;
 155                tsc_msr.guest_physical_address = vmalloc_to_pfn(tsc_pg);
 156
 157                wrmsrl(HV_X64_MSR_REFERENCE_TSC, tsc_msr.as_uint64);
 158                clocksource_register_hz(&hyperv_cs_tsc, NSEC_PER_SEC/100);
 159                return;
 160        }
 161register_msr_cs:
 162#endif
 163        /*
 164         * For 32 bit guests just use the MSR based mechanism for reading
 165         * the partition counter.
 166         */
 167
 168        hyperv_cs = &hyperv_cs_msr;
 169        if (ms_hyperv.features & HV_X64_MSR_TIME_REF_COUNT_AVAILABLE)
 170                clocksource_register_hz(&hyperv_cs_msr, NSEC_PER_SEC/100);
 171}
 172
 173/*
 174 * This routine is called before kexec/kdump, it does the required cleanup.
 175 */
 176void hyperv_cleanup(void)
 177{
 178        union hv_x64_msr_hypercall_contents hypercall_msr;
 179
 180        /* Reset our OS id */
 181        wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0);
 182
 183        /* Reset the hypercall page */
 184        hypercall_msr.as_uint64 = 0;
 185        wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 186
 187        /* Reset the TSC page */
 188        hypercall_msr.as_uint64 = 0;
 189        wrmsrl(HV_X64_MSR_REFERENCE_TSC, hypercall_msr.as_uint64);
 190}
 191EXPORT_SYMBOL_GPL(hyperv_cleanup);
 192
 193/*
 194 * hv_do_hypercall- Invoke the specified hypercall
 195 */
 196u64 hv_do_hypercall(u64 control, void *input, void *output)
 197{
 198        u64 input_address = (input) ? virt_to_phys(input) : 0;
 199        u64 output_address = (output) ? virt_to_phys(output) : 0;
 200#ifdef CONFIG_X86_64
 201        u64 hv_status = 0;
 202
 203        if (!hypercall_pg)
 204                return (u64)ULLONG_MAX;
 205
 206        __asm__ __volatile__("mov %0, %%r8" : : "r" (output_address) : "r8");
 207        __asm__ __volatile__("call *%3" : "=a" (hv_status) :
 208                             "c" (control), "d" (input_address),
 209                             "m" (hypercall_pg));
 210
 211        return hv_status;
 212
 213#else
 214
 215        u32 control_hi = control >> 32;
 216        u32 control_lo = control & 0xFFFFFFFF;
 217        u32 hv_status_hi = 1;
 218        u32 hv_status_lo = 1;
 219        u32 input_address_hi = input_address >> 32;
 220        u32 input_address_lo = input_address & 0xFFFFFFFF;
 221        u32 output_address_hi = output_address >> 32;
 222        u32 output_address_lo = output_address & 0xFFFFFFFF;
 223
 224        if (!hypercall_pg)
 225                return (u64)ULLONG_MAX;
 226
 227        __asm__ __volatile__ ("call *%8" : "=d"(hv_status_hi),
 228                              "=a"(hv_status_lo) : "d" (control_hi),
 229                              "a" (control_lo), "b" (input_address_hi),
 230                              "c" (input_address_lo), "D"(output_address_hi),
 231                              "S"(output_address_lo), "m" (hypercall_pg));
 232
 233        return hv_status_lo | ((u64)hv_status_hi << 32);
 234#endif /* !x86_64 */
 235}
 236EXPORT_SYMBOL_GPL(hv_do_hypercall);
 237
 238void hyperv_report_panic(struct pt_regs *regs)
 239{
 240        static bool panic_reported;
 241
 242        /*
 243         * We prefer to report panic on 'die' chain as we have proper
 244         * registers to report, but if we miss it (e.g. on BUG()) we need
 245         * to report it on 'panic'.
 246         */
 247        if (panic_reported)
 248                return;
 249        panic_reported = true;
 250
 251        wrmsrl(HV_X64_MSR_CRASH_P0, regs->ip);
 252        wrmsrl(HV_X64_MSR_CRASH_P1, regs->ax);
 253        wrmsrl(HV_X64_MSR_CRASH_P2, regs->bx);
 254        wrmsrl(HV_X64_MSR_CRASH_P3, regs->cx);
 255        wrmsrl(HV_X64_MSR_CRASH_P4, regs->dx);
 256
 257        /*
 258         * Let Hyper-V know there is crash data available
 259         */
 260        wrmsrl(HV_X64_MSR_CRASH_CTL, HV_CRASH_CTL_CRASH_NOTIFY);
 261}
 262EXPORT_SYMBOL_GPL(hyperv_report_panic);
 263
 264bool hv_is_hypercall_page_setup(void)
 265{
 266        union hv_x64_msr_hypercall_contents hypercall_msr;
 267
 268        /* Check if the hypercall page is setup */
 269        hypercall_msr.as_uint64 = 0;
 270        rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 271
 272        if (!hypercall_msr.enable)
 273                return false;
 274
 275        return true;
 276}
 277EXPORT_SYMBOL_GPL(hv_is_hypercall_page_setup);
 278