| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright (C) 2004 Matthew Wilcox <matthew@wil.cx> | 
|  | 3 | * Copyright (C) 2004 Intel Corp. | 
|  | 4 | * | 
|  | 5 | * This code is released under the GNU General Public License version 2. | 
|  | 6 | */ | 
|  | 7 |  | 
|  | 8 | /* | 
|  | 9 | * mmconfig.c - Low-level direct PCI config space access via MMCONFIG | 
|  | 10 | */ | 
|  | 11 |  | 
|  | 12 | #include <linux/pci.h> | 
|  | 13 | #include <linux/init.h> | 
|  | 14 | #include "pci.h" | 
|  | 15 |  | 
|  | 16 | /* The physical address of the MMCONFIG aperture.  Set from ACPI tables. */ | 
|  | 17 | u32 pci_mmcfg_base_addr; | 
|  | 18 |  | 
|  | 19 | #define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG)) | 
|  | 20 |  | 
|  | 21 | /* The base address of the last MMCONFIG device accessed */ | 
|  | 22 | static u32 mmcfg_last_accessed_device; | 
|  | 23 |  | 
|  | 24 | /* | 
|  | 25 | * Functions for accessing PCI configuration space with MMCONFIG accesses | 
|  | 26 | */ | 
|  | 27 |  | 
|  | 28 | static inline void pci_exp_set_dev_base(int bus, int devfn) | 
|  | 29 | { | 
|  | 30 | u32 dev_base = pci_mmcfg_base_addr | (bus << 20) | (devfn << 12); | 
|  | 31 | if (dev_base != mmcfg_last_accessed_device) { | 
|  | 32 | mmcfg_last_accessed_device = dev_base; | 
|  | 33 | set_fixmap_nocache(FIX_PCIE_MCFG, dev_base); | 
|  | 34 | } | 
|  | 35 | } | 
|  | 36 |  | 
|  | 37 | static int pci_mmcfg_read(unsigned int seg, unsigned int bus, | 
|  | 38 | unsigned int devfn, int reg, int len, u32 *value) | 
|  | 39 | { | 
|  | 40 | unsigned long flags; | 
|  | 41 |  | 
|  | 42 | if (!value || (bus > 255) || (devfn > 255) || (reg > 4095)) | 
|  | 43 | return -EINVAL; | 
|  | 44 |  | 
|  | 45 | spin_lock_irqsave(&pci_config_lock, flags); | 
|  | 46 |  | 
|  | 47 | pci_exp_set_dev_base(bus, devfn); | 
|  | 48 |  | 
|  | 49 | switch (len) { | 
|  | 50 | case 1: | 
|  | 51 | *value = readb(mmcfg_virt_addr + reg); | 
|  | 52 | break; | 
|  | 53 | case 2: | 
|  | 54 | *value = readw(mmcfg_virt_addr + reg); | 
|  | 55 | break; | 
|  | 56 | case 4: | 
|  | 57 | *value = readl(mmcfg_virt_addr + reg); | 
|  | 58 | break; | 
|  | 59 | } | 
|  | 60 |  | 
|  | 61 | spin_unlock_irqrestore(&pci_config_lock, flags); | 
|  | 62 |  | 
|  | 63 | return 0; | 
|  | 64 | } | 
|  | 65 |  | 
|  | 66 | static int pci_mmcfg_write(unsigned int seg, unsigned int bus, | 
|  | 67 | unsigned int devfn, int reg, int len, u32 value) | 
|  | 68 | { | 
|  | 69 | unsigned long flags; | 
|  | 70 |  | 
|  | 71 | if ((bus > 255) || (devfn > 255) || (reg > 4095)) | 
|  | 72 | return -EINVAL; | 
|  | 73 |  | 
|  | 74 | spin_lock_irqsave(&pci_config_lock, flags); | 
|  | 75 |  | 
|  | 76 | pci_exp_set_dev_base(bus, devfn); | 
|  | 77 |  | 
|  | 78 | switch (len) { | 
|  | 79 | case 1: | 
|  | 80 | writeb(value, mmcfg_virt_addr + reg); | 
|  | 81 | break; | 
|  | 82 | case 2: | 
|  | 83 | writew(value, mmcfg_virt_addr + reg); | 
|  | 84 | break; | 
|  | 85 | case 4: | 
|  | 86 | writel(value, mmcfg_virt_addr + reg); | 
|  | 87 | break; | 
|  | 88 | } | 
|  | 89 |  | 
|  | 90 | spin_unlock_irqrestore(&pci_config_lock, flags); | 
|  | 91 |  | 
|  | 92 | return 0; | 
|  | 93 | } | 
|  | 94 |  | 
|  | 95 | static struct pci_raw_ops pci_mmcfg = { | 
|  | 96 | .read =		pci_mmcfg_read, | 
|  | 97 | .write =	pci_mmcfg_write, | 
|  | 98 | }; | 
|  | 99 |  | 
|  | 100 | static int __init pci_mmcfg_init(void) | 
|  | 101 | { | 
|  | 102 | if ((pci_probe & PCI_PROBE_MMCONF) == 0) | 
|  | 103 | goto out; | 
|  | 104 | if (!pci_mmcfg_base_addr) | 
|  | 105 | goto out; | 
|  | 106 |  | 
|  | 107 | /* Kludge for now. Don't use mmconfig on AMD systems because | 
|  | 108 | those have some busses where mmconfig doesn't work, | 
|  | 109 | and we don't parse ACPI MCFG well enough to handle that. | 
|  | 110 | Remove when proper handling is added. */ | 
|  | 111 | if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) | 
|  | 112 | goto out; | 
|  | 113 |  | 
|  | 114 | printk(KERN_INFO "PCI: Using MMCONFIG\n"); | 
|  | 115 | raw_pci_ops = &pci_mmcfg; | 
|  | 116 | pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF; | 
|  | 117 |  | 
|  | 118 | out: | 
|  | 119 | return 0; | 
|  | 120 | } | 
|  | 121 |  | 
|  | 122 | arch_initcall(pci_mmcfg_init); |