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 vtd_iommu * iommu)33 struct acpi_drhd_unit *find_ats_dev_drhd(struct vtd_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 vtd_iommu * iommu,const struct pci_dev * pdev,uint16_t did)74 static bool device_in_domain(const struct vtd_iommu *iommu,
75 const struct pci_dev *pdev, uint16_t did)
76 {
77 struct root_entry *root_entry;
78 struct context_entry *ctxt_entry = NULL;
79 unsigned int tt;
80 bool found = false;
81
82 if ( unlikely(!iommu->root_maddr) )
83 {
84 ASSERT_UNREACHABLE();
85 return false;
86 }
87
88 root_entry = map_vtd_domain_page(iommu->root_maddr);
89 if ( !root_present(root_entry[pdev->bus]) )
90 goto out;
91
92 ctxt_entry = map_vtd_domain_page(root_entry[pdev->bus].val);
93 if ( context_domain_id(ctxt_entry[pdev->devfn]) != did )
94 goto out;
95
96 tt = context_translation_type(ctxt_entry[pdev->devfn]);
97 if ( tt != CONTEXT_TT_DEV_IOTLB )
98 goto out;
99
100 found = true;
101 out:
102 if ( root_entry )
103 unmap_vtd_domain_page(root_entry);
104
105 if ( ctxt_entry )
106 unmap_vtd_domain_page(ctxt_entry);
107
108 return found;
109 }
110
dev_invalidate_iotlb(struct vtd_iommu * iommu,u16 did,u64 addr,unsigned int size_order,u64 type)111 int dev_invalidate_iotlb(struct vtd_iommu *iommu, u16 did,
112 u64 addr, unsigned int size_order, u64 type)
113 {
114 struct pci_dev *pdev, *temp;
115 int ret = 0;
116
117 if ( !ecap_dev_iotlb(iommu->ecap) )
118 return ret;
119
120 list_for_each_entry_safe( pdev, temp, &iommu->ats_devices, ats.list )
121 {
122 bool_t sbit;
123 int rc = 0;
124
125 switch ( type )
126 {
127 case DMA_TLB_DSI_FLUSH:
128 if ( !device_in_domain(iommu, pdev, did) )
129 break;
130 /* fall through if DSI condition met */
131 case DMA_TLB_GLOBAL_FLUSH:
132 /* invalidate all translations: sbit=1,bit_63=0,bit[62:12]=1 */
133 sbit = 1;
134 addr = (~0UL << PAGE_SHIFT_4K) & 0x7FFFFFFFFFFFFFFF;
135 rc = qinval_device_iotlb_sync(iommu, pdev, did, sbit, addr);
136 break;
137 case DMA_TLB_PSI_FLUSH:
138 if ( !device_in_domain(iommu, pdev, did) )
139 break;
140
141 /* if size <= 4K, set sbit = 0, else set sbit = 1 */
142 sbit = size_order ? 1 : 0;
143
144 /* clear lower bits */
145 addr &= ~0UL << PAGE_SHIFT_4K;
146
147 /* if sbit == 1, zero out size_order bit and set lower bits to 1 */
148 if ( sbit )
149 {
150 addr &= ~((u64)PAGE_SIZE_4K << (size_order - 1));
151 addr |= (((u64)1 << (size_order - 1)) - 1) << PAGE_SHIFT_4K;
152 }
153
154 rc = qinval_device_iotlb_sync(iommu, pdev, did, sbit, addr);
155 break;
156 default:
157 dprintk(XENLOG_WARNING VTDPREFIX, "invalid vt-d flush type\n");
158 return -EOPNOTSUPP;
159 }
160
161 if ( !ret )
162 ret = rc;
163 }
164
165 return ret;
166 }
167