|
@@ -36,6 +36,8 @@
|
|
|
#include <linux/hash.h>
|
|
|
#include <linux/fault-inject.h>
|
|
|
#include <linux/pci.h>
|
|
|
+#include <linux/iommu.h>
|
|
|
+#include <linux/sched.h>
|
|
|
#include <asm/io.h>
|
|
|
#include <asm/prom.h>
|
|
|
#include <asm/iommu.h>
|
|
@@ -44,6 +46,7 @@
|
|
|
#include <asm/kdump.h>
|
|
|
#include <asm/fadump.h>
|
|
|
#include <asm/vio.h>
|
|
|
+#include <asm/tce.h>
|
|
|
|
|
|
#define DBG(...)
|
|
|
|
|
@@ -724,6 +727,13 @@ void iommu_free_table(struct iommu_table *tbl, const char *node_name)
|
|
|
if (tbl->it_offset == 0)
|
|
|
clear_bit(0, tbl->it_map);
|
|
|
|
|
|
+#ifdef CONFIG_IOMMU_API
|
|
|
+ if (tbl->it_group) {
|
|
|
+ iommu_group_put(tbl->it_group);
|
|
|
+ BUG_ON(tbl->it_group);
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/* verify that table contains no entries */
|
|
|
if (!bitmap_empty(tbl->it_map, tbl->it_size))
|
|
|
pr_warn("%s: Unexpected TCEs for %s\n", __func__, node_name);
|
|
@@ -860,3 +870,316 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,
|
|
|
free_pages((unsigned long)vaddr, get_order(size));
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+#ifdef CONFIG_IOMMU_API
|
|
|
+/*
|
|
|
+ * SPAPR TCE API
|
|
|
+ */
|
|
|
+static void group_release(void *iommu_data)
|
|
|
+{
|
|
|
+ struct iommu_table *tbl = iommu_data;
|
|
|
+ tbl->it_group = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+void iommu_register_group(struct iommu_table *tbl,
|
|
|
+ int pci_domain_number, unsigned long pe_num)
|
|
|
+{
|
|
|
+ struct iommu_group *grp;
|
|
|
+ char *name;
|
|
|
+
|
|
|
+ grp = iommu_group_alloc();
|
|
|
+ if (IS_ERR(grp)) {
|
|
|
+ pr_warn("powerpc iommu api: cannot create new group, err=%ld\n",
|
|
|
+ PTR_ERR(grp));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ tbl->it_group = grp;
|
|
|
+ iommu_group_set_iommudata(grp, tbl, group_release);
|
|
|
+ name = kasprintf(GFP_KERNEL, "domain%d-pe%lx",
|
|
|
+ pci_domain_number, pe_num);
|
|
|
+ if (!name)
|
|
|
+ return;
|
|
|
+ iommu_group_set_name(grp, name);
|
|
|
+ kfree(name);
|
|
|
+}
|
|
|
+
|
|
|
+enum dma_data_direction iommu_tce_direction(unsigned long tce)
|
|
|
+{
|
|
|
+ if ((tce & TCE_PCI_READ) && (tce & TCE_PCI_WRITE))
|
|
|
+ return DMA_BIDIRECTIONAL;
|
|
|
+ else if (tce & TCE_PCI_READ)
|
|
|
+ return DMA_TO_DEVICE;
|
|
|
+ else if (tce & TCE_PCI_WRITE)
|
|
|
+ return DMA_FROM_DEVICE;
|
|
|
+ else
|
|
|
+ return DMA_NONE;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_tce_direction);
|
|
|
+
|
|
|
+void iommu_flush_tce(struct iommu_table *tbl)
|
|
|
+{
|
|
|
+ /* Flush/invalidate TLB caches if necessary */
|
|
|
+ if (ppc_md.tce_flush)
|
|
|
+ ppc_md.tce_flush(tbl);
|
|
|
+
|
|
|
+ /* Make sure updates are seen by hardware */
|
|
|
+ mb();
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_flush_tce);
|
|
|
+
|
|
|
+int iommu_tce_clear_param_check(struct iommu_table *tbl,
|
|
|
+ unsigned long ioba, unsigned long tce_value,
|
|
|
+ unsigned long npages)
|
|
|
+{
|
|
|
+ /* ppc_md.tce_free() does not support any value but 0 */
|
|
|
+ if (tce_value)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (ioba & ~IOMMU_PAGE_MASK)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ ioba >>= IOMMU_PAGE_SHIFT;
|
|
|
+ if (ioba < tbl->it_offset)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if ((ioba + npages) > (tbl->it_offset + tbl->it_size))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_tce_clear_param_check);
|
|
|
+
|
|
|
+int iommu_tce_put_param_check(struct iommu_table *tbl,
|
|
|
+ unsigned long ioba, unsigned long tce)
|
|
|
+{
|
|
|
+ if (!(tce & (TCE_PCI_WRITE | TCE_PCI_READ)))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (tce & ~(IOMMU_PAGE_MASK | TCE_PCI_WRITE | TCE_PCI_READ))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (ioba & ~IOMMU_PAGE_MASK)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ ioba >>= IOMMU_PAGE_SHIFT;
|
|
|
+ if (ioba < tbl->it_offset)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if ((ioba + 1) > (tbl->it_offset + tbl->it_size))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_tce_put_param_check);
|
|
|
+
|
|
|
+unsigned long iommu_clear_tce(struct iommu_table *tbl, unsigned long entry)
|
|
|
+{
|
|
|
+ unsigned long oldtce;
|
|
|
+ struct iommu_pool *pool = get_pool(tbl, entry);
|
|
|
+
|
|
|
+ spin_lock(&(pool->lock));
|
|
|
+
|
|
|
+ oldtce = ppc_md.tce_get(tbl, entry);
|
|
|
+ if (oldtce & (TCE_PCI_WRITE | TCE_PCI_READ))
|
|
|
+ ppc_md.tce_free(tbl, entry, 1);
|
|
|
+ else
|
|
|
+ oldtce = 0;
|
|
|
+
|
|
|
+ spin_unlock(&(pool->lock));
|
|
|
+
|
|
|
+ return oldtce;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_clear_tce);
|
|
|
+
|
|
|
+int iommu_clear_tces_and_put_pages(struct iommu_table *tbl,
|
|
|
+ unsigned long entry, unsigned long pages)
|
|
|
+{
|
|
|
+ unsigned long oldtce;
|
|
|
+ struct page *page;
|
|
|
+
|
|
|
+ for ( ; pages; --pages, ++entry) {
|
|
|
+ oldtce = iommu_clear_tce(tbl, entry);
|
|
|
+ if (!oldtce)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
|
|
|
+ WARN_ON(!page);
|
|
|
+ if (page) {
|
|
|
+ if (oldtce & TCE_PCI_WRITE)
|
|
|
+ SetPageDirty(page);
|
|
|
+ put_page(page);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_clear_tces_and_put_pages);
|
|
|
+
|
|
|
+/*
|
|
|
+ * hwaddr is a kernel virtual address here (0xc... bazillion),
|
|
|
+ * tce_build converts it to a physical address.
|
|
|
+ */
|
|
|
+int iommu_tce_build(struct iommu_table *tbl, unsigned long entry,
|
|
|
+ unsigned long hwaddr, enum dma_data_direction direction)
|
|
|
+{
|
|
|
+ int ret = -EBUSY;
|
|
|
+ unsigned long oldtce;
|
|
|
+ struct iommu_pool *pool = get_pool(tbl, entry);
|
|
|
+
|
|
|
+ spin_lock(&(pool->lock));
|
|
|
+
|
|
|
+ oldtce = ppc_md.tce_get(tbl, entry);
|
|
|
+ /* Add new entry if it is not busy */
|
|
|
+ if (!(oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)))
|
|
|
+ ret = ppc_md.tce_build(tbl, entry, 1, hwaddr, direction, NULL);
|
|
|
+
|
|
|
+ spin_unlock(&(pool->lock));
|
|
|
+
|
|
|
+ /* if (unlikely(ret))
|
|
|
+ pr_err("iommu_tce: %s failed on hwaddr=%lx ioba=%lx kva=%lx ret=%d\n",
|
|
|
+ __func__, hwaddr, entry << IOMMU_PAGE_SHIFT,
|
|
|
+ hwaddr, ret); */
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_tce_build);
|
|
|
+
|
|
|
+int iommu_put_tce_user_mode(struct iommu_table *tbl, unsigned long entry,
|
|
|
+ unsigned long tce)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ struct page *page = NULL;
|
|
|
+ unsigned long hwaddr, offset = tce & IOMMU_PAGE_MASK & ~PAGE_MASK;
|
|
|
+ enum dma_data_direction direction = iommu_tce_direction(tce);
|
|
|
+
|
|
|
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
|
|
|
+ direction != DMA_TO_DEVICE, &page);
|
|
|
+ if (unlikely(ret != 1)) {
|
|
|
+ /* pr_err("iommu_tce: get_user_pages_fast failed tce=%lx ioba=%lx ret=%d\n",
|
|
|
+ tce, entry << IOMMU_PAGE_SHIFT, ret); */
|
|
|
+ return -EFAULT;
|
|
|
+ }
|
|
|
+ hwaddr = (unsigned long) page_address(page) + offset;
|
|
|
+
|
|
|
+ ret = iommu_tce_build(tbl, entry, hwaddr, direction);
|
|
|
+ if (ret)
|
|
|
+ put_page(page);
|
|
|
+
|
|
|
+ if (ret < 0)
|
|
|
+ pr_err("iommu_tce: %s failed ioba=%lx, tce=%lx, ret=%d\n",
|
|
|
+ __func__, entry << IOMMU_PAGE_SHIFT, tce, ret);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_put_tce_user_mode);
|
|
|
+
|
|
|
+int iommu_take_ownership(struct iommu_table *tbl)
|
|
|
+{
|
|
|
+ unsigned long sz = (tbl->it_size + 7) >> 3;
|
|
|
+
|
|
|
+ if (tbl->it_offset == 0)
|
|
|
+ clear_bit(0, tbl->it_map);
|
|
|
+
|
|
|
+ if (!bitmap_empty(tbl->it_map, tbl->it_size)) {
|
|
|
+ pr_err("iommu_tce: it_map is not empty");
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+
|
|
|
+ memset(tbl->it_map, 0xff, sz);
|
|
|
+ iommu_clear_tces_and_put_pages(tbl, tbl->it_offset, tbl->it_size);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_take_ownership);
|
|
|
+
|
|
|
+void iommu_release_ownership(struct iommu_table *tbl)
|
|
|
+{
|
|
|
+ unsigned long sz = (tbl->it_size + 7) >> 3;
|
|
|
+
|
|
|
+ iommu_clear_tces_and_put_pages(tbl, tbl->it_offset, tbl->it_size);
|
|
|
+ memset(tbl->it_map, 0, sz);
|
|
|
+
|
|
|
+ /* Restore bit#0 set by iommu_init_table() */
|
|
|
+ if (tbl->it_offset == 0)
|
|
|
+ set_bit(0, tbl->it_map);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iommu_release_ownership);
|
|
|
+
|
|
|
+static int iommu_add_device(struct device *dev)
|
|
|
+{
|
|
|
+ struct iommu_table *tbl;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (WARN_ON(dev->iommu_group)) {
|
|
|
+ pr_warn("iommu_tce: device %s is already in iommu group %d, skipping\n",
|
|
|
+ dev_name(dev),
|
|
|
+ iommu_group_id(dev->iommu_group));
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+
|
|
|
+ tbl = get_iommu_table_base(dev);
|
|
|
+ if (!tbl || !tbl->it_group) {
|
|
|
+ pr_debug("iommu_tce: skipping device %s with no tbl\n",
|
|
|
+ dev_name(dev));
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ pr_debug("iommu_tce: adding %s to iommu group %d\n",
|
|
|
+ dev_name(dev), iommu_group_id(tbl->it_group));
|
|
|
+
|
|
|
+ ret = iommu_group_add_device(tbl->it_group, dev);
|
|
|
+ if (ret < 0)
|
|
|
+ pr_err("iommu_tce: %s has not been added, ret=%d\n",
|
|
|
+ dev_name(dev), ret);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void iommu_del_device(struct device *dev)
|
|
|
+{
|
|
|
+ iommu_group_remove_device(dev);
|
|
|
+}
|
|
|
+
|
|
|
+static int iommu_bus_notifier(struct notifier_block *nb,
|
|
|
+ unsigned long action, void *data)
|
|
|
+{
|
|
|
+ struct device *dev = data;
|
|
|
+
|
|
|
+ switch (action) {
|
|
|
+ case BUS_NOTIFY_ADD_DEVICE:
|
|
|
+ return iommu_add_device(dev);
|
|
|
+ case BUS_NOTIFY_DEL_DEVICE:
|
|
|
+ iommu_del_device(dev);
|
|
|
+ return 0;
|
|
|
+ default:
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static struct notifier_block tce_iommu_bus_nb = {
|
|
|
+ .notifier_call = iommu_bus_notifier,
|
|
|
+};
|
|
|
+
|
|
|
+static int __init tce_iommu_init(void)
|
|
|
+{
|
|
|
+ struct pci_dev *pdev = NULL;
|
|
|
+
|
|
|
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
|
|
|
+
|
|
|
+ for_each_pci_dev(pdev)
|
|
|
+ iommu_add_device(&pdev->dev);
|
|
|
+
|
|
|
+ bus_register_notifier(&pci_bus_type, &tce_iommu_bus_nb);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+subsys_initcall_sync(tce_iommu_init);
|
|
|
+
|
|
|
+#else
|
|
|
+
|
|
|
+void iommu_register_group(struct iommu_table *tbl,
|
|
|
+ int pci_domain_number, unsigned long pe_num)
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+#endif /* CONFIG_IOMMU_API */
|