diff options
author | Prarit Bhargava <prarit@sgi.com> | 2005-07-06 18:29:53 -0400 |
---|---|---|
committer | Tony Luck <tony.luck@intel.com> | 2005-07-06 18:29:53 -0400 |
commit | 6f354b014b51716166f13f68b29212d3c44ed2c4 (patch) | |
tree | 396c09a5d519630a53652a1187bb85fceba82cee /drivers/pci/hotplug/sgi_hotplug.c | |
parent | 283c7f6ac6adb57e7dd13cdbc8d60b6ea4de6faf (diff) |
[IA64] hotplug/ia64: SN Hotplug Driver - SN Hotplug Driver code
This patch is the SGI hotplug driver and additional changes required for
the driver. These modifications include changes to the SN io_init.c code
for memory management, the inclusion of new SAL calls to enable and disable
PCI slots, and a hotplug-style driver.
Signed-off-by: Prarit Bhargava <prarit@sgi.com>
Signed-off-by: Tony Luck <tony.luck@intel.com>
Diffstat (limited to 'drivers/pci/hotplug/sgi_hotplug.c')
-rw-r--r-- | drivers/pci/hotplug/sgi_hotplug.c | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c new file mode 100644 index 000000000000..323041fd41dc --- /dev/null +++ b/drivers/pci/hotplug/sgi_hotplug.c | |||
@@ -0,0 +1,611 @@ | |||
1 | /* | ||
2 | * This file is subject to the terms and conditions of the GNU General Public | ||
3 | * License. See the file "COPYING" in the main directory of this archive | ||
4 | * for more details. | ||
5 | * | ||
6 | * Copyright (C) 2005 Silicon Graphics, Inc. All rights reserved. | ||
7 | * | ||
8 | * This work was based on the 2.4/2.6 kernel development by Dick Reigner. | ||
9 | * Work to add BIOS PROM support was completed by Mike Habeck. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/pci.h> | ||
16 | #include <linux/proc_fs.h> | ||
17 | #include <linux/types.h> | ||
18 | |||
19 | #include <asm/sn/addrs.h> | ||
20 | #include <asm/sn/l1.h> | ||
21 | #include <asm/sn/module.h> | ||
22 | #include <asm/sn/pcibr_provider.h> | ||
23 | #include <asm/sn/pcibus_provider_defs.h> | ||
24 | #include <asm/sn/pcidev.h> | ||
25 | #include <asm/sn/sn_sal.h> | ||
26 | #include <asm/sn/types.h> | ||
27 | |||
28 | #include "../pci.h" | ||
29 | #include "pci_hotplug.h" | ||
30 | |||
31 | MODULE_LICENSE("GPL"); | ||
32 | MODULE_AUTHOR("SGI (prarit@sgi.com, dickie@sgi.com, habeck@sgi.com)"); | ||
33 | MODULE_DESCRIPTION("SGI Altix Hot Plug PCI Controller Driver"); | ||
34 | |||
35 | #define PCIIO_ASIC_TYPE_TIOCA 4 | ||
36 | #define PCI_SLOT_ALREADY_UP 2 /* slot already up */ | ||
37 | #define PCI_SLOT_ALREADY_DOWN 3 /* slot already down */ | ||
38 | #define PCI_L1_ERR 7 /* L1 console command error */ | ||
39 | #define PCI_EMPTY_33MHZ 15 /* empty 33 MHz bus */ | ||
40 | #define PCI_L1_QSIZE 128 /* our L1 message buffer size */ | ||
41 | #define SN_MAX_HP_SLOTS 32 /* max number of hotplug slots */ | ||
42 | #define SGI_HOTPLUG_PROM_REV 0x0420 /* Min. required PROM version */ | ||
43 | |||
44 | /* internal list head */ | ||
45 | static struct list_head sn_hp_list; | ||
46 | |||
47 | /* hotplug_slot struct's private pointer */ | ||
48 | struct slot { | ||
49 | int device_num; | ||
50 | struct pci_bus *pci_bus; | ||
51 | /* this struct for glue internal only */ | ||
52 | struct hotplug_slot *hotplug_slot; | ||
53 | struct list_head hp_list; | ||
54 | }; | ||
55 | |||
56 | struct pcibr_slot_enable_resp { | ||
57 | int resp_sub_errno; | ||
58 | char resp_l1_msg[PCI_L1_QSIZE + 1]; | ||
59 | }; | ||
60 | |||
61 | struct pcibr_slot_disable_resp { | ||
62 | int resp_sub_errno; | ||
63 | char resp_l1_msg[PCI_L1_QSIZE + 1]; | ||
64 | }; | ||
65 | |||
66 | enum sn_pci_req_e { | ||
67 | PCI_REQ_SLOT_ELIGIBLE, | ||
68 | PCI_REQ_SLOT_DISABLE | ||
69 | }; | ||
70 | |||
71 | static int enable_slot(struct hotplug_slot *slot); | ||
72 | static int disable_slot(struct hotplug_slot *slot); | ||
73 | static int get_power_status(struct hotplug_slot *slot, u8 *value); | ||
74 | |||
75 | static struct hotplug_slot_ops sn_hotplug_slot_ops = { | ||
76 | .owner = THIS_MODULE, | ||
77 | .enable_slot = enable_slot, | ||
78 | .disable_slot = disable_slot, | ||
79 | .get_power_status = get_power_status, | ||
80 | }; | ||
81 | |||
82 | static DECLARE_MUTEX(sn_hotplug_sem); | ||
83 | |||
84 | static int sn_pci_slot_valid(struct pci_bus *pci_bus, int device) | ||
85 | { | ||
86 | struct pcibus_info *pcibus_info; | ||
87 | int bricktype; | ||
88 | int bus_num; | ||
89 | |||
90 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); | ||
91 | |||
92 | /* Check to see if this is a valid slot on 'pci_bus' */ | ||
93 | if (!(pcibus_info->pbi_valid_devices & (1 << device))) | ||
94 | return -EPERM; | ||
95 | |||
96 | bricktype = MODULE_GET_BTYPE(pcibus_info->pbi_moduleid); | ||
97 | bus_num = pcibus_info->pbi_buscommon.bs_persist_busnum & 0xf; | ||
98 | |||
99 | /* Do not allow hotplug operations on base I/O cards */ | ||
100 | if ((bricktype == L1_BRICKTYPE_IX || bricktype == L1_BRICKTYPE_IA) && | ||
101 | (bus_num == 1 && device != 1)) | ||
102 | return -EPERM; | ||
103 | |||
104 | return 1; | ||
105 | } | ||
106 | |||
107 | static int sn_pci_bus_valid(struct pci_bus *pci_bus) | ||
108 | { | ||
109 | struct pcibus_info *pcibus_info; | ||
110 | int asic_type; | ||
111 | int bricktype; | ||
112 | |||
113 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); | ||
114 | |||
115 | /* Don't register slots hanging off the TIOCA bus */ | ||
116 | asic_type = pcibus_info->pbi_buscommon.bs_asic_type; | ||
117 | if (asic_type == PCIIO_ASIC_TYPE_TIOCA) | ||
118 | return -EPERM; | ||
119 | |||
120 | /* Only register slots in I/O Bricks that support hotplug */ | ||
121 | bricktype = MODULE_GET_BTYPE(pcibus_info->pbi_moduleid); | ||
122 | switch (bricktype) { | ||
123 | case L1_BRICKTYPE_IX: | ||
124 | case L1_BRICKTYPE_PX: | ||
125 | case L1_BRICKTYPE_IA: | ||
126 | case L1_BRICKTYPE_PA: | ||
127 | return 1; | ||
128 | break; | ||
129 | default: | ||
130 | return -EPERM; | ||
131 | break; | ||
132 | } | ||
133 | |||
134 | return -EIO; | ||
135 | } | ||
136 | |||
137 | static int sn_hp_slot_private_alloc(struct hotplug_slot *bss_hotplug_slot, | ||
138 | struct pci_bus *pci_bus, int device) | ||
139 | { | ||
140 | struct pcibus_info *pcibus_info; | ||
141 | struct slot *slot; | ||
142 | |||
143 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(pci_bus); | ||
144 | |||
145 | bss_hotplug_slot->private = kcalloc(1, sizeof(struct slot), | ||
146 | GFP_KERNEL); | ||
147 | if (!bss_hotplug_slot->private) | ||
148 | return -ENOMEM; | ||
149 | slot = (struct slot *)bss_hotplug_slot->private; | ||
150 | |||
151 | bss_hotplug_slot->name = kmalloc(33, GFP_KERNEL); | ||
152 | if (!bss_hotplug_slot->name) { | ||
153 | kfree(bss_hotplug_slot->private); | ||
154 | return -ENOMEM; | ||
155 | } | ||
156 | |||
157 | slot->device_num = device; | ||
158 | slot->pci_bus = pci_bus; | ||
159 | |||
160 | sprintf(bss_hotplug_slot->name, "module_%c%c%c%c%.2d_b_%d_s_%d", | ||
161 | '0'+RACK_GET_CLASS(MODULE_GET_RACK(pcibus_info->pbi_moduleid)), | ||
162 | '0'+RACK_GET_GROUP(MODULE_GET_RACK(pcibus_info->pbi_moduleid)), | ||
163 | '0'+RACK_GET_NUM(MODULE_GET_RACK(pcibus_info->pbi_moduleid)), | ||
164 | MODULE_GET_BTCHAR(pcibus_info->pbi_moduleid), | ||
165 | MODULE_GET_BPOS(pcibus_info->pbi_moduleid), | ||
166 | ((int)pcibus_info->pbi_buscommon.bs_persist_busnum) & 0xf, | ||
167 | device + 1); | ||
168 | |||
169 | slot->hotplug_slot = bss_hotplug_slot; | ||
170 | list_add(&slot->hp_list, &sn_hp_list); | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | static struct hotplug_slot * sn_hp_destroy(void) | ||
176 | { | ||
177 | struct slot *slot; | ||
178 | struct list_head *list; | ||
179 | struct hotplug_slot *bss_hotplug_slot = NULL; | ||
180 | |||
181 | list_for_each(list, &sn_hp_list) { | ||
182 | slot = list_entry(list, struct slot, hp_list); | ||
183 | bss_hotplug_slot = slot->hotplug_slot; | ||
184 | list_del(&((struct slot *)bss_hotplug_slot->private)-> | ||
185 | hp_list); | ||
186 | break; | ||
187 | } | ||
188 | return bss_hotplug_slot; | ||
189 | } | ||
190 | |||
191 | static void sn_bus_alloc_data(struct pci_dev *dev) | ||
192 | { | ||
193 | struct list_head *node; | ||
194 | struct pci_bus *subordinate_bus; | ||
195 | struct pci_dev *child; | ||
196 | |||
197 | sn_pci_fixup_slot(dev); | ||
198 | |||
199 | /* Recursively sets up the sn_irq_info structs */ | ||
200 | if (dev->subordinate) { | ||
201 | subordinate_bus = dev->subordinate; | ||
202 | list_for_each(node, &subordinate_bus->devices) { | ||
203 | child = list_entry(node, struct pci_dev, bus_list); | ||
204 | sn_bus_alloc_data(child); | ||
205 | } | ||
206 | } | ||
207 | } | ||
208 | |||
209 | static void sn_bus_free_data(struct pci_dev *dev) | ||
210 | { | ||
211 | struct list_head *node; | ||
212 | struct pci_bus *subordinate_bus; | ||
213 | struct pci_dev *child; | ||
214 | |||
215 | /* Recursively clean up sn_irq_info structs */ | ||
216 | if (dev->subordinate) { | ||
217 | subordinate_bus = dev->subordinate; | ||
218 | list_for_each(node, &subordinate_bus->devices) { | ||
219 | child = list_entry(node, struct pci_dev, bus_list); | ||
220 | sn_bus_free_data(child); | ||
221 | } | ||
222 | } | ||
223 | sn_pci_unfixup_slot(dev); | ||
224 | } | ||
225 | |||
226 | static u8 sn_power_status_get(struct hotplug_slot *bss_hotplug_slot) | ||
227 | { | ||
228 | struct slot *slot = (struct slot *)bss_hotplug_slot->private; | ||
229 | struct pcibus_info *pcibus_info; | ||
230 | u8 retval; | ||
231 | |||
232 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); | ||
233 | retval = pcibus_info->pbi_enabled_devices & (1 << slot->device_num); | ||
234 | |||
235 | return retval ? 1 : 0; | ||
236 | } | ||
237 | |||
238 | static void sn_slot_mark_enable(struct hotplug_slot *bss_hotplug_slot, | ||
239 | int device_num) | ||
240 | { | ||
241 | struct slot *slot = (struct slot *)bss_hotplug_slot->private; | ||
242 | struct pcibus_info *pcibus_info; | ||
243 | |||
244 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); | ||
245 | pcibus_info->pbi_enabled_devices |= (1 << device_num); | ||
246 | } | ||
247 | |||
248 | static void sn_slot_mark_disable(struct hotplug_slot *bss_hotplug_slot, | ||
249 | int device_num) | ||
250 | { | ||
251 | struct slot *slot = (struct slot *)bss_hotplug_slot->private; | ||
252 | struct pcibus_info *pcibus_info; | ||
253 | |||
254 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); | ||
255 | pcibus_info->pbi_enabled_devices &= ~(1 << device_num); | ||
256 | } | ||
257 | |||
258 | static int sn_slot_enable(struct hotplug_slot *bss_hotplug_slot, | ||
259 | int device_num) | ||
260 | { | ||
261 | struct slot *slot = (struct slot *)bss_hotplug_slot->private; | ||
262 | struct pcibus_info *pcibus_info; | ||
263 | struct pcibr_slot_enable_resp resp; | ||
264 | int rc; | ||
265 | |||
266 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); | ||
267 | |||
268 | /* | ||
269 | * Power-on and initialize the slot in the SN | ||
270 | * PCI infrastructure. | ||
271 | */ | ||
272 | rc = sal_pcibr_slot_enable(pcibus_info, device_num, &resp); | ||
273 | |||
274 | if (rc == PCI_SLOT_ALREADY_UP) { | ||
275 | dev_dbg(slot->pci_bus->self, "is already active\n"); | ||
276 | return -EPERM; | ||
277 | } | ||
278 | |||
279 | if (rc == PCI_L1_ERR) { | ||
280 | dev_dbg(slot->pci_bus->self, | ||
281 | "L1 failure %d with message: %s", | ||
282 | resp.resp_sub_errno, resp.resp_l1_msg); | ||
283 | return -EPERM; | ||
284 | } | ||
285 | |||
286 | if (rc) { | ||
287 | dev_dbg(slot->pci_bus->self, | ||
288 | "insert failed with error %d sub-error %d\n", | ||
289 | rc, resp.resp_sub_errno); | ||
290 | return -EIO; | ||
291 | } | ||
292 | |||
293 | sn_slot_mark_enable(bss_hotplug_slot, device_num); | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | static int sn_slot_disable(struct hotplug_slot *bss_hotplug_slot, | ||
299 | int device_num, int action) | ||
300 | { | ||
301 | struct slot *slot = (struct slot *)bss_hotplug_slot->private; | ||
302 | struct pcibus_info *pcibus_info; | ||
303 | struct pcibr_slot_disable_resp resp; | ||
304 | int rc; | ||
305 | |||
306 | pcibus_info = SN_PCIBUS_BUSSOFT_INFO(slot->pci_bus); | ||
307 | |||
308 | rc = sal_pcibr_slot_disable(pcibus_info, device_num, action, &resp); | ||
309 | |||
310 | if (action == PCI_REQ_SLOT_ELIGIBLE && rc == PCI_SLOT_ALREADY_DOWN) { | ||
311 | dev_dbg(slot->pci_bus->self, "Slot %s already inactive\n"); | ||
312 | return -ENODEV; | ||
313 | } | ||
314 | |||
315 | if (action == PCI_REQ_SLOT_ELIGIBLE && rc == PCI_EMPTY_33MHZ) { | ||
316 | dev_dbg(slot->pci_bus->self, | ||
317 | "Cannot remove last 33MHz card\n"); | ||
318 | return -EPERM; | ||
319 | } | ||
320 | |||
321 | if (action == PCI_REQ_SLOT_ELIGIBLE && rc == PCI_L1_ERR) { | ||
322 | dev_dbg(slot->pci_bus->self, | ||
323 | "L1 failure %d with message \n%s\n", | ||
324 | resp.resp_sub_errno, resp.resp_l1_msg); | ||
325 | return -EPERM; | ||
326 | } | ||
327 | |||
328 | if (action == PCI_REQ_SLOT_ELIGIBLE && rc) { | ||
329 | dev_dbg(slot->pci_bus->self, | ||
330 | "remove failed with error %d sub-error %d\n", | ||
331 | rc, resp.resp_sub_errno); | ||
332 | return -EIO; | ||
333 | } | ||
334 | |||
335 | if (action == PCI_REQ_SLOT_ELIGIBLE && !rc) | ||
336 | return 0; | ||
337 | |||
338 | if (action == PCI_REQ_SLOT_DISABLE && !rc) { | ||
339 | sn_slot_mark_disable(bss_hotplug_slot, device_num); | ||
340 | dev_dbg(slot->pci_bus->self, "remove successful\n"); | ||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | if (action == PCI_REQ_SLOT_DISABLE && rc) { | ||
345 | dev_dbg(slot->pci_bus->self,"remove failed rc = %d\n", rc); | ||
346 | return rc; | ||
347 | } | ||
348 | |||
349 | return rc; | ||
350 | } | ||
351 | |||
352 | static int enable_slot(struct hotplug_slot *bss_hotplug_slot) | ||
353 | { | ||
354 | struct slot *slot = (struct slot *)bss_hotplug_slot->private; | ||
355 | struct pci_bus *new_bus = NULL; | ||
356 | struct pci_dev *dev; | ||
357 | int func, num_funcs; | ||
358 | int new_ppb = 0; | ||
359 | int rc; | ||
360 | |||
361 | /* Serialize the Linux PCI infrastructure */ | ||
362 | down(&sn_hotplug_sem); | ||
363 | |||
364 | /* | ||
365 | * Power-on and initialize the slot in the SN | ||
366 | * PCI infrastructure. | ||
367 | */ | ||
368 | rc = sn_slot_enable(bss_hotplug_slot, slot->device_num); | ||
369 | if (rc) { | ||
370 | up(&sn_hotplug_sem); | ||
371 | return rc; | ||
372 | } | ||
373 | |||
374 | num_funcs = pci_scan_slot(slot->pci_bus, PCI_DEVFN(slot->device_num+1, | ||
375 | PCI_FUNC(0))); | ||
376 | if (!num_funcs) { | ||
377 | dev_dbg(slot->pci_bus->self, "no device in slot\n"); | ||
378 | up(&sn_hotplug_sem); | ||
379 | return -ENODEV; | ||
380 | } | ||
381 | |||
382 | sn_pci_controller_fixup(pci_domain_nr(slot->pci_bus), | ||
383 | slot->pci_bus->number, | ||
384 | slot->pci_bus); | ||
385 | /* | ||
386 | * Map SN resources for all functions on the card | ||
387 | * to the Linux PCI interface and tell the drivers | ||
388 | * about them. | ||
389 | */ | ||
390 | for (func = 0; func < num_funcs; func++) { | ||
391 | dev = pci_get_slot(slot->pci_bus, | ||
392 | PCI_DEVFN(slot->device_num + 1, | ||
393 | PCI_FUNC(func))); | ||
394 | |||
395 | |||
396 | if (dev) { | ||
397 | if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { | ||
398 | unsigned char sec_bus; | ||
399 | pci_read_config_byte(dev, PCI_SECONDARY_BUS, | ||
400 | &sec_bus); | ||
401 | new_bus = pci_add_new_bus(dev->bus, dev, | ||
402 | sec_bus); | ||
403 | pci_scan_child_bus(new_bus); | ||
404 | sn_pci_controller_fixup(pci_domain_nr(new_bus), | ||
405 | new_bus->number, | ||
406 | new_bus); | ||
407 | new_ppb = 1; | ||
408 | } | ||
409 | sn_bus_alloc_data(dev); | ||
410 | pci_dev_put(dev); | ||
411 | } | ||
412 | } | ||
413 | |||
414 | /* Call the driver for the new device */ | ||
415 | pci_bus_add_devices(slot->pci_bus); | ||
416 | /* Call the drivers for the new devices subordinate to PPB */ | ||
417 | if (new_ppb) | ||
418 | pci_bus_add_devices(new_bus); | ||
419 | |||
420 | up(&sn_hotplug_sem); | ||
421 | |||
422 | if (rc == 0) | ||
423 | dev_dbg(slot->pci_bus->self, | ||
424 | "insert operation successful\n"); | ||
425 | else | ||
426 | dev_dbg(slot->pci_bus->self, | ||
427 | "insert operation failed rc = %d\n", rc); | ||
428 | |||
429 | return rc; | ||
430 | } | ||
431 | |||
432 | static int disable_slot(struct hotplug_slot *bss_hotplug_slot) | ||
433 | { | ||
434 | struct slot *slot = (struct slot *)bss_hotplug_slot->private; | ||
435 | struct pci_dev *dev; | ||
436 | int func; | ||
437 | int rc; | ||
438 | |||
439 | /* Acquire update access to the bus */ | ||
440 | down(&sn_hotplug_sem); | ||
441 | |||
442 | /* is it okay to bring this slot down? */ | ||
443 | rc = sn_slot_disable(bss_hotplug_slot, slot->device_num, | ||
444 | PCI_REQ_SLOT_ELIGIBLE); | ||
445 | if (rc) | ||
446 | goto leaving; | ||
447 | |||
448 | /* Free the SN resources assigned to the Linux device.*/ | ||
449 | for (func = 0; func < 8; func++) { | ||
450 | dev = pci_get_slot(slot->pci_bus, | ||
451 | PCI_DEVFN(slot->device_num+1, | ||
452 | PCI_FUNC(func))); | ||
453 | if (dev) { | ||
454 | /* | ||
455 | * Some drivers may use dma accesses during the | ||
456 | * driver remove function. We release the sysdata | ||
457 | * areas after the driver remove functions have | ||
458 | * been called. | ||
459 | */ | ||
460 | sn_bus_store_sysdata(dev); | ||
461 | sn_bus_free_data(dev); | ||
462 | pci_remove_bus_device(dev); | ||
463 | pci_dev_put(dev); | ||
464 | } | ||
465 | } | ||
466 | |||
467 | /* free the collected sysdata pointers */ | ||
468 | sn_bus_free_sysdata(); | ||
469 | |||
470 | /* Deactivate slot */ | ||
471 | rc = sn_slot_disable(bss_hotplug_slot, slot->device_num, | ||
472 | PCI_REQ_SLOT_DISABLE); | ||
473 | leaving: | ||
474 | /* Release the bus lock */ | ||
475 | up(&sn_hotplug_sem); | ||
476 | |||
477 | return rc; | ||
478 | } | ||
479 | |||
480 | static int get_power_status(struct hotplug_slot *bss_hotplug_slot, u8 *value) | ||
481 | { | ||
482 | down(&sn_hotplug_sem); | ||
483 | *value = sn_power_status_get(bss_hotplug_slot); | ||
484 | up(&sn_hotplug_sem); | ||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | static void sn_release_slot(struct hotplug_slot *bss_hotplug_slot) | ||
489 | { | ||
490 | kfree(bss_hotplug_slot->info); | ||
491 | kfree(bss_hotplug_slot->name); | ||
492 | kfree(bss_hotplug_slot->private); | ||
493 | kfree(bss_hotplug_slot); | ||
494 | } | ||
495 | |||
496 | static int sn_hotplug_slot_register(struct pci_bus *pci_bus) | ||
497 | { | ||
498 | int device; | ||
499 | struct hotplug_slot *bss_hotplug_slot; | ||
500 | int rc = 0; | ||
501 | |||
502 | /* | ||
503 | * Currently only four devices are supported, | ||
504 | * in the future there maybe more -- up to 32. | ||
505 | */ | ||
506 | |||
507 | for (device = 0; device < SN_MAX_HP_SLOTS ; device++) { | ||
508 | if (sn_pci_slot_valid(pci_bus, device) != 1) | ||
509 | continue; | ||
510 | |||
511 | bss_hotplug_slot = kcalloc(1,sizeof(struct hotplug_slot), | ||
512 | GFP_KERNEL); | ||
513 | if (!bss_hotplug_slot) { | ||
514 | rc = -ENOMEM; | ||
515 | goto alloc_err; | ||
516 | } | ||
517 | |||
518 | bss_hotplug_slot->info = | ||
519 | kcalloc(1,sizeof(struct hotplug_slot_info), | ||
520 | GFP_KERNEL); | ||
521 | if (!bss_hotplug_slot->info) { | ||
522 | rc = -ENOMEM; | ||
523 | goto alloc_err; | ||
524 | } | ||
525 | |||
526 | if (sn_hp_slot_private_alloc(bss_hotplug_slot, | ||
527 | pci_bus, device)) { | ||
528 | rc = -ENOMEM; | ||
529 | goto alloc_err; | ||
530 | } | ||
531 | |||
532 | bss_hotplug_slot->ops = &sn_hotplug_slot_ops; | ||
533 | bss_hotplug_slot->release = &sn_release_slot; | ||
534 | |||
535 | rc = pci_hp_register(bss_hotplug_slot); | ||
536 | if (rc) | ||
537 | goto register_err; | ||
538 | } | ||
539 | dev_dbg(pci_bus->self, "Registered bus with hotplug\n"); | ||
540 | return rc; | ||
541 | |||
542 | register_err: | ||
543 | dev_dbg(pci_bus->self, "bus failed to register with err = %d\n", | ||
544 | rc); | ||
545 | |||
546 | alloc_err: | ||
547 | if (rc == -ENOMEM) | ||
548 | dev_dbg(pci_bus->self, "Memory allocation error\n"); | ||
549 | |||
550 | /* destroy THIS element */ | ||
551 | if (bss_hotplug_slot) | ||
552 | sn_release_slot(bss_hotplug_slot); | ||
553 | |||
554 | /* destroy anything else on the list */ | ||
555 | while ((bss_hotplug_slot = sn_hp_destroy())) | ||
556 | pci_hp_deregister(bss_hotplug_slot); | ||
557 | |||
558 | return rc; | ||
559 | } | ||
560 | |||
561 | static int sn_pci_hotplug_init(void) | ||
562 | { | ||
563 | struct pci_bus *pci_bus = NULL; | ||
564 | int rc; | ||
565 | int registered = 0; | ||
566 | |||
567 | INIT_LIST_HEAD(&sn_hp_list); | ||
568 | |||
569 | if (sn_sal_rev() < SGI_HOTPLUG_PROM_REV) { | ||
570 | printk(KERN_ERR "%s: PROM version must be greater than 4.05\n", | ||
571 | __FUNCTION__); | ||
572 | return -EPERM; | ||
573 | } | ||
574 | |||
575 | while ((pci_bus = pci_find_next_bus(pci_bus))) { | ||
576 | if (!pci_bus->sysdata) | ||
577 | continue; | ||
578 | |||
579 | rc = sn_pci_bus_valid(pci_bus); | ||
580 | if (rc != 1) { | ||
581 | dev_dbg(pci_bus->self, "not a valid hotplug bus\n"); | ||
582 | continue; | ||
583 | } | ||
584 | dev_dbg(pci_bus->self, "valid hotplug bus\n"); | ||
585 | |||
586 | rc = sn_hotplug_slot_register(pci_bus); | ||
587 | if (!rc) | ||
588 | registered = 1; | ||
589 | else { | ||
590 | registered = 0; | ||
591 | break; | ||
592 | } | ||
593 | } | ||
594 | |||
595 | return registered == 1 ? 0 : -ENODEV; | ||
596 | } | ||
597 | |||
598 | static void sn_pci_hotplug_exit(void) | ||
599 | { | ||
600 | struct hotplug_slot *bss_hotplug_slot; | ||
601 | |||
602 | while ((bss_hotplug_slot = sn_hp_destroy())) { | ||
603 | pci_hp_deregister(bss_hotplug_slot); | ||
604 | } | ||
605 | |||
606 | if (!list_empty(&sn_hp_list)) | ||
607 | printk(KERN_ERR "%s: internal list is not empty\n", __FILE__); | ||
608 | } | ||
609 | |||
610 | module_init(sn_pci_hotplug_init); | ||
611 | module_exit(sn_pci_hotplug_exit); | ||