]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
84cd8fc4 DJS |
2 | /* |
3 | * Physical device callbacks for vfio_ccw | |
4 | * | |
5 | * Copyright IBM Corp. 2017 | |
6 | * | |
7 | * Author(s): Dong Jia Shi <bjsdjshi@linux.vnet.ibm.com> | |
8 | * Xiao Feng Ren <renxiaof@linux.vnet.ibm.com> | |
9 | */ | |
10 | ||
11 | #include <linux/vfio.h> | |
12 | #include <linux/mdev.h> | |
13 | ||
14 | #include "vfio_ccw_private.h" | |
15 | ||
83d1193a DJS |
16 | static int vfio_ccw_mdev_reset(struct mdev_device *mdev) |
17 | { | |
18 | struct vfio_ccw_private *private; | |
19 | struct subchannel *sch; | |
20 | int ret; | |
21 | ||
22 | private = dev_get_drvdata(mdev_parent_dev(mdev)); | |
83d1193a DJS |
23 | sch = private->sch; |
24 | /* | |
25 | * TODO: | |
26 | * In the cureent stage, some things like "no I/O running" and "no | |
27 | * interrupt pending" are clear, but we are not sure what other state | |
28 | * we need to care about. | |
29 | * There are still a lot more instructions need to be handled. We | |
30 | * should come back here later. | |
31 | */ | |
32 | ret = vfio_ccw_sch_quiesce(sch); | |
33 | if (ret) | |
34 | return ret; | |
35 | ||
bbe37e4c DJS |
36 | ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); |
37 | if (!ret) | |
38 | private->state = VFIO_CCW_STATE_IDLE; | |
39 | ||
40 | return ret; | |
83d1193a DJS |
41 | } |
42 | ||
84cd8fc4 DJS |
43 | static int vfio_ccw_mdev_notifier(struct notifier_block *nb, |
44 | unsigned long action, | |
45 | void *data) | |
46 | { | |
47 | struct vfio_ccw_private *private = | |
48 | container_of(nb, struct vfio_ccw_private, nb); | |
49 | ||
84cd8fc4 | 50 | /* |
84cd8fc4 DJS |
51 | * Vendor drivers MUST unpin pages in response to an |
52 | * invalidation. | |
53 | */ | |
4e149e43 DJS |
54 | if (action == VFIO_IOMMU_NOTIFY_DMA_UNMAP) { |
55 | struct vfio_iommu_type1_dma_unmap *unmap = data; | |
4e149e43 DJS |
56 | |
57 | if (!cp_iova_pinned(&private->cp, unmap->iova)) | |
58 | return NOTIFY_OK; | |
59 | ||
83d1193a | 60 | if (vfio_ccw_mdev_reset(private->mdev)) |
4e149e43 DJS |
61 | return NOTIFY_BAD; |
62 | ||
63 | cp_free(&private->cp); | |
64 | return NOTIFY_OK; | |
65 | } | |
84cd8fc4 DJS |
66 | |
67 | return NOTIFY_DONE; | |
68 | } | |
69 | ||
70 | static ssize_t name_show(struct kobject *kobj, struct device *dev, char *buf) | |
71 | { | |
72 | return sprintf(buf, "I/O subchannel (Non-QDIO)\n"); | |
73 | } | |
5bf18536 | 74 | static MDEV_TYPE_ATTR_RO(name); |
84cd8fc4 DJS |
75 | |
76 | static ssize_t device_api_show(struct kobject *kobj, struct device *dev, | |
77 | char *buf) | |
78 | { | |
79 | return sprintf(buf, "%s\n", VFIO_DEVICE_API_CCW_STRING); | |
80 | } | |
5bf18536 | 81 | static MDEV_TYPE_ATTR_RO(device_api); |
84cd8fc4 DJS |
82 | |
83 | static ssize_t available_instances_show(struct kobject *kobj, | |
84 | struct device *dev, char *buf) | |
85 | { | |
86 | struct vfio_ccw_private *private = dev_get_drvdata(dev); | |
87 | ||
88 | return sprintf(buf, "%d\n", atomic_read(&private->avail)); | |
89 | } | |
5bf18536 | 90 | static MDEV_TYPE_ATTR_RO(available_instances); |
84cd8fc4 DJS |
91 | |
92 | static struct attribute *mdev_types_attrs[] = { | |
93 | &mdev_type_attr_name.attr, | |
94 | &mdev_type_attr_device_api.attr, | |
95 | &mdev_type_attr_available_instances.attr, | |
96 | NULL, | |
97 | }; | |
98 | ||
99 | static struct attribute_group mdev_type_group = { | |
100 | .name = "io", | |
101 | .attrs = mdev_types_attrs, | |
102 | }; | |
103 | ||
5bf18536 | 104 | static struct attribute_group *mdev_type_groups[] = { |
84cd8fc4 DJS |
105 | &mdev_type_group, |
106 | NULL, | |
107 | }; | |
108 | ||
109 | static int vfio_ccw_mdev_create(struct kobject *kobj, struct mdev_device *mdev) | |
110 | { | |
111 | struct vfio_ccw_private *private = | |
112 | dev_get_drvdata(mdev_parent_dev(mdev)); | |
113 | ||
bbe37e4c DJS |
114 | if (private->state == VFIO_CCW_STATE_NOT_OPER) |
115 | return -ENODEV; | |
116 | ||
84cd8fc4 DJS |
117 | if (atomic_dec_if_positive(&private->avail) < 0) |
118 | return -EPERM; | |
119 | ||
120 | private->mdev = mdev; | |
bbe37e4c | 121 | private->state = VFIO_CCW_STATE_IDLE; |
84cd8fc4 DJS |
122 | |
123 | return 0; | |
124 | } | |
125 | ||
126 | static int vfio_ccw_mdev_remove(struct mdev_device *mdev) | |
127 | { | |
83d1193a DJS |
128 | struct vfio_ccw_private *private = |
129 | dev_get_drvdata(mdev_parent_dev(mdev)); | |
84cd8fc4 | 130 | |
129cc19a DJS |
131 | if ((private->state != VFIO_CCW_STATE_NOT_OPER) && |
132 | (private->state != VFIO_CCW_STATE_STANDBY)) { | |
443df891 | 133 | if (!vfio_ccw_sch_quiesce(private->sch)) |
129cc19a DJS |
134 | private->state = VFIO_CCW_STATE_STANDBY; |
135 | /* The state will be NOT_OPER on error. */ | |
136 | } | |
bbe37e4c | 137 | |
443df891 | 138 | cp_free(&private->cp); |
84cd8fc4 DJS |
139 | private->mdev = NULL; |
140 | atomic_inc(&private->avail); | |
141 | ||
142 | return 0; | |
143 | } | |
144 | ||
145 | static int vfio_ccw_mdev_open(struct mdev_device *mdev) | |
146 | { | |
147 | struct vfio_ccw_private *private = | |
148 | dev_get_drvdata(mdev_parent_dev(mdev)); | |
149 | unsigned long events = VFIO_IOMMU_NOTIFY_DMA_UNMAP; | |
150 | ||
151 | private->nb.notifier_call = vfio_ccw_mdev_notifier; | |
152 | ||
153 | return vfio_register_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY, | |
154 | &events, &private->nb); | |
155 | } | |
156 | ||
5bf18536 | 157 | static void vfio_ccw_mdev_release(struct mdev_device *mdev) |
84cd8fc4 DJS |
158 | { |
159 | struct vfio_ccw_private *private = | |
160 | dev_get_drvdata(mdev_parent_dev(mdev)); | |
161 | ||
443df891 FA |
162 | if ((private->state != VFIO_CCW_STATE_NOT_OPER) && |
163 | (private->state != VFIO_CCW_STATE_STANDBY)) { | |
164 | if (!vfio_ccw_mdev_reset(mdev)) | |
165 | private->state = VFIO_CCW_STATE_STANDBY; | |
166 | /* The state will be NOT_OPER on error. */ | |
167 | } | |
168 | ||
169 | cp_free(&private->cp); | |
84cd8fc4 DJS |
170 | vfio_unregister_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY, |
171 | &private->nb); | |
172 | } | |
173 | ||
060d2b5a DJS |
174 | static ssize_t vfio_ccw_mdev_read(struct mdev_device *mdev, |
175 | char __user *buf, | |
176 | size_t count, | |
177 | loff_t *ppos) | |
178 | { | |
179 | struct vfio_ccw_private *private; | |
180 | struct ccw_io_region *region; | |
181 | ||
182 | if (*ppos + count > sizeof(*region)) | |
183 | return -EINVAL; | |
184 | ||
185 | private = dev_get_drvdata(mdev_parent_dev(mdev)); | |
060d2b5a DJS |
186 | region = &private->io_region; |
187 | if (copy_to_user(buf, (void *)region + *ppos, count)) | |
188 | return -EFAULT; | |
189 | ||
190 | return count; | |
191 | } | |
192 | ||
193 | static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, | |
194 | const char __user *buf, | |
195 | size_t count, | |
196 | loff_t *ppos) | |
197 | { | |
198 | struct vfio_ccw_private *private; | |
199 | struct ccw_io_region *region; | |
200 | ||
201 | if (*ppos + count > sizeof(*region)) | |
202 | return -EINVAL; | |
203 | ||
204 | private = dev_get_drvdata(mdev_parent_dev(mdev)); | |
bbe37e4c DJS |
205 | if (private->state != VFIO_CCW_STATE_IDLE) |
206 | return -EACCES; | |
060d2b5a DJS |
207 | |
208 | region = &private->io_region; | |
209 | if (copy_from_user((void *)region + *ppos, buf, count)) | |
210 | return -EFAULT; | |
4e149e43 | 211 | |
bbe37e4c DJS |
212 | vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_IO_REQ); |
213 | if (region->ret_code != 0) { | |
214 | private->state = VFIO_CCW_STATE_IDLE; | |
4e149e43 | 215 | return region->ret_code; |
bbe37e4c | 216 | } |
060d2b5a DJS |
217 | |
218 | return count; | |
219 | } | |
220 | ||
e01bcdd6 DJS |
221 | static int vfio_ccw_mdev_get_device_info(struct vfio_device_info *info) |
222 | { | |
83d1193a | 223 | info->flags = VFIO_DEVICE_FLAGS_CCW | VFIO_DEVICE_FLAGS_RESET; |
e01bcdd6 | 224 | info->num_regions = VFIO_CCW_NUM_REGIONS; |
120e214e | 225 | info->num_irqs = VFIO_CCW_NUM_IRQS; |
e01bcdd6 DJS |
226 | |
227 | return 0; | |
228 | } | |
229 | ||
230 | static int vfio_ccw_mdev_get_region_info(struct vfio_region_info *info, | |
231 | u16 *cap_type_id, | |
232 | void **cap_type) | |
233 | { | |
234 | switch (info->index) { | |
235 | case VFIO_CCW_CONFIG_REGION_INDEX: | |
236 | info->offset = 0; | |
237 | info->size = sizeof(struct ccw_io_region); | |
238 | info->flags = VFIO_REGION_INFO_FLAG_READ | |
239 | | VFIO_REGION_INFO_FLAG_WRITE; | |
240 | return 0; | |
241 | default: | |
242 | return -EINVAL; | |
243 | } | |
244 | } | |
245 | ||
5bf18536 | 246 | static int vfio_ccw_mdev_get_irq_info(struct vfio_irq_info *info) |
120e214e DJS |
247 | { |
248 | if (info->index != VFIO_CCW_IO_IRQ_INDEX) | |
249 | return -EINVAL; | |
250 | ||
251 | info->count = 1; | |
252 | info->flags = VFIO_IRQ_INFO_EVENTFD; | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | static int vfio_ccw_mdev_set_irqs(struct mdev_device *mdev, | |
258 | uint32_t flags, | |
259 | void __user *data) | |
260 | { | |
261 | struct vfio_ccw_private *private; | |
262 | struct eventfd_ctx **ctx; | |
263 | ||
264 | if (!(flags & VFIO_IRQ_SET_ACTION_TRIGGER)) | |
265 | return -EINVAL; | |
266 | ||
267 | private = dev_get_drvdata(mdev_parent_dev(mdev)); | |
120e214e DJS |
268 | ctx = &private->io_trigger; |
269 | ||
270 | switch (flags & VFIO_IRQ_SET_DATA_TYPE_MASK) { | |
271 | case VFIO_IRQ_SET_DATA_NONE: | |
272 | { | |
273 | if (*ctx) | |
274 | eventfd_signal(*ctx, 1); | |
275 | return 0; | |
276 | } | |
277 | case VFIO_IRQ_SET_DATA_BOOL: | |
278 | { | |
279 | uint8_t trigger; | |
280 | ||
281 | if (get_user(trigger, (uint8_t __user *)data)) | |
282 | return -EFAULT; | |
283 | ||
284 | if (trigger && *ctx) | |
285 | eventfd_signal(*ctx, 1); | |
286 | return 0; | |
287 | } | |
288 | case VFIO_IRQ_SET_DATA_EVENTFD: | |
289 | { | |
290 | int32_t fd; | |
291 | ||
292 | if (get_user(fd, (int32_t __user *)data)) | |
293 | return -EFAULT; | |
294 | ||
295 | if (fd == -1) { | |
296 | if (*ctx) | |
297 | eventfd_ctx_put(*ctx); | |
298 | *ctx = NULL; | |
299 | } else if (fd >= 0) { | |
300 | struct eventfd_ctx *efdctx; | |
301 | ||
302 | efdctx = eventfd_ctx_fdget(fd); | |
303 | if (IS_ERR(efdctx)) | |
304 | return PTR_ERR(efdctx); | |
305 | ||
306 | if (*ctx) | |
307 | eventfd_ctx_put(*ctx); | |
308 | ||
309 | *ctx = efdctx; | |
310 | } else | |
311 | return -EINVAL; | |
312 | ||
313 | return 0; | |
314 | } | |
315 | default: | |
316 | return -EINVAL; | |
317 | } | |
318 | } | |
319 | ||
e01bcdd6 DJS |
320 | static ssize_t vfio_ccw_mdev_ioctl(struct mdev_device *mdev, |
321 | unsigned int cmd, | |
322 | unsigned long arg) | |
323 | { | |
324 | int ret = 0; | |
325 | unsigned long minsz; | |
326 | ||
327 | switch (cmd) { | |
328 | case VFIO_DEVICE_GET_INFO: | |
329 | { | |
330 | struct vfio_device_info info; | |
331 | ||
332 | minsz = offsetofend(struct vfio_device_info, num_irqs); | |
333 | ||
334 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
335 | return -EFAULT; | |
336 | ||
337 | if (info.argsz < minsz) | |
338 | return -EINVAL; | |
339 | ||
340 | ret = vfio_ccw_mdev_get_device_info(&info); | |
341 | if (ret) | |
342 | return ret; | |
343 | ||
344 | return copy_to_user((void __user *)arg, &info, minsz); | |
345 | } | |
346 | case VFIO_DEVICE_GET_REGION_INFO: | |
347 | { | |
348 | struct vfio_region_info info; | |
349 | u16 cap_type_id = 0; | |
350 | void *cap_type = NULL; | |
351 | ||
352 | minsz = offsetofend(struct vfio_region_info, offset); | |
353 | ||
354 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
355 | return -EFAULT; | |
356 | ||
357 | if (info.argsz < minsz) | |
358 | return -EINVAL; | |
359 | ||
360 | ret = vfio_ccw_mdev_get_region_info(&info, &cap_type_id, | |
361 | &cap_type); | |
362 | if (ret) | |
363 | return ret; | |
364 | ||
365 | return copy_to_user((void __user *)arg, &info, minsz); | |
366 | } | |
120e214e DJS |
367 | case VFIO_DEVICE_GET_IRQ_INFO: |
368 | { | |
369 | struct vfio_irq_info info; | |
370 | ||
371 | minsz = offsetofend(struct vfio_irq_info, count); | |
372 | ||
373 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
374 | return -EFAULT; | |
375 | ||
376 | if (info.argsz < minsz || info.index >= VFIO_CCW_NUM_IRQS) | |
377 | return -EINVAL; | |
378 | ||
379 | ret = vfio_ccw_mdev_get_irq_info(&info); | |
380 | if (ret) | |
381 | return ret; | |
382 | ||
383 | if (info.count == -1) | |
384 | return -EINVAL; | |
385 | ||
386 | return copy_to_user((void __user *)arg, &info, minsz); | |
387 | } | |
388 | case VFIO_DEVICE_SET_IRQS: | |
389 | { | |
390 | struct vfio_irq_set hdr; | |
391 | size_t data_size; | |
392 | void __user *data; | |
393 | ||
394 | minsz = offsetofend(struct vfio_irq_set, count); | |
395 | ||
396 | if (copy_from_user(&hdr, (void __user *)arg, minsz)) | |
397 | return -EFAULT; | |
398 | ||
399 | ret = vfio_set_irqs_validate_and_prepare(&hdr, 1, | |
400 | VFIO_CCW_NUM_IRQS, | |
401 | &data_size); | |
402 | if (ret) | |
403 | return ret; | |
404 | ||
405 | data = (void __user *)(arg + minsz); | |
406 | return vfio_ccw_mdev_set_irqs(mdev, hdr.flags, data); | |
407 | } | |
83d1193a DJS |
408 | case VFIO_DEVICE_RESET: |
409 | return vfio_ccw_mdev_reset(mdev); | |
e01bcdd6 DJS |
410 | default: |
411 | return -ENOTTY; | |
412 | } | |
413 | } | |
414 | ||
84cd8fc4 DJS |
415 | static const struct mdev_parent_ops vfio_ccw_mdev_ops = { |
416 | .owner = THIS_MODULE, | |
417 | .supported_type_groups = mdev_type_groups, | |
418 | .create = vfio_ccw_mdev_create, | |
419 | .remove = vfio_ccw_mdev_remove, | |
420 | .open = vfio_ccw_mdev_open, | |
421 | .release = vfio_ccw_mdev_release, | |
060d2b5a DJS |
422 | .read = vfio_ccw_mdev_read, |
423 | .write = vfio_ccw_mdev_write, | |
e01bcdd6 | 424 | .ioctl = vfio_ccw_mdev_ioctl, |
84cd8fc4 DJS |
425 | }; |
426 | ||
427 | int vfio_ccw_mdev_reg(struct subchannel *sch) | |
428 | { | |
429 | return mdev_register_device(&sch->dev, &vfio_ccw_mdev_ops); | |
430 | } | |
431 | ||
432 | void vfio_ccw_mdev_unreg(struct subchannel *sch) | |
433 | { | |
434 | mdev_unregister_device(&sch->dev); | |
435 | } |