Blame view

virt/kvm/iommu.c 7.1 KB
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /*
   * Copyright (c) 2006, Intel Corporation.
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms and conditions of the GNU General Public License,
   * version 2, as published by the Free Software Foundation.
   *
   * This program is distributed in the hope it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   * more details.
   *
   * You should have received a copy of the GNU General Public License along with
   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
   * Place - Suite 330, Boston, MA 02111-1307 USA.
   *
   * Copyright (C) 2006-2008 Intel Corporation
   * Copyright IBM Corporation, 2008
221d059d1   Avi Kivity   KVM: Update Red H...
19
20
   * Copyright 2010 Red Hat, Inc. and/or its affiliates.
   *
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
21
22
23
24
25
26
27
28
29
   * Author: Allen M. Kay <allen.m.kay@intel.com>
   * Author: Weidong Han <weidong.han@intel.com>
   * Author: Ben-Ami Yassour <benami@il.ibm.com>
   */
  
  #include <linux/list.h>
  #include <linux/kvm_host.h>
  #include <linux/pci.h>
  #include <linux/dmar.h>
19de40a84   Joerg Roedel   KVM: change KVM t...
30
  #include <linux/iommu.h>
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
31
32
33
34
35
  #include <linux/intel-iommu.h>
  
  static int kvm_iommu_unmap_memslots(struct kvm *kvm);
  static void kvm_iommu_put_pages(struct kvm *kvm,
  				gfn_t base_gfn, unsigned long npages);
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  static pfn_t kvm_pin_pages(struct kvm *kvm, struct kvm_memory_slot *slot,
  			   gfn_t gfn, unsigned long size)
  {
  	gfn_t end_gfn;
  	pfn_t pfn;
  
  	pfn     = gfn_to_pfn_memslot(kvm, slot, gfn);
  	end_gfn = gfn + (size >> PAGE_SHIFT);
  	gfn    += 1;
  
  	if (is_error_pfn(pfn))
  		return pfn;
  
  	while (gfn < end_gfn)
  		gfn_to_pfn_memslot(kvm, slot, gfn++);
  
  	return pfn;
  }
3ad26d813   Marcelo Tosatti   KVM: use gfn_to_p...
54
  int kvm_iommu_map_pages(struct kvm *kvm, struct kvm_memory_slot *slot)
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
55
  {
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
56
  	gfn_t gfn, end_gfn;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
57
  	pfn_t pfn;
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
58
  	int r = 0;
19de40a84   Joerg Roedel   KVM: change KVM t...
59
  	struct iommu_domain *domain = kvm->arch.iommu_domain;
522c68c44   Sheng Yang   KVM: Enable snoop...
60
  	int flags;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
61
62
63
64
  
  	/* check if iommu exists and in use */
  	if (!domain)
  		return 0;
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
65
66
  	gfn     = slot->base_gfn;
  	end_gfn = gfn + slot->npages;
522c68c44   Sheng Yang   KVM: Enable snoop...
67
68
69
  	flags = IOMMU_READ | IOMMU_WRITE;
  	if (kvm->arch.iommu_flags & KVM_IOMMU_CACHE_COHERENCY)
  		flags |= IOMMU_CACHE;
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  
  	while (gfn < end_gfn) {
  		unsigned long page_size;
  
  		/* Check if already mapped */
  		if (iommu_iova_to_phys(domain, gfn_to_gpa(gfn))) {
  			gfn += 1;
  			continue;
  		}
  
  		/* Get the page size we could use to map */
  		page_size = kvm_host_page_size(kvm, gfn);
  
  		/* Make sure the page_size does not exceed the memslot */
  		while ((gfn + (page_size >> PAGE_SHIFT)) > end_gfn)
  			page_size >>= 1;
  
  		/* Make sure gfn is aligned to the page size we want to map */
  		while ((gfn << PAGE_SHIFT) & (page_size - 1))
  			page_size >>= 1;
  
  		/*
  		 * Pin all pages we are about to map in memory. This is
  		 * important because we unmap and unpin in 4kb steps later.
  		 */
  		pfn = kvm_pin_pages(kvm, slot, gfn, page_size);
  		if (is_error_pfn(pfn)) {
  			gfn += 1;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
98
  			continue;
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
99
  		}
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
100

fcd95807f   Joerg Roedel   kvm: Change kvm_i...
101
102
103
  		/* Map into IO address space */
  		r = iommu_map(domain, gfn_to_gpa(gfn), pfn_to_hpa(pfn),
  			      get_order(page_size), flags);
e5fcfc821   Weidong Han   KVM: Device Assig...
104
  		if (r) {
260782bcf   Weidong Han   KVM: use the new ...
105
  			printk(KERN_ERR "kvm_iommu_map_address:"
5689cc53f   Joerg Roedel   KVM: Use u64 for ...
106
107
  			       "iommu failed to map pfn=%llx
  ", pfn);
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
108
109
  			goto unmap_pages;
  		}
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
110
111
  
  		gfn += page_size >> PAGE_SHIFT;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
112
  	}
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
113

62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
114
115
116
  	return 0;
  
  unmap_pages:
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
117
  	kvm_iommu_put_pages(kvm, slot->base_gfn, gfn);
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
118
119
120
121
122
  	return r;
  }
  
  static int kvm_iommu_map_memslots(struct kvm *kvm)
  {
95c87e2b4   Sheng Yang   KVM: Fix IOMMU me...
123
  	int i, idx, r = 0;
46a26bf55   Marcelo Tosatti   KVM: modify memsl...
124
  	struct kvm_memslots *slots;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
125

95c87e2b4   Sheng Yang   KVM: Fix IOMMU me...
126
  	idx = srcu_read_lock(&kvm->srcu);
90d83dc3d   Lai Jiangshan   KVM: use the corr...
127
  	slots = kvm_memslots(kvm);
46a26bf55   Marcelo Tosatti   KVM: modify memsl...
128
129
  
  	for (i = 0; i < slots->nmemslots; i++) {
3ad26d813   Marcelo Tosatti   KVM: use gfn_to_p...
130
  		r = kvm_iommu_map_pages(kvm, &slots->memslots[i]);
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
131
132
133
  		if (r)
  			break;
  	}
95c87e2b4   Sheng Yang   KVM: Fix IOMMU me...
134
  	srcu_read_unlock(&kvm->srcu, idx);
682edb4c0   Mark McLoughlin   KVM: Fix assigned...
135

62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
136
137
  	return r;
  }
260782bcf   Weidong Han   KVM: use the new ...
138
139
  int kvm_assign_device(struct kvm *kvm,
  		      struct kvm_assigned_dev_kernel *assigned_dev)
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
140
141
  {
  	struct pci_dev *pdev = NULL;
19de40a84   Joerg Roedel   KVM: change KVM t...
142
  	struct iommu_domain *domain = kvm->arch.iommu_domain;
522c68c44   Sheng Yang   KVM: Enable snoop...
143
  	int r, last_flags;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
144

260782bcf   Weidong Han   KVM: use the new ...
145
146
147
148
149
150
  	/* check if iommu exists and in use */
  	if (!domain)
  		return 0;
  
  	pdev = assigned_dev->dev;
  	if (pdev == NULL)
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
151
  		return -ENODEV;
260782bcf   Weidong Han   KVM: use the new ...
152

19de40a84   Joerg Roedel   KVM: change KVM t...
153
  	r = iommu_attach_device(domain, &pdev->dev);
260782bcf   Weidong Han   KVM: use the new ...
154
  	if (r) {
ab9f4ecbb   Zhai, Edwin   KVM: enable PCI m...
155
156
  		printk(KERN_ERR "assign device %x:%x:%x.%x failed",
  			pci_domain_nr(pdev->bus),
260782bcf   Weidong Han   KVM: use the new ...
157
158
159
160
  			pdev->bus->number,
  			PCI_SLOT(pdev->devfn),
  			PCI_FUNC(pdev->devfn));
  		return r;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
161
  	}
522c68c44   Sheng Yang   KVM: Enable snoop...
162
163
164
165
166
167
168
169
170
171
172
173
174
  	last_flags = kvm->arch.iommu_flags;
  	if (iommu_domain_has_cap(kvm->arch.iommu_domain,
  				 IOMMU_CAP_CACHE_COHERENCY))
  		kvm->arch.iommu_flags |= KVM_IOMMU_CACHE_COHERENCY;
  
  	/* Check if need to update IOMMU page table for guest memory */
  	if ((last_flags ^ kvm->arch.iommu_flags) ==
  			KVM_IOMMU_CACHE_COHERENCY) {
  		kvm_iommu_unmap_memslots(kvm);
  		r = kvm_iommu_map_memslots(kvm);
  		if (r)
  			goto out_unmap;
  	}
ab9f4ecbb   Zhai, Edwin   KVM: enable PCI m...
175
176
177
  	printk(KERN_DEBUG "assign device %x:%x:%x.%x
  ",
  		assigned_dev->host_segnr,
260782bcf   Weidong Han   KVM: use the new ...
178
179
180
  		assigned_dev->host_busnr,
  		PCI_SLOT(assigned_dev->host_devfn),
  		PCI_FUNC(assigned_dev->host_devfn));
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
181

260782bcf   Weidong Han   KVM: use the new ...
182
  	return 0;
522c68c44   Sheng Yang   KVM: Enable snoop...
183
184
185
  out_unmap:
  	kvm_iommu_unmap_memslots(kvm);
  	return r;
260782bcf   Weidong Han   KVM: use the new ...
186
  }
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
187

0a9203567   Weidong Han   KVM: support devi...
188
189
190
  int kvm_deassign_device(struct kvm *kvm,
  			struct kvm_assigned_dev_kernel *assigned_dev)
  {
19de40a84   Joerg Roedel   KVM: change KVM t...
191
  	struct iommu_domain *domain = kvm->arch.iommu_domain;
0a9203567   Weidong Han   KVM: support devi...
192
193
194
195
196
197
198
199
200
  	struct pci_dev *pdev = NULL;
  
  	/* check if iommu exists and in use */
  	if (!domain)
  		return 0;
  
  	pdev = assigned_dev->dev;
  	if (pdev == NULL)
  		return -ENODEV;
19de40a84   Joerg Roedel   KVM: change KVM t...
201
  	iommu_detach_device(domain, &pdev->dev);
0a9203567   Weidong Han   KVM: support devi...
202

ab9f4ecbb   Zhai, Edwin   KVM: enable PCI m...
203
204
205
  	printk(KERN_DEBUG "deassign device %x:%x:%x.%x
  ",
  		assigned_dev->host_segnr,
0a9203567   Weidong Han   KVM: support devi...
206
207
208
209
210
211
  		assigned_dev->host_busnr,
  		PCI_SLOT(assigned_dev->host_devfn),
  		PCI_FUNC(assigned_dev->host_devfn));
  
  	return 0;
  }
260782bcf   Weidong Han   KVM: use the new ...
212
213
214
  int kvm_iommu_map_guest(struct kvm *kvm)
  {
  	int r;
19de40a84   Joerg Roedel   KVM: change KVM t...
215
216
217
  	if (!iommu_found()) {
  		printk(KERN_ERR "%s: iommu not found
  ", __func__);
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
218
219
  		return -ENODEV;
  	}
19de40a84   Joerg Roedel   KVM: change KVM t...
220
221
  	kvm->arch.iommu_domain = iommu_domain_alloc();
  	if (!kvm->arch.iommu_domain)
260782bcf   Weidong Han   KVM: use the new ...
222
  		return -ENOMEM;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
223
224
225
226
  
  	r = kvm_iommu_map_memslots(kvm);
  	if (r)
  		goto out_unmap;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
227
228
229
230
231
232
  	return 0;
  
  out_unmap:
  	kvm_iommu_unmap_memslots(kvm);
  	return r;
  }
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
233
234
235
236
237
238
239
  static void kvm_unpin_pages(struct kvm *kvm, pfn_t pfn, unsigned long npages)
  {
  	unsigned long i;
  
  	for (i = 0; i < npages; ++i)
  		kvm_release_pfn_clean(pfn + i);
  }
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
240
  static void kvm_iommu_put_pages(struct kvm *kvm,
260782bcf   Weidong Han   KVM: use the new ...
241
  				gfn_t base_gfn, unsigned long npages)
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
242
  {
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
243
244
  	struct iommu_domain *domain;
  	gfn_t end_gfn, gfn;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
245
  	pfn_t pfn;
260782bcf   Weidong Han   KVM: use the new ...
246
  	u64 phys;
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
247
248
249
  	domain  = kvm->arch.iommu_domain;
  	end_gfn = base_gfn + npages;
  	gfn     = base_gfn;
260782bcf   Weidong Han   KVM: use the new ...
250
251
252
  	/* check if iommu exists and in use */
  	if (!domain)
  		return;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
253

fcd95807f   Joerg Roedel   kvm: Change kvm_i...
254
255
256
257
258
  	while (gfn < end_gfn) {
  		unsigned long unmap_pages;
  		int order;
  
  		/* Get physical address */
19de40a84   Joerg Roedel   KVM: change KVM t...
259
  		phys = iommu_iova_to_phys(domain, gfn_to_gpa(gfn));
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
260
261
262
  		pfn  = phys >> PAGE_SHIFT;
  
  		/* Unmap address from IO address space */
05b782ab9   Jan Kiszka   KVM: Fix order pa...
263
  		order       = iommu_unmap(domain, gfn_to_gpa(gfn), 0);
fcd95807f   Joerg Roedel   kvm: Change kvm_i...
264
  		unmap_pages = 1ULL << order;
260782bcf   Weidong Han   KVM: use the new ...
265

fcd95807f   Joerg Roedel   kvm: Change kvm_i...
266
267
268
269
270
  		/* Unpin all pages we just unmapped to not leak any memory */
  		kvm_unpin_pages(kvm, pfn, unmap_pages);
  
  		gfn += unmap_pages;
  	}
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
271
272
273
274
  }
  
  static int kvm_iommu_unmap_memslots(struct kvm *kvm)
  {
95c87e2b4   Sheng Yang   KVM: Fix IOMMU me...
275
  	int i, idx;
46a26bf55   Marcelo Tosatti   KVM: modify memsl...
276
  	struct kvm_memslots *slots;
95c87e2b4   Sheng Yang   KVM: Fix IOMMU me...
277
  	idx = srcu_read_lock(&kvm->srcu);
90d83dc3d   Lai Jiangshan   KVM: use the corr...
278
  	slots = kvm_memslots(kvm);
682edb4c0   Mark McLoughlin   KVM: Fix assigned...
279

46a26bf55   Marcelo Tosatti   KVM: modify memsl...
280
281
282
  	for (i = 0; i < slots->nmemslots; i++) {
  		kvm_iommu_put_pages(kvm, slots->memslots[i].base_gfn,
  				    slots->memslots[i].npages);
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
283
  	}
95c87e2b4   Sheng Yang   KVM: Fix IOMMU me...
284
  	srcu_read_unlock(&kvm->srcu, idx);
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
285
286
287
288
289
290
  
  	return 0;
  }
  
  int kvm_iommu_unmap_guest(struct kvm *kvm)
  {
19de40a84   Joerg Roedel   KVM: change KVM t...
291
  	struct iommu_domain *domain = kvm->arch.iommu_domain;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
292
293
294
295
  
  	/* check if iommu exists and in use */
  	if (!domain)
  		return 0;
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
296
  	kvm_iommu_unmap_memslots(kvm);
19de40a84   Joerg Roedel   KVM: change KVM t...
297
  	iommu_domain_free(domain);
62c476c7c   Ben-Ami Yassour   KVM: Device Assig...
298
299
  	return 0;
  }