]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * PCI Hot Plug Controller Driver for RPA-compliant PPC64 platform. | |
3 | * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> | |
4 | * | |
5 | * All rights reserved. | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or (at | |
10 | * your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | |
15 | * NON INFRINGEMENT. See the GNU General Public License for more | |
16 | * details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
21 | * | |
22 | * Send feedback to <lxie@us.ibm.com> | |
23 | * | |
24 | */ | |
25 | #include <linux/pci.h> | |
4e57b681 TS |
26 | #include <linux/string.h> |
27 | ||
1da177e4 LT |
28 | #include <asm/pci-bridge.h> |
29 | #include <asm/rtas.h> | |
30 | #include <asm/machdep.h> | |
1da177e4 | 31 | |
4e57b681 | 32 | #include "../pci.h" /* for pci_add_new_bus */ |
1da177e4 LT |
33 | #include "rpaphp.h" |
34 | ||
0945cd5f | 35 | static struct pci_bus *find_bus_among_children(struct pci_bus *bus, |
9c209c91 JR |
36 | struct device_node *dn) |
37 | { | |
38 | struct pci_bus *child = NULL; | |
39 | struct list_head *tmp; | |
40 | struct device_node *busdn; | |
41 | ||
42 | busdn = pci_bus_to_OF_node(bus); | |
43 | if (busdn == dn) | |
44 | return bus; | |
45 | ||
46 | list_for_each(tmp, &bus->children) { | |
47 | child = find_bus_among_children(pci_bus_b(tmp), dn); | |
48 | if (child) | |
49 | break; | |
50 | } | |
51 | return child; | |
52 | } | |
53 | ||
56d8456b | 54 | struct pci_bus *rpaphp_find_pci_bus(struct device_node *dn) |
9c209c91 | 55 | { |
1635317f PM |
56 | struct pci_dn *pdn = dn->data; |
57 | ||
58 | if (!pdn || !pdn->phb || !pdn->phb->bus) | |
0945cd5f | 59 | return NULL; |
9c209c91 | 60 | |
1635317f | 61 | return find_bus_among_children(pdn->phb->bus, dn); |
9c209c91 | 62 | } |
56d8456b | 63 | EXPORT_SYMBOL_GPL(rpaphp_find_pci_bus); |
9c209c91 | 64 | |
1da177e4 LT |
65 | int rpaphp_claim_resource(struct pci_dev *dev, int resource) |
66 | { | |
67 | struct resource *res = &dev->resource[resource]; | |
68 | struct resource *root = pci_find_parent_resource(dev, res); | |
69 | char *dtype = resource < PCI_BRIDGE_RESOURCES ? "device" : "bridge"; | |
70 | int err = -EINVAL; | |
71 | ||
72 | if (root != NULL) { | |
73 | err = request_resource(root, res); | |
74 | } | |
75 | ||
76 | if (err) { | |
77 | err("PCI: %s region %d of %s %s [%lx:%lx]\n", | |
78 | root ? "Address space collision on" : | |
79 | "No parent found for", | |
80 | resource, dtype, pci_name(dev), res->start, res->end); | |
81 | } | |
82 | return err; | |
83 | } | |
84 | ||
85 | EXPORT_SYMBOL_GPL(rpaphp_claim_resource); | |
86 | ||
1da177e4 LT |
87 | static int rpaphp_get_sensor_state(struct slot *slot, int *state) |
88 | { | |
89 | int rc; | |
90 | int setlevel; | |
91 | ||
92 | rc = rtas_get_sensor(DR_ENTITY_SENSE, slot->index, state); | |
93 | ||
94 | if (rc < 0) { | |
95 | if (rc == -EFAULT || rc == -EEXIST) { | |
96 | dbg("%s: slot must be power up to get sensor-state\n", | |
97 | __FUNCTION__); | |
98 | ||
99 | /* some slots have to be powered up | |
100 | * before get-sensor will succeed. | |
101 | */ | |
102 | rc = rtas_set_power_level(slot->power_domain, POWER_ON, | |
103 | &setlevel); | |
104 | if (rc < 0) { | |
105 | dbg("%s: power on slot[%s] failed rc=%d.\n", | |
106 | __FUNCTION__, slot->name, rc); | |
107 | } else { | |
108 | rc = rtas_get_sensor(DR_ENTITY_SENSE, | |
109 | slot->index, state); | |
110 | } | |
111 | } else if (rc == -ENODEV) | |
112 | info("%s: slot is unusable\n", __FUNCTION__); | |
113 | else | |
114 | err("%s failed to get sensor state\n", __FUNCTION__); | |
115 | } | |
116 | return rc; | |
117 | } | |
118 | ||
119 | /** | |
120 | * get_pci_adapter_status - get the status of a slot | |
121 | * | |
122 | * 0-- slot is empty | |
123 | * 1-- adapter is configured | |
124 | * 2-- adapter is not configured | |
125 | * 3-- not valid | |
126 | */ | |
127 | int rpaphp_get_pci_adapter_status(struct slot *slot, int is_init, u8 * value) | |
128 | { | |
0945cd5f | 129 | struct pci_bus *bus; |
1da177e4 | 130 | int state, rc; |
1da177e4 LT |
131 | |
132 | *value = NOT_VALID; | |
133 | rc = rpaphp_get_sensor_state(slot, &state); | |
134 | if (rc) | |
135 | goto exit; | |
136 | ||
56d8456b | 137 | if (state == EMPTY) |
1da177e4 | 138 | *value = EMPTY; |
1da177e4 LT |
139 | else if (state == PRESENT) { |
140 | if (!is_init) { | |
141 | /* at run-time slot->state can be changed by */ | |
142 | /* config/unconfig adapter */ | |
143 | *value = slot->state; | |
144 | } else { | |
0945cd5f JR |
145 | bus = rpaphp_find_pci_bus(slot->dn); |
146 | if (bus && !list_empty(&bus->devices)) | |
147 | *value = CONFIGURED; | |
148 | else | |
1da177e4 | 149 | *value = NOT_CONFIGURED; |
1da177e4 LT |
150 | } |
151 | } | |
152 | exit: | |
153 | return rc; | |
154 | } | |
155 | ||
156 | /* Must be called before pci_bus_add_devices */ | |
157 | static void | |
158 | rpaphp_fixup_new_pci_devices(struct pci_bus *bus, int fix_bus) | |
159 | { | |
160 | struct pci_dev *dev; | |
161 | ||
162 | list_for_each_entry(dev, &bus->devices, bus_list) { | |
163 | /* | |
164 | * Skip already-present devices (which are on the | |
165 | * global device list.) | |
166 | */ | |
167 | if (list_empty(&dev->global_list)) { | |
168 | int i; | |
169 | ||
170 | /* Need to setup IOMMU tables */ | |
171 | ppc_md.iommu_dev_setup(dev); | |
172 | ||
173 | if(fix_bus) | |
174 | pcibios_fixup_device_resources(dev, bus); | |
175 | pci_read_irq_line(dev); | |
176 | for (i = 0; i < PCI_NUM_RESOURCES; i++) { | |
177 | struct resource *r = &dev->resource[i]; | |
178 | ||
179 | if (r->parent || !r->start || !r->flags) | |
180 | continue; | |
181 | rpaphp_claim_resource(dev, i); | |
182 | } | |
183 | } | |
184 | } | |
185 | } | |
186 | ||
1da177e4 LT |
187 | static int rpaphp_pci_config_bridge(struct pci_dev *dev) |
188 | { | |
189 | u8 sec_busno; | |
190 | struct pci_bus *child_bus; | |
191 | struct pci_dev *child_dev; | |
192 | ||
193 | dbg("Enter %s: BRIDGE dev=%s\n", __FUNCTION__, pci_name(dev)); | |
194 | ||
195 | /* get busno of downstream bus */ | |
196 | pci_read_config_byte(dev, PCI_SECONDARY_BUS, &sec_busno); | |
197 | ||
198 | /* add to children of PCI bridge dev->bus */ | |
199 | child_bus = pci_add_new_bus(dev->bus, dev, sec_busno); | |
200 | if (!child_bus) { | |
201 | err("%s: could not add second bus\n", __FUNCTION__); | |
202 | return -EIO; | |
203 | } | |
204 | sprintf(child_bus->name, "PCI Bus #%02x", child_bus->number); | |
205 | /* do pci_scan_child_bus */ | |
206 | pci_scan_child_bus(child_bus); | |
207 | ||
208 | list_for_each_entry(child_dev, &child_bus->devices, bus_list) { | |
209 | eeh_add_device_late(child_dev); | |
210 | } | |
211 | ||
212 | /* fixup new pci devices without touching bus struct */ | |
213 | rpaphp_fixup_new_pci_devices(child_bus, 0); | |
214 | ||
215 | /* Make the discovered devices available */ | |
216 | pci_bus_add_devices(child_bus); | |
217 | return 0; | |
218 | } | |
219 | ||
bde16841 JR |
220 | /***************************************************************************** |
221 | rpaphp_pci_config_slot() will configure all devices under the | |
222 | given slot->dn and return the the first pci_dev. | |
223 | *****************************************************************************/ | |
224 | static struct pci_dev * | |
940903c5 | 225 | rpaphp_pci_config_slot(struct pci_bus *bus) |
bde16841 | 226 | { |
940903c5 | 227 | struct device_node *dn = pci_bus_to_OF_node(bus); |
bde16841 | 228 | struct pci_dev *dev = NULL; |
9c209c91 | 229 | int slotno; |
bde16841 JR |
230 | int num; |
231 | ||
232 | dbg("Enter %s: dn=%s bus=%s\n", __FUNCTION__, dn->full_name, bus->name); | |
940903c5 | 233 | if (!dn || !dn->child) |
0945cd5f | 234 | return NULL; |
bde16841 | 235 | |
1635317f | 236 | slotno = PCI_SLOT(PCI_DN(dn->child)->devfn); |
9c209c91 | 237 | |
0945cd5f JR |
238 | /* pci_scan_slot should find all children */ |
239 | num = pci_scan_slot(bus, PCI_DEVFN(slotno, 0)); | |
240 | if (num) { | |
241 | rpaphp_fixup_new_pci_devices(bus, 1); | |
242 | pci_bus_add_devices(bus); | |
243 | } | |
244 | if (list_empty(&bus->devices)) { | |
245 | err("%s: No new device found\n", __FUNCTION__); | |
246 | return NULL; | |
247 | } | |
248 | list_for_each_entry(dev, &bus->devices, bus_list) { | |
bde16841 JR |
249 | if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) |
250 | rpaphp_pci_config_bridge(dev); | |
251 | } | |
0945cd5f | 252 | |
bde16841 JR |
253 | return dev; |
254 | } | |
255 | ||
1da177e4 LT |
256 | static void enable_eeh(struct device_node *dn) |
257 | { | |
258 | struct device_node *sib; | |
259 | ||
260 | for (sib = dn->child; sib; sib = sib->sibling) | |
261 | enable_eeh(sib); | |
262 | eeh_add_device_early(dn); | |
263 | return; | |
264 | ||
265 | } | |
266 | ||
940903c5 | 267 | static void print_slot_pci_funcs(struct pci_bus *bus) |
1da177e4 | 268 | { |
940903c5 | 269 | struct device_node *dn; |
1da177e4 LT |
270 | struct pci_dev *dev; |
271 | ||
940903c5 JR |
272 | dn = pci_bus_to_OF_node(bus); |
273 | if (!dn) | |
274 | return; | |
275 | ||
276 | dbg("%s: pci_devs of slot[%s]\n", __FUNCTION__, dn->full_name); | |
277 | list_for_each_entry (dev, &bus->devices, bus_list) | |
5eeb8c63 | 278 | dbg("\t%s\n", pci_name(dev)); |
1da177e4 LT |
279 | return; |
280 | } | |
281 | ||
940903c5 | 282 | int rpaphp_config_pci_adapter(struct pci_bus *bus) |
1da177e4 | 283 | { |
940903c5 | 284 | struct device_node *dn = pci_bus_to_OF_node(bus); |
1da177e4 LT |
285 | struct pci_dev *dev; |
286 | int rc = -ENODEV; | |
287 | ||
940903c5 JR |
288 | dbg("Entry %s: slot[%s]\n", __FUNCTION__, dn->full_name); |
289 | if (!dn) | |
290 | goto exit; | |
1da177e4 | 291 | |
940903c5 JR |
292 | enable_eeh(dn); |
293 | dev = rpaphp_pci_config_slot(bus); | |
9c209c91 JR |
294 | if (!dev) { |
295 | err("%s: can't find any devices.\n", __FUNCTION__); | |
296 | goto exit; | |
1da177e4 | 297 | } |
940903c5 | 298 | print_slot_pci_funcs(bus); |
9c209c91 | 299 | rc = 0; |
1da177e4 LT |
300 | exit: |
301 | dbg("Exit %s: rc=%d\n", __FUNCTION__, rc); | |
302 | return rc; | |
303 | } | |
940903c5 | 304 | EXPORT_SYMBOL_GPL(rpaphp_config_pci_adapter); |
1da177e4 LT |
305 | |
306 | static void rpaphp_eeh_remove_bus_device(struct pci_dev *dev) | |
307 | { | |
308 | eeh_remove_device(dev); | |
309 | if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { | |
310 | struct pci_bus *bus = dev->subordinate; | |
311 | struct list_head *ln; | |
312 | if (!bus) | |
313 | return; | |
314 | for (ln = bus->devices.next; ln != &bus->devices; ln = ln->next) { | |
315 | struct pci_dev *pdev = pci_dev_b(ln); | |
316 | if (pdev) | |
317 | rpaphp_eeh_remove_bus_device(pdev); | |
318 | } | |
319 | ||
320 | } | |
321 | return; | |
322 | } | |
323 | ||
934199e9 | 324 | int rpaphp_unconfig_pci_adapter(struct pci_bus *bus) |
1da177e4 | 325 | { |
9c209c91 | 326 | struct pci_dev *dev, *tmp; |
1da177e4 | 327 | |
934199e9 | 328 | list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) { |
1da177e4 | 329 | rpaphp_eeh_remove_bus_device(dev); |
9c209c91 JR |
330 | pci_remove_bus_device(dev); |
331 | } | |
934199e9 | 332 | return 0; |
1da177e4 LT |
333 | } |
334 | ||
335 | static int setup_pci_hotplug_slot_info(struct slot *slot) | |
336 | { | |
337 | dbg("%s Initilize the PCI slot's hotplug->info structure ...\n", | |
338 | __FUNCTION__); | |
339 | rpaphp_get_power_status(slot, &slot->hotplug_slot->info->power_status); | |
340 | rpaphp_get_pci_adapter_status(slot, 1, | |
341 | &slot->hotplug_slot->info-> | |
342 | adapter_status); | |
343 | if (slot->hotplug_slot->info->adapter_status == NOT_VALID) { | |
344 | err("%s: NOT_VALID: skip dn->full_name=%s\n", | |
345 | __FUNCTION__, slot->dn->full_name); | |
346 | return -EINVAL; | |
347 | } | |
348 | return 0; | |
349 | } | |
350 | ||
9c209c91 | 351 | static void set_slot_name(struct slot *slot) |
1da177e4 | 352 | { |
9c209c91 JR |
353 | struct pci_bus *bus = slot->bus; |
354 | struct pci_dev *bridge; | |
355 | ||
356 | bridge = bus->self; | |
357 | if (bridge) | |
358 | strcpy(slot->name, pci_name(bridge)); | |
359 | else | |
360 | sprintf(slot->name, "%04x:%02x:00.0", pci_domain_nr(bus), | |
361 | bus->number); | |
1da177e4 LT |
362 | } |
363 | ||
364 | static int setup_pci_slot(struct slot *slot) | |
365 | { | |
9c209c91 | 366 | struct device_node *dn = slot->dn; |
1da177e4 | 367 | struct pci_bus *bus; |
1da177e4 | 368 | |
9c209c91 JR |
369 | BUG_ON(!dn); |
370 | bus = rpaphp_find_pci_bus(dn); | |
371 | if (!bus) { | |
372 | err("%s: no pci_bus for dn %s\n", __FUNCTION__, dn->full_name); | |
373 | goto exit_rc; | |
1da177e4 LT |
374 | } |
375 | ||
9c209c91 JR |
376 | slot->bus = bus; |
377 | slot->pci_devs = &bus->devices; | |
378 | set_slot_name(slot); | |
379 | ||
1da177e4 LT |
380 | /* find slot's pci_dev if it's not empty */ |
381 | if (slot->hotplug_slot->info->adapter_status == EMPTY) { | |
382 | slot->state = EMPTY; /* slot is empty */ | |
383 | } else { | |
384 | /* slot is occupied */ | |
9c209c91 | 385 | if (!dn->child) { |
1da177e4 LT |
386 | /* non-empty slot has to have child */ |
387 | err("%s: slot[%s]'s device_node doesn't have child for adapter\n", | |
388 | __FUNCTION__, slot->name); | |
389 | goto exit_rc; | |
390 | } | |
391 | ||
392 | if (slot->hotplug_slot->info->adapter_status == NOT_CONFIGURED) { | |
393 | dbg("%s CONFIGURING pci adapter in slot[%s]\n", | |
394 | __FUNCTION__, slot->name); | |
940903c5 | 395 | if (rpaphp_config_pci_adapter(slot->bus)) { |
1da177e4 LT |
396 | err("%s: CONFIG pci adapter failed\n", __FUNCTION__); |
397 | goto exit_rc; | |
398 | } | |
399 | ||
400 | } else if (slot->hotplug_slot->info->adapter_status != CONFIGURED) { | |
401 | err("%s: slot[%s]'s adapter_status is NOT_VALID.\n", | |
402 | __FUNCTION__, slot->name); | |
403 | goto exit_rc; | |
404 | } | |
940903c5 | 405 | print_slot_pci_funcs(slot->bus); |
5eeb8c63 | 406 | if (!list_empty(slot->pci_devs)) { |
1da177e4 LT |
407 | slot->state = CONFIGURED; |
408 | } else { | |
409 | /* DLPAR add as opposed to | |
410 | * boot time */ | |
411 | slot->state = NOT_CONFIGURED; | |
412 | } | |
413 | } | |
414 | return 0; | |
415 | exit_rc: | |
416 | dealloc_slot_struct(slot); | |
417 | return -EINVAL; | |
418 | } | |
419 | ||
420 | int register_pci_slot(struct slot *slot) | |
421 | { | |
422 | int rc = -EINVAL; | |
423 | ||
1da177e4 LT |
424 | if (setup_pci_hotplug_slot_info(slot)) |
425 | goto exit_rc; | |
426 | if (setup_pci_slot(slot)) | |
427 | goto exit_rc; | |
428 | rc = register_slot(slot); | |
429 | exit_rc: | |
430 | return rc; | |
431 | } | |
432 | ||
433 | int rpaphp_enable_pci_slot(struct slot *slot) | |
434 | { | |
435 | int retval = 0, state; | |
436 | ||
437 | retval = rpaphp_get_sensor_state(slot, &state); | |
438 | if (retval) | |
439 | goto exit; | |
440 | dbg("%s: sensor state[%d]\n", __FUNCTION__, state); | |
441 | /* if slot is not empty, enable the adapter */ | |
442 | if (state == PRESENT) { | |
443 | dbg("%s : slot[%s] is occupied.\n", __FUNCTION__, slot->name); | |
940903c5 | 444 | retval = rpaphp_config_pci_adapter(slot->bus); |
1da177e4 LT |
445 | if (!retval) { |
446 | slot->state = CONFIGURED; | |
447 | dbg("%s: PCI devices in slot[%s] has been configured\n", | |
448 | __FUNCTION__, slot->name); | |
449 | } else { | |
450 | slot->state = NOT_CONFIGURED; | |
451 | dbg("%s: no pci_dev struct for adapter in slot[%s]\n", | |
452 | __FUNCTION__, slot->name); | |
453 | } | |
454 | } else if (state == EMPTY) { | |
455 | dbg("%s : slot[%s] is empty\n", __FUNCTION__, slot->name); | |
456 | slot->state = EMPTY; | |
457 | } else { | |
458 | err("%s: slot[%s] is in invalid state\n", __FUNCTION__, | |
459 | slot->name); | |
460 | slot->state = NOT_VALID; | |
461 | retval = -EINVAL; | |
462 | } | |
463 | exit: | |
464 | dbg("%s - Exit: rc[%d]\n", __FUNCTION__, retval); | |
465 | return retval; | |
466 | } |