Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


0439b51243ae791e6076c1df0a64c9c262ce1681
[palacios.git] / linux_module / iface-host-pci-hw.h
1 /* Linux host side PCI passthrough support
2  * Jack Lange <jacklange@cs.pitt.edu>, 2012
3  */
4
5 #include <linux/pci.h>
6 #include <linux/iommu.h>
7 #include <linux/interrupt.h>
8 #include <linux/version.h>
9
10
11 #define PCI_HDR_SIZE 256
12
13
14 static int setup_hw_pci_dev(struct host_pci_device * host_dev) {
15     int ret = 0;
16     struct pci_dev * dev = NULL;
17     struct v3_host_pci_dev * v3_dev = &(host_dev->v3_dev);
18
19     dev = pci_get_bus_and_slot(host_dev->hw_dev.bus,
20                                host_dev->hw_dev.devfn);
21
22
23     if (dev == NULL) {
24         printk("Could not find HW pci device (bus=%d, devfn=%d)\n", 
25                host_dev->hw_dev.bus, host_dev->hw_dev.devfn); 
26         return -1;
27     }
28
29     // record pointer in dev state
30     host_dev->hw_dev.dev = dev;
31
32     host_dev->hw_dev.intx_disabled = 1;
33     palacios_spinlock_init(&(host_dev->hw_dev.intx_lock));
34
35     if (pci_enable_device(dev)) {
36         printk("Could not enable Device\n");
37         return -1;
38     }
39     
40     ret = pci_request_regions(dev, "v3vee");
41     if (ret != 0) {
42         printk("Could not reservce PCI regions\n");
43         return -1;
44     }
45
46
47     pci_reset_function(host_dev->hw_dev.dev);
48     pci_save_state(host_dev->hw_dev.dev);
49
50
51     {
52         int i = 0;
53         for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
54             printk("Resource %d\n", i);
55             printk("\tflags = 0x%lx\n", pci_resource_flags(dev, i));
56             printk("\t name=%s, start=%lx, size=%d\n", 
57                    host_dev->hw_dev.dev->resource[i].name, (uintptr_t)pci_resource_start(dev, i),
58                    (u32)pci_resource_len(dev, i));
59
60         }
61
62         printk("Rom BAR=%d\n", dev->rom_base_reg);
63     }
64
65     /* Cache first 6 BAR regs */
66     {
67         int i = 0;
68
69         for (i = 0; i < 6; i++) {
70             struct v3_host_pci_bar * bar = &(v3_dev->bars[i]);
71             unsigned long flags;
72             
73             bar->size = pci_resource_len(dev, i);
74             bar->addr = pci_resource_start(dev, i);
75             flags = pci_resource_flags(dev, i);
76
77             if (flags & IORESOURCE_IO) {
78                 bar->type = PT_BAR_IO;
79             } else if (flags & IORESOURCE_MEM) {
80                 if (flags & IORESOURCE_MEM_64) {
81                     struct v3_host_pci_bar * hi_bar = &(v3_dev->bars[i + 1]); 
82             
83                     bar->type = PT_BAR_MEM64_LO;
84
85                     hi_bar->type = PT_BAR_MEM64_HI;
86                     hi_bar->size = bar->size;
87                     hi_bar->addr = bar->addr;
88                     hi_bar->cacheable = ((flags & IORESOURCE_CACHEABLE) != 0);
89                     hi_bar->prefetchable = ((flags & IORESOURCE_PREFETCH) != 0);
90                     
91                     i++;
92                 } else if (flags & IORESOURCE_DMA) {
93                     bar->type = PT_BAR_MEM24;
94                 } else {
95                     bar->type = PT_BAR_MEM32;
96                 }
97                 
98                 bar->cacheable = ((flags & IORESOURCE_CACHEABLE) != 0);
99                 bar->prefetchable = ((flags & IORESOURCE_PREFETCH) != 0);
100
101             } else {
102                 bar->type = PT_BAR_NONE;
103             }
104         }
105     }
106
107     /* Cache expansion rom bar */
108     {
109         struct resource * rom_res = &(dev->resource[PCI_ROM_RESOURCE]);
110         int rom_size = pci_resource_len(dev, PCI_ROM_RESOURCE);
111
112         if (rom_size > 0) {
113           //unsigned long flags;
114
115             v3_dev->exp_rom.size = rom_size;
116             v3_dev->exp_rom.addr = pci_resource_start(dev, PCI_ROM_RESOURCE);
117             // flags = pci_resource_flags(dev, PCI_ROM_RESOURCE); 
118
119             v3_dev->exp_rom.type = PT_EXP_ROM;
120
121             v3_dev->exp_rom.exp_rom_enabled = rom_res->flags & IORESOURCE_ROM_ENABLE;
122         }
123     }
124
125     /* Cache entire configuration space */
126     {
127         int m = 0;
128
129         // Copy the configuration space to the local cached version
130         for (m = 0; m < PCI_HDR_SIZE; m += 4) {
131             pci_read_config_dword(dev, m, (u32 *)&(v3_dev->cfg_space[m]));
132         }
133     }
134
135
136     /* HARDCODED for now but this will need to depend on IOMMU support detection */
137     if (iommu_found()) {
138         printk("Setting host PCI device (%s) as IOMMU\n", host_dev->name);
139         v3_dev->iface = IOMMU;
140     } else {
141         printk("Setting host PCI device (%s) as SYMBIOTIC\n", host_dev->name);
142         v3_dev->iface = SYMBIOTIC;
143     }
144
145     return 0;
146
147 }
148
149
150
151 static irqreturn_t host_pci_intx_irq_handler(int irq, void * priv_data) {
152     struct host_pci_device * host_dev = priv_data;
153
154     //   printk("Host PCI IRQ handler (%d)\n", irq);
155
156     palacios_spinlock_lock(&(host_dev->hw_dev.intx_lock));
157     disable_irq_nosync(irq);
158     host_dev->hw_dev.intx_disabled = 1;
159     palacios_spinlock_unlock(&(host_dev->hw_dev.intx_lock));
160
161     V3_host_pci_raise_irq(&(host_dev->v3_dev), 0);
162
163     return IRQ_HANDLED;
164 }
165
166
167
168 static irqreturn_t host_pci_msi_irq_handler(int irq, void * priv_data) {
169     struct host_pci_device * host_dev = priv_data;
170     //    printk("Host PCI MSI IRQ Handler (%d)\n", irq);
171
172     V3_host_pci_raise_irq(&(host_dev->v3_dev), 0);
173
174     return IRQ_HANDLED;
175 }
176
177 static irqreturn_t host_pci_msix_irq_handler(int irq, void * priv_data) {
178     struct host_pci_device * host_dev = priv_data;
179     int i = 0;
180     
181     //    printk("Host PCI MSIX IRQ Handler (%d)\n", irq);
182     
183     // find vector index
184     for (i = 0; i < host_dev->hw_dev.num_msix_vecs; i++) {
185         if (irq == host_dev->hw_dev.msix_entries[i].vector) {
186             V3_host_pci_raise_irq(&(host_dev->v3_dev), i);
187         } else {
188             printk("Error Could not find matching MSIX vector for IRQ %d\n", irq);
189         }
190     }    
191     return IRQ_HANDLED;
192 }
193
194
195 static int hw_pci_cmd(struct host_pci_device * host_dev, host_pci_cmd_t cmd, u64 arg) {
196     //struct v3_host_pci_dev * v3_dev = &(host_dev->v3_dev);
197     struct pci_dev * dev = host_dev->hw_dev.dev;
198
199     switch (cmd) {
200         case HOST_PCI_CMD_DMA_DISABLE:
201             printk("Passthrough PCI device disabling BMDMA\n");
202             pci_clear_master(host_dev->hw_dev.dev);
203             break;
204         case HOST_PCI_CMD_DMA_ENABLE:
205             printk("Passthrough PCI device Enabling BMDMA\n");
206             pci_set_master(host_dev->hw_dev.dev);
207             break;
208
209         case HOST_PCI_CMD_INTX_DISABLE:
210             printk("Passthrough PCI device disabling INTx IRQ\n");
211
212             disable_irq(dev->irq);
213             free_irq(dev->irq, (void *)host_dev);
214
215             break;
216         case HOST_PCI_CMD_INTX_ENABLE:
217             printk("Passthrough PCI device Enabling INTx IRQ\n");
218         
219             if (request_threaded_irq(dev->irq, NULL, host_pci_intx_irq_handler, 
220                                      IRQF_ONESHOT, "V3Vee_Host_PCI_INTx", (void *)host_dev)) {
221                 printk("ERROR Could not assign IRQ to host PCI device (%s)\n", host_dev->name);
222             }
223
224             break;
225
226         case HOST_PCI_CMD_MSI_DISABLE:
227             printk("Passthrough PCI device Disabling MSIs\n");
228
229             disable_irq(dev->irq);
230             free_irq(dev->irq, (void *)host_dev);
231
232             pci_disable_msi(dev);
233
234             break;
235         case HOST_PCI_CMD_MSI_ENABLE:
236             printk("Passthrough PCI device Enabling MSI\n");
237             
238             if (!dev->msi_enabled) {
239                 pci_enable_msi(dev);
240
241                 if (request_irq(dev->irq, host_pci_msi_irq_handler, 
242                                 0, "V3Vee_host_PCI_MSI", (void *)host_dev)) {
243                     printk("Error Requesting IRQ %d for Passthrough MSI IRQ\n", dev->irq);
244                 }
245             }
246
247             break;
248
249
250
251         case HOST_PCI_CMD_MSIX_ENABLE: {
252             int i = 0;
253             
254             printk("Passthrough PCI device Enabling MSIX\n");
255             host_dev->hw_dev.num_msix_vecs = arg;;
256             host_dev->hw_dev.msix_entries = kcalloc(host_dev->hw_dev.num_msix_vecs, 
257                                                     sizeof(struct msix_entry), GFP_KERNEL);
258             
259             for (i = 0; i < host_dev->hw_dev.num_msix_vecs; i++) {
260                 host_dev->hw_dev.msix_entries[i].entry = i;
261             }
262             
263             pci_enable_msix(dev, host_dev->hw_dev.msix_entries, 
264                             host_dev->hw_dev.num_msix_vecs);
265             
266             for (i = 0; i < host_dev->hw_dev.num_msix_vecs; i++) {
267                 if (request_irq(host_dev->hw_dev.msix_entries[i].vector, 
268                                 host_pci_msix_irq_handler, 
269                                 0, "V3VEE_host_PCI_MSIX", (void *)host_dev)) {
270                     printk("Error requesting IRQ %d for Passthrough MSIX IRQ\n", 
271                            host_dev->hw_dev.msix_entries[i].vector);
272                 }
273             }
274
275             break;
276         }
277
278         case HOST_PCI_CMD_MSIX_DISABLE: {
279             int i = 0;
280
281             printk("Passthrough PCI device Disabling MSIX\n");
282             
283             for (i = 0; i < host_dev->hw_dev.num_msix_vecs; i++) {
284                 disable_irq(host_dev->hw_dev.msix_entries[i].vector);
285             }
286
287             for (i = 0; i < host_dev->hw_dev.num_msix_vecs; i++) {
288                 free_irq(host_dev->hw_dev.msix_entries[i].vector, (void *)host_dev);
289             }
290
291             host_dev->hw_dev.num_msix_vecs = 0;
292             palacios_free(host_dev->hw_dev.msix_entries);
293
294             pci_disable_msix(dev);
295
296             break;
297         }
298         default:
299             printk("Error: unhandled passthrough PCI command: %d\n", cmd);
300             return -1;
301            
302     }
303
304     return 0;
305 }
306
307
308 static int hw_ack_irq(struct host_pci_device * host_dev, u32 vector) {
309     struct pci_dev * dev = host_dev->hw_dev.dev;
310     unsigned long flags;
311
312     //    printk("Acking IRQ vector %d\n", vector);
313
314     palacios_spinlock_lock_irqsave(&(host_dev->hw_dev.intx_lock), flags);
315     //    printk("Enabling IRQ %d\n", dev->irq);
316     enable_irq(dev->irq);
317     host_dev->hw_dev.intx_disabled = 0;
318     palacios_spinlock_unlock_irqrestore(&(host_dev->hw_dev.intx_lock), flags);
319     
320     return 0;
321 }
322
323
324
325
326 static int reserve_hw_pci_dev(struct host_pci_device * host_dev, void * v3_ctx) {
327     int ret = 0;
328     unsigned long flags;
329     struct v3_host_pci_dev * v3_dev = &(host_dev->v3_dev);
330     struct pci_dev * dev = host_dev->hw_dev.dev;
331
332     palacios_spinlock_lock_irqsave(&lock, flags);
333     if (host_dev->hw_dev.in_use == 0) {
334         host_dev->hw_dev.in_use = 1;
335     } else {
336         ret = -1;
337     }
338     palacios_spinlock_unlock_irqrestore(&lock, flags);
339
340
341     if (v3_dev->iface == IOMMU) {
342         struct v3_guest_mem_region region;
343         int flags = 0;
344         uintptr_t gpa = 0;
345
346         host_dev->hw_dev.iommu_domain = iommu_domain_alloc();
347
348         while (V3_get_guest_mem_region(v3_ctx, &region, gpa)) {
349         
350             printk("Memory region: start=%p, end=%p\n", (void *)region.start, (void *)region.end);
351
352
353             flags = IOMMU_READ | IOMMU_WRITE; // Need to see what IOMMU_CACHE means
354         
355             /* This version could be wrong */
356 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) 
357             // Guest VAs start at zero and go to end of memory
358             iommu_map_range(host_dev->hw_dev.iommu_domain, 0, region.start, (region.end - region.start), flags);
359 #else 
360             /* Linux actually made the interface worse... Now you can only map memory in powers of 2 (meant to only be pages...) */
361             {   
362                 u64 size = region.end - region.start;
363                 u32 page_size = 512 * 4096; // assume large 64bit pages (2MB)
364                 u64 hpa = region.start;
365
366                 do {
367                     if (size < page_size) {
368                         page_size = 4096; // less than a 2MB granularity, so we switch to small pages (4KB)
369                     }
370                     
371                     printk("Mapping IOMMU region gpa=%p hpa=%p (size=%d)\n", (void *)gpa, (void *)hpa, page_size);
372                     
373                     if (iommu_map(host_dev->hw_dev.iommu_domain, gpa, hpa, 
374                                   get_order(page_size), flags)) {
375                         printk("ERROR: Could not map sub region (GPA=%p) (HPA=%p) (order=%d)\n", 
376                                (void *)gpa, (void *)hpa, get_order(page_size));
377                         break;
378                     }
379                     
380                     hpa += page_size;
381                     gpa += page_size;
382                     
383                     size -= page_size;
384                 } while (size > 0);
385             }
386 #endif
387         }
388
389         if (iommu_attach_device(host_dev->hw_dev.iommu_domain, &(dev->dev))) {
390             printk("ERROR attaching host PCI device to IOMMU domain\n");
391         }
392
393     }
394
395
396     printk("Requesting Threaded IRQ handler for IRQ %d\n", dev->irq);
397     // setup regular IRQs until advanced IRQ mechanisms are enabled
398     if (request_threaded_irq(dev->irq, NULL, host_pci_intx_irq_handler, 
399                              IRQF_ONESHOT, "V3Vee_Host_PCI_INTx", (void *)host_dev)) {
400         printk("ERROR Could not assign IRQ to host PCI device (%s)\n", host_dev->name);
401     }
402
403
404
405     
406     return ret;
407 }
408
409
410
411 static int write_hw_pci_config(struct host_pci_device * host_dev, u32 reg, void * data, u32 length) {
412     struct pci_dev * dev = host_dev->hw_dev.dev;
413
414     if (reg < 64) {
415         return 0;
416     }
417         
418     if (length == 1) {
419         pci_write_config_byte(dev, reg, *(u8 *)data);
420     } else if (length == 2) {
421         pci_write_config_word(dev, reg, *(u16 *)data);
422     } else if (length == 4) {
423         pci_write_config_dword(dev, reg, *(u32 *)data);
424     } else {
425         printk("Invalid length of host PCI config update\n");
426         return -1;
427     }
428     
429     return 0;
430 }
431
432
433
434 static int read_hw_pci_config(struct host_pci_device * host_dev, u32 reg, void * data, u32 length) {
435     struct pci_dev * dev = host_dev->hw_dev.dev;
436
437         
438     if (length == 1) {
439         pci_read_config_byte(dev, reg, data);
440     } else if (length == 2) {
441         pci_read_config_word(dev, reg, data);
442     } else if (length == 4) {
443         pci_read_config_dword(dev, reg, data);
444     } else {
445         printk("Invalid length of host PCI config read\n");
446         return -1;
447     }
448
449
450     return 0; 
451 }
452
453
454 //
455 // Should be a matching teardown function here, otherwise we
456 // are at least leaking the lock from the lockchecker's perspective
457 // we would like to be able to do a palacios_spinlock_deinit() here...