linux/drivers/staging/hv/hv_util.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2010, 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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  22
  23#include <linux/kernel.h>
  24#include <linux/init.h>
  25#include <linux/module.h>
  26#include <linux/slab.h>
  27#include <linux/sysctl.h>
  28#include <linux/reboot.h>
  29#include <linux/dmi.h>
  30#include <linux/pci.h>
  31
  32#include "hyperv.h"
  33#include "hv_kvp.h"
  34
  35static u8 *shut_txf_buf;
  36static u8 *time_txf_buf;
  37static u8 *hbeat_txf_buf;
  38
  39static void shutdown_onchannelcallback(void *context)
  40{
  41        struct vmbus_channel *channel = context;
  42        u32 recvlen;
  43        u64 requestid;
  44        u8  execute_shutdown = false;
  45
  46        struct shutdown_msg_data *shutdown_msg;
  47
  48        struct icmsg_hdr *icmsghdrp;
  49        struct icmsg_negotiate *negop = NULL;
  50
  51        vmbus_recvpacket(channel, shut_txf_buf,
  52                         PAGE_SIZE, &recvlen, &requestid);
  53
  54        if (recvlen > 0) {
  55                icmsghdrp = (struct icmsg_hdr *)&shut_txf_buf[
  56                        sizeof(struct vmbuspipe_hdr)];
  57
  58                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
  59                        prep_negotiate_resp(icmsghdrp, negop, shut_txf_buf);
  60                } else {
  61                        shutdown_msg =
  62                                (struct shutdown_msg_data *)&shut_txf_buf[
  63                                        sizeof(struct vmbuspipe_hdr) +
  64                                        sizeof(struct icmsg_hdr)];
  65
  66                        switch (shutdown_msg->flags) {
  67                        case 0:
  68                        case 1:
  69                                icmsghdrp->status = HV_S_OK;
  70                                execute_shutdown = true;
  71
  72                                pr_info("Shutdown request received -"
  73                                            " graceful shutdown initiated\n");
  74                                break;
  75                        default:
  76                                icmsghdrp->status = HV_E_FAIL;
  77                                execute_shutdown = false;
  78
  79                                pr_info("Shutdown request received -"
  80                                            " Invalid request\n");
  81                                break;
  82                        }
  83                }
  84
  85                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
  86                        | ICMSGHDRFLAG_RESPONSE;
  87
  88                vmbus_sendpacket(channel, shut_txf_buf,
  89                                       recvlen, requestid,
  90                                       VM_PKT_DATA_INBAND, 0);
  91        }
  92
  93        if (execute_shutdown == true)
  94                orderly_poweroff(false);
  95}
  96
  97/*
  98 * Set guest time to host UTC time.
  99 */
 100static inline void do_adj_guesttime(u64 hosttime)
 101{
 102        s64 host_tns;
 103        struct timespec host_ts;
 104
 105        host_tns = (hosttime - WLTIMEDELTA) * 100;
 106        host_ts = ns_to_timespec(host_tns);
 107
 108        do_settimeofday(&host_ts);
 109}
 110
 111/*
 112 * Synchronize time with host after reboot, restore, etc.
 113 *
 114 * ICTIMESYNCFLAG_SYNC flag bit indicates reboot, restore events of the VM.
 115 * After reboot the flag ICTIMESYNCFLAG_SYNC is included in the first time
 116 * message after the timesync channel is opened. Since the hv_utils module is
 117 * loaded after hv_vmbus, the first message is usually missed. The other
 118 * thing is, systime is automatically set to emulated hardware clock which may
 119 * not be UTC time or in the same time zone. So, to override these effects, we
 120 * use the first 50 time samples for initial system time setting.
 121 */
 122static inline void adj_guesttime(u64 hosttime, u8 flags)
 123{
 124        static s32 scnt = 50;
 125
 126        if ((flags & ICTIMESYNCFLAG_SYNC) != 0) {
 127                do_adj_guesttime(hosttime);
 128                return;
 129        }
 130
 131        if ((flags & ICTIMESYNCFLAG_SAMPLE) != 0 && scnt > 0) {
 132                scnt--;
 133                do_adj_guesttime(hosttime);
 134        }
 135}
 136
 137/*
 138 * Time Sync Channel message handler.
 139 */
 140static void timesync_onchannelcallback(void *context)
 141{
 142        struct vmbus_channel *channel = context;
 143        u32 recvlen;
 144        u64 requestid;
 145        struct icmsg_hdr *icmsghdrp;
 146        struct ictimesync_data *timedatap;
 147
 148        vmbus_recvpacket(channel, time_txf_buf,
 149                         PAGE_SIZE, &recvlen, &requestid);
 150
 151        if (recvlen > 0) {
 152                icmsghdrp = (struct icmsg_hdr *)&time_txf_buf[
 153                                sizeof(struct vmbuspipe_hdr)];
 154
 155                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
 156                        prep_negotiate_resp(icmsghdrp, NULL, time_txf_buf);
 157                } else {
 158                        timedatap = (struct ictimesync_data *)&time_txf_buf[
 159                                sizeof(struct vmbuspipe_hdr) +
 160                                sizeof(struct icmsg_hdr)];
 161                        adj_guesttime(timedatap->parenttime, timedatap->flags);
 162                }
 163
 164                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
 165                        | ICMSGHDRFLAG_RESPONSE;
 166
 167                vmbus_sendpacket(channel, time_txf_buf,
 168                                recvlen, requestid,
 169                                VM_PKT_DATA_INBAND, 0);
 170        }
 171}
 172
 173/*
 174 * Heartbeat functionality.
 175 * Every two seconds, Hyper-V send us a heartbeat request message.
 176 * we respond to this message, and Hyper-V knows we are alive.
 177 */
 178static void heartbeat_onchannelcallback(void *context)
 179{
 180        struct vmbus_channel *channel = context;
 181        u32 recvlen;
 182        u64 requestid;
 183        struct icmsg_hdr *icmsghdrp;
 184        struct heartbeat_msg_data *heartbeat_msg;
 185
 186        vmbus_recvpacket(channel, hbeat_txf_buf,
 187                         PAGE_SIZE, &recvlen, &requestid);
 188
 189        if (recvlen > 0) {
 190                icmsghdrp = (struct icmsg_hdr *)&hbeat_txf_buf[
 191                                sizeof(struct vmbuspipe_hdr)];
 192
 193                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
 194                        prep_negotiate_resp(icmsghdrp, NULL, hbeat_txf_buf);
 195                } else {
 196                        heartbeat_msg =
 197                                (struct heartbeat_msg_data *)&hbeat_txf_buf[
 198                                        sizeof(struct vmbuspipe_hdr) +
 199                                        sizeof(struct icmsg_hdr)];
 200
 201                        heartbeat_msg->seq_num += 1;
 202                }
 203
 204                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
 205                        | ICMSGHDRFLAG_RESPONSE;
 206
 207                vmbus_sendpacket(channel, hbeat_txf_buf,
 208                                       recvlen, requestid,
 209                                       VM_PKT_DATA_INBAND, 0);
 210        }
 211}
 212
 213static const struct pci_device_id __initconst
 214hv_utils_pci_table[] __maybe_unused = {
 215        { PCI_DEVICE(0x1414, 0x5353) }, /* Hyper-V emulated VGA controller */
 216        { 0 }
 217};
 218MODULE_DEVICE_TABLE(pci, hv_utils_pci_table);
 219
 220
 221static const struct dmi_system_id __initconst
 222hv_utils_dmi_table[] __maybe_unused  = {
 223        {
 224                .ident = "Hyper-V",
 225                .matches = {
 226                        DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
 227                        DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"),
 228                        DMI_MATCH(DMI_BOARD_NAME, "Virtual Machine"),
 229                },
 230        },
 231        { },
 232};
 233MODULE_DEVICE_TABLE(dmi, hv_utils_dmi_table);
 234
 235
 236static int __init init_hyperv_utils(void)
 237{
 238        pr_info("Registering HyperV Utility Driver\n");
 239
 240        if (hv_kvp_init())
 241                return -ENODEV;
 242
 243
 244        if (!dmi_check_system(hv_utils_dmi_table))
 245                return -ENODEV;
 246
 247        shut_txf_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
 248        time_txf_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
 249        hbeat_txf_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
 250
 251        if (!shut_txf_buf || !time_txf_buf || !hbeat_txf_buf) {
 252                pr_info("Unable to allocate memory for receive buffer\n");
 253                kfree(shut_txf_buf);
 254                kfree(time_txf_buf);
 255                kfree(hbeat_txf_buf);
 256                return -ENOMEM;
 257        }
 258
 259        hv_cb_utils[HV_SHUTDOWN_MSG].callback = &shutdown_onchannelcallback;
 260
 261        hv_cb_utils[HV_TIMESYNC_MSG].callback = &timesync_onchannelcallback;
 262
 263        hv_cb_utils[HV_HEARTBEAT_MSG].callback = &heartbeat_onchannelcallback;
 264
 265        hv_cb_utils[HV_KVP_MSG].callback = &hv_kvp_onchannelcallback;
 266
 267        return 0;
 268}
 269
 270static void exit_hyperv_utils(void)
 271{
 272        pr_info("De-Registered HyperV Utility Driver\n");
 273
 274        if (hv_cb_utils[HV_SHUTDOWN_MSG].channel != NULL)
 275                hv_cb_utils[HV_SHUTDOWN_MSG].channel->onchannel_callback =
 276                        &chn_cb_negotiate;
 277        hv_cb_utils[HV_SHUTDOWN_MSG].callback = NULL;
 278
 279        if (hv_cb_utils[HV_TIMESYNC_MSG].channel != NULL)
 280                hv_cb_utils[HV_TIMESYNC_MSG].channel->onchannel_callback =
 281                        &chn_cb_negotiate;
 282        hv_cb_utils[HV_TIMESYNC_MSG].callback = NULL;
 283
 284        if (hv_cb_utils[HV_HEARTBEAT_MSG].channel != NULL)
 285                hv_cb_utils[HV_HEARTBEAT_MSG].channel->onchannel_callback =
 286                        &chn_cb_negotiate;
 287        hv_cb_utils[HV_HEARTBEAT_MSG].callback = NULL;
 288
 289        if (hv_cb_utils[HV_KVP_MSG].channel != NULL)
 290                hv_cb_utils[HV_KVP_MSG].channel->onchannel_callback =
 291                        &chn_cb_negotiate;
 292        hv_cb_utils[HV_KVP_MSG].callback = NULL;
 293
 294        hv_kvp_deinit();
 295
 296        kfree(shut_txf_buf);
 297        kfree(time_txf_buf);
 298        kfree(hbeat_txf_buf);
 299}
 300
 301module_init(init_hyperv_utils);
 302module_exit(exit_hyperv_utils);
 303
 304MODULE_DESCRIPTION("Hyper-V Utilities");
 305MODULE_VERSION(HV_DRV_VERSION);
 306MODULE_LICENSE("GPL");
 307