diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2019-05-13 19:34:45 -0400 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2019-05-13 19:34:45 -0400 |
commit | f2e94683162565ff02d8d7386fadee175ab55e40 (patch) | |
tree | fb9752bf2288737623456872d93791b40c30582f | |
parent | ee6df38da8485b143cc5eccee569ae3e238be10a (diff) | |
parent | 90199c951bd2a6248e55a8db3368a2568e3b3edc (diff) |
Merge branch 'pci/iova-dma-ranges'
- Add list of legal DMA address ranges to PCI host bridge (Srinath
Mannam)
- Reserve inaccessible DMA ranges so IOMMU doesn't allocate them (Srinath
Mannam)
- Parse iProc DT dma-ranges to learn what PCI devices can reach via DMA
(Srinath Mannam)
* pci/iova-dma-ranges:
PCI: iproc: Add sorted dma ranges resource entries to host bridge
iommu/dma: Reserve IOVA for PCIe inaccessible DMA address
PCI: Add dma_ranges window list
# Conflicts:
# drivers/pci/probe.c
-rw-r--r-- | drivers/iommu/dma-iommu.c | 35 | ||||
-rw-r--r-- | drivers/pci/controller/pcie-iproc.c | 44 | ||||
-rw-r--r-- | drivers/pci/probe.c | 2 | ||||
-rw-r--r-- | include/linux/pci.h | 1 |
4 files changed, 78 insertions, 4 deletions
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 77aabe637a60..954ae11461a3 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c | |||
@@ -206,12 +206,13 @@ static int cookie_init_hw_msi_region(struct iommu_dma_cookie *cookie, | |||
206 | return 0; | 206 | return 0; |
207 | } | 207 | } |
208 | 208 | ||
209 | static void iova_reserve_pci_windows(struct pci_dev *dev, | 209 | static int iova_reserve_pci_windows(struct pci_dev *dev, |
210 | struct iova_domain *iovad) | 210 | struct iova_domain *iovad) |
211 | { | 211 | { |
212 | struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus); | 212 | struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus); |
213 | struct resource_entry *window; | 213 | struct resource_entry *window; |
214 | unsigned long lo, hi; | 214 | unsigned long lo, hi; |
215 | phys_addr_t start = 0, end; | ||
215 | 216 | ||
216 | resource_list_for_each_entry(window, &bridge->windows) { | 217 | resource_list_for_each_entry(window, &bridge->windows) { |
217 | if (resource_type(window->res) != IORESOURCE_MEM) | 218 | if (resource_type(window->res) != IORESOURCE_MEM) |
@@ -221,6 +222,31 @@ static void iova_reserve_pci_windows(struct pci_dev *dev, | |||
221 | hi = iova_pfn(iovad, window->res->end - window->offset); | 222 | hi = iova_pfn(iovad, window->res->end - window->offset); |
222 | reserve_iova(iovad, lo, hi); | 223 | reserve_iova(iovad, lo, hi); |
223 | } | 224 | } |
225 | |||
226 | /* Get reserved DMA windows from host bridge */ | ||
227 | resource_list_for_each_entry(window, &bridge->dma_ranges) { | ||
228 | end = window->res->start - window->offset; | ||
229 | resv_iova: | ||
230 | if (end > start) { | ||
231 | lo = iova_pfn(iovad, start); | ||
232 | hi = iova_pfn(iovad, end); | ||
233 | reserve_iova(iovad, lo, hi); | ||
234 | } else { | ||
235 | /* dma_ranges list should be sorted */ | ||
236 | dev_err(&dev->dev, "Failed to reserve IOVA\n"); | ||
237 | return -EINVAL; | ||
238 | } | ||
239 | |||
240 | start = window->res->end - window->offset + 1; | ||
241 | /* If window is last entry */ | ||
242 | if (window->node.next == &bridge->dma_ranges && | ||
243 | end != ~(dma_addr_t)0) { | ||
244 | end = ~(dma_addr_t)0; | ||
245 | goto resv_iova; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | return 0; | ||
224 | } | 250 | } |
225 | 251 | ||
226 | static int iova_reserve_iommu_regions(struct device *dev, | 252 | static int iova_reserve_iommu_regions(struct device *dev, |
@@ -232,8 +258,11 @@ static int iova_reserve_iommu_regions(struct device *dev, | |||
232 | LIST_HEAD(resv_regions); | 258 | LIST_HEAD(resv_regions); |
233 | int ret = 0; | 259 | int ret = 0; |
234 | 260 | ||
235 | if (dev_is_pci(dev)) | 261 | if (dev_is_pci(dev)) { |
236 | iova_reserve_pci_windows(to_pci_dev(dev), iovad); | 262 | ret = iova_reserve_pci_windows(to_pci_dev(dev), iovad); |
263 | if (ret) | ||
264 | return ret; | ||
265 | } | ||
237 | 266 | ||
238 | iommu_get_resv_regions(dev, &resv_regions); | 267 | iommu_get_resv_regions(dev, &resv_regions); |
239 | list_for_each_entry(region, &resv_regions, list) { | 268 | list_for_each_entry(region, &resv_regions, list) { |
diff --git a/drivers/pci/controller/pcie-iproc.c b/drivers/pci/controller/pcie-iproc.c index aa4768a2c0ca..e3ca46497470 100644 --- a/drivers/pci/controller/pcie-iproc.c +++ b/drivers/pci/controller/pcie-iproc.c | |||
@@ -1182,11 +1182,43 @@ err_ib: | |||
1182 | return ret; | 1182 | return ret; |
1183 | } | 1183 | } |
1184 | 1184 | ||
1185 | static int iproc_pcie_add_dma_range(struct device *dev, | ||
1186 | struct list_head *resources, | ||
1187 | struct of_pci_range *range) | ||
1188 | { | ||
1189 | struct resource *res; | ||
1190 | struct resource_entry *entry, *tmp; | ||
1191 | struct list_head *head = resources; | ||
1192 | |||
1193 | res = devm_kzalloc(dev, sizeof(struct resource), GFP_KERNEL); | ||
1194 | if (!res) | ||
1195 | return -ENOMEM; | ||
1196 | |||
1197 | resource_list_for_each_entry(tmp, resources) { | ||
1198 | if (tmp->res->start < range->cpu_addr) | ||
1199 | head = &tmp->node; | ||
1200 | } | ||
1201 | |||
1202 | res->start = range->cpu_addr; | ||
1203 | res->end = res->start + range->size - 1; | ||
1204 | |||
1205 | entry = resource_list_create_entry(res, 0); | ||
1206 | if (!entry) | ||
1207 | return -ENOMEM; | ||
1208 | |||
1209 | entry->offset = res->start - range->cpu_addr; | ||
1210 | resource_list_add(entry, head); | ||
1211 | |||
1212 | return 0; | ||
1213 | } | ||
1214 | |||
1185 | static int iproc_pcie_map_dma_ranges(struct iproc_pcie *pcie) | 1215 | static int iproc_pcie_map_dma_ranges(struct iproc_pcie *pcie) |
1186 | { | 1216 | { |
1217 | struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie); | ||
1187 | struct of_pci_range range; | 1218 | struct of_pci_range range; |
1188 | struct of_pci_range_parser parser; | 1219 | struct of_pci_range_parser parser; |
1189 | int ret; | 1220 | int ret; |
1221 | LIST_HEAD(resources); | ||
1190 | 1222 | ||
1191 | /* Get the dma-ranges from DT */ | 1223 | /* Get the dma-ranges from DT */ |
1192 | ret = of_pci_dma_range_parser_init(&parser, pcie->dev->of_node); | 1224 | ret = of_pci_dma_range_parser_init(&parser, pcie->dev->of_node); |
@@ -1194,13 +1226,23 @@ static int iproc_pcie_map_dma_ranges(struct iproc_pcie *pcie) | |||
1194 | return ret; | 1226 | return ret; |
1195 | 1227 | ||
1196 | for_each_of_pci_range(&parser, &range) { | 1228 | for_each_of_pci_range(&parser, &range) { |
1229 | ret = iproc_pcie_add_dma_range(pcie->dev, | ||
1230 | &resources, | ||
1231 | &range); | ||
1232 | if (ret) | ||
1233 | goto out; | ||
1197 | /* Each range entry corresponds to an inbound mapping region */ | 1234 | /* Each range entry corresponds to an inbound mapping region */ |
1198 | ret = iproc_pcie_setup_ib(pcie, &range, IPROC_PCIE_IB_MAP_MEM); | 1235 | ret = iproc_pcie_setup_ib(pcie, &range, IPROC_PCIE_IB_MAP_MEM); |
1199 | if (ret) | 1236 | if (ret) |
1200 | return ret; | 1237 | goto out; |
1201 | } | 1238 | } |
1202 | 1239 | ||
1240 | list_splice_init(&resources, &host->dma_ranges); | ||
1241 | |||
1203 | return 0; | 1242 | return 0; |
1243 | out: | ||
1244 | pci_free_resource_list(&resources); | ||
1245 | return ret; | ||
1204 | } | 1246 | } |
1205 | 1247 | ||
1206 | static int iproce_pcie_get_msi(struct iproc_pcie *pcie, | 1248 | static int iproce_pcie_get_msi(struct iproc_pcie *pcie, |
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 50cd9c17c08f..0dc42f0eb66d 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c | |||
@@ -589,6 +589,7 @@ static void pci_release_host_bridge_dev(struct device *dev) | |||
589 | static void pci_init_host_bridge(struct pci_host_bridge *bridge) | 589 | static void pci_init_host_bridge(struct pci_host_bridge *bridge) |
590 | { | 590 | { |
591 | INIT_LIST_HEAD(&bridge->windows); | 591 | INIT_LIST_HEAD(&bridge->windows); |
592 | INIT_LIST_HEAD(&bridge->dma_ranges); | ||
592 | 593 | ||
593 | /* | 594 | /* |
594 | * We assume we can manage these PCIe features. Some systems may | 595 | * We assume we can manage these PCIe features. Some systems may |
@@ -637,6 +638,7 @@ EXPORT_SYMBOL(devm_pci_alloc_host_bridge); | |||
637 | void pci_free_host_bridge(struct pci_host_bridge *bridge) | 638 | void pci_free_host_bridge(struct pci_host_bridge *bridge) |
638 | { | 639 | { |
639 | pci_free_resource_list(&bridge->windows); | 640 | pci_free_resource_list(&bridge->windows); |
641 | pci_free_resource_list(&bridge->dma_ranges); | ||
640 | 642 | ||
641 | kfree(bridge); | 643 | kfree(bridge); |
642 | } | 644 | } |
diff --git a/include/linux/pci.h b/include/linux/pci.h index 6fc361a69e0c..87edbd23eddb 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h | |||
@@ -492,6 +492,7 @@ struct pci_host_bridge { | |||
492 | void *sysdata; | 492 | void *sysdata; |
493 | int busnr; | 493 | int busnr; |
494 | struct list_head windows; /* resource_entry */ | 494 | struct list_head windows; /* resource_entry */ |
495 | struct list_head dma_ranges; /* dma ranges resource list */ | ||
495 | u8 (*swizzle_irq)(struct pci_dev *, u8 *); /* Platform IRQ swizzler */ | 496 | u8 (*swizzle_irq)(struct pci_dev *, u8 *); /* Platform IRQ swizzler */ |
496 | int (*map_irq)(const struct pci_dev *, u8, u8); | 497 | int (*map_irq)(const struct pci_dev *, u8, u8); |
497 | void (*release_fn)(struct pci_host_bridge *); | 498 | void (*release_fn)(struct pci_host_bridge *); |