These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / hv / hv_snapshot.c
index 9d5e0d1..815405f 100644 (file)
@@ -24,6 +24,9 @@
 #include <linux/workqueue.h>
 #include <linux/hyperv.h>
 
+#include "hyperv_vmbus.h"
+#include "hv_utils_transport.h"
+
 #define VSS_MAJOR  5
 #define VSS_MINOR  0
 #define VSS_VERSION    (VSS_MAJOR << 16 | VSS_MINOR)
 #define VSS_USERSPACE_TIMEOUT (msecs_to_jiffies(10 * 1000))
 
 /*
- * Global state maintained for transaction that is being processed.
- * Note that only one transaction can be active at any point in time.
+ * Global state maintained for transaction that is being processed. For a class
+ * of integration services, including the "VSS service", the specified protocol
+ * is a "request/response" protocol which means that there can only be single
+ * outstanding transaction from the host at any given point in time. We use
+ * this to simplify memory management in this driver - we cache and process
+ * only one message at a time.
  *
- * This state is set when we receive a request from the host; we
- * cleanup this state when the transaction is completed - when we respond
- * to the host with the key value.
+ * While the request/response protocol is guaranteed by the host, we further
+ * ensure this by serializing packet processing in this driver - we do not
+ * read additional packets from the VMBUs until the current packet is fully
+ * handled.
  */
 
 static struct {
-       bool active; /* transaction status - active or not */
+       int state;   /* hvutil_device_state */
        int recv_len; /* number of bytes received. */
        struct vmbus_channel *recv_channel; /* chn we got the request */
        u64 recv_req_id; /* request ID. */
        struct hv_vss_msg  *msg; /* current message */
+       void *vss_context; /* for the channel callback */
 } vss_transaction;
 
 
 static void vss_respond_to_host(int error);
 
-static struct cb_id vss_id = { CN_VSS_IDX, CN_VSS_VAL };
-static const char vss_name[] = "vss_kernel_module";
+/*
+ * This state maintains the version number registered by the daemon.
+ */
+static int dm_reg_value;
+
+static const char vss_devname[] = "vmbus/hv_vss";
 static __u8 *recv_buffer;
+static struct hvutil_transport *hvt;
 
 static void vss_send_op(struct work_struct *dummy);
 static void vss_timeout_func(struct work_struct *dummy);
@@ -71,25 +85,69 @@ static void vss_timeout_func(struct work_struct *dummy)
         */
        pr_warn("VSS: timeout waiting for daemon to reply\n");
        vss_respond_to_host(HV_E_FAIL);
+
+       /* Transaction is finished, reset the state. */
+       if (vss_transaction.state > HVUTIL_READY)
+               vss_transaction.state = HVUTIL_READY;
+
+       hv_poll_channel(vss_transaction.vss_context,
+                       hv_vss_onchannelcallback);
 }
 
-static void
-vss_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
+static int vss_handle_handshake(struct hv_vss_msg *vss_msg)
 {
-       struct hv_vss_msg *vss_msg;
+       u32 our_ver = VSS_OP_REGISTER1;
+
+       switch (vss_msg->vss_hdr.operation) {
+       case VSS_OP_REGISTER:
+               /* Daemon doesn't expect us to reply */
+               dm_reg_value = VSS_OP_REGISTER;
+               break;
+       case VSS_OP_REGISTER1:
+               /* Daemon expects us to reply with our own version*/
+               if (hvutil_transport_send(hvt, &our_ver, sizeof(our_ver)))
+                       return -EFAULT;
+               dm_reg_value = VSS_OP_REGISTER1;
+               break;
+       default:
+               return -EINVAL;
+       }
+       vss_transaction.state = HVUTIL_READY;
+       pr_debug("VSS: userspace daemon ver. %d registered\n", dm_reg_value);
+       return 0;
+}
 
-       vss_msg = (struct hv_vss_msg *)msg->data;
+static int vss_on_msg(void *msg, int len)
+{
+       struct hv_vss_msg *vss_msg = (struct hv_vss_msg *)msg;
 
-       if (vss_msg->vss_hdr.operation == VSS_OP_REGISTER) {
-               pr_info("VSS daemon registered\n");
-               vss_transaction.active = false;
-               if (vss_transaction.recv_channel != NULL)
-                       hv_vss_onchannelcallback(vss_transaction.recv_channel);
-               return;
+       if (len != sizeof(*vss_msg))
+               return -EINVAL;
 
+       if (vss_msg->vss_hdr.operation == VSS_OP_REGISTER ||
+           vss_msg->vss_hdr.operation == VSS_OP_REGISTER1) {
+               /*
+                * Don't process registration messages if we're in the middle
+                * of a transaction processing.
+                */
+               if (vss_transaction.state > HVUTIL_READY)
+                       return -EINVAL;
+               return vss_handle_handshake(vss_msg);
+       } else if (vss_transaction.state == HVUTIL_USERSPACE_REQ) {
+               vss_transaction.state = HVUTIL_USERSPACE_RECV;
+               if (cancel_delayed_work_sync(&vss_timeout_work)) {
+                       vss_respond_to_host(vss_msg->error);
+                       /* Transaction is finished, reset the state. */
+                       vss_transaction.state = HVUTIL_READY;
+                       hv_poll_channel(vss_transaction.vss_context,
+                                       hv_vss_onchannelcallback);
+               }
+       } else {
+               /* This is a spurious call! */
+               pr_warn("VSS: Transaction not active\n");
+               return -EINVAL;
        }
-       if (cancel_delayed_work_sync(&vss_timeout_work))
-               vss_respond_to_host(vss_msg->error);
+       return 0;
 }
 
 
@@ -97,28 +155,29 @@ static void vss_send_op(struct work_struct *dummy)
 {
        int op = vss_transaction.msg->vss_hdr.operation;
        int rc;
-       struct cn_msg *msg;
        struct hv_vss_msg *vss_msg;
 
-       msg = kzalloc(sizeof(*msg) + sizeof(*vss_msg), GFP_ATOMIC);
-       if (!msg)
+       /* The transaction state is wrong. */
+       if (vss_transaction.state != HVUTIL_HOSTMSG_RECEIVED)
                return;
 
-       vss_msg = (struct hv_vss_msg *)msg->data;
-
-       msg->id.idx =  CN_VSS_IDX;
-       msg->id.val = CN_VSS_VAL;
+       vss_msg = kzalloc(sizeof(*vss_msg), GFP_KERNEL);
+       if (!vss_msg)
+               return;
 
        vss_msg->vss_hdr.operation = op;
-       msg->len = sizeof(struct hv_vss_msg);
 
-       rc = cn_netlink_send(msg, 0, 0, GFP_ATOMIC);
+       vss_transaction.state = HVUTIL_USERSPACE_REQ;
+       rc = hvutil_transport_send(hvt, vss_msg, sizeof(*vss_msg));
        if (rc) {
                pr_warn("VSS: failed to communicate to the daemon: %d\n", rc);
-               if (cancel_delayed_work_sync(&vss_timeout_work))
+               if (cancel_delayed_work_sync(&vss_timeout_work)) {
                        vss_respond_to_host(HV_E_FAIL);
+                       vss_transaction.state = HVUTIL_READY;
+               }
        }
-       kfree(msg);
+
+       kfree(vss_msg);
 
        return;
 }
@@ -135,17 +194,6 @@ vss_respond_to_host(int error)
        struct vmbus_channel *channel;
        u64     req_id;
 
-       /*
-        * If a transaction is not active; log and return.
-        */
-
-       if (!vss_transaction.active) {
-               /*
-                * This is a spurious call!
-                */
-               pr_warn("VSS: Transaction not active\n");
-               return;
-       }
        /*
         * Copy the global state for completing the transaction. Note that
         * only one transaction can be active at a time.
@@ -154,7 +202,6 @@ vss_respond_to_host(int error)
        buf_len = vss_transaction.recv_len;
        channel = vss_transaction.recv_channel;
        req_id = vss_transaction.recv_req_id;
-       vss_transaction.active = false;
 
        icmsghdrp = (struct icmsg_hdr *)
                        &recv_buffer[sizeof(struct vmbuspipe_hdr)];
@@ -191,14 +238,15 @@ void hv_vss_onchannelcallback(void *context)
        struct icmsg_hdr *icmsghdrp;
        struct icmsg_negotiate *negop = NULL;
 
-       if (vss_transaction.active) {
+       if (vss_transaction.state > HVUTIL_READY) {
                /*
                 * We will defer processing this callback once
                 * the current transaction is complete.
                 */
-               vss_transaction.recv_channel = channel;
+               vss_transaction.vss_context = context;
                return;
        }
+       vss_transaction.vss_context = NULL;
 
        vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen,
                         &requestid);
@@ -224,7 +272,6 @@ void hv_vss_onchannelcallback(void *context)
                        vss_transaction.recv_len = recvlen;
                        vss_transaction.recv_channel = channel;
                        vss_transaction.recv_req_id = requestid;
-                       vss_transaction.active = true;
                        vss_transaction.msg = (struct hv_vss_msg *)vss_msg;
 
                        switch (vss_msg->vss_hdr.operation) {
@@ -241,6 +288,12 @@ void hv_vss_onchannelcallback(void *context)
                                 */
                        case VSS_OP_FREEZE:
                        case VSS_OP_THAW:
+                               if (vss_transaction.state < HVUTIL_READY) {
+                                       /* Userspace is not registered yet */
+                                       vss_respond_to_host(HV_E_FAIL);
+                                       return;
+                               }
+                               vss_transaction.state = HVUTIL_HOSTMSG_RECEIVED;
                                schedule_work(&vss_send_op_work);
                                schedule_delayed_work(&vss_timeout_work,
                                                      VSS_USERSPACE_TIMEOUT);
@@ -275,14 +328,16 @@ void hv_vss_onchannelcallback(void *context)
 
 }
 
+static void vss_on_reset(void)
+{
+       if (cancel_delayed_work_sync(&vss_timeout_work))
+               vss_respond_to_host(HV_E_FAIL);
+       vss_transaction.state = HVUTIL_DEVICE_INIT;
+}
+
 int
 hv_vss_init(struct hv_util_service *srv)
 {
-       int err;
-
-       err = cn_add_callback(&vss_id, vss_name, vss_cn_callback);
-       if (err)
-               return err;
        recv_buffer = srv->recv_buffer;
 
        /*
@@ -291,13 +346,20 @@ hv_vss_init(struct hv_util_service *srv)
         * Defer processing channel callbacks until the daemon
         * has registered.
         */
-       vss_transaction.active = true;
+       vss_transaction.state = HVUTIL_DEVICE_INIT;
+
+       hvt = hvutil_transport_init(vss_devname, CN_VSS_IDX, CN_VSS_VAL,
+                                   vss_on_msg, vss_on_reset);
+       if (!hvt)
+               return -EFAULT;
+
        return 0;
 }
 
 void hv_vss_deinit(void)
 {
-       cn_del_callback(&vss_id);
+       vss_transaction.state = HVUTIL_DEVICE_DYING;
        cancel_delayed_work_sync(&vss_timeout_work);
        cancel_work_sync(&vss_send_op_work);
+       hvutil_transport_destroy(hvt);
 }