blob: 2002c741a383b02ed564fee6c5fc88bf95bda810 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
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>
Greg Kroah-Hartman54549392005-06-23 17:35:56 -070014#include <linux/acpi.h>
Arjan van de Ven946f2ee2006-04-07 19:49:30 +020015#include <asm/e820.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070016#include "pci.h"
17
Arjan van de Ven946f2ee2006-04-07 19:49:30 +020018#define MMCONFIG_APER_SIZE (256*1024*1024)
19
Andi Kleen8c30b1a742006-04-07 19:50:12 +020020/* Assume systems with more busses have correct MCFG */
21#define MAX_CHECK_BUS 16
22
Linus Torvalds1da177e2005-04-16 15:20:36 -070023#define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG))
24
25/* The base address of the last MMCONFIG device accessed */
26static u32 mmcfg_last_accessed_device;
27
Andi Kleen8c30b1a742006-04-07 19:50:12 +020028static DECLARE_BITMAP(fallback_slots, MAX_CHECK_BUS*32);
Andi Kleend6ece542005-12-12 22:17:11 -080029
Linus Torvalds1da177e2005-04-16 15:20:36 -070030/*
31 * Functions for accessing PCI configuration space with MMCONFIG accesses
32 */
Andi Kleend6ece542005-12-12 22:17:11 -080033static u32 get_base_addr(unsigned int seg, int bus, unsigned devfn)
Linus Torvalds1da177e2005-04-16 15:20:36 -070034{
Greg Kroah-Hartmand57e26c2005-06-23 17:35:56 -070035 int cfg_num = -1;
36 struct acpi_table_mcfg_config *cfg;
37
Andi Kleen8c30b1a742006-04-07 19:50:12 +020038 if (seg == 0 && bus < MAX_CHECK_BUS &&
39 test_bit(PCI_SLOT(devfn) + 32*bus, fallback_slots))
Andi Kleend6ece542005-12-12 22:17:11 -080040 return 0;
41
Greg Kroah-Hartmand57e26c2005-06-23 17:35:56 -070042 while (1) {
43 ++cfg_num;
44 if (cfg_num >= pci_mmcfg_config_num) {
Andi Kleen31030392006-01-27 02:03:50 +010045 break;
Greg Kroah-Hartmand57e26c2005-06-23 17:35:56 -070046 }
47 cfg = &pci_mmcfg_config[cfg_num];
48 if (cfg->pci_segment_group_number != seg)
49 continue;
50 if ((cfg->start_bus_number <= bus) &&
51 (cfg->end_bus_number >= bus))
52 return cfg->base_address;
53 }
Andi Kleen31030392006-01-27 02:03:50 +010054
55 /* Handle more broken MCFG tables on Asus etc.
56 They only contain a single entry for bus 0-0. Assume
57 this applies to all busses. */
58 cfg = &pci_mmcfg_config[0];
59 if (pci_mmcfg_config_num == 1 &&
60 cfg->pci_segment_group_number == 0 &&
61 (cfg->start_bus_number | cfg->end_bus_number) == 0)
62 return cfg->base_address;
63
64 /* Fall back to type 0 */
65 return 0;
Greg Kroah-Hartmand57e26c2005-06-23 17:35:56 -070066}
67
Andi Kleen928cf8c2005-12-12 22:17:10 -080068static inline void pci_exp_set_dev_base(unsigned int base, int bus, int devfn)
Greg Kroah-Hartmand57e26c2005-06-23 17:35:56 -070069{
Andi Kleen928cf8c2005-12-12 22:17:10 -080070 u32 dev_base = base | (bus << 20) | (devfn << 12);
Linus Torvalds1da177e2005-04-16 15:20:36 -070071 if (dev_base != mmcfg_last_accessed_device) {
72 mmcfg_last_accessed_device = dev_base;
73 set_fixmap_nocache(FIX_PCIE_MCFG, dev_base);
74 }
75}
76
77static int pci_mmcfg_read(unsigned int seg, unsigned int bus,
78 unsigned int devfn, int reg, int len, u32 *value)
79{
80 unsigned long flags;
Andi Kleen928cf8c2005-12-12 22:17:10 -080081 u32 base;
Linus Torvalds1da177e2005-04-16 15:20:36 -070082
83 if (!value || (bus > 255) || (devfn > 255) || (reg > 4095))
84 return -EINVAL;
85
Andi Kleend6ece542005-12-12 22:17:11 -080086 base = get_base_addr(seg, bus, devfn);
Andi Kleen928cf8c2005-12-12 22:17:10 -080087 if (!base)
88 return pci_conf1_read(seg,bus,devfn,reg,len,value);
89
Linus Torvalds1da177e2005-04-16 15:20:36 -070090 spin_lock_irqsave(&pci_config_lock, flags);
91
Andi Kleen928cf8c2005-12-12 22:17:10 -080092 pci_exp_set_dev_base(base, bus, devfn);
Linus Torvalds1da177e2005-04-16 15:20:36 -070093
94 switch (len) {
95 case 1:
96 *value = readb(mmcfg_virt_addr + reg);
97 break;
98 case 2:
99 *value = readw(mmcfg_virt_addr + reg);
100 break;
101 case 4:
102 *value = readl(mmcfg_virt_addr + reg);
103 break;
104 }
105
106 spin_unlock_irqrestore(&pci_config_lock, flags);
107
108 return 0;
109}
110
111static int pci_mmcfg_write(unsigned int seg, unsigned int bus,
112 unsigned int devfn, int reg, int len, u32 value)
113{
114 unsigned long flags;
Andi Kleen928cf8c2005-12-12 22:17:10 -0800115 u32 base;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116
117 if ((bus > 255) || (devfn > 255) || (reg > 4095))
118 return -EINVAL;
119
Andi Kleend6ece542005-12-12 22:17:11 -0800120 base = get_base_addr(seg, bus, devfn);
Andi Kleen928cf8c2005-12-12 22:17:10 -0800121 if (!base)
122 return pci_conf1_write(seg,bus,devfn,reg,len,value);
123
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124 spin_lock_irqsave(&pci_config_lock, flags);
125
Andi Kleen928cf8c2005-12-12 22:17:10 -0800126 pci_exp_set_dev_base(base, bus, devfn);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127
128 switch (len) {
129 case 1:
130 writeb(value, mmcfg_virt_addr + reg);
131 break;
132 case 2:
133 writew(value, mmcfg_virt_addr + reg);
134 break;
135 case 4:
136 writel(value, mmcfg_virt_addr + reg);
137 break;
138 }
139
140 spin_unlock_irqrestore(&pci_config_lock, flags);
141
142 return 0;
143}
144
145static struct pci_raw_ops pci_mmcfg = {
146 .read = pci_mmcfg_read,
147 .write = pci_mmcfg_write,
148};
149
Andi Kleend6ece542005-12-12 22:17:11 -0800150/* K8 systems have some devices (typically in the builtin northbridge)
151 that are only accessible using type1
152 Normally this can be expressed in the MCFG by not listing them
153 and assigning suitable _SEGs, but this isn't implemented in some BIOS.
154 Instead try to discover all devices on bus 0 that are unreachable using MM
Andi Kleen8c30b1a742006-04-07 19:50:12 +0200155 and fallback for them. */
Andi Kleend6ece542005-12-12 22:17:11 -0800156static __init void unreachable_devices(void)
157{
Andi Kleen8c30b1a742006-04-07 19:50:12 +0200158 int i, k;
Andi Kleend6ece542005-12-12 22:17:11 -0800159 unsigned long flags;
160
Andi Kleen8c30b1a742006-04-07 19:50:12 +0200161 for (k = 0; k < MAX_CHECK_BUS; k++) {
162 for (i = 0; i < 32; i++) {
163 u32 val1;
164 u32 addr;
Andi Kleend6ece542005-12-12 22:17:11 -0800165
Andi Kleen8c30b1a742006-04-07 19:50:12 +0200166 pci_conf1_read(0, k, PCI_DEVFN(i, 0), 0, 4, &val1);
167 if (val1 == 0xffffffff)
168 continue;
Andi Kleend6ece542005-12-12 22:17:11 -0800169
Andi Kleen8c30b1a742006-04-07 19:50:12 +0200170 /* Locking probably not needed, but safer */
171 spin_lock_irqsave(&pci_config_lock, flags);
172 addr = get_base_addr(0, k, PCI_DEVFN(i, 0));
173 if (addr != 0)
174 pci_exp_set_dev_base(addr, k, PCI_DEVFN(i, 0));
175 if (addr == 0 ||
176 readl((u32 __iomem *)mmcfg_virt_addr) != val1) {
177 set_bit(i, fallback_slots);
178 printk(KERN_NOTICE
179 "PCI: No mmconfig possible on %x:%x\n", k, i);
180 }
181 spin_unlock_irqrestore(&pci_config_lock, flags);
182 }
Andi Kleend6ece542005-12-12 22:17:11 -0800183 }
184}
185
Andi Kleen92c05fc2006-03-23 14:35:12 -0800186void __init pci_mmcfg_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700187{
188 if ((pci_probe & PCI_PROBE_MMCONF) == 0)
Andi Kleen92c05fc2006-03-23 14:35:12 -0800189 return;
Greg Kroah-Hartman54549392005-06-23 17:35:56 -0700190
191 acpi_table_parse(ACPI_MCFG, acpi_parse_mcfg);
192 if ((pci_mmcfg_config_num == 0) ||
193 (pci_mmcfg_config == NULL) ||
194 (pci_mmcfg_config[0].base_address == 0))
Andi Kleen92c05fc2006-03-23 14:35:12 -0800195 return;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700196
Arjan van de Ven946f2ee2006-04-07 19:49:30 +0200197 if (!e820_all_mapped(pci_mmcfg_config[0].base_address,
198 pci_mmcfg_config[0].base_address + MMCONFIG_APER_SIZE,
199 E820_RESERVED)) {
200 printk(KERN_ERR "PCI: BIOS Bug: MCFG area is not E820-reserved\n");
201 printk(KERN_ERR "PCI: Not using MMCONFIG.\n");
202 return;
203 }
204
Linus Torvalds1da177e2005-04-16 15:20:36 -0700205 printk(KERN_INFO "PCI: Using MMCONFIG\n");
206 raw_pci_ops = &pci_mmcfg;
207 pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF;
208
Andi Kleend6ece542005-12-12 22:17:11 -0800209 unreachable_devices();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700210}