2010-05-05 02:55:05 +04:00
/*
* Copyright ( c ) 2010 , Microsoft Corporation .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License along with
* this program ; if not , write to the Free Software Foundation , Inc . , 59 Temple
* Place - Suite 330 , Boston , MA 02111 - 1307 USA .
*
* Authors :
* Haiyang Zhang < haiyangz @ microsoft . com >
* Hank Janssen < hjanssen @ microsoft . com >
*/
2011-03-30 00:58:49 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-05-05 02:55:05 +04:00
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/sysctl.h>
2010-05-04 19:26:23 +04:00
# include <linux/reboot.h>
2011-10-04 23:29:52 +04:00
# include <linux/hyperv.h>
2010-05-05 02:55:05 +04:00
2014-02-16 23:34:30 +04:00
# include "hyperv_vmbus.h"
2013-07-02 21:31:30 +04:00
2013-09-06 22:49:56 +04:00
# define SD_MAJOR 3
# define SD_MINOR 0
# define SD_VERSION (SD_MAJOR << 16 | SD_MINOR)
2013-07-02 21:31:30 +04:00
2013-09-06 22:49:56 +04:00
# define SD_WS2008_MAJOR 1
# define SD_WS2008_VERSION (SD_WS2008_MAJOR << 16 | SD_MINOR)
# define TS_MAJOR 3
# define TS_MINOR 0
# define TS_VERSION (TS_MAJOR << 16 | TS_MINOR)
# define TS_WS2008_MAJOR 1
# define TS_WS2008_VERSION (TS_WS2008_MAJOR << 16 | TS_MINOR)
# define HB_MAJOR 3
# define HB_MINOR 0
# define HB_VERSION (HB_MAJOR << 16 | HB_MINOR)
# define HB_WS2008_MAJOR 1
# define HB_WS2008_VERSION (HB_WS2008_MAJOR << 16 | HB_MINOR)
static int sd_srv_version ;
static int ts_srv_version ;
static int hb_srv_version ;
static int util_fw_version ;
2013-07-02 21:31:30 +04:00
2011-09-18 21:31:33 +04:00
static void shutdown_onchannelcallback ( void * context ) ;
static struct hv_util_service util_shutdown = {
. util_cb = shutdown_onchannelcallback ,
} ;
static void timesync_onchannelcallback ( void * context ) ;
static struct hv_util_service util_timesynch = {
. util_cb = timesync_onchannelcallback ,
} ;
static void heartbeat_onchannelcallback ( void * context ) ;
static struct hv_util_service util_heartbeat = {
. util_cb = heartbeat_onchannelcallback ,
} ;
static struct hv_util_service util_kvp = {
. util_cb = hv_kvp_onchannelcallback ,
. util_init = hv_kvp_init ,
. util_deinit = hv_kvp_deinit ,
} ;
2010-05-05 02:55:05 +04:00
2013-03-15 23:30:06 +04:00
static struct hv_util_service util_vss = {
. util_cb = hv_vss_onchannelcallback ,
. util_init = hv_vss_init ,
. util_deinit = hv_vss_deinit ,
} ;
2014-02-16 23:34:30 +04:00
static struct hv_util_service util_fcopy = {
. util_cb = hv_fcopy_onchannelcallback ,
. util_init = hv_fcopy_init ,
. util_deinit = hv_fcopy_deinit ,
} ;
2013-01-24 05:42:45 +04:00
static void perform_shutdown ( struct work_struct * dummy )
{
orderly_poweroff ( true ) ;
}
/*
* Perform the shutdown operation in a thread context .
*/
static DECLARE_WORK ( shutdown_work , perform_shutdown ) ;
2010-05-05 01:31:18 +04:00
static void shutdown_onchannelcallback ( void * context )
2010-05-05 02:55:05 +04:00
{
struct vmbus_channel * channel = context ;
2010-12-14 03:23:36 +03:00
u32 recvlen ;
2010-05-05 02:55:05 +04:00
u64 requestid ;
2013-09-22 02:27:34 +04:00
bool execute_shutdown = false ;
2011-09-18 21:31:33 +04:00
u8 * shut_txf_buf = util_shutdown . recv_buffer ;
2010-05-05 02:55:05 +04:00
struct shutdown_msg_data * shutdown_msg ;
struct icmsg_hdr * icmsghdrp ;
struct icmsg_negotiate * negop = NULL ;
2010-12-14 03:23:36 +03:00
vmbus_recvpacket ( channel , shut_txf_buf ,
PAGE_SIZE , & recvlen , & requestid ) ;
2010-05-05 02:55:05 +04:00
if ( recvlen > 0 ) {
2010-12-14 03:23:36 +03:00
icmsghdrp = ( struct icmsg_hdr * ) & shut_txf_buf [
2010-05-05 02:55:05 +04:00
sizeof ( struct vmbuspipe_hdr ) ] ;
if ( icmsghdrp - > icmsgtype = = ICMSGTYPE_NEGOTIATE ) {
2012-05-13 00:44:58 +04:00
vmbus_prep_negotiate_resp ( icmsghdrp , negop ,
2013-09-06 22:49:56 +04:00
shut_txf_buf , util_fw_version ,
sd_srv_version ) ;
2010-05-05 02:55:05 +04:00
} else {
2010-12-14 03:23:36 +03:00
shutdown_msg =
( struct shutdown_msg_data * ) & shut_txf_buf [
sizeof ( struct vmbuspipe_hdr ) +
sizeof ( struct icmsg_hdr ) ] ;
2010-05-05 02:55:05 +04:00
switch ( shutdown_msg - > flags ) {
case 0 :
case 1 :
icmsghdrp - > status = HV_S_OK ;
execute_shutdown = true ;
2011-03-30 00:58:49 +04:00
pr_info ( " Shutdown request received - "
2011-04-13 23:14:05 +04:00
" graceful shutdown initiated \n " ) ;
2010-05-05 02:55:05 +04:00
break ;
default :
icmsghdrp - > status = HV_E_FAIL ;
execute_shutdown = false ;
2011-03-30 00:58:49 +04:00
pr_info ( " Shutdown request received - "
" Invalid request \n " ) ;
2010-05-05 02:55:05 +04:00
break ;
2011-04-11 01:31:35 +04:00
}
2010-05-05 02:55:05 +04:00
}
icmsghdrp - > icflags = ICMSGHDRFLAG_TRANSACTION
| ICMSGHDRFLAG_RESPONSE ;
2010-12-14 03:23:36 +03:00
vmbus_sendpacket ( channel , shut_txf_buf ,
2010-05-05 02:55:05 +04:00
recvlen , requestid ,
2011-01-26 23:12:13 +03:00
VM_PKT_DATA_INBAND , 0 ) ;
2010-05-05 02:55:05 +04:00
}
if ( execute_shutdown = = true )
2013-01-24 05:42:45 +04:00
schedule_work ( & shutdown_work ) ;
2010-05-05 02:55:05 +04:00
}
2010-05-05 23:23:46 +04:00
/*
2010-05-11 19:11:24 +04:00
* Set guest time to host UTC time .
2010-05-05 23:23:46 +04:00
*/
2010-05-11 19:11:24 +04:00
static inline void do_adj_guesttime ( u64 hosttime )
2010-05-05 23:23:46 +04:00
{
s64 host_tns ;
struct timespec host_ts ;
host_tns = ( hosttime - WLTIMEDELTA ) * 100 ;
host_ts = ns_to_timespec ( host_tns ) ;
2010-05-11 19:11:24 +04:00
do_settimeofday ( & host_ts ) ;
}
2011-08-27 22:31:31 +04:00
/*
* Set the host time in a process context .
*/
struct adj_time_work {
struct work_struct work ;
u64 host_time ;
} ;
static void hv_set_host_time ( struct work_struct * work )
{
struct adj_time_work * wrk ;
wrk = container_of ( work , struct adj_time_work , work ) ;
do_adj_guesttime ( wrk - > host_time ) ;
kfree ( wrk ) ;
}
2010-05-11 19:11:24 +04:00
/*
* Synchronize time with host after reboot , restore , etc .
*
* ICTIMESYNCFLAG_SYNC flag bit indicates reboot , restore events of the VM .
* After reboot the flag ICTIMESYNCFLAG_SYNC is included in the first time
* message after the timesync channel is opened . Since the hv_utils module is
* loaded after hv_vmbus , the first message is usually missed . The other
* thing is , systime is automatically set to emulated hardware clock which may
* not be UTC time or in the same time zone . So , to override these effects , we
* use the first 50 time samples for initial system time setting .
*/
static inline void adj_guesttime ( u64 hosttime , u8 flags )
{
2011-08-27 22:31:31 +04:00
struct adj_time_work * wrk ;
2010-05-11 19:11:24 +04:00
static s32 scnt = 50 ;
2011-08-27 22:31:31 +04:00
wrk = kmalloc ( sizeof ( struct adj_time_work ) , GFP_ATOMIC ) ;
if ( wrk = = NULL )
return ;
wrk - > host_time = hosttime ;
2010-05-05 23:23:46 +04:00
if ( ( flags & ICTIMESYNCFLAG_SYNC ) ! = 0 ) {
2011-08-27 22:31:31 +04:00
INIT_WORK ( & wrk - > work , hv_set_host_time ) ;
schedule_work ( & wrk - > work ) ;
2010-05-05 23:23:46 +04:00
return ;
}
2010-05-11 19:11:24 +04:00
if ( ( flags & ICTIMESYNCFLAG_SAMPLE ) ! = 0 & & scnt > 0 ) {
2010-05-05 23:23:46 +04:00
scnt - - ;
2011-08-27 22:31:31 +04:00
INIT_WORK ( & wrk - > work , hv_set_host_time ) ;
schedule_work ( & wrk - > work ) ;
} else
kfree ( wrk ) ;
2010-05-05 23:23:46 +04:00
}
/*
* Time Sync Channel message handler .
*/
static void timesync_onchannelcallback ( void * context )
{
struct vmbus_channel * channel = context ;
2010-12-14 03:23:36 +03:00
u32 recvlen ;
2010-05-05 23:23:46 +04:00
u64 requestid ;
struct icmsg_hdr * icmsghdrp ;
struct ictimesync_data * timedatap ;
2011-09-18 21:31:33 +04:00
u8 * time_txf_buf = util_timesynch . recv_buffer ;
2013-09-06 22:49:56 +04:00
struct icmsg_negotiate * negop = NULL ;
2010-05-05 23:23:46 +04:00
2010-12-14 03:23:36 +03:00
vmbus_recvpacket ( channel , time_txf_buf ,
PAGE_SIZE , & recvlen , & requestid ) ;
2010-05-05 23:23:46 +04:00
if ( recvlen > 0 ) {
2010-12-14 03:23:36 +03:00
icmsghdrp = ( struct icmsg_hdr * ) & time_txf_buf [
2010-05-05 23:23:46 +04:00
sizeof ( struct vmbuspipe_hdr ) ] ;
if ( icmsghdrp - > icmsgtype = = ICMSGTYPE_NEGOTIATE ) {
2013-09-06 22:49:56 +04:00
vmbus_prep_negotiate_resp ( icmsghdrp , negop ,
time_txf_buf ,
util_fw_version ,
ts_srv_version ) ;
2010-05-05 23:23:46 +04:00
} else {
2010-12-14 03:23:36 +03:00
timedatap = ( struct ictimesync_data * ) & time_txf_buf [
2010-05-05 23:23:46 +04:00
sizeof ( struct vmbuspipe_hdr ) +
sizeof ( struct icmsg_hdr ) ] ;
adj_guesttime ( timedatap - > parenttime , timedatap - > flags ) ;
}
icmsghdrp - > icflags = ICMSGHDRFLAG_TRANSACTION
| ICMSGHDRFLAG_RESPONSE ;
2010-12-14 03:23:36 +03:00
vmbus_sendpacket ( channel , time_txf_buf ,
2010-05-05 23:23:46 +04:00
recvlen , requestid ,
2011-01-26 23:12:13 +03:00
VM_PKT_DATA_INBAND , 0 ) ;
2010-05-05 23:23:46 +04:00
}
}
2010-05-16 01:39:58 +04:00
/*
* Heartbeat functionality .
* Every two seconds , Hyper - V send us a heartbeat request message .
* we respond to this message , and Hyper - V knows we are alive .
*/
static void heartbeat_onchannelcallback ( void * context )
{
struct vmbus_channel * channel = context ;
2010-12-14 03:23:36 +03:00
u32 recvlen ;
2010-05-16 01:39:58 +04:00
u64 requestid ;
struct icmsg_hdr * icmsghdrp ;
struct heartbeat_msg_data * heartbeat_msg ;
2011-09-18 21:31:33 +04:00
u8 * hbeat_txf_buf = util_heartbeat . recv_buffer ;
2013-09-06 22:49:56 +04:00
struct icmsg_negotiate * negop = NULL ;
2010-05-16 01:39:58 +04:00
2010-12-14 03:23:36 +03:00
vmbus_recvpacket ( channel , hbeat_txf_buf ,
PAGE_SIZE , & recvlen , & requestid ) ;
2010-05-16 01:39:58 +04:00
if ( recvlen > 0 ) {
2010-12-14 03:23:36 +03:00
icmsghdrp = ( struct icmsg_hdr * ) & hbeat_txf_buf [
2010-05-16 01:39:58 +04:00
sizeof ( struct vmbuspipe_hdr ) ] ;
if ( icmsghdrp - > icmsgtype = = ICMSGTYPE_NEGOTIATE ) {
2013-09-06 22:49:56 +04:00
vmbus_prep_negotiate_resp ( icmsghdrp , negop ,
hbeat_txf_buf , util_fw_version ,
hb_srv_version ) ;
2010-05-16 01:39:58 +04:00
} else {
2010-12-14 03:23:36 +03:00
heartbeat_msg =
( struct heartbeat_msg_data * ) & hbeat_txf_buf [
sizeof ( struct vmbuspipe_hdr ) +
sizeof ( struct icmsg_hdr ) ] ;
2010-05-16 01:39:58 +04:00
heartbeat_msg - > seq_num + = 1 ;
}
icmsghdrp - > icflags = ICMSGHDRFLAG_TRANSACTION
| ICMSGHDRFLAG_RESPONSE ;
2010-12-14 03:23:36 +03:00
vmbus_sendpacket ( channel , hbeat_txf_buf ,
2010-05-16 01:39:58 +04:00
recvlen , requestid ,
2011-01-26 23:12:13 +03:00
VM_PKT_DATA_INBAND , 0 ) ;
2010-05-16 01:39:58 +04:00
}
}
2010-05-05 23:23:46 +04:00
2011-09-13 21:59:38 +04:00
static int util_probe ( struct hv_device * dev ,
const struct hv_vmbus_device_id * dev_id )
2011-08-25 20:48:36 +04:00
{
2011-09-18 21:31:33 +04:00
struct hv_util_service * srv =
( struct hv_util_service * ) dev_id - > driver_data ;
int ret ;
2014-07-08 03:34:25 +04:00
srv - > recv_buffer = kmalloc ( PAGE_SIZE * 4 , GFP_KERNEL ) ;
2011-09-18 21:31:33 +04:00
if ( ! srv - > recv_buffer )
return - ENOMEM ;
if ( srv - > util_init ) {
ret = srv - > util_init ( srv ) ;
if ( ret ) {
2011-09-18 21:31:34 +04:00
ret = - ENODEV ;
goto error1 ;
2011-09-18 21:31:33 +04:00
}
}
2012-12-01 18:46:34 +04:00
/*
* The set of services managed by the util driver are not performance
* critical and do not need batched reading . Furthermore , some services
* such as KVP can only handle one message from the host at a time .
* Turn off batched reading for all util drivers before we open the
* channel .
*/
set_channel_read_state ( dev - > channel , false ) ;
2011-09-18 21:31:33 +04:00
hv_set_drvdata ( dev , srv ) ;
2015-02-27 22:25:58 +03:00
2013-09-06 22:49:56 +04:00
/*
* Based on the host ; initialize the framework and
* service version numbers we will negotiate .
*/
switch ( vmbus_proto_version ) {
case ( VERSION_WS2008 ) :
util_fw_version = UTIL_WS2K8_FW_VERSION ;
sd_srv_version = SD_WS2008_VERSION ;
ts_srv_version = TS_WS2008_VERSION ;
hb_srv_version = HB_WS2008_VERSION ;
break ;
default :
util_fw_version = UTIL_FW_VERSION ;
sd_srv_version = SD_VERSION ;
ts_srv_version = TS_VERSION ;
hb_srv_version = HB_VERSION ;
}
2015-02-27 22:25:58 +03:00
ret = vmbus_open ( dev - > channel , 4 * PAGE_SIZE , 4 * PAGE_SIZE , NULL , 0 ,
srv - > util_cb , dev - > channel ) ;
if ( ret )
goto error ;
2011-08-25 20:48:36 +04:00
return 0 ;
2011-09-18 21:31:34 +04:00
error :
if ( srv - > util_deinit )
srv - > util_deinit ( ) ;
error1 :
kfree ( srv - > recv_buffer ) ;
return ret ;
2011-08-25 20:48:36 +04:00
}
static int util_remove ( struct hv_device * dev )
{
2011-09-18 21:31:33 +04:00
struct hv_util_service * srv = hv_get_drvdata ( dev ) ;
if ( srv - > util_deinit )
srv - > util_deinit ( ) ;
2015-02-28 22:18:20 +03:00
vmbus_close ( dev - > channel ) ;
2011-09-18 21:31:33 +04:00
kfree ( srv - > recv_buffer ) ;
2011-08-25 20:48:36 +04:00
return 0 ;
}
static const struct hv_vmbus_device_id id_table [ ] = {
2011-08-25 22:41:33 +04:00
/* Shutdown guid */
2013-01-24 05:42:41 +04:00
{ HV_SHUTDOWN_GUID ,
. driver_data = ( unsigned long ) & util_shutdown
} ,
2011-08-25 22:41:33 +04:00
/* Time synch guid */
2013-01-24 05:42:41 +04:00
{ HV_TS_GUID ,
. driver_data = ( unsigned long ) & util_timesynch
} ,
2011-08-25 22:41:33 +04:00
/* Heartbeat guid */
2013-01-24 05:42:41 +04:00
{ HV_HEART_BEAT_GUID ,
. driver_data = ( unsigned long ) & util_heartbeat
} ,
2011-08-25 22:41:33 +04:00
/* KVP guid */
2013-01-24 05:42:41 +04:00
{ HV_KVP_GUID ,
. driver_data = ( unsigned long ) & util_kvp
} ,
2013-03-15 23:30:06 +04:00
/* VSS GUID */
{ HV_VSS_GUID ,
. driver_data = ( unsigned long ) & util_vss
} ,
2014-02-16 23:34:30 +04:00
/* File copy GUID */
{ HV_FCOPY_GUID ,
. driver_data = ( unsigned long ) & util_fcopy
} ,
2011-08-25 22:41:33 +04:00
{ } ,
2011-08-25 20:48:36 +04:00
} ;
MODULE_DEVICE_TABLE ( vmbus , id_table ) ;
/* The one and only one */
static struct hv_driver util_drv = {
2011-08-26 02:07:32 +04:00
. name = " hv_util " ,
2011-08-25 20:48:36 +04:00
. id_table = id_table ,
. probe = util_probe ,
. remove = util_remove ,
} ;
2010-05-05 02:55:05 +04:00
static int __init init_hyperv_utils ( void )
{
2011-03-30 00:58:49 +04:00
pr_info ( " Registering HyperV Utility Driver \n " ) ;
2010-05-05 02:55:05 +04:00
2011-09-18 21:31:34 +04:00
return vmbus_driver_register ( & util_drv ) ;
2010-05-05 02:55:05 +04:00
}
static void exit_hyperv_utils ( void )
{
2011-03-30 00:58:49 +04:00
pr_info ( " De-Registered HyperV Utility Driver \n " ) ;
2010-05-05 02:55:05 +04:00
2011-08-26 02:07:32 +04:00
vmbus_driver_unregister ( & util_drv ) ;
2010-05-05 02:55:05 +04:00
}
module_init ( init_hyperv_utils ) ;
module_exit ( exit_hyperv_utils ) ;
MODULE_DESCRIPTION ( " Hyper-V Utilities " ) ;
MODULE_LICENSE ( " GPL " ) ;