diff options
-rw-r--r-- | drivers/iommu/dmar.c | 207 | ||||
-rw-r--r-- | drivers/iommu/intel-iommu.c | 54 | ||||
-rw-r--r-- | include/linux/dmar.h | 24 |
3 files changed, 283 insertions, 2 deletions
diff --git a/drivers/iommu/dmar.c b/drivers/iommu/dmar.c index 6e4d851991f1..bf6bfd1f69aa 100644 --- a/drivers/iommu/dmar.c +++ b/drivers/iommu/dmar.c | |||
@@ -194,6 +194,209 @@ void dmar_free_dev_scope(struct pci_dev __rcu ***devices, int *cnt) | |||
194 | *cnt = 0; | 194 | *cnt = 0; |
195 | } | 195 | } |
196 | 196 | ||
197 | /* Optimize out kzalloc()/kfree() for normal cases */ | ||
198 | static char dmar_pci_notify_info_buf[64]; | ||
199 | |||
200 | static struct dmar_pci_notify_info * | ||
201 | dmar_alloc_pci_notify_info(struct pci_dev *dev, unsigned long event) | ||
202 | { | ||
203 | int level = 0; | ||
204 | size_t size; | ||
205 | struct pci_dev *tmp; | ||
206 | struct dmar_pci_notify_info *info; | ||
207 | |||
208 | BUG_ON(dev->is_virtfn); | ||
209 | |||
210 | /* Only generate path[] for device addition event */ | ||
211 | if (event == BUS_NOTIFY_ADD_DEVICE) | ||
212 | for (tmp = dev; tmp; tmp = tmp->bus->self) | ||
213 | level++; | ||
214 | |||
215 | size = sizeof(*info) + level * sizeof(struct acpi_dmar_pci_path); | ||
216 | if (size <= sizeof(dmar_pci_notify_info_buf)) { | ||
217 | info = (struct dmar_pci_notify_info *)dmar_pci_notify_info_buf; | ||
218 | } else { | ||
219 | info = kzalloc(size, GFP_KERNEL); | ||
220 | if (!info) { | ||
221 | pr_warn("Out of memory when allocating notify_info " | ||
222 | "for %s.\n", pci_name(dev)); | ||
223 | return NULL; | ||
224 | } | ||
225 | } | ||
226 | |||
227 | info->event = event; | ||
228 | info->dev = dev; | ||
229 | info->seg = pci_domain_nr(dev->bus); | ||
230 | info->level = level; | ||
231 | if (event == BUS_NOTIFY_ADD_DEVICE) { | ||
232 | for (tmp = dev, level--; tmp; tmp = tmp->bus->self) { | ||
233 | info->path[level].device = PCI_SLOT(tmp->devfn); | ||
234 | info->path[level].function = PCI_FUNC(tmp->devfn); | ||
235 | if (pci_is_root_bus(tmp->bus)) | ||
236 | info->bus = tmp->bus->number; | ||
237 | } | ||
238 | } | ||
239 | |||
240 | return info; | ||
241 | } | ||
242 | |||
243 | static inline void dmar_free_pci_notify_info(struct dmar_pci_notify_info *info) | ||
244 | { | ||
245 | if ((void *)info != dmar_pci_notify_info_buf) | ||
246 | kfree(info); | ||
247 | } | ||
248 | |||
249 | static bool dmar_match_pci_path(struct dmar_pci_notify_info *info, int bus, | ||
250 | struct acpi_dmar_pci_path *path, int count) | ||
251 | { | ||
252 | int i; | ||
253 | |||
254 | if (info->bus != bus) | ||
255 | return false; | ||
256 | if (info->level != count) | ||
257 | return false; | ||
258 | |||
259 | for (i = 0; i < count; i++) { | ||
260 | if (path[i].device != info->path[i].device || | ||
261 | path[i].function != info->path[i].function) | ||
262 | return false; | ||
263 | } | ||
264 | |||
265 | return true; | ||
266 | } | ||
267 | |||
268 | /* Return: > 0 if match found, 0 if no match found, < 0 if error happens */ | ||
269 | int dmar_insert_dev_scope(struct dmar_pci_notify_info *info, | ||
270 | void *start, void*end, u16 segment, | ||
271 | struct pci_dev __rcu **devices, int devices_cnt) | ||
272 | { | ||
273 | int i, level; | ||
274 | struct pci_dev *tmp, *dev = info->dev; | ||
275 | struct acpi_dmar_device_scope *scope; | ||
276 | struct acpi_dmar_pci_path *path; | ||
277 | |||
278 | if (segment != info->seg) | ||
279 | return 0; | ||
280 | |||
281 | for (; start < end; start += scope->length) { | ||
282 | scope = start; | ||
283 | if (scope->entry_type != ACPI_DMAR_SCOPE_TYPE_ENDPOINT && | ||
284 | scope->entry_type != ACPI_DMAR_SCOPE_TYPE_BRIDGE) | ||
285 | continue; | ||
286 | |||
287 | path = (struct acpi_dmar_pci_path *)(scope + 1); | ||
288 | level = (scope->length - sizeof(*scope)) / sizeof(*path); | ||
289 | if (!dmar_match_pci_path(info, scope->bus, path, level)) | ||
290 | continue; | ||
291 | |||
292 | if ((scope->entry_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT) ^ | ||
293 | (dev->hdr_type == PCI_HEADER_TYPE_NORMAL)) { | ||
294 | pr_warn("Device scope type does not match for %s\n", | ||
295 | pci_name(dev)); | ||
296 | return -EINVAL; | ||
297 | } | ||
298 | |||
299 | for_each_dev_scope(devices, devices_cnt, i, tmp) | ||
300 | if (tmp == NULL) { | ||
301 | rcu_assign_pointer(devices[i], | ||
302 | pci_dev_get(dev)); | ||
303 | return 1; | ||
304 | } | ||
305 | BUG_ON(i >= devices_cnt); | ||
306 | } | ||
307 | |||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | int dmar_remove_dev_scope(struct dmar_pci_notify_info *info, u16 segment, | ||
312 | struct pci_dev __rcu **devices, int count) | ||
313 | { | ||
314 | int index; | ||
315 | struct pci_dev *tmp; | ||
316 | |||
317 | if (info->seg != segment) | ||
318 | return 0; | ||
319 | |||
320 | for_each_active_dev_scope(devices, count, index, tmp) | ||
321 | if (tmp == info->dev) { | ||
322 | rcu_assign_pointer(devices[index], NULL); | ||
323 | synchronize_rcu(); | ||
324 | pci_dev_put(tmp); | ||
325 | return 1; | ||
326 | } | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | static int dmar_pci_bus_add_dev(struct dmar_pci_notify_info *info) | ||
332 | { | ||
333 | int ret = 0; | ||
334 | struct dmar_drhd_unit *dmaru; | ||
335 | struct acpi_dmar_hardware_unit *drhd; | ||
336 | |||
337 | for_each_drhd_unit(dmaru) { | ||
338 | if (dmaru->include_all) | ||
339 | continue; | ||
340 | |||
341 | drhd = container_of(dmaru->hdr, | ||
342 | struct acpi_dmar_hardware_unit, header); | ||
343 | ret = dmar_insert_dev_scope(info, (void *)(drhd + 1), | ||
344 | ((void *)drhd) + drhd->header.length, | ||
345 | dmaru->segment, | ||
346 | dmaru->devices, dmaru->devices_cnt); | ||
347 | if (ret != 0) | ||
348 | break; | ||
349 | } | ||
350 | if (ret >= 0) | ||
351 | ret = dmar_iommu_notify_scope_dev(info); | ||
352 | |||
353 | return ret; | ||
354 | } | ||
355 | |||
356 | static void dmar_pci_bus_del_dev(struct dmar_pci_notify_info *info) | ||
357 | { | ||
358 | struct dmar_drhd_unit *dmaru; | ||
359 | |||
360 | for_each_drhd_unit(dmaru) | ||
361 | if (dmar_remove_dev_scope(info, dmaru->segment, | ||
362 | dmaru->devices, dmaru->devices_cnt)) | ||
363 | break; | ||
364 | dmar_iommu_notify_scope_dev(info); | ||
365 | } | ||
366 | |||
367 | static int dmar_pci_bus_notifier(struct notifier_block *nb, | ||
368 | unsigned long action, void *data) | ||
369 | { | ||
370 | struct pci_dev *pdev = to_pci_dev(data); | ||
371 | struct dmar_pci_notify_info *info; | ||
372 | |||
373 | /* Only care about add/remove events for physical functions */ | ||
374 | if (pdev->is_virtfn) | ||
375 | return NOTIFY_DONE; | ||
376 | if (action != BUS_NOTIFY_ADD_DEVICE && action != BUS_NOTIFY_DEL_DEVICE) | ||
377 | return NOTIFY_DONE; | ||
378 | |||
379 | info = dmar_alloc_pci_notify_info(pdev, action); | ||
380 | if (!info) | ||
381 | return NOTIFY_DONE; | ||
382 | |||
383 | down_write(&dmar_global_lock); | ||
384 | if (action == BUS_NOTIFY_ADD_DEVICE) | ||
385 | dmar_pci_bus_add_dev(info); | ||
386 | else if (action == BUS_NOTIFY_DEL_DEVICE) | ||
387 | dmar_pci_bus_del_dev(info); | ||
388 | up_write(&dmar_global_lock); | ||
389 | |||
390 | dmar_free_pci_notify_info(info); | ||
391 | |||
392 | return NOTIFY_OK; | ||
393 | } | ||
394 | |||
395 | static struct notifier_block dmar_pci_bus_nb = { | ||
396 | .notifier_call = dmar_pci_bus_notifier, | ||
397 | .priority = INT_MIN, | ||
398 | }; | ||
399 | |||
197 | /** | 400 | /** |
198 | * dmar_parse_one_drhd - parses exactly one DMA remapping hardware definition | 401 | * dmar_parse_one_drhd - parses exactly one DMA remapping hardware definition |
199 | * structure which uniquely represent one DMA remapping hardware unit | 402 | * structure which uniquely represent one DMA remapping hardware unit |
@@ -482,6 +685,8 @@ int __init dmar_dev_scope_init(void) | |||
482 | if (ret) | 685 | if (ret) |
483 | goto fail; | 686 | goto fail; |
484 | 687 | ||
688 | bus_register_notifier(&pci_bus_type, &dmar_pci_bus_nb); | ||
689 | |||
485 | dmar_dev_scope_initialized = 1; | 690 | dmar_dev_scope_initialized = 1; |
486 | return 0; | 691 | return 0; |
487 | 692 | ||
@@ -1412,6 +1617,8 @@ static int __init dmar_free_unused_resources(void) | |||
1412 | if (irq_remapping_enabled || intel_iommu_enabled) | 1617 | if (irq_remapping_enabled || intel_iommu_enabled) |
1413 | return 0; | 1618 | return 0; |
1414 | 1619 | ||
1620 | bus_unregister_notifier(&pci_bus_type, &dmar_pci_bus_nb); | ||
1621 | |||
1415 | down_write(&dmar_global_lock); | 1622 | down_write(&dmar_global_lock); |
1416 | list_for_each_entry_safe(dmaru, dmaru_n, &dmar_drhd_units, list) { | 1623 | list_for_each_entry_safe(dmaru, dmaru_n, &dmar_drhd_units, list) { |
1417 | list_del(&dmaru->list); | 1624 | list_del(&dmaru->list); |
diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index e1679a6fe468..d9c0dc5a5d35 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c | |||
@@ -3625,6 +3625,60 @@ int __init dmar_parse_rmrr_atsr_dev(void) | |||
3625 | return 0; | 3625 | return 0; |
3626 | } | 3626 | } |
3627 | 3627 | ||
3628 | int dmar_iommu_notify_scope_dev(struct dmar_pci_notify_info *info) | ||
3629 | { | ||
3630 | int ret = 0; | ||
3631 | struct dmar_rmrr_unit *rmrru; | ||
3632 | struct dmar_atsr_unit *atsru; | ||
3633 | struct acpi_dmar_atsr *atsr; | ||
3634 | struct acpi_dmar_reserved_memory *rmrr; | ||
3635 | |||
3636 | if (!intel_iommu_enabled && system_state != SYSTEM_BOOTING) | ||
3637 | return 0; | ||
3638 | |||
3639 | list_for_each_entry(rmrru, &dmar_rmrr_units, list) { | ||
3640 | rmrr = container_of(rmrru->hdr, | ||
3641 | struct acpi_dmar_reserved_memory, header); | ||
3642 | if (info->event == BUS_NOTIFY_ADD_DEVICE) { | ||
3643 | ret = dmar_insert_dev_scope(info, (void *)(rmrr + 1), | ||
3644 | ((void *)rmrr) + rmrr->header.length, | ||
3645 | rmrr->segment, rmrru->devices, | ||
3646 | rmrru->devices_cnt); | ||
3647 | if (ret > 0) | ||
3648 | break; | ||
3649 | else if(ret < 0) | ||
3650 | return ret; | ||
3651 | } else if (info->event == BUS_NOTIFY_DEL_DEVICE) { | ||
3652 | if (dmar_remove_dev_scope(info, rmrr->segment, | ||
3653 | rmrru->devices, rmrru->devices_cnt)) | ||
3654 | break; | ||
3655 | } | ||
3656 | } | ||
3657 | |||
3658 | list_for_each_entry(atsru, &dmar_atsr_units, list) { | ||
3659 | if (atsru->include_all) | ||
3660 | continue; | ||
3661 | |||
3662 | atsr = container_of(atsru->hdr, struct acpi_dmar_atsr, header); | ||
3663 | if (info->event == BUS_NOTIFY_ADD_DEVICE) { | ||
3664 | ret = dmar_insert_dev_scope(info, (void *)(atsr + 1), | ||
3665 | (void *)atsr + atsr->header.length, | ||
3666 | atsr->segment, atsru->devices, | ||
3667 | atsru->devices_cnt); | ||
3668 | if (ret > 0) | ||
3669 | break; | ||
3670 | else if(ret < 0) | ||
3671 | return ret; | ||
3672 | } else if (info->event == BUS_NOTIFY_DEL_DEVICE) { | ||
3673 | if (dmar_remove_dev_scope(info, atsr->segment, | ||
3674 | atsru->devices, atsru->devices_cnt)) | ||
3675 | break; | ||
3676 | } | ||
3677 | } | ||
3678 | |||
3679 | return 0; | ||
3680 | } | ||
3681 | |||
3628 | /* | 3682 | /* |
3629 | * Here we only respond to action of unbound device from driver. | 3683 | * Here we only respond to action of unbound device from driver. |
3630 | * | 3684 | * |
diff --git a/include/linux/dmar.h b/include/linux/dmar.h index bedebab934b4..4e196430f1b2 100644 --- a/include/linux/dmar.h +++ b/include/linux/dmar.h | |||
@@ -50,6 +50,15 @@ struct dmar_drhd_unit { | |||
50 | struct intel_iommu *iommu; | 50 | struct intel_iommu *iommu; |
51 | }; | 51 | }; |
52 | 52 | ||
53 | struct dmar_pci_notify_info { | ||
54 | struct pci_dev *dev; | ||
55 | unsigned long event; | ||
56 | int bus; | ||
57 | u16 seg; | ||
58 | u16 level; | ||
59 | struct acpi_dmar_pci_path path[]; | ||
60 | } __attribute__((packed)); | ||
61 | |||
53 | extern struct rw_semaphore dmar_global_lock; | 62 | extern struct rw_semaphore dmar_global_lock; |
54 | extern struct list_head dmar_drhd_units; | 63 | extern struct list_head dmar_drhd_units; |
55 | 64 | ||
@@ -89,12 +98,18 @@ extern int dmar_parse_dev_scope(void *start, void *end, int *cnt, | |||
89 | struct pci_dev ***devices, u16 segment); | 98 | struct pci_dev ***devices, u16 segment); |
90 | extern void *dmar_alloc_dev_scope(void *start, void *end, int *cnt); | 99 | extern void *dmar_alloc_dev_scope(void *start, void *end, int *cnt); |
91 | extern void dmar_free_dev_scope(struct pci_dev __rcu ***devices, int *cnt); | 100 | extern void dmar_free_dev_scope(struct pci_dev __rcu ***devices, int *cnt); |
92 | extern void dmar_free_dev_scope(struct pci_dev ***devices, int *cnt); | 101 | extern int dmar_insert_dev_scope(struct dmar_pci_notify_info *info, |
93 | 102 | void *start, void*end, u16 segment, | |
103 | struct pci_dev __rcu **devices, | ||
104 | int devices_cnt); | ||
105 | extern int dmar_remove_dev_scope(struct dmar_pci_notify_info *info, | ||
106 | u16 segment, struct pci_dev __rcu **devices, | ||
107 | int count); | ||
94 | /* Intel IOMMU detection */ | 108 | /* Intel IOMMU detection */ |
95 | extern int detect_intel_iommu(void); | 109 | extern int detect_intel_iommu(void); |
96 | extern int enable_drhd_fault_handling(void); | 110 | extern int enable_drhd_fault_handling(void); |
97 | #else | 111 | #else |
112 | struct dmar_pci_notify_info; | ||
98 | static inline int detect_intel_iommu(void) | 113 | static inline int detect_intel_iommu(void) |
99 | { | 114 | { |
100 | return -ENODEV; | 115 | return -ENODEV; |
@@ -161,6 +176,7 @@ extern int iommu_detected, no_iommu; | |||
161 | extern int dmar_parse_rmrr_atsr_dev(void); | 176 | extern int dmar_parse_rmrr_atsr_dev(void); |
162 | extern int dmar_parse_one_rmrr(struct acpi_dmar_header *header); | 177 | extern int dmar_parse_one_rmrr(struct acpi_dmar_header *header); |
163 | extern int dmar_parse_one_atsr(struct acpi_dmar_header *header); | 178 | extern int dmar_parse_one_atsr(struct acpi_dmar_header *header); |
179 | extern int dmar_iommu_notify_scope_dev(struct dmar_pci_notify_info *info); | ||
164 | extern int intel_iommu_init(void); | 180 | extern int intel_iommu_init(void); |
165 | #else /* !CONFIG_INTEL_IOMMU: */ | 181 | #else /* !CONFIG_INTEL_IOMMU: */ |
166 | static inline int intel_iommu_init(void) { return -ENODEV; } | 182 | static inline int intel_iommu_init(void) { return -ENODEV; } |
@@ -176,6 +192,10 @@ static inline int dmar_parse_rmrr_atsr_dev(void) | |||
176 | { | 192 | { |
177 | return 0; | 193 | return 0; |
178 | } | 194 | } |
195 | static inline int dmar_iommu_notify_scope_dev(struct dmar_pci_notify_info *info) | ||
196 | { | ||
197 | return 0; | ||
198 | } | ||
179 | #endif /* CONFIG_INTEL_IOMMU */ | 199 | #endif /* CONFIG_INTEL_IOMMU */ |
180 | 200 | ||
181 | #endif /* __DMAR_H__ */ | 201 | #endif /* __DMAR_H__ */ |