1 /*
2 * Copyright (c) 2006, Intel Corporation.
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along with
14 * this program; If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Allen Kay <allen.m.kay@intel.com>
17 */
18
19 #include <xen/sched.h>
20 #include <xen/iommu.h>
21 #include <xen/time.h>
22 #include <xen/pci.h>
23 #include <xen/pci_regs.h>
24 #include <asm/msi.h>
25 #include "../iommu.h"
26 #include "../dmar.h"
27 #include "../vtd.h"
28 #include "../extern.h"
29 #include "../../ats.h"
30
31 static LIST_HEAD(ats_dev_drhd_units);
32
find_ats_dev_drhd(struct iommu * iommu)33 struct acpi_drhd_unit * find_ats_dev_drhd(struct iommu *iommu)
34 {
35 struct acpi_drhd_unit *drhd;
36 list_for_each_entry ( drhd, &ats_dev_drhd_units, list )
37 {
38 if ( drhd->iommu == iommu )
39 return drhd;
40 }
41 return NULL;
42 }
43
ats_device(const struct pci_dev * pdev,const struct acpi_drhd_unit * drhd)44 int ats_device(const struct pci_dev *pdev, const struct acpi_drhd_unit *drhd)
45 {
46 struct acpi_drhd_unit *ats_drhd;
47 int pos;
48
49 if ( !ats_enabled || !iommu_qinval )
50 return 0;
51
52 if ( !ecap_queued_inval(drhd->iommu->ecap) ||
53 !ecap_dev_iotlb(drhd->iommu->ecap) )
54 return 0;
55
56 if ( !acpi_find_matched_atsr_unit(pdev) )
57 return 0;
58
59 ats_drhd = find_ats_dev_drhd(drhd->iommu);
60 pos = pci_find_ext_capability(pdev->seg, pdev->bus, pdev->devfn,
61 PCI_EXT_CAP_ID_ATS);
62
63 if ( pos && (ats_drhd == NULL) )
64 {
65 ats_drhd = xmalloc(struct acpi_drhd_unit);
66 if ( !ats_drhd )
67 return -ENOMEM;
68 *ats_drhd = *drhd;
69 list_add_tail(&ats_drhd->list, &ats_dev_drhd_units);
70 }
71 return pos;
72 }
73
device_in_domain(const struct iommu * iommu,const struct pci_dev * pdev,u16 did)74 static int device_in_domain(const struct iommu *iommu,
75 const struct pci_dev *pdev, u16 did)
76 {
77 struct root_entry *root_entry = NULL;
78 struct context_entry *ctxt_entry = NULL;
79 int tt, found = 0;
80
81 root_entry = (struct root_entry *) map_vtd_domain_page(iommu->root_maddr);
82 if ( !root_entry || !root_present(root_entry[pdev->bus]) )
83 goto out;
84
85 ctxt_entry = (struct context_entry *)
86 map_vtd_domain_page(root_entry[pdev->bus].val);
87
88 if ( ctxt_entry == NULL )
89 goto out;
90
91 if ( context_domain_id(ctxt_entry[pdev->devfn]) != did )
92 goto out;
93
94 tt = context_translation_type(ctxt_entry[pdev->devfn]);
95 if ( tt != CONTEXT_TT_DEV_IOTLB )
96 goto out;
97
98 found = 1;
99 out:
100 if ( root_entry )
101 unmap_vtd_domain_page(root_entry);
102
103 if ( ctxt_entry )
104 unmap_vtd_domain_page(ctxt_entry);
105
106 return found;
107 }
108
dev_invalidate_iotlb(struct iommu * iommu,u16 did,u64 addr,unsigned int size_order,u64 type)109 int dev_invalidate_iotlb(struct iommu *iommu, u16 did,
110 u64 addr, unsigned int size_order, u64 type)
111 {
112 struct pci_dev *pdev, *temp;
113 int ret = 0;
114
115 if ( !ecap_dev_iotlb(iommu->ecap) )
116 return ret;
117
118 list_for_each_entry_safe( pdev, temp, &iommu->ats_devices, ats.list )
119 {
120 bool_t sbit;
121 int rc = 0;
122
123 switch ( type )
124 {
125 case DMA_TLB_DSI_FLUSH:
126 if ( !device_in_domain(iommu, pdev, did) )
127 break;
128 /* fall through if DSI condition met */
129 case DMA_TLB_GLOBAL_FLUSH:
130 /* invalidate all translations: sbit=1,bit_63=0,bit[62:12]=1 */
131 sbit = 1;
132 addr = (~0UL << PAGE_SHIFT_4K) & 0x7FFFFFFFFFFFFFFF;
133 rc = qinval_device_iotlb_sync(iommu, pdev, did, sbit, addr);
134 break;
135 case DMA_TLB_PSI_FLUSH:
136 if ( !device_in_domain(iommu, pdev, did) )
137 break;
138
139 /* if size <= 4K, set sbit = 0, else set sbit = 1 */
140 sbit = size_order ? 1 : 0;
141
142 /* clear lower bits */
143 addr &= ~0UL << PAGE_SHIFT_4K;
144
145 /* if sbit == 1, zero out size_order bit and set lower bits to 1 */
146 if ( sbit )
147 {
148 addr &= ~((u64)PAGE_SIZE_4K << (size_order - 1));
149 addr |= (((u64)1 << (size_order - 1)) - 1) << PAGE_SHIFT_4K;
150 }
151
152 rc = qinval_device_iotlb_sync(iommu, pdev, did, sbit, addr);
153 break;
154 default:
155 dprintk(XENLOG_WARNING VTDPREFIX, "invalid vt-d flush type\n");
156 return -EOPNOTSUPP;
157 }
158
159 if ( !ret )
160 ret = rc;
161 }
162
163 return ret;
164 }
165