summaryrefslogtreecommitdiffstats
path: root/drivers/hv
diff options
context:
space:
mode:
authorVitaly Kuznetsov <vkuznets@redhat.com>2015-04-11 21:07:51 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-05-24 15:17:41 -0400
commit14b50f80c32dd4e84b6baeaa8bf4049cc5ecf56d (patch)
tree3395e643fb235bfb8568b29a5178d46589a4bc5c /drivers/hv
parent3f0dccf86cab38d96b67efdfc944954f5490d057 (diff)
Drivers: hv: util: introduce hv_utils_transport abstraction
The intention is to make KVP/VSS drivers work through misc char devices. Introduce an abstraction for kernel/userspace communication to make the migration smoother. Transport operational mode (netlink or char device) is determined by the first received message. To support driver upgrades the switch from netlink to chardev operational mode is supported. Every hv_util daemon is supposed to register 2 callbacks: 1) on_msg() to get notified when the userspace daemon sent a message; 2) on_reset() to get notified when the userspace daemon drops the connection. Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com> Tested-by: Alex Ng <alexng@microsoft.com> Signed-off-by: K. Y. Srinivasan <kys@microsoft.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/hv')
-rw-r--r--drivers/hv/Makefile2
-rw-r--r--drivers/hv/hv_utils_transport.c276
-rw-r--r--drivers/hv/hv_utils_transport.h51
3 files changed, 328 insertions, 1 deletions
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 5e4dfa4cfe22..39c9b2c08d33 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -5,4 +5,4 @@ obj-$(CONFIG_HYPERV_BALLOON) += hv_balloon.o
5hv_vmbus-y := vmbus_drv.o \ 5hv_vmbus-y := vmbus_drv.o \
6 hv.o connection.o channel.o \ 6 hv.o connection.o channel.o \
7 channel_mgmt.o ring_buffer.o 7 channel_mgmt.o ring_buffer.o
8hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o 8hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o hv_utils_transport.o
diff --git a/drivers/hv/hv_utils_transport.c b/drivers/hv/hv_utils_transport.c
new file mode 100644
index 000000000000..ea7ba5ef16a9
--- /dev/null
+++ b/drivers/hv/hv_utils_transport.c
@@ -0,0 +1,276 @@
1/*
2 * Kernel/userspace transport abstraction for Hyper-V util driver.
3 *
4 * Copyright (C) 2015, Vitaly Kuznetsov <vkuznets@redhat.com>
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 as published
8 * by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
13 * NON INFRINGEMENT. See the GNU General Public License for more
14 * details.
15 *
16 */
17
18#include <linux/slab.h>
19#include <linux/fs.h>
20#include <linux/poll.h>
21
22#include "hyperv_vmbus.h"
23#include "hv_utils_transport.h"
24
25static DEFINE_SPINLOCK(hvt_list_lock);
26static struct list_head hvt_list = LIST_HEAD_INIT(hvt_list);
27
28static void hvt_reset(struct hvutil_transport *hvt)
29{
30 mutex_lock(&hvt->outmsg_lock);
31 kfree(hvt->outmsg);
32 hvt->outmsg = NULL;
33 hvt->outmsg_len = 0;
34 mutex_unlock(&hvt->outmsg_lock);
35 if (hvt->on_reset)
36 hvt->on_reset();
37}
38
39static ssize_t hvt_op_read(struct file *file, char __user *buf,
40 size_t count, loff_t *ppos)
41{
42 struct hvutil_transport *hvt;
43 int ret;
44
45 hvt = container_of(file->f_op, struct hvutil_transport, fops);
46
47 if (wait_event_interruptible(hvt->outmsg_q, hvt->outmsg_len > 0))
48 return -EINTR;
49
50 mutex_lock(&hvt->outmsg_lock);
51 if (!hvt->outmsg) {
52 ret = -EAGAIN;
53 goto out_unlock;
54 }
55
56 if (count < hvt->outmsg_len) {
57 ret = -EINVAL;
58 goto out_unlock;
59 }
60
61 if (!copy_to_user(buf, hvt->outmsg, hvt->outmsg_len))
62 ret = hvt->outmsg_len;
63 else
64 ret = -EFAULT;
65
66 kfree(hvt->outmsg);
67 hvt->outmsg = NULL;
68 hvt->outmsg_len = 0;
69
70out_unlock:
71 mutex_unlock(&hvt->outmsg_lock);
72 return ret;
73}
74
75static ssize_t hvt_op_write(struct file *file, const char __user *buf,
76 size_t count, loff_t *ppos)
77{
78 struct hvutil_transport *hvt;
79 u8 *inmsg;
80
81 hvt = container_of(file->f_op, struct hvutil_transport, fops);
82
83 inmsg = kzalloc(count, GFP_KERNEL);
84 if (copy_from_user(inmsg, buf, count)) {
85 kfree(inmsg);
86 return -EFAULT;
87 }
88 if (hvt->on_msg(inmsg, count))
89 return -EFAULT;
90 kfree(inmsg);
91
92 return count;
93}
94
95static unsigned int hvt_op_poll(struct file *file, poll_table *wait)
96{
97 struct hvutil_transport *hvt;
98
99 hvt = container_of(file->f_op, struct hvutil_transport, fops);
100
101 poll_wait(file, &hvt->outmsg_q, wait);
102 if (hvt->outmsg_len > 0)
103 return POLLIN | POLLRDNORM;
104
105 return 0;
106}
107
108static int hvt_op_open(struct inode *inode, struct file *file)
109{
110 struct hvutil_transport *hvt;
111
112 hvt = container_of(file->f_op, struct hvutil_transport, fops);
113
114 /*
115 * Switching to CHARDEV mode. We switch bach to INIT when device
116 * gets released.
117 */
118 if (hvt->mode == HVUTIL_TRANSPORT_INIT)
119 hvt->mode = HVUTIL_TRANSPORT_CHARDEV;
120 else if (hvt->mode == HVUTIL_TRANSPORT_NETLINK) {
121 /*
122 * We're switching from netlink communication to using char
123 * device. Issue the reset first.
124 */
125 hvt_reset(hvt);
126 hvt->mode = HVUTIL_TRANSPORT_CHARDEV;
127 } else
128 return -EBUSY;
129
130 return 0;
131}
132
133static int hvt_op_release(struct inode *inode, struct file *file)
134{
135 struct hvutil_transport *hvt;
136
137 hvt = container_of(file->f_op, struct hvutil_transport, fops);
138
139 hvt->mode = HVUTIL_TRANSPORT_INIT;
140 /*
141 * Cleanup message buffers to avoid spurious messages when the daemon
142 * connects back.
143 */
144 hvt_reset(hvt);
145
146 return 0;
147}
148
149static void hvt_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
150{
151 struct hvutil_transport *hvt, *hvt_found = NULL;
152
153 spin_lock(&hvt_list_lock);
154 list_for_each_entry(hvt, &hvt_list, list) {
155 if (hvt->cn_id.idx == msg->id.idx &&
156 hvt->cn_id.val == msg->id.val) {
157 hvt_found = hvt;
158 break;
159 }
160 }
161 spin_unlock(&hvt_list_lock);
162 if (!hvt_found) {
163 pr_warn("hvt_cn_callback: spurious message received!\n");
164 return;
165 }
166
167 /*
168 * Switching to NETLINK mode. Switching to CHARDEV happens when someone
169 * opens the device.
170 */
171 if (hvt->mode == HVUTIL_TRANSPORT_INIT)
172 hvt->mode = HVUTIL_TRANSPORT_NETLINK;
173
174 if (hvt->mode == HVUTIL_TRANSPORT_NETLINK)
175 hvt_found->on_msg(msg->data, msg->len);
176 else
177 pr_warn("hvt_cn_callback: unexpected netlink message!\n");
178}
179
180int hvutil_transport_send(struct hvutil_transport *hvt, void *msg, int len)
181{
182 struct cn_msg *cn_msg;
183 int ret = 0;
184
185 if (hvt->mode == HVUTIL_TRANSPORT_INIT) {
186 return -EINVAL;
187 } else if (hvt->mode == HVUTIL_TRANSPORT_NETLINK) {
188 cn_msg = kzalloc(sizeof(*cn_msg) + len, GFP_ATOMIC);
189 if (!msg)
190 return -ENOMEM;
191 cn_msg->id.idx = hvt->cn_id.idx;
192 cn_msg->id.val = hvt->cn_id.val;
193 cn_msg->len = len;
194 memcpy(cn_msg->data, msg, len);
195 ret = cn_netlink_send(cn_msg, 0, 0, GFP_ATOMIC);
196 kfree(cn_msg);
197 return ret;
198 }
199 /* HVUTIL_TRANSPORT_CHARDEV */
200 mutex_lock(&hvt->outmsg_lock);
201 if (hvt->outmsg) {
202 /* Previous message wasn't received */
203 ret = -EFAULT;
204 goto out_unlock;
205 }
206 hvt->outmsg = kzalloc(len, GFP_KERNEL);
207 memcpy(hvt->outmsg, msg, len);
208 hvt->outmsg_len = len;
209 wake_up_interruptible(&hvt->outmsg_q);
210out_unlock:
211 mutex_unlock(&hvt->outmsg_lock);
212 return ret;
213}
214
215struct hvutil_transport *hvutil_transport_init(const char *name,
216 u32 cn_idx, u32 cn_val,
217 int (*on_msg)(void *, int),
218 void (*on_reset)(void))
219{
220 struct hvutil_transport *hvt;
221
222 hvt = kzalloc(sizeof(*hvt), GFP_KERNEL);
223 if (!hvt)
224 return NULL;
225
226 hvt->cn_id.idx = cn_idx;
227 hvt->cn_id.val = cn_val;
228
229 hvt->mdev.minor = MISC_DYNAMIC_MINOR;
230 hvt->mdev.name = name;
231
232 hvt->fops.owner = THIS_MODULE;
233 hvt->fops.read = hvt_op_read;
234 hvt->fops.write = hvt_op_write;
235 hvt->fops.poll = hvt_op_poll;
236 hvt->fops.open = hvt_op_open;
237 hvt->fops.release = hvt_op_release;
238
239 hvt->mdev.fops = &hvt->fops;
240
241 init_waitqueue_head(&hvt->outmsg_q);
242 mutex_init(&hvt->outmsg_lock);
243
244 spin_lock(&hvt_list_lock);
245 list_add(&hvt->list, &hvt_list);
246 spin_unlock(&hvt_list_lock);
247
248 hvt->on_msg = on_msg;
249 hvt->on_reset = on_reset;
250
251 if (misc_register(&hvt->mdev))
252 goto err_free_hvt;
253
254 /* Use cn_id.idx/cn_id.val to determine if we need to setup netlink */
255 if (hvt->cn_id.idx > 0 && hvt->cn_id.val > 0 &&
256 cn_add_callback(&hvt->cn_id, name, hvt_cn_callback))
257 goto err_free_hvt;
258
259 return hvt;
260
261err_free_hvt:
262 kfree(hvt);
263 return NULL;
264}
265
266void hvutil_transport_destroy(struct hvutil_transport *hvt)
267{
268 spin_lock(&hvt_list_lock);
269 list_del(&hvt->list);
270 spin_unlock(&hvt_list_lock);
271 if (hvt->cn_id.idx > 0 && hvt->cn_id.val > 0)
272 cn_del_callback(&hvt->cn_id);
273 misc_deregister(&hvt->mdev);
274 kfree(hvt->outmsg);
275 kfree(hvt);
276}
diff --git a/drivers/hv/hv_utils_transport.h b/drivers/hv/hv_utils_transport.h
new file mode 100644
index 000000000000..314c76ce1b07
--- /dev/null
+++ b/drivers/hv/hv_utils_transport.h
@@ -0,0 +1,51 @@
1/*
2 * Kernel/userspace transport abstraction for Hyper-V util driver.
3 *
4 * Copyright (C) 2015, Vitaly Kuznetsov <vkuznets@redhat.com>
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 as published
8 * by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
13 * NON INFRINGEMENT. See the GNU General Public License for more
14 * details.
15 *
16 */
17
18#ifndef _HV_UTILS_TRANSPORT_H
19#define _HV_UTILS_TRANSPORT_H
20
21#include <linux/connector.h>
22#include <linux/miscdevice.h>
23
24enum hvutil_transport_mode {
25 HVUTIL_TRANSPORT_INIT = 0,
26 HVUTIL_TRANSPORT_NETLINK,
27 HVUTIL_TRANSPORT_CHARDEV,
28};
29
30struct hvutil_transport {
31 int mode; /* hvutil_transport_mode */
32 struct file_operations fops; /* file operations */
33 struct miscdevice mdev; /* misc device */
34 struct cb_id cn_id; /* CN_*_IDX/CN_*_VAL */
35 struct list_head list; /* hvt_list */
36 int (*on_msg)(void *, int); /* callback on new user message */
37 void (*on_reset)(void); /* callback when userspace drops */
38 u8 *outmsg; /* message to the userspace */
39 int outmsg_len; /* its length */
40 wait_queue_head_t outmsg_q; /* poll/read wait queue */
41 struct mutex outmsg_lock; /* protects outmsg */
42};
43
44struct hvutil_transport *hvutil_transport_init(const char *name,
45 u32 cn_idx, u32 cn_val,
46 int (*on_msg)(void *, int),
47 void (*on_reset)(void));
48int hvutil_transport_send(struct hvutil_transport *hvt, void *msg, int len);
49void hvutil_transport_destroy(struct hvutil_transport *hvt);
50
51#endif /* _HV_UTILS_TRANSPORT_H */