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