Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * mmconfig.c - Low-level direct PCI config space access via MMCONFIG | |
3 | * | |
4 | * This is an 64bit optimized version that always keeps the full mmconfig | |
5 | * space mapped. This allows lockless config space operation. | |
6 | */ | |
7 | ||
8 | #include <linux/pci.h> | |
9 | #include <linux/init.h> | |
54549391 | 10 | #include <linux/acpi.h> |
1da177e4 LT |
11 | #include "pci.h" |
12 | ||
13 | #define MMCONFIG_APER_SIZE (256*1024*1024) | |
14 | ||
1da177e4 | 15 | /* Static virtual mapping of the MMCONFIG aperture */ |
1cde8a16 GKH |
16 | struct mmcfg_virt { |
17 | struct acpi_table_mcfg_config *cfg; | |
18 | char *virt; | |
19 | }; | |
20 | static struct mmcfg_virt *pci_mmcfg_virt; | |
1da177e4 | 21 | |
928cf8c6 | 22 | static char *get_virt(unsigned int seg, unsigned bus) |
1da177e4 | 23 | { |
1cde8a16 GKH |
24 | int cfg_num = -1; |
25 | struct acpi_table_mcfg_config *cfg; | |
26 | ||
27 | while (1) { | |
28 | ++cfg_num; | |
29 | if (cfg_num >= pci_mmcfg_config_num) { | |
928cf8c6 AK |
30 | /* Not found - fall back to type 1. This happens |
31 | e.g. on the internal devices of a K8 northbridge. */ | |
32 | return NULL; | |
1cde8a16 GKH |
33 | } |
34 | cfg = pci_mmcfg_virt[cfg_num].cfg; | |
35 | if (cfg->pci_segment_group_number != seg) | |
36 | continue; | |
37 | if ((cfg->start_bus_number <= bus) && | |
38 | (cfg->end_bus_number >= bus)) | |
39 | return pci_mmcfg_virt[cfg_num].virt; | |
40 | } | |
41 | } | |
42 | ||
43 | static inline char *pci_dev_base(unsigned int seg, unsigned int bus, unsigned int devfn) | |
44 | { | |
928cf8c6 AK |
45 | char *addr = get_virt(seg, bus); |
46 | if (!addr) | |
47 | return NULL; | |
48 | return addr + ((bus << 20) | (devfn << 12)); | |
1da177e4 LT |
49 | } |
50 | ||
51 | static int pci_mmcfg_read(unsigned int seg, unsigned int bus, | |
52 | unsigned int devfn, int reg, int len, u32 *value) | |
53 | { | |
928cf8c6 | 54 | char *addr; |
1da177e4 | 55 | |
928cf8c6 | 56 | /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ |
1da177e4 LT |
57 | if (unlikely(!value || (bus > 255) || (devfn > 255) || (reg > 4095))) |
58 | return -EINVAL; | |
59 | ||
928cf8c6 AK |
60 | addr = pci_dev_base(seg, bus, devfn); |
61 | if (!addr) | |
62 | return pci_conf1_read(seg,bus,devfn,reg,len,value); | |
63 | ||
1da177e4 LT |
64 | switch (len) { |
65 | case 1: | |
66 | *value = readb(addr + reg); | |
67 | break; | |
68 | case 2: | |
69 | *value = readw(addr + reg); | |
70 | break; | |
71 | case 4: | |
72 | *value = readl(addr + reg); | |
73 | break; | |
74 | } | |
75 | ||
76 | return 0; | |
77 | } | |
78 | ||
79 | static int pci_mmcfg_write(unsigned int seg, unsigned int bus, | |
80 | unsigned int devfn, int reg, int len, u32 value) | |
81 | { | |
928cf8c6 | 82 | char *addr; |
1da177e4 | 83 | |
928cf8c6 | 84 | /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ |
1da177e4 LT |
85 | if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) |
86 | return -EINVAL; | |
87 | ||
928cf8c6 AK |
88 | addr = pci_dev_base(seg, bus, devfn); |
89 | if (!addr) | |
90 | return pci_conf1_write(seg,bus,devfn,reg,len,value); | |
91 | ||
1da177e4 LT |
92 | switch (len) { |
93 | case 1: | |
94 | writeb(value, addr + reg); | |
95 | break; | |
96 | case 2: | |
97 | writew(value, addr + reg); | |
98 | break; | |
99 | case 4: | |
100 | writel(value, addr + reg); | |
101 | break; | |
102 | } | |
103 | ||
104 | return 0; | |
105 | } | |
106 | ||
107 | static struct pci_raw_ops pci_mmcfg = { | |
108 | .read = pci_mmcfg_read, | |
109 | .write = pci_mmcfg_write, | |
110 | }; | |
111 | ||
112 | static int __init pci_mmcfg_init(void) | |
113 | { | |
1cde8a16 GKH |
114 | int i; |
115 | ||
1da177e4 LT |
116 | if ((pci_probe & PCI_PROBE_MMCONF) == 0) |
117 | return 0; | |
54549391 GKH |
118 | |
119 | acpi_table_parse(ACPI_MCFG, acpi_parse_mcfg); | |
120 | if ((pci_mmcfg_config_num == 0) || | |
121 | (pci_mmcfg_config == NULL) || | |
122 | (pci_mmcfg_config[0].base_address == 0)) | |
1da177e4 LT |
123 | return 0; |
124 | ||
1da177e4 | 125 | /* RED-PEN i386 doesn't do _nocache right now */ |
1cde8a16 GKH |
126 | pci_mmcfg_virt = kmalloc(sizeof(*pci_mmcfg_virt) * pci_mmcfg_config_num, GFP_KERNEL); |
127 | if (pci_mmcfg_virt == NULL) { | |
128 | printk("PCI: Can not allocate memory for mmconfig structures\n"); | |
1da177e4 | 129 | return 0; |
1cde8a16 GKH |
130 | } |
131 | for (i = 0; i < pci_mmcfg_config_num; ++i) { | |
132 | pci_mmcfg_virt[i].cfg = &pci_mmcfg_config[i]; | |
133 | pci_mmcfg_virt[i].virt = ioremap_nocache(pci_mmcfg_config[i].base_address, MMCONFIG_APER_SIZE); | |
134 | if (!pci_mmcfg_virt[i].virt) { | |
135 | printk("PCI: Cannot map mmconfig aperture for segment %d\n", | |
136 | pci_mmcfg_config[i].pci_segment_group_number); | |
137 | return 0; | |
138 | } | |
139 | printk(KERN_INFO "PCI: Using MMCONFIG at %x\n", pci_mmcfg_config[i].base_address); | |
140 | } | |
1da177e4 | 141 | |
1da177e4 LT |
142 | raw_pci_ops = &pci_mmcfg; |
143 | pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF; | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
148 | arch_initcall(pci_mmcfg_init); |