X-Git-Url: http://git.cascardo.info/?a=blobdiff_plain;f=drivers%2Fiommu%2Fiommu.c;h=49e7542510d15caac5622cdb01fdcf8b77bb80e8;hb=5ffde2f67181195d457b95df44b8f88e8d969d89;hp=ef73923db2f1a267a0dda70e16f490aed3e375c5;hpb=426a273834eae65abcfc7132a21a85b3151e0bce;p=cascardo%2Flinux.git diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index ef73923db2f1..49e7542510d1 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -52,6 +52,7 @@ struct iommu_group { char *name; int id; struct iommu_domain *default_domain; + struct iommu_domain *domain; }; struct iommu_device { @@ -78,6 +79,12 @@ struct iommu_group_attribute iommu_group_attr_##_name = \ static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus, unsigned type); +static int __iommu_attach_device(struct iommu_domain *domain, + struct device *dev); +static int __iommu_attach_group(struct iommu_domain *domain, + struct iommu_group *group); +static void __iommu_detach_group(struct iommu_domain *domain, + struct iommu_group *group); static ssize_t iommu_group_attr_show(struct kobject *kobj, struct attribute *__attr, char *buf) @@ -318,6 +325,52 @@ int iommu_group_set_name(struct iommu_group *group, const char *name) } EXPORT_SYMBOL_GPL(iommu_group_set_name); +static int iommu_group_create_direct_mappings(struct iommu_group *group, + struct device *dev) +{ + struct iommu_domain *domain = group->default_domain; + struct iommu_dm_region *entry; + struct list_head mappings; + unsigned long pg_size; + int ret = 0; + + if (!domain || domain->type != IOMMU_DOMAIN_DMA) + return 0; + + BUG_ON(!domain->ops->pgsize_bitmap); + + pg_size = 1UL << __ffs(domain->ops->pgsize_bitmap); + INIT_LIST_HEAD(&mappings); + + iommu_get_dm_regions(dev, &mappings); + + /* We need to consider overlapping regions for different devices */ + list_for_each_entry(entry, &mappings, list) { + dma_addr_t start, end, addr; + + start = ALIGN(entry->start, pg_size); + end = ALIGN(entry->start + entry->length, pg_size); + + for (addr = start; addr < end; addr += pg_size) { + phys_addr_t phys_addr; + + phys_addr = iommu_iova_to_phys(domain, addr); + if (phys_addr) + continue; + + ret = iommu_map(domain, addr, addr, pg_size, entry->prot); + if (ret) + goto out; + } + + } + +out: + iommu_put_dm_regions(dev, &mappings); + + return ret; +} + /** * iommu_group_add_device - add a device to an iommu group * @group: the group into which to add the device (reference should be held) @@ -374,8 +427,12 @@ rename: dev->iommu_group = group; + iommu_group_create_direct_mappings(group, dev); + mutex_lock(&group->mutex); list_add_tail(&device->list, &group->devices); + if (group->domain) + __iommu_attach_device(group->domain, dev); mutex_unlock(&group->mutex); /* Notify any listeners about change to group. */ @@ -455,19 +512,30 @@ static int iommu_group_device_count(struct iommu_group *group) * The group->mutex is held across callbacks, which will block calls to * iommu_group_add/remove_device. */ -int iommu_group_for_each_dev(struct iommu_group *group, void *data, - int (*fn)(struct device *, void *)) +static int __iommu_group_for_each_dev(struct iommu_group *group, void *data, + int (*fn)(struct device *, void *)) { struct iommu_device *device; int ret = 0; - mutex_lock(&group->mutex); list_for_each_entry(device, &group->devices, list) { ret = fn(device->dev, data); if (ret) break; } + return ret; +} + + +int iommu_group_for_each_dev(struct iommu_group *group, void *data, + int (*fn)(struct device *, void *)) +{ + int ret; + + mutex_lock(&group->mutex); + ret = __iommu_group_for_each_dev(group, data, fn); mutex_unlock(&group->mutex); + return ret; } EXPORT_SYMBOL_GPL(iommu_group_for_each_dev); @@ -720,14 +788,16 @@ static struct iommu_group *iommu_group_get_for_pci_dev(struct pci_dev *pdev) /* No shared group found, allocate new */ group = iommu_group_alloc(); - if (group) { - /* - * Try to allocate a default domain - needs support from the - * IOMMU driver. - */ - group->default_domain = __iommu_domain_alloc(pdev->dev.bus, - IOMMU_DOMAIN_DMA); - } + if (IS_ERR(group)) + return NULL; + + /* + * Try to allocate a default domain - needs support from the + * IOMMU driver. + */ + group->default_domain = __iommu_domain_alloc(pdev->dev.bus, + IOMMU_DOMAIN_DMA); + group->domain = group->default_domain; return group; } @@ -768,6 +838,11 @@ struct iommu_group *iommu_group_get_for_dev(struct device *dev) return group; } +struct iommu_domain *iommu_group_default_domain(struct iommu_group *group) +{ + return group->default_domain; +} + static int add_iommu_group(struct device *dev, void *data) { struct iommu_callback_data *cb = data; @@ -1012,7 +1087,7 @@ int iommu_attach_device(struct iommu_domain *domain, struct device *dev) if (iommu_group_device_count(group) != 1) goto out_unlock; - ret = __iommu_attach_device(domain, dev); + ret = __iommu_attach_group(domain, group); out_unlock: mutex_unlock(&group->mutex); @@ -1047,7 +1122,7 @@ void iommu_detach_device(struct iommu_domain *domain, struct device *dev) goto out_unlock; } - __iommu_detach_device(domain, dev); + __iommu_detach_group(domain, group); out_unlock: mutex_unlock(&group->mutex); @@ -1055,6 +1130,24 @@ out_unlock: } EXPORT_SYMBOL_GPL(iommu_detach_device); +struct iommu_domain *iommu_get_domain_for_dev(struct device *dev) +{ + struct iommu_domain *domain; + struct iommu_group *group; + + group = iommu_group_get(dev); + /* FIXME: Remove this when groups a mandatory for iommu drivers */ + if (group == NULL) + return NULL; + + domain = group->domain; + + iommu_group_put(group); + + return domain; +} +EXPORT_SYMBOL_GPL(iommu_get_domain_for_dev); + /* * IOMMU groups are really the natrual working unit of the IOMMU, but * the IOMMU API works on domains and devices. Bridge that gap by @@ -1072,10 +1165,31 @@ static int iommu_group_do_attach_device(struct device *dev, void *data) return __iommu_attach_device(domain, dev); } +static int __iommu_attach_group(struct iommu_domain *domain, + struct iommu_group *group) +{ + int ret; + + if (group->default_domain && group->domain != group->default_domain) + return -EBUSY; + + ret = __iommu_group_for_each_dev(group, domain, + iommu_group_do_attach_device); + if (ret == 0) + group->domain = domain; + + return ret; +} + int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group) { - return iommu_group_for_each_dev(group, domain, - iommu_group_do_attach_device); + int ret; + + mutex_lock(&group->mutex); + ret = __iommu_attach_group(domain, group); + mutex_unlock(&group->mutex); + + return ret; } EXPORT_SYMBOL_GPL(iommu_attach_group); @@ -1088,9 +1202,35 @@ static int iommu_group_do_detach_device(struct device *dev, void *data) return 0; } +static void __iommu_detach_group(struct iommu_domain *domain, + struct iommu_group *group) +{ + int ret; + + if (!group->default_domain) { + __iommu_group_for_each_dev(group, domain, + iommu_group_do_detach_device); + group->domain = NULL; + return; + } + + if (group->domain == group->default_domain) + return; + + /* Detach by re-attaching to the default domain */ + ret = __iommu_group_for_each_dev(group, group->default_domain, + iommu_group_do_attach_device); + if (ret != 0) + WARN_ON(1); + else + group->domain = group->default_domain; +} + void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group) { - iommu_group_for_each_dev(group, domain, iommu_group_do_detach_device); + mutex_lock(&group->mutex); + __iommu_detach_group(domain, group); + mutex_unlock(&group->mutex); } EXPORT_SYMBOL_GPL(iommu_detach_group); @@ -1317,7 +1457,7 @@ static int __init iommu_init(void) return 0; } -arch_initcall(iommu_init); +core_initcall(iommu_init); int iommu_domain_get_attr(struct iommu_domain *domain, enum iommu_attr attr, void *data) @@ -1383,3 +1523,72 @@ int iommu_domain_set_attr(struct iommu_domain *domain, return ret; } EXPORT_SYMBOL_GPL(iommu_domain_set_attr); + +void iommu_get_dm_regions(struct device *dev, struct list_head *list) +{ + const struct iommu_ops *ops = dev->bus->iommu_ops; + + if (ops && ops->get_dm_regions) + ops->get_dm_regions(dev, list); +} + +void iommu_put_dm_regions(struct device *dev, struct list_head *list) +{ + const struct iommu_ops *ops = dev->bus->iommu_ops; + + if (ops && ops->put_dm_regions) + ops->put_dm_regions(dev, list); +} + +/* Request that a device is direct mapped by the IOMMU */ +int iommu_request_dm_for_dev(struct device *dev) +{ + struct iommu_domain *dm_domain; + struct iommu_group *group; + int ret; + + /* Device must already be in a group before calling this function */ + group = iommu_group_get_for_dev(dev); + if (IS_ERR(group)) + return PTR_ERR(group); + + mutex_lock(&group->mutex); + + /* Check if the default domain is already direct mapped */ + ret = 0; + if (group->default_domain && + group->default_domain->type == IOMMU_DOMAIN_IDENTITY) + goto out; + + /* Don't change mappings of existing devices */ + ret = -EBUSY; + if (iommu_group_device_count(group) != 1) + goto out; + + /* Allocate a direct mapped domain */ + ret = -ENOMEM; + dm_domain = __iommu_domain_alloc(dev->bus, IOMMU_DOMAIN_IDENTITY); + if (!dm_domain) + goto out; + + /* Attach the device to the domain */ + ret = __iommu_attach_group(dm_domain, group); + if (ret) { + iommu_domain_free(dm_domain); + goto out; + } + + /* Make the direct mapped domain the default for this group */ + if (group->default_domain) + iommu_domain_free(group->default_domain); + group->default_domain = dm_domain; + + pr_info("Using direct mapping for device %s\n", dev_name(dev)); + + ret = 0; +out: + mutex_unlock(&group->mutex); + iommu_group_put(group); + + return ret; +}