aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/kernel/isa-bridge.c
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2007-06-04 01:15:36 -0400
committerPaul Mackerras <paulus@samba.org>2007-06-14 08:29:56 -0400
commit3d5134ee8341bffc4f539049abb9e90d469b448d (patch)
tree037958e0daa97b4ef350908a53182167ee2c8a03 /arch/powerpc/kernel/isa-bridge.c
parentc19c03fc749147f565e807fa65f1729066800571 (diff)
[POWERPC] Rewrite IO allocation & mapping on powerpc64
This rewrites pretty much from scratch the handling of MMIO and PIO space allocations on powerpc64. The main goals are: - Get rid of imalloc and use more common code where possible - Simplify the current mess so that PIO space is allocated and mapped in a single place for PCI bridges - Handle allocation constraints of PIO for all bridges including hot plugged ones within the 2GB space reserved for IO ports, so that devices on hotplugged busses will now work with drivers that assume IO ports fit in an int. - Cleanup and separate tracking of the ISA space in the reserved low 64K of IO space. No ISA -> Nothing mapped there. I booted a cell blade with IDE on PIO and MMIO and a dual G5 so far, that's it :-) With this patch, all allocations are done using the code in mm/vmalloc.c, though we use the low level __get_vm_area with explicit start/stop constraints in order to manage separate areas for vmalloc/vmap, ioremap, and PCI IOs. This greatly simplifies a lot of things, as you can see in the diffstat of that patch :-) A new pair of functions pcibios_map/unmap_io_space() now replace all of the previous code that used to manipulate PCI IOs space. The allocation is done at mapping time, which is now called from scan_phb's, just before the devices are probed (instead of after, which is by itself a bug fix). The only other caller is the PCI hotplug code for hot adding PCI-PCI bridges (slots). imalloc is gone, as is the "sub-allocation" thing, but I do beleive that hotplug should still work in the sense that the space allocation is always done by the PHB, but if you unmap a child bus of this PHB (which seems to be possible), then the code should properly tear down all the HPTE mappings for that area of the PHB allocated IO space. I now always reserve the first 64K of IO space for the bridge with the ISA bus on it. I have moved the code for tracking ISA in a separate file which should also make it smarter if we ever are capable of hot unplugging or re-plugging an ISA bridge. This should have a side effect on platforms like powermac where VGA IOs will no longer work. This is done on purpose though as they would have worked semi-randomly before. The idea at this point is to isolate drivers that might need to access those and fix them by providing a proper function to obtain an offset to the legacy IOs of a given bus. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/kernel/isa-bridge.c')
-rw-r--r--arch/powerpc/kernel/isa-bridge.c271
1 files changed, 271 insertions, 0 deletions
diff --git a/arch/powerpc/kernel/isa-bridge.c b/arch/powerpc/kernel/isa-bridge.c
new file mode 100644
index 000000000000..f0f49d1be3d5
--- /dev/null
+++ b/arch/powerpc/kernel/isa-bridge.c
@@ -0,0 +1,271 @@
1/*
2 * Routines for tracking a legacy ISA bridge
3 *
4 * Copyrigh 2007 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
5 *
6 * Some bits and pieces moved over from pci_64.c
7 *
8 * Copyrigh 2003 Anton Blanchard <anton@au.ibm.com>, IBM Corp.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version
13 * 2 of the License, or (at your option) any later version.
14 */
15
16#define DEBUG
17
18#include <linux/kernel.h>
19#include <linux/pci.h>
20#include <linux/string.h>
21#include <linux/init.h>
22#include <linux/mm.h>
23#include <linux/notifier.h>
24
25#include <asm/processor.h>
26#include <asm/io.h>
27#include <asm/prom.h>
28#include <asm/pci-bridge.h>
29#include <asm/machdep.h>
30#include <asm/ppc-pci.h>
31#include <asm/firmware.h>
32
33unsigned long isa_io_base; /* NULL if no ISA bus */
34EXPORT_SYMBOL(isa_io_base);
35
36/* Cached ISA bridge dev. */
37static struct device_node *isa_bridge_devnode;
38struct pci_dev *isa_bridge_pcidev;
39EXPORT_SYMBOL_GPL(isa_bridge_pcidev);
40
41#define ISA_SPACE_MASK 0x1
42#define ISA_SPACE_IO 0x1
43
44static void __devinit pci_process_ISA_OF_ranges(struct device_node *isa_node,
45 unsigned long phb_io_base_phys)
46{
47 /* We should get some saner parsing here and remove these structs */
48 struct pci_address {
49 u32 a_hi;
50 u32 a_mid;
51 u32 a_lo;
52 };
53
54 struct isa_address {
55 u32 a_hi;
56 u32 a_lo;
57 };
58
59 struct isa_range {
60 struct isa_address isa_addr;
61 struct pci_address pci_addr;
62 unsigned int size;
63 };
64
65 const struct isa_range *range;
66 unsigned long pci_addr;
67 unsigned int isa_addr;
68 unsigned int size;
69 int rlen = 0;
70
71 range = of_get_property(isa_node, "ranges", &rlen);
72 if (range == NULL || (rlen < sizeof(struct isa_range)))
73 goto inval_range;
74
75 /* From "ISA Binding to 1275"
76 * The ranges property is laid out as an array of elements,
77 * each of which comprises:
78 * cells 0 - 1: an ISA address
79 * cells 2 - 4: a PCI address
80 * (size depending on dev->n_addr_cells)
81 * cell 5: the size of the range
82 */
83 if ((range->isa_addr.a_hi && ISA_SPACE_MASK) != ISA_SPACE_IO) {
84 range++;
85 rlen -= sizeof(struct isa_range);
86 if (rlen < sizeof(struct isa_range))
87 goto inval_range;
88 }
89 if ((range->isa_addr.a_hi && ISA_SPACE_MASK) != ISA_SPACE_IO)
90 goto inval_range;
91
92 isa_addr = range->isa_addr.a_lo;
93 pci_addr = (unsigned long) range->pci_addr.a_mid << 32 |
94 range->pci_addr.a_lo;
95
96 /* Assume these are both zero. Note: We could fix that and
97 * do a proper parsing instead ... oh well, that will do for
98 * now as nobody uses fancy mappings for ISA bridges
99 */
100 if ((pci_addr != 0) || (isa_addr != 0)) {
101 printk(KERN_ERR "unexpected isa to pci mapping: %s\n",
102 __FUNCTION__);
103 return;
104 }
105
106 /* Align size and make sure it's cropped to 64K */
107 size = PAGE_ALIGN(range->size);
108 if (size > 0x10000)
109 size = 0x10000;
110
111 printk(KERN_ERR "no ISA IO ranges or unexpected isa range,"
112 "mapping 64k\n");
113
114 __ioremap_at(phb_io_base_phys, (void *)ISA_IO_BASE,
115 size, _PAGE_NO_CACHE|_PAGE_GUARDED);
116 return;
117
118inval_range:
119 printk(KERN_ERR "no ISA IO ranges or unexpected isa range,"
120 "mapping 64k\n");
121 __ioremap_at(phb_io_base_phys, (void *)ISA_IO_BASE,
122 0x10000, _PAGE_NO_CACHE|_PAGE_GUARDED);
123}
124
125
126/**
127 * isa_bridge_find_early - Find and map the ISA IO space early before
128 * main PCI discovery. This is optionally called by
129 * the arch code when adding PCI PHBs to get early
130 * access to ISA IO ports
131 */
132void __init isa_bridge_find_early(struct pci_controller *hose)
133{
134 struct device_node *np, *parent = NULL, *tmp;
135
136 /* If we already have an ISA bridge, bail off */
137 if (isa_bridge_devnode != NULL)
138 return;
139
140 /* For each "isa" node in the system. Note : we do a search by
141 * type and not by name. It might be better to do by name but that's
142 * what the code used to do and I don't want to break too much at
143 * once. We can look into changing that separately
144 */
145 for_each_node_by_type(np, "isa") {
146 /* Look for our hose being a parent */
147 for (parent = of_get_parent(np); parent;) {
148 if (parent == hose->arch_data) {
149 of_node_put(parent);
150 break;
151 }
152 tmp = parent;
153 parent = of_get_parent(parent);
154 of_node_put(tmp);
155 }
156 if (parent != NULL)
157 break;
158 }
159 if (np == NULL)
160 return;
161 isa_bridge_devnode = np;
162
163 /* Now parse the "ranges" property and setup the ISA mapping */
164 pci_process_ISA_OF_ranges(np, hose->io_base_phys);
165
166 /* Set the global ISA io base to indicate we have an ISA bridge */
167 isa_io_base = ISA_IO_BASE;
168
169 pr_debug("ISA bridge (early) is %s\n", np->full_name);
170}
171
172/**
173 * isa_bridge_find_late - Find and map the ISA IO space upon discovery of
174 * a new ISA bridge
175 */
176static void __devinit isa_bridge_find_late(struct pci_dev *pdev,
177 struct device_node *devnode)
178{
179 struct pci_controller *hose = pci_bus_to_host(pdev->bus);
180
181 /* Store ISA device node and PCI device */
182 isa_bridge_devnode = of_node_get(devnode);
183 isa_bridge_pcidev = pdev;
184
185 /* Now parse the "ranges" property and setup the ISA mapping */
186 pci_process_ISA_OF_ranges(devnode, hose->io_base_phys);
187
188 /* Set the global ISA io base to indicate we have an ISA bridge */
189 isa_io_base = ISA_IO_BASE;
190
191 pr_debug("ISA bridge (late) is %s on %s\n",
192 devnode->full_name, pci_name(pdev));
193}
194
195/**
196 * isa_bridge_remove - Remove/unmap an ISA bridge
197 */
198static void isa_bridge_remove(void)
199{
200 pr_debug("ISA bridge removed !\n");
201
202 /* Clear the global ISA io base to indicate that we have no more
203 * ISA bridge. Note that drivers don't quite handle that, though
204 * we should probably do something about it. But do we ever really
205 * have ISA bridges being removed on machines using legacy devices ?
206 */
207 isa_io_base = ISA_IO_BASE;
208
209 /* Clear references to the bridge */
210 of_node_put(isa_bridge_devnode);
211 isa_bridge_devnode = NULL;
212 isa_bridge_pcidev = NULL;
213
214 /* Unmap the ISA area */
215 __iounmap_at((void *)ISA_IO_BASE, 0x10000);
216}
217
218/**
219 * isa_bridge_notify - Get notified of PCI devices addition/removal
220 */
221static int __devinit isa_bridge_notify(struct notifier_block *nb,
222 unsigned long action, void *data)
223{
224 struct device *dev = data;
225 struct pci_dev *pdev = to_pci_dev(dev);
226 struct device_node *devnode = pci_device_to_OF_node(pdev);
227
228 switch(action) {
229 case BUS_NOTIFY_ADD_DEVICE:
230 /* Check if we have an early ISA device, without PCI dev */
231 if (isa_bridge_devnode && isa_bridge_devnode == devnode &&
232 !isa_bridge_pcidev) {
233 pr_debug("ISA bridge PCI attached: %s\n",
234 pci_name(pdev));
235 isa_bridge_pcidev = pdev;
236 }
237
238 /* Check if we have no ISA device, and this happens to be one,
239 * register it as such if it has an OF device
240 */
241 if (!isa_bridge_devnode && devnode && devnode->type &&
242 !strcmp(devnode->type, "isa"))
243 isa_bridge_find_late(pdev, devnode);
244
245 return 0;
246 case BUS_NOTIFY_DEL_DEVICE:
247 /* Check if this our existing ISA device */
248 if (pdev == isa_bridge_pcidev ||
249 (devnode && devnode == isa_bridge_devnode))
250 isa_bridge_remove();
251 return 0;
252 }
253 return 0;
254}
255
256static struct notifier_block isa_bridge_notifier = {
257 .notifier_call = isa_bridge_notify
258};
259
260/**
261 * isa_bridge_init - register to be notified of ISA bridge addition/removal
262 *
263 */
264static int __init isa_bridge_init(void)
265{
266 if (firmware_has_feature(FW_FEATURE_ISERIES))
267 return 0;
268 bus_register_notifier(&pci_bus_type, &isa_bridge_notifier);
269 return 0;
270}
271arch_initcall(isa_bridge_init);