1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
|
// SPDX-License-Identifier: GPL-2.0
/*
* vfio-ISM driver for s390
*
* Copyright IBM Corp.
*/
#include <linux/slab.h>
#include "../vfio_pci_priv.h"
#define ISM_VFIO_PCI_OFFSET_SHIFT 48
#define ISM_VFIO_PCI_OFFSET_TO_INDEX(off) ((off) >> ISM_VFIO_PCI_OFFSET_SHIFT)
#define ISM_VFIO_PCI_INDEX_TO_OFFSET(index) ((u64)(index) << ISM_VFIO_PCI_OFFSET_SHIFT)
#define ISM_VFIO_PCI_OFFSET_MASK (((u64)(1) << ISM_VFIO_PCI_OFFSET_SHIFT) - 1)
/*
* Use __zpci_load() to bypass automatic use of
* PCI MIO instructions which are not supported on ISM devices
*/
#define ISM_READ(size) \
static int ism_read##size(struct zpci_dev *zdev, int bar, \
size_t *filled, char __user *buf, \
loff_t off) \
{ \
u64 req, tmp; \
u##size val; \
int ret; \
\
req = ZPCI_CREATE_REQ(READ_ONCE(zdev->fh), bar, sizeof(val)); \
ret = __zpci_load(&tmp, req, off); \
if (ret) \
return ret; \
val = (u##size)tmp; \
if (copy_to_user(buf, &val, sizeof(val))) \
return -EFAULT; \
*filled = sizeof(val); \
return 0; \
}
ISM_READ(64);
ISM_READ(32);
ISM_READ(16);
ISM_READ(8);
struct ism_vfio_pci_core_device {
struct vfio_pci_core_device core_device;
struct kmem_cache *store_block_cache;
};
static int ism_vfio_pci_open_device(struct vfio_device *core_vdev)
{
struct ism_vfio_pci_core_device *ivpcd;
struct vfio_pci_core_device *vdev;
int ret;
ivpcd = container_of(core_vdev, struct ism_vfio_pci_core_device,
core_device.vdev);
vdev = &ivpcd->core_device;
ret = vfio_pci_core_enable(vdev);
if (ret)
return ret;
vfio_pci_core_finish_enable(vdev);
return 0;
}
/*
* ism_vfio_pci_do_io_r()
*
* On s390, kernel primitives such as ioread() and iowrite() are switched over
* from function-handle-based PCI load/stores instructions to PCI memory-I/O (MIO)
* loads/stores when these are available and not explicitly disabled. Since these
* instructions cannot be used with ISM devices, ensure that classic
* function-handle-based PCI instructions are used instead.
*/
static ssize_t ism_vfio_pci_do_io_r(struct vfio_pci_core_device *vdev,
char __user *buf, loff_t off, size_t count,
int bar)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
ssize_t done = 0;
int ret;
while (count) {
size_t filled;
if (count >= 8 && IS_ALIGNED(off, 8)) {
ret = ism_read64(zdev, bar, &filled, buf, off);
if (ret)
return ret;
} else if (count >= 4 && IS_ALIGNED(off, 4)) {
ret = ism_read32(zdev, bar, &filled, buf, off);
if (ret)
return ret;
} else if (count >= 2 && IS_ALIGNED(off, 2)) {
ret = ism_read16(zdev, bar, &filled, buf, off);
if (ret)
return ret;
} else {
ret = ism_read8(zdev, bar, &filled, buf, off);
if (ret)
return ret;
}
count -= filled;
done += filled;
off += filled;
buf += filled;
}
return done;
}
/*
* ism_vfio_pci_do_io_w()
*
* Ensure that the PCI store block (PCISTB) instruction is used as required by the
* ISM device. The ISM device also uses a 256 TiB BAR 0 for write operations,
* which requires a 48bit region address space (ISM_VFIO_PCI_OFFSET_SHIFT).
*/
static ssize_t ism_vfio_pci_do_io_w(struct vfio_pci_core_device *vdev,
char __user *buf, loff_t off, size_t count,
int bar)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
struct ism_vfio_pci_core_device *ivpcd;
ssize_t ret;
void *data;
u64 req;
if (count > zdev->maxstbl)
return -EINVAL;
if (((off % PAGE_SIZE) + count) > PAGE_SIZE)
return -EINVAL;
ivpcd = container_of(vdev, struct ism_vfio_pci_core_device,
core_device);
data = kmem_cache_alloc(ivpcd->store_block_cache, GFP_KERNEL);
if (!data)
return -ENOMEM;
if (copy_from_user(data, buf, count)) {
ret = -EFAULT;
goto out_free;
}
req = ZPCI_CREATE_REQ(READ_ONCE(zdev->fh), bar, count);
ret = __zpci_store_block(data, req, off);
if (ret)
goto out_free;
ret = count;
out_free:
kmem_cache_free(ivpcd->store_block_cache, data);
return ret;
}
static ssize_t ism_vfio_pci_bar_rw(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count, loff_t *ppos,
bool iswrite)
{
int bar = ISM_VFIO_PCI_OFFSET_TO_INDEX(*ppos);
loff_t pos = *ppos & ISM_VFIO_PCI_OFFSET_MASK;
resource_size_t end;
ssize_t done = 0;
if (pci_resource_start(vdev->pdev, bar))
end = pci_resource_len(vdev->pdev, bar);
else
return -EINVAL;
if (pos >= end)
return -EINVAL;
count = min(count, (size_t)(end - pos));
if (iswrite)
done = ism_vfio_pci_do_io_w(vdev, buf, pos, count, bar);
else
done = ism_vfio_pci_do_io_r(vdev, buf, pos, count, bar);
if (done >= 0)
*ppos += done;
return done;
}
static ssize_t ism_vfio_pci_config_rw(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count,
loff_t *ppos, bool iswrite)
{
loff_t pos = *ppos;
size_t done = 0;
int ret = 0;
pos &= ISM_VFIO_PCI_OFFSET_MASK;
while (count) {
/*
* zPCI must not use MIO instructions for config space access,
* so we can use common code path here.
*/
ret = vfio_pci_config_rw_single(vdev, buf, count, &pos, iswrite);
if (ret < 0)
return ret;
count -= ret;
done += ret;
buf += ret;
pos += ret;
}
*ppos += done;
return done;
}
static ssize_t ism_vfio_pci_rw(struct vfio_device *core_vdev, char __user *buf,
size_t count, loff_t *ppos, bool iswrite)
{
unsigned int index = ISM_VFIO_PCI_OFFSET_TO_INDEX(*ppos);
struct vfio_pci_core_device *vdev;
int ret;
vdev = container_of(core_vdev, struct vfio_pci_core_device, vdev);
if (!count)
return 0;
switch (index) {
case VFIO_PCI_CONFIG_REGION_INDEX:
ret = ism_vfio_pci_config_rw(vdev, buf, count, ppos, iswrite);
break;
case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX:
ret = ism_vfio_pci_bar_rw(vdev, buf, count, ppos, iswrite);
break;
default:
return -EINVAL;
}
return ret;
}
static ssize_t ism_vfio_pci_read(struct vfio_device *core_vdev,
char __user *buf, size_t count, loff_t *ppos)
{
return ism_vfio_pci_rw(core_vdev, buf, count, ppos, false);
}
static ssize_t ism_vfio_pci_write(struct vfio_device *core_vdev,
const char __user *buf, size_t count,
loff_t *ppos)
{
return ism_vfio_pci_rw(core_vdev, (char __user *)buf, count, ppos,
true);
}
static int ism_vfio_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
struct vfio_region_info *info,
struct vfio_info_cap *caps)
{
struct vfio_pci_core_device *vdev =
container_of(core_vdev, struct vfio_pci_core_device, vdev);
struct pci_dev *pdev = vdev->pdev;
switch (info->index) {
case VFIO_PCI_CONFIG_REGION_INDEX:
info->offset = ISM_VFIO_PCI_INDEX_TO_OFFSET(info->index);
info->size = pdev->cfg_size;
info->flags = VFIO_REGION_INFO_FLAG_READ |
VFIO_REGION_INFO_FLAG_WRITE;
break;
case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX:
info->offset = ISM_VFIO_PCI_INDEX_TO_OFFSET(info->index);
info->size = pci_resource_len(pdev, info->index);
if (!info->size) {
info->flags = 0;
break;
}
info->flags = VFIO_REGION_INFO_FLAG_READ |
VFIO_REGION_INFO_FLAG_WRITE;
break;
default:
info->offset = 0;
info->size = 0;
info->flags = 0;
return -EINVAL;
}
return 0;
}
static int ism_vfio_pci_init_dev(struct vfio_device *core_vdev)
{
struct zpci_dev *zdev = to_zpci(to_pci_dev(core_vdev->dev));
struct ism_vfio_pci_core_device *ivpcd;
char cache_name[20];
int ret;
ivpcd = container_of(core_vdev, struct ism_vfio_pci_core_device,
core_device.vdev);
snprintf(cache_name, sizeof(cache_name), "ism_sb_fid_%08x", zdev->fid);
ivpcd->store_block_cache =
kmem_cache_create(cache_name, zdev->maxstbl,
(&(struct kmem_cache_args){
.align = PAGE_SIZE,
.useroffset = 0,
.usersize = zdev->maxstbl,
}),
(SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT));
if (!ivpcd->store_block_cache)
return -ENOMEM;
ret = vfio_pci_core_init_dev(core_vdev);
if (ret)
kmem_cache_destroy(ivpcd->store_block_cache);
return ret;
}
static void ism_vfio_pci_release_dev(struct vfio_device *core_vdev)
{
struct ism_vfio_pci_core_device *ivpcd = container_of(
core_vdev, struct ism_vfio_pci_core_device, core_device.vdev);
kmem_cache_destroy(ivpcd->store_block_cache);
vfio_pci_core_release_dev(core_vdev);
}
static const struct vfio_device_ops ism_pci_ops = {
.name = "ism-vfio-pci",
.init = ism_vfio_pci_init_dev,
.release = ism_vfio_pci_release_dev,
.open_device = ism_vfio_pci_open_device,
.close_device = vfio_pci_core_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = ism_vfio_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = ism_vfio_pci_read,
.write = ism_vfio_pci_write,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
static int ism_vfio_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct ism_vfio_pci_core_device *ivpcd;
int ret;
ivpcd = vfio_alloc_device(ism_vfio_pci_core_device, core_device.vdev,
&pdev->dev, &ism_pci_ops);
if (IS_ERR(ivpcd))
return PTR_ERR(ivpcd);
dev_set_drvdata(&pdev->dev, &ivpcd->core_device);
ret = vfio_pci_core_register_device(&ivpcd->core_device);
if (ret)
vfio_put_device(&ivpcd->core_device.vdev);
return ret;
}
static void ism_vfio_pci_remove(struct pci_dev *pdev)
{
struct vfio_pci_core_device *core_device;
struct ism_vfio_pci_core_device *ivpcd;
core_device = dev_get_drvdata(&pdev->dev);
ivpcd = container_of(core_device, struct ism_vfio_pci_core_device,
core_device);
vfio_pci_core_unregister_device(&ivpcd->core_device);
vfio_put_device(&ivpcd->core_device.vdev);
}
static const struct pci_device_id ism_device_table[] = {
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_IBM,
PCI_DEVICE_ID_IBM_ISM) },
{}
};
MODULE_DEVICE_TABLE(pci, ism_device_table);
static struct pci_driver ism_vfio_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = ism_device_table,
.probe = ism_vfio_pci_probe,
.remove = ism_vfio_pci_remove,
.err_handler = &vfio_pci_core_err_handlers,
.driver_managed_dma = true,
};
module_pci_driver(ism_vfio_pci_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("vfio-pci variant driver for the IBM Internal Shared Memory (ISM) device");
MODULE_AUTHOR("IBM Corporation");
|