linux/drivers/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/hyperv.h>
  30
  31static void shutdown_onchannelcallback(void *context);
  32static struct hv_util_service util_shutdown = {
  33        .util_cb = shutdown_onchannelcallback,
  34};
  35
  36static void timesync_onchannelcallback(void *context);
  37static struct hv_util_service util_timesynch = {
  38        .util_cb = timesync_onchannelcallback,
  39};
  40
  41static void heartbeat_onchannelcallback(void *context);
  42static struct hv_util_service util_heartbeat = {
  43        .util_cb = heartbeat_onchannelcallback,
  44};
  45
  46static struct hv_util_service util_kvp = {
  47        .util_cb = hv_kvp_onchannelcallback,
  48        .util_init = hv_kvp_init,
  49        .util_deinit = hv_kvp_deinit,
  50};
  51
  52static struct hv_util_service util_vss = {
  53        .util_cb = hv_vss_onchannelcallback,
  54        .util_init = hv_vss_init,
  55        .util_deinit = hv_vss_deinit,
  56};
  57
  58static void perform_shutdown(struct work_struct *dummy)
  59{
  60        orderly_poweroff(true);
  61}
  62
  63/*
  64 * Perform the shutdown operation in a thread context.
  65 */
  66static DECLARE_WORK(shutdown_work, perform_shutdown);
  67
  68static void shutdown_onchannelcallback(void *context)
  69{
  70        struct vmbus_channel *channel = context;
  71        u32 recvlen;
  72        u64 requestid;
  73        u8  execute_shutdown = false;
  74        u8  *shut_txf_buf = util_shutdown.recv_buffer;
  75
  76        struct shutdown_msg_data *shutdown_msg;
  77
  78        struct icmsg_hdr *icmsghdrp;
  79        struct icmsg_negotiate *negop = NULL;
  80
  81        vmbus_recvpacket(channel, shut_txf_buf,
  82                         PAGE_SIZE, &recvlen, &requestid);
  83
  84        if (recvlen > 0) {
  85                icmsghdrp = (struct icmsg_hdr *)&shut_txf_buf[
  86                        sizeof(struct vmbuspipe_hdr)];
  87
  88                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
  89                        vmbus_prep_negotiate_resp(icmsghdrp, negop,
  90                                        shut_txf_buf, MAX_SRV_VER, MAX_SRV_VER);
  91                } else {
  92                        shutdown_msg =
  93                                (struct shutdown_msg_data *)&shut_txf_buf[
  94                                        sizeof(struct vmbuspipe_hdr) +
  95                                        sizeof(struct icmsg_hdr)];
  96
  97                        switch (shutdown_msg->flags) {
  98                        case 0:
  99                        case 1:
 100                                icmsghdrp->status = HV_S_OK;
 101                                execute_shutdown = true;
 102
 103                                pr_info("Shutdown request received -"
 104                                            " graceful shutdown initiated\n");
 105                                break;
 106                        default:
 107                                icmsghdrp->status = HV_E_FAIL;
 108                                execute_shutdown = false;
 109
 110                                pr_info("Shutdown request received -"
 111                                            " Invalid request\n");
 112                                break;
 113                        }
 114                }
 115
 116                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
 117                        | ICMSGHDRFLAG_RESPONSE;
 118
 119                vmbus_sendpacket(channel, shut_txf_buf,
 120                                       recvlen, requestid,
 121                                       VM_PKT_DATA_INBAND, 0);
 122        }
 123
 124        if (execute_shutdown == true)
 125                schedule_work(&shutdown_work);
 126}
 127
 128/*
 129 * Set guest time to host UTC time.
 130 */
 131static inline void do_adj_guesttime(u64 hosttime)
 132{
 133        s64 host_tns;
 134        struct timespec host_ts;
 135
 136        host_tns = (hosttime - WLTIMEDELTA) * 100;
 137        host_ts = ns_to_timespec(host_tns);
 138
 139        do_settimeofday(&host_ts);
 140}
 141
 142/*
 143 * Set the host time in a process context.
 144 */
 145
 146struct adj_time_work {
 147        struct work_struct work;
 148        u64     host_time;
 149};
 150
 151static void hv_set_host_time(struct work_struct *work)
 152{
 153        struct adj_time_work    *wrk;
 154
 155        wrk = container_of(work, struct adj_time_work, work);
 156        do_adj_guesttime(wrk->host_time);
 157        kfree(wrk);
 158}
 159
 160/*
 161 * Synchronize time with host after reboot, restore, etc.
 162 *
 163 * ICTIMESYNCFLAG_SYNC flag bit indicates reboot, restore events of the VM.
 164 * After reboot the flag ICTIMESYNCFLAG_SYNC is included in the first time
 165 * message after the timesync channel is opened. Since the hv_utils module is
 166 * loaded after hv_vmbus, the first message is usually missed. The other
 167 * thing is, systime is automatically set to emulated hardware clock which may
 168 * not be UTC time or in the same time zone. So, to override these effects, we
 169 * use the first 50 time samples for initial system time setting.
 170 */
 171static inline void adj_guesttime(u64 hosttime, u8 flags)
 172{
 173        struct adj_time_work    *wrk;
 174        static s32 scnt = 50;
 175
 176        wrk = kmalloc(sizeof(struct adj_time_work), GFP_ATOMIC);
 177        if (wrk == NULL)
 178                return;
 179
 180        wrk->host_time = hosttime;
 181        if ((flags & ICTIMESYNCFLAG_SYNC) != 0) {
 182                INIT_WORK(&wrk->work, hv_set_host_time);
 183                schedule_work(&wrk->work);
 184                return;
 185        }
 186
 187        if ((flags & ICTIMESYNCFLAG_SAMPLE) != 0 && scnt > 0) {
 188                scnt--;
 189                INIT_WORK(&wrk->work, hv_set_host_time);
 190                schedule_work(&wrk->work);
 191        } else
 192                kfree(wrk);
 193}
 194
 195/*
 196 * Time Sync Channel message handler.
 197 */
 198static void timesync_onchannelcallback(void *context)
 199{
 200        struct vmbus_channel *channel = context;
 201        u32 recvlen;
 202        u64 requestid;
 203        struct icmsg_hdr *icmsghdrp;
 204        struct ictimesync_data *timedatap;
 205        u8 *time_txf_buf = util_timesynch.recv_buffer;
 206
 207        vmbus_recvpacket(channel, time_txf_buf,
 208                         PAGE_SIZE, &recvlen, &requestid);
 209
 210        if (recvlen > 0) {
 211                icmsghdrp = (struct icmsg_hdr *)&time_txf_buf[
 212                                sizeof(struct vmbuspipe_hdr)];
 213
 214                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
 215                        vmbus_prep_negotiate_resp(icmsghdrp, NULL, time_txf_buf,
 216                                                MAX_SRV_VER, MAX_SRV_VER);
 217                } else {
 218                        timedatap = (struct ictimesync_data *)&time_txf_buf[
 219                                sizeof(struct vmbuspipe_hdr) +
 220                                sizeof(struct icmsg_hdr)];
 221                        adj_guesttime(timedatap->parenttime, timedatap->flags);
 222                }
 223
 224                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
 225                        | ICMSGHDRFLAG_RESPONSE;
 226
 227                vmbus_sendpacket(channel, time_txf_buf,
 228                                recvlen, requestid,
 229                                VM_PKT_DATA_INBAND, 0);
 230        }
 231}
 232
 233/*
 234 * Heartbeat functionality.
 235 * Every two seconds, Hyper-V send us a heartbeat request message.
 236 * we respond to this message, and Hyper-V knows we are alive.
 237 */
 238static void heartbeat_onchannelcallback(void *context)
 239{
 240        struct vmbus_channel *channel = context;
 241        u32 recvlen;
 242        u64 requestid;
 243        struct icmsg_hdr *icmsghdrp;
 244        struct heartbeat_msg_data *heartbeat_msg;
 245        u8 *hbeat_txf_buf = util_heartbeat.recv_buffer;
 246
 247        vmbus_recvpacket(channel, hbeat_txf_buf,
 248                         PAGE_SIZE, &recvlen, &requestid);
 249
 250        if (recvlen > 0) {
 251                icmsghdrp = (struct icmsg_hdr *)&hbeat_txf_buf[
 252                                sizeof(struct vmbuspipe_hdr)];
 253
 254                if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
 255                        vmbus_prep_negotiate_resp(icmsghdrp, NULL,
 256                                hbeat_txf_buf, MAX_SRV_VER, MAX_SRV_VER);
 257                } else {
 258                        heartbeat_msg =
 259                                (struct heartbeat_msg_data *)&hbeat_txf_buf[
 260                                        sizeof(struct vmbuspipe_hdr) +
 261                                        sizeof(struct icmsg_hdr)];
 262
 263                        heartbeat_msg->seq_num += 1;
 264                }
 265
 266                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
 267                        | ICMSGHDRFLAG_RESPONSE;
 268
 269                vmbus_sendpacket(channel, hbeat_txf_buf,
 270                                       recvlen, requestid,
 271                                       VM_PKT_DATA_INBAND, 0);
 272        }
 273}
 274
 275static int util_probe(struct hv_device *dev,
 276                        const struct hv_vmbus_device_id *dev_id)
 277{
 278        struct hv_util_service *srv =
 279                (struct hv_util_service *)dev_id->driver_data;
 280        int ret;
 281
 282        srv->recv_buffer = kmalloc(PAGE_SIZE * 2, GFP_KERNEL);
 283        if (!srv->recv_buffer)
 284                return -ENOMEM;
 285        if (srv->util_init) {
 286                ret = srv->util_init(srv);
 287                if (ret) {
 288                        ret = -ENODEV;
 289                        goto error1;
 290                }
 291        }
 292
 293        /*
 294         * The set of services managed by the util driver are not performance
 295         * critical and do not need batched reading. Furthermore, some services
 296         * such as KVP can only handle one message from the host at a time.
 297         * Turn off batched reading for all util drivers before we open the
 298         * channel.
 299         */
 300
 301        set_channel_read_state(dev->channel, false);
 302
 303        ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0,
 304                        srv->util_cb, dev->channel);
 305        if (ret)
 306                goto error;
 307
 308        hv_set_drvdata(dev, srv);
 309        return 0;
 310
 311error:
 312        if (srv->util_deinit)
 313                srv->util_deinit();
 314error1:
 315        kfree(srv->recv_buffer);
 316        return ret;
 317}
 318
 319static int util_remove(struct hv_device *dev)
 320{
 321        struct hv_util_service *srv = hv_get_drvdata(dev);
 322
 323        vmbus_close(dev->channel);
 324        if (srv->util_deinit)
 325                srv->util_deinit();
 326        kfree(srv->recv_buffer);
 327
 328        return 0;
 329}
 330
 331static const struct hv_vmbus_device_id id_table[] = {
 332        /* Shutdown guid */
 333        { HV_SHUTDOWN_GUID,
 334          .driver_data = (unsigned long)&util_shutdown
 335        },
 336        /* Time synch guid */
 337        { HV_TS_GUID,
 338          .driver_data = (unsigned long)&util_timesynch
 339        },
 340        /* Heartbeat guid */
 341        { HV_HEART_BEAT_GUID,
 342          .driver_data = (unsigned long)&util_heartbeat
 343        },
 344        /* KVP guid */
 345        { HV_KVP_GUID,
 346          .driver_data = (unsigned long)&util_kvp
 347        },
 348        /* VSS GUID */
 349        { HV_VSS_GUID,
 350          .driver_data = (unsigned long)&util_vss
 351        },
 352        { },
 353};
 354
 355MODULE_DEVICE_TABLE(vmbus, id_table);
 356
 357/* The one and only one */
 358static  struct hv_driver util_drv = {
 359        .name = "hv_util",
 360        .id_table = id_table,
 361        .probe =  util_probe,
 362        .remove =  util_remove,
 363};
 364
 365static int __init init_hyperv_utils(void)
 366{
 367        pr_info("Registering HyperV Utility Driver\n");
 368
 369        return vmbus_driver_register(&util_drv);
 370}
 371
 372static void exit_hyperv_utils(void)
 373{
 374        pr_info("De-Registered HyperV Utility Driver\n");
 375
 376        vmbus_driver_unregister(&util_drv);
 377}
 378
 379module_init(init_hyperv_utils);
 380module_exit(exit_hyperv_utils);
 381
 382MODULE_DESCRIPTION("Hyper-V Utilities");
 383MODULE_VERSION(HV_DRV_VERSION);
 384MODULE_LICENSE("GPL");
 385
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.