From patchwork Mon May 23 22:27:55 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Morton X-Patchwork-Id: 810322 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter2.kernel.org (8.14.4/8.14.3) with ESMTP id p4NMS4kT013931 for ; Mon, 23 May 2011 22:28:04 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1757664Ab1EWW2E (ORCPT ); Mon, 23 May 2011 18:28:04 -0400 Received: from smtp1.linux-foundation.org ([140.211.169.13]:50252 "EHLO smtp1.linux-foundation.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757659Ab1EWW2D (ORCPT ); Mon, 23 May 2011 18:28:03 -0400 Received: from imap1.linux-foundation.org (imap1.linux-foundation.org [140.211.169.55]) by smtp1.linux-foundation.org (8.14.2/8.13.5/Debian-3ubuntu1.1) with ESMTP id p4NMRujJ023299 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Mon, 23 May 2011 15:27:56 -0700 Received: from localhost.localdomain (localhost [127.0.0.1]) by imap1.linux-foundation.org (8.13.5.20060308/8.13.5/Debian-3ubuntu1.1) with ESMTP id p4NMRtGJ016016; Mon, 23 May 2011 15:27:55 -0700 Message-Id: <201105232227.p4NMRtGJ016016@imap1.linux-foundation.org> Subject: [patch 1/2] pci, dmar: update dmar units devices list during hotplug To: jbarnes@virtuousgeek.org Cc: linux-pci@vger.kernel.org, akpm@linux-foundation.org, yinghai@kernel.org, dan.j.williams@intel.com, dwmw2@infradead.org, vinod.koul@intel.com From: akpm@linux-foundation.org Date: Mon, 23 May 2011 15:27:55 -0700 MIME-Version: 1.0 X-Spam-Status: No, hits=-103.482 required=5 tests=AWL, BAYES_00, OSDL_HEADER_SUBJECT_BRACKETED, USER_IN_WHITELIST X-Spam-Checker-Version: SpamAssassin 3.2.4-osdl_revision__1.47__ X-MIMEDefang-Filter: lf$Revision: 1.188 $ X-Scanned-By: MIMEDefang 2.63 on 140.211.169.13 Sender: linux-pci-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter2.kernel.org [140.211.167.43]); Mon, 23 May 2011 22:28:05 +0000 (UTC) From: Yinghai Lu When do pci remove/rescan on system that have more iommus, got [ 894.089745] Set context mapping for c4:00.0 [ 894.110890] mpt2sas3: Allocated physical memory: size(4293 kB) [ 894.112556] mpt2sas3: Current Controller Queue Depth(1883), Max Controller Queue Depth(2144) [ 894.127278] mpt2sas3: Scatter Gather Elements per IO(128) [ 894.361295] DRHD: handling fault status reg 2 [ 894.364053] DMAR:[DMA Read] Request device [c4:00.0] fault addr fffbe000 [ 894.364056] DMAR:[fault reason 02] Present bit in context entry is cl it turns out when remove/rescan, pci dev will be freed and will get another new dev. but drhd units still keep old one... so dmar_find_matched_drhd_unit will return wrong drhd and iommu for the device that is not on first iommu. So need to update devices in drhd_units during pci remove/rescan. Could save domain/bus/device/function aside in the list and according that info restore new dev to drhd_units later. Then dmar_find_matched_drdh_unit and device_to_iommu could return right drhd and iommu. Add remove_dev_from_drhd/restore_dev_to_drhd functions to do the real work. call them in device ADD_DEVICE and UNBOUND_DRIVER Need to do the samething to atsr. (expose dmar_atsr_units and add atsru->segment) After patch, will right iommu for the new dev and will not get DMAR error any more. Signed-off-by: Yinghai Lu Cc: Jesse Barnes Cc: David Woodhouse Cc: Vinod Koul Cc: Dan Williams Signed-off-by: Andrew Morton --- drivers/pci/dmar.c | 7 - drivers/pci/intel-iommu.c | 177 ++++++++++++++++++++++++++++++++++-- include/linux/dmar.h | 5 + 3 files changed, 180 insertions(+), 9 deletions(-) diff -puN drivers/pci/dmar.c~pci-dmar-update-dmar-units-devices-list-during-hotplug drivers/pci/dmar.c --- a/drivers/pci/dmar.c~pci-dmar-update-dmar-units-devices-list-during-hotplug +++ a/drivers/pci/dmar.c @@ -263,7 +263,7 @@ rmrr_parse_dev(struct dmar_rmrr_unit *rm return ret; } -static LIST_HEAD(dmar_atsr_units); +LIST_HEAD(dmar_atsr_units); static int __init dmar_parse_one_atsr(struct acpi_dmar_header *hdr) { @@ -277,6 +277,7 @@ static int __init dmar_parse_one_atsr(st atsru->hdr = hdr; atsru->include_all = atsr->flags & 0x1; + atsru->segment = atsr->segment; list_add(&atsru->list, &dmar_atsr_units); @@ -308,14 +309,12 @@ int dmar_find_matched_atsr_unit(struct p { int i; struct pci_bus *bus; - struct acpi_dmar_atsr *atsr; struct dmar_atsr_unit *atsru; dev = pci_physfn(dev); list_for_each_entry(atsru, &dmar_atsr_units, list) { - atsr = container_of(atsru->hdr, struct acpi_dmar_atsr, header); - if (atsr->segment == pci_domain_nr(dev->bus)) + if (atsru->segment == pci_domain_nr(dev->bus)) goto found; } diff -puN drivers/pci/intel-iommu.c~pci-dmar-update-dmar-units-devices-list-during-hotplug drivers/pci/intel-iommu.c --- a/drivers/pci/intel-iommu.c~pci-dmar-update-dmar-units-devices-list-during-hotplug +++ a/drivers/pci/intel-iommu.c @@ -592,6 +592,157 @@ static struct intel_iommu *device_to_iom return NULL; } +#ifdef CONFIG_HOTPLUG +struct dev_dmaru { + struct list_head list; + void *dmaru; + int index; + int segment; + unsigned char bus; + unsigned int devfn; +}; + +static int +save_dev_dmaru(int segment, unsigned char bus, unsigned int devfn, + void *dmaru, int index, struct list_head *lh) +{ + struct dev_dmaru *m; + + m = kzalloc(sizeof(*m), GFP_KERNEL); + if (!m) + return -ENOMEM; + + m->segment = segment; + m->bus = bus; + m->devfn = devfn; + m->dmaru = dmaru; + m->index = index; + + list_add(&m->list, lh); + + return 0; +} + +static void +*get_dev_dmaru(int segment, unsigned char bus, unsigned int devfn, + int *index, struct list_head *lh) +{ + struct dev_dmaru *m; + void *dmaru = NULL; + + list_for_each_entry(m, lh, list) { + if (m->segment == segment && + m->bus == bus && m->devfn == devfn) { + *index = m->index; + dmaru = m->dmaru; + list_del(&m->list); + kfree(m); + break; + } + } + + return dmaru; +} + +static LIST_HEAD(saved_dev_drhd_list); + +static void remove_dev_from_drhd(struct pci_dev *dev) +{ + struct dmar_drhd_unit *drhd = NULL; + int segment = pci_domain_nr(dev->bus); + int i; + + for_each_drhd_unit(drhd) { + if (drhd->ignored) + continue; + if (segment != drhd->segment) + continue; + + for (i = 0; i < drhd->devices_cnt; i++) { + if (drhd->devices[i] == dev) { + /* save it at first if it is in drhd */ + save_dev_dmaru(segment, dev->bus->number, + dev->devfn, drhd, i, + &saved_dev_drhd_list); + /* always remove it */ + drhd->devices[i] = NULL; + return; + } + } + } +} + +static void restore_dev_to_drhd(struct pci_dev *dev) +{ + struct dmar_drhd_unit *drhd = NULL; + int i; + + /* find the stored drhd */ + drhd = get_dev_dmaru(pci_domain_nr(dev->bus), dev->bus->number, + dev->devfn, &i, &saved_dev_drhd_list); + /* restore that into drhd */ + if (drhd) + drhd->devices[i] = dev; +} +#else +static void remove_dev_from_drhd(struct pci_dev *dev) +{ +} + +static void restore_dev_to_drhd(struct pci_dev *dev) +{ +} +#endif + +#if defined(CONFIG_DMAR) && defined(CONFIG_HOTPLUG) +static LIST_HEAD(saved_dev_atsr_list); + +static void remove_dev_from_atsr(struct pci_dev *dev) +{ + struct dmar_atsr_unit *atsr = NULL; + int segment = pci_domain_nr(dev->bus); + int i; + + for_each_atsr_unit(atsr) { + if (segment != atsr->segment) + continue; + + for (i = 0; i < atsr->devices_cnt; i++) { + if (atsr->devices[i] == dev) { + /* save it at first if it is in drhd */ + save_dev_dmaru(segment, dev->bus->number, + dev->devfn, atsr, i, + &saved_dev_atsr_list); + /* always remove it */ + atsr->devices[i] = NULL; + return; + } + } + } +} + +static void restore_dev_to_atsr(struct pci_dev *dev) +{ + struct dmar_atsr_unit *atsr = NULL; + int i; + + /* find the stored atsr */ + atsr = get_dev_dmaru(pci_domain_nr(dev->bus), dev->bus->number, + dev->devfn, &i, &saved_dev_atsr_list); + /* restore that into atsr */ + if (atsr) + atsr->devices[i] = dev; +} +#else +static void remove_dev_from_atsr(struct pci_dev *dev) +{ +} + +static void restore_dev_to_atsr(struct pci_dev *dev) +{ +} +#endif + static void domain_flush_cache(struct dmar_domain *domain, void *addr, int size) { @@ -3245,20 +3396,36 @@ static int device_notifier(struct notifi struct pci_dev *pdev = to_pci_dev(dev); struct dmar_domain *domain; - if (iommu_no_mapping(dev)) + if (unlikely(dev->bus != &pci_bus_type)) return 0; - domain = find_domain(pdev); - if (!domain) - return 0; + switch (action) { + case BUS_NOTIFY_UNBOUND_DRIVER: + if (iommu_no_mapping(dev)) + goto out; + + if (iommu_pass_through) + goto out; + + domain = find_domain(pdev); + if (!domain) + goto out; - if (action == BUS_NOTIFY_UNBOUND_DRIVER && !iommu_pass_through) { domain_remove_one_dev_info(domain, pdev); if (!(domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE) && !(domain->flags & DOMAIN_FLAG_STATIC_IDENTITY) && list_empty(&domain->devices)) domain_exit(domain); +out: + remove_dev_from_drhd(pdev); + remove_dev_from_atsr(pdev); + + break; + case BUS_NOTIFY_ADD_DEVICE: + restore_dev_to_drhd(pdev); + restore_dev_to_atsr(pdev); + break; } return 0; diff -puN include/linux/dmar.h~pci-dmar-update-dmar-units-devices-list-during-hotplug include/linux/dmar.h --- a/include/linux/dmar.h~pci-dmar-update-dmar-units-devices-list-during-hotplug +++ a/include/linux/dmar.h @@ -219,14 +219,19 @@ struct dmar_rmrr_unit { #define for_each_rmrr_units(rmrr) \ list_for_each_entry(rmrr, &dmar_rmrr_units, list) +extern struct list_head dmar_atsr_units; struct dmar_atsr_unit { struct list_head list; /* list of ATSR units */ struct acpi_dmar_header *hdr; /* ACPI header */ struct pci_dev **devices; /* target devices */ int devices_cnt; /* target device count */ + u16 segment; /* PCI domain */ u8 include_all:1; /* include all ports */ }; +#define for_each_atsr_unit(atsr) \ + list_for_each_entry(atsr, &dmar_atsr_units, list) + extern int intel_iommu_init(void); #else /* !CONFIG_DMAR: */ static inline int intel_iommu_init(void) { return -ENODEV; }