Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86 calgary: fix handling of devices that aren't behind the Calgary

The calgary code can give drivers addresses above 4GB which is very bad
for hardware that is only 32bit DMA addressable.

With this patch, the calgary code sets the global dma_ops to swiotlb or
nommu properly, and the dma_ops of devices behind the Calgary/CalIOC2
to calgary_dma_ops. So the calgary code can handle devices safely that
aren't behind the Calgary/CalIOC2.

[akpm@linux-foundation.org: coding-style fixes]
Signed-off-by: Alexis Bruemmer <alexisb@us.ibm.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Cc: Muli Ben-Yehuda <muli@il.ibm.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Alexis Bruemmer and committed by
Linus Torvalds
1956a96d 8d8bb39b

+27 -45
+26 -45
arch/x86/kernel/pci-calgary_64.c
··· 37 37 #include <linux/delay.h> 38 38 #include <linux/scatterlist.h> 39 39 #include <linux/iommu-helper.h> 40 + 40 41 #include <asm/iommu.h> 41 42 #include <asm/calgary.h> 42 43 #include <asm/tce.h> ··· 414 413 } 415 414 } 416 415 417 - static int calgary_nontranslate_map_sg(struct device* dev, 418 - struct scatterlist *sg, int nelems, int direction) 419 - { 420 - struct scatterlist *s; 421 - int i; 422 - 423 - for_each_sg(sg, s, nelems, i) { 424 - struct page *p = sg_page(s); 425 - 426 - BUG_ON(!p); 427 - s->dma_address = virt_to_bus(sg_virt(s)); 428 - s->dma_length = s->length; 429 - } 430 - return nelems; 431 - } 432 - 433 416 static int calgary_map_sg(struct device *dev, struct scatterlist *sg, 434 417 int nelems, int direction) 435 418 { ··· 423 438 unsigned int npages; 424 439 unsigned long entry; 425 440 int i; 426 - 427 - if (!translation_enabled(tbl)) 428 - return calgary_nontranslate_map_sg(dev, sg, nelems, direction); 429 441 430 442 for_each_sg(sg, s, nelems, i) { 431 443 BUG_ON(!sg_page(s)); ··· 459 477 static dma_addr_t calgary_map_single(struct device *dev, phys_addr_t paddr, 460 478 size_t size, int direction) 461 479 { 462 - dma_addr_t dma_handle = bad_dma_address; 463 480 void *vaddr = phys_to_virt(paddr); 464 481 unsigned long uaddr; 465 482 unsigned int npages; ··· 467 486 uaddr = (unsigned long)vaddr; 468 487 npages = num_dma_pages(uaddr, size); 469 488 470 - if (translation_enabled(tbl)) 471 - dma_handle = iommu_alloc(dev, tbl, vaddr, npages, direction); 472 - else 473 - dma_handle = virt_to_bus(vaddr); 474 - 475 - return dma_handle; 489 + return iommu_alloc(dev, tbl, vaddr, npages, direction); 476 490 } 477 491 478 492 static void calgary_unmap_single(struct device *dev, dma_addr_t dma_handle, ··· 475 499 { 476 500 struct iommu_table *tbl = find_iommu_table(dev); 477 501 unsigned int npages; 478 - 479 - if (!translation_enabled(tbl)) 480 - return; 481 502 482 503 npages = num_dma_pages(dma_handle, size); 483 504 iommu_free(tbl, dma_handle, npages); ··· 498 525 goto error; 499 526 memset(ret, 0, size); 500 527 501 - if (translation_enabled(tbl)) { 502 - /* set up tces to cover the allocated range */ 503 - mapping = iommu_alloc(dev, tbl, ret, npages, DMA_BIDIRECTIONAL); 504 - if (mapping == bad_dma_address) 505 - goto free; 506 - 507 - *dma_handle = mapping; 508 - } else /* non translated slot */ 509 - *dma_handle = virt_to_bus(ret); 510 - 528 + /* set up tces to cover the allocated range */ 529 + mapping = iommu_alloc(dev, tbl, ret, npages, DMA_BIDIRECTIONAL); 530 + if (mapping == bad_dma_address) 531 + goto free; 532 + *dma_handle = mapping; 511 533 return ret; 512 - 513 534 free: 514 535 free_pages((unsigned long)ret, get_order(size)); 515 536 ret = NULL; ··· 1208 1241 goto error; 1209 1242 } while (1); 1210 1243 1244 + dev = NULL; 1245 + for_each_pci_dev(dev) { 1246 + struct iommu_table *tbl; 1247 + 1248 + tbl = find_iommu_table(&dev->dev); 1249 + 1250 + if (translation_enabled(tbl)) 1251 + dev->dev.archdata.dma_ops = &calgary_dma_ops; 1252 + } 1253 + 1211 1254 return ret; 1212 1255 1213 1256 error: ··· 1239 1262 calgary_disable_translation(dev); 1240 1263 calgary_free_bus(dev); 1241 1264 pci_dev_put(dev); /* Undo calgary_init_one()'s pci_dev_get() */ 1265 + dev->dev.archdata.dma_ops = NULL; 1242 1266 } while (1); 1243 1267 1244 1268 return ret; ··· 1481 1503 printk(KERN_INFO "PCI-DMA: Calgary TCE table spec is %d, " 1482 1504 "CONFIG_IOMMU_DEBUG is %s.\n", specified_table_size, 1483 1505 debugging ? "enabled" : "disabled"); 1506 + 1507 + /* swiotlb for devices that aren't behind the Calgary. */ 1508 + if (max_pfn > MAX_DMA32_PFN) 1509 + swiotlb = 1; 1484 1510 } 1485 1511 return; 1486 1512 ··· 1501 1519 { 1502 1520 int ret; 1503 1521 1504 - if (no_iommu || swiotlb) 1522 + if (no_iommu || (swiotlb && !calgary_detected)) 1505 1523 return -ENODEV; 1506 1524 1507 1525 if (!calgary_detected) ··· 1514 1532 if (ret) { 1515 1533 printk(KERN_ERR "PCI-DMA: Calgary init failed %d, " 1516 1534 "falling back to no_iommu\n", ret); 1517 - if (max_pfn > MAX_DMA32_PFN) 1518 - printk(KERN_ERR "WARNING more than 4GB of memory, " 1519 - "32bit PCI may malfunction.\n"); 1520 1535 return ret; 1521 1536 } 1522 1537 1523 1538 force_iommu = 1; 1524 1539 bad_dma_address = 0x0; 1525 - dma_ops = &calgary_dma_ops; 1540 + /* dma_ops is set to swiotlb or nommu */ 1541 + if (!dma_ops) 1542 + dma_ops = &nommu_dma_ops; 1526 1543 1527 1544 return 0; 1528 1545 }
+1
include/asm-x86/iommu.h
··· 3 3 4 4 extern void pci_iommu_shutdown(void); 5 5 extern void no_iommu_init(void); 6 + extern struct dma_mapping_ops nommu_dma_ops; 6 7 extern int force_iommu, no_iommu; 7 8 extern int iommu_detected; 8 9