]>
Commit | Line | Data |
---|---|---|
0ea2730b EA |
1 | /* |
2 | * vfio based device assignment support - platform devices | |
3 | * | |
4 | * Copyright Linaro Limited, 2014 | |
5 | * | |
6 | * Authors: | |
7 | * Kim Phillips <kim.phillips@linaro.org> | |
8 | * Eric Auger <eric.auger@linaro.org> | |
9 | * | |
10 | * This work is licensed under the terms of the GNU GPL, version 2. See | |
11 | * the COPYING file in the top-level directory. | |
12 | * | |
13 | * Based on vfio based PCI device assignment support: | |
14 | * Copyright Red Hat, Inc. 2012 | |
15 | */ | |
16 | ||
17 | #include <linux/vfio.h> | |
18 | #include <sys/ioctl.h> | |
19 | ||
20 | #include "hw/vfio/vfio-platform.h" | |
21 | #include "qemu/error-report.h" | |
22 | #include "qemu/range.h" | |
23 | #include "sysemu/sysemu.h" | |
24 | #include "exec/memory.h" | |
25 | #include "hw/sysbus.h" | |
26 | #include "trace.h" | |
27 | #include "hw/platform-bus.h" | |
28 | ||
29 | /* VFIO skeleton */ | |
30 | ||
31 | static void vfio_platform_compute_needs_reset(VFIODevice *vbasedev) | |
32 | { | |
33 | vbasedev->needs_reset = true; | |
34 | } | |
35 | ||
36 | /* not implemented yet */ | |
37 | static int vfio_platform_hot_reset_multi(VFIODevice *vbasedev) | |
38 | { | |
39 | return -1; | |
40 | } | |
41 | ||
42 | /** | |
43 | * vfio_populate_device - Allocate and populate MMIO region | |
44 | * structs according to driver returned information | |
45 | * @vbasedev: the VFIO device handle | |
46 | * | |
47 | */ | |
48 | static int vfio_populate_device(VFIODevice *vbasedev) | |
49 | { | |
50 | int i, ret = -1; | |
51 | VFIOPlatformDevice *vdev = | |
52 | container_of(vbasedev, VFIOPlatformDevice, vbasedev); | |
53 | ||
54 | if (!(vbasedev->flags & VFIO_DEVICE_FLAGS_PLATFORM)) { | |
55 | error_report("vfio: Um, this isn't a platform device"); | |
56 | return ret; | |
57 | } | |
58 | ||
59 | vdev->regions = g_malloc0_n(vbasedev->num_regions, | |
60 | sizeof(VFIORegion *)); | |
61 | ||
62 | for (i = 0; i < vbasedev->num_regions; i++) { | |
63 | struct vfio_region_info reg_info = { .argsz = sizeof(reg_info) }; | |
64 | VFIORegion *ptr; | |
65 | ||
66 | vdev->regions[i] = g_malloc0(sizeof(VFIORegion)); | |
67 | ptr = vdev->regions[i]; | |
68 | reg_info.index = i; | |
69 | ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_REGION_INFO, ®_info); | |
70 | if (ret) { | |
71 | error_report("vfio: Error getting region %d info: %m", i); | |
72 | goto reg_error; | |
73 | } | |
74 | ptr->flags = reg_info.flags; | |
75 | ptr->size = reg_info.size; | |
76 | ptr->fd_offset = reg_info.offset; | |
77 | ptr->nr = i; | |
78 | ptr->vbasedev = vbasedev; | |
79 | ||
80 | trace_vfio_platform_populate_regions(ptr->nr, | |
81 | (unsigned long)ptr->flags, | |
82 | (unsigned long)ptr->size, | |
83 | ptr->vbasedev->fd, | |
84 | (unsigned long)ptr->fd_offset); | |
85 | } | |
86 | ||
87 | return 0; | |
88 | reg_error: | |
89 | for (i = 0; i < vbasedev->num_regions; i++) { | |
90 | g_free(vdev->regions[i]); | |
91 | } | |
92 | g_free(vdev->regions); | |
93 | return ret; | |
94 | } | |
95 | ||
96 | /* specialized functions for VFIO Platform devices */ | |
97 | static VFIODeviceOps vfio_platform_ops = { | |
98 | .vfio_compute_needs_reset = vfio_platform_compute_needs_reset, | |
99 | .vfio_hot_reset_multi = vfio_platform_hot_reset_multi, | |
100 | }; | |
101 | ||
102 | /** | |
103 | * vfio_base_device_init - perform preliminary VFIO setup | |
104 | * @vbasedev: the VFIO device handle | |
105 | * | |
106 | * Implement the VFIO command sequence that allows to discover | |
107 | * assigned device resources: group extraction, device | |
108 | * fd retrieval, resource query. | |
109 | * Precondition: the device name must be initialized | |
110 | */ | |
111 | static int vfio_base_device_init(VFIODevice *vbasedev) | |
112 | { | |
113 | VFIOGroup *group; | |
114 | VFIODevice *vbasedev_iter; | |
115 | char path[PATH_MAX], iommu_group_path[PATH_MAX], *group_name; | |
116 | ssize_t len; | |
117 | struct stat st; | |
118 | int groupid; | |
119 | int ret; | |
120 | ||
121 | /* name must be set prior to the call */ | |
122 | if (!vbasedev->name || strchr(vbasedev->name, '/')) { | |
123 | return -EINVAL; | |
124 | } | |
125 | ||
126 | /* Check that the host device exists */ | |
127 | g_snprintf(path, sizeof(path), "/sys/bus/platform/devices/%s/", | |
128 | vbasedev->name); | |
129 | ||
130 | if (stat(path, &st) < 0) { | |
131 | error_report("vfio: error: no such host device: %s", path); | |
132 | return -errno; | |
133 | } | |
134 | ||
135 | g_strlcat(path, "iommu_group", sizeof(path)); | |
136 | len = readlink(path, iommu_group_path, sizeof(iommu_group_path)); | |
137 | if (len < 0 || len >= sizeof(iommu_group_path)) { | |
138 | error_report("vfio: error no iommu_group for device"); | |
139 | return len < 0 ? -errno : -ENAMETOOLONG; | |
140 | } | |
141 | ||
142 | iommu_group_path[len] = 0; | |
143 | group_name = basename(iommu_group_path); | |
144 | ||
145 | if (sscanf(group_name, "%d", &groupid) != 1) { | |
146 | error_report("vfio: error reading %s: %m", path); | |
147 | return -errno; | |
148 | } | |
149 | ||
150 | trace_vfio_platform_base_device_init(vbasedev->name, groupid); | |
151 | ||
152 | group = vfio_get_group(groupid, &address_space_memory); | |
153 | if (!group) { | |
154 | error_report("vfio: failed to get group %d", groupid); | |
155 | return -ENOENT; | |
156 | } | |
157 | ||
158 | g_snprintf(path, sizeof(path), "%s", vbasedev->name); | |
159 | ||
160 | QLIST_FOREACH(vbasedev_iter, &group->device_list, next) { | |
161 | if (strcmp(vbasedev_iter->name, vbasedev->name) == 0) { | |
162 | error_report("vfio: error: device %s is already attached", path); | |
163 | vfio_put_group(group); | |
164 | return -EBUSY; | |
165 | } | |
166 | } | |
167 | ret = vfio_get_device(group, path, vbasedev); | |
168 | if (ret) { | |
169 | error_report("vfio: failed to get device %s", path); | |
170 | vfio_put_group(group); | |
171 | return ret; | |
172 | } | |
173 | ||
174 | ret = vfio_populate_device(vbasedev); | |
175 | if (ret) { | |
176 | error_report("vfio: failed to populate device %s", path); | |
177 | vfio_put_group(group); | |
178 | } | |
179 | ||
180 | return ret; | |
181 | } | |
182 | ||
183 | /** | |
184 | * vfio_map_region - initialize the 2 memory regions for a given | |
185 | * MMIO region index | |
186 | * @vdev: the VFIO platform device handle | |
187 | * @nr: the index of the region | |
188 | * | |
189 | * Init the top memory region and the mmapped memory region beneath | |
190 | * VFIOPlatformDevice is used since VFIODevice is not a QOM Object | |
191 | * and could not be passed to memory region functions | |
192 | */ | |
193 | static void vfio_map_region(VFIOPlatformDevice *vdev, int nr) | |
194 | { | |
195 | VFIORegion *region = vdev->regions[nr]; | |
196 | uint64_t size = region->size; | |
197 | char name[64]; | |
198 | ||
199 | if (!size) { | |
200 | return; | |
201 | } | |
202 | ||
203 | g_snprintf(name, sizeof(name), "VFIO %s region %d", | |
204 | vdev->vbasedev.name, nr); | |
205 | ||
206 | /* A "slow" read/write mapping underlies all regions */ | |
207 | memory_region_init_io(®ion->mem, OBJECT(vdev), &vfio_region_ops, | |
208 | region, name, size); | |
209 | ||
210 | g_strlcat(name, " mmap", sizeof(name)); | |
211 | ||
212 | if (vfio_mmap_region(OBJECT(vdev), region, ®ion->mem, | |
213 | ®ion->mmap_mem, ®ion->mmap, size, 0, name)) { | |
214 | error_report("%s unsupported. Performance may be slow", name); | |
215 | } | |
216 | } | |
217 | ||
218 | /** | |
219 | * vfio_platform_realize - the device realize function | |
220 | * @dev: device state pointer | |
221 | * @errp: error | |
222 | * | |
223 | * initialize the device, its memory regions and IRQ structures | |
224 | * IRQ are started separately | |
225 | */ | |
226 | static void vfio_platform_realize(DeviceState *dev, Error **errp) | |
227 | { | |
228 | VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev); | |
229 | SysBusDevice *sbdev = SYS_BUS_DEVICE(dev); | |
230 | VFIODevice *vbasedev = &vdev->vbasedev; | |
231 | int i, ret; | |
232 | ||
233 | vbasedev->type = VFIO_DEVICE_TYPE_PLATFORM; | |
234 | vbasedev->ops = &vfio_platform_ops; | |
235 | ||
236 | trace_vfio_platform_realize(vbasedev->name, vdev->compat); | |
237 | ||
238 | ret = vfio_base_device_init(vbasedev); | |
239 | if (ret) { | |
240 | error_setg(errp, "vfio: vfio_base_device_init failed for %s", | |
241 | vbasedev->name); | |
242 | return; | |
243 | } | |
244 | ||
245 | for (i = 0; i < vbasedev->num_regions; i++) { | |
246 | vfio_map_region(vdev, i); | |
247 | sysbus_init_mmio(sbdev, &vdev->regions[i]->mem); | |
248 | } | |
249 | } | |
250 | ||
251 | static const VMStateDescription vfio_platform_vmstate = { | |
252 | .name = TYPE_VFIO_PLATFORM, | |
253 | .unmigratable = 1, | |
254 | }; | |
255 | ||
256 | static Property vfio_platform_dev_properties[] = { | |
257 | DEFINE_PROP_STRING("host", VFIOPlatformDevice, vbasedev.name), | |
258 | DEFINE_PROP_BOOL("x-mmap", VFIOPlatformDevice, vbasedev.allow_mmap, true), | |
259 | DEFINE_PROP_END_OF_LIST(), | |
260 | }; | |
261 | ||
262 | static void vfio_platform_class_init(ObjectClass *klass, void *data) | |
263 | { | |
264 | DeviceClass *dc = DEVICE_CLASS(klass); | |
265 | ||
266 | dc->realize = vfio_platform_realize; | |
267 | dc->props = vfio_platform_dev_properties; | |
268 | dc->vmsd = &vfio_platform_vmstate; | |
269 | dc->desc = "VFIO-based platform device assignment"; | |
270 | set_bit(DEVICE_CATEGORY_MISC, dc->categories); | |
271 | } | |
272 | ||
273 | static const TypeInfo vfio_platform_dev_info = { | |
274 | .name = TYPE_VFIO_PLATFORM, | |
275 | .parent = TYPE_SYS_BUS_DEVICE, | |
276 | .instance_size = sizeof(VFIOPlatformDevice), | |
277 | .class_init = vfio_platform_class_init, | |
278 | .class_size = sizeof(VFIOPlatformDeviceClass), | |
279 | .abstract = true, | |
280 | }; | |
281 | ||
282 | static void register_vfio_platform_dev_type(void) | |
283 | { | |
284 | type_register_static(&vfio_platform_dev_info); | |
285 | } | |
286 | ||
287 | type_init(register_vfio_platform_dev_type) |