1 /*
2  * PCI HotPlug Controller Core
3  *
4  * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com)
5  * Copyright (C) 2001-2002 IBM Corp.
6  *
7  * All rights reserved.
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or (at
12  * your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
17  * NON INFRINGEMENT.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  * Send feedback to <greg@kroah.com>
25  *
26  * Filesystem portion based on work done by Pat Mochel on ddfs/driverfs
27  *
28  */
29 
30 #include <linux/config.h>
31 #include <linux/module.h>
32 #include <linux/moduleparam.h>
33 #include <linux/kernel.h>
34 #include <linux/types.h>
35 #include <linux/list.h>
36 #include <linux/pagemap.h>
37 #include <linux/slab.h>
38 #include <linux/smp_lock.h>
39 #include <linux/init.h>
40 #include <linux/mount.h>
41 #include <linux/namei.h>
42 #include <linux/pci.h>
43 #include <asm/uaccess.h>
44 #include <linux/kobject.h>
45 #include <linux/sysfs.h>
46 #include "pci_hotplug.h"
47 
48 
49 #define MY_NAME	"pci_hotplug"
50 
51 #define dbg(fmt, arg...) do { if (debug) printk(KERN_DEBUG "%s: %s: " fmt , MY_NAME , __FUNCTION__ , ## arg); } while (0)
52 #define err(format, arg...) printk(KERN_ERR "%s: " format , MY_NAME , ## arg)
53 #define info(format, arg...) printk(KERN_INFO "%s: " format , MY_NAME , ## arg)
54 #define warn(format, arg...) printk(KERN_WARNING "%s: " format , MY_NAME , ## arg)
55 
56 
57 /* local variables */
58 static int debug;
59 
60 #define DRIVER_VERSION	"0.5"
61 #define DRIVER_AUTHOR	"Greg Kroah-Hartman <greg@kroah.com>, Scott Murray <scottm@somanetworks.com>"
62 #define DRIVER_DESC	"PCI Hot Plug PCI Core"
63 
64 
65 //////////////////////////////////////////////////////////////////
66 
67 static LIST_HEAD(pci_hotplug_slot_list);
68 
69 struct subsystem pci_hotplug_slots_subsys;
70 
71 static ssize_t hotplug_slot_attr_show(struct kobject *kobj,
72 		struct attribute *attr, char *buf)
73 {
74 	struct hotplug_slot *slot = to_hotplug_slot(kobj);
75 	struct hotplug_slot_attribute *attribute = to_hotplug_attr(attr);
76 	return attribute->show ? attribute->show(slot, buf) : 0;
77 }
78 
79 static ssize_t hotplug_slot_attr_store(struct kobject *kobj,
80 		struct attribute *attr, const char *buf, size_t len)
81 {
82 	struct hotplug_slot *slot = to_hotplug_slot(kobj);
83 	struct hotplug_slot_attribute *attribute = to_hotplug_attr(attr);
84 	return attribute->store ? attribute->store(slot, buf, len) : 0;
85 }
86 
87 static struct sysfs_ops hotplug_slot_sysfs_ops = {
88 	.show = hotplug_slot_attr_show,
89 	.store = hotplug_slot_attr_store,
90 };
91 
92 static void hotplug_slot_release(struct kobject *kobj)
93 {
94 	struct hotplug_slot *slot = to_hotplug_slot(kobj);
95 	if (slot->release)
96 		slot->release(slot);
97 }
98 
99 static struct kobj_type hotplug_slot_ktype = {
100 	.sysfs_ops = &hotplug_slot_sysfs_ops,
101 	.release = &hotplug_slot_release,
102 };
103 
104 decl_subsys_name(pci_hotplug_slots, slots, &hotplug_slot_ktype, NULL);
105 
106 /* these strings match up with the values in pci_bus_speed */
107 static char *pci_bus_speed_strings[] = {
108 	"33 MHz PCI",		/* 0x00 */
109 	"66 MHz PCI",		/* 0x01 */
110 	"66 MHz PCIX", 		/* 0x02 */
111 	"100 MHz PCIX",		/* 0x03 */
112 	"133 MHz PCIX",		/* 0x04 */
113 	NULL,			/* 0x05 */
114 	NULL,			/* 0x06 */
115 	NULL,			/* 0x07 */
116 	NULL,			/* 0x08 */
117 	"66 MHz PCIX 266",	/* 0x09 */
118 	"100 MHz PCIX 266",	/* 0x0a */
119 	"133 MHz PCIX 266",	/* 0x0b */
120 	NULL,			/* 0x0c */
121 	NULL,			/* 0x0d */
122 	NULL,			/* 0x0e */
123 	NULL,			/* 0x0f */
124 	NULL,			/* 0x10 */
125 	"66 MHz PCIX 533",	/* 0x11 */
126 	"100 MHz PCIX 533",	/* 0x12 */
127 	"133 MHz PCIX 533",	/* 0x13 */
128 	"25 GBps PCI-E",	/* 0x14 */
129 };
130 
131 #ifdef CONFIG_HOTPLUG_PCI_CPCI
132 extern int cpci_hotplug_init(int debug);
133 extern void cpci_hotplug_exit(void);
134 #else
135 static inline int cpci_hotplug_init(int debug) { return 0; }
136 static inline void cpci_hotplug_exit(void) { }
137 #endif
138 
139 /* Weee, fun with macros... */
140 #define GET_STATUS(name,type)	\
141 static int get_##name (struct hotplug_slot *slot, type *value)		\
142 {									\
143 	struct hotplug_slot_ops *ops = slot->ops;			\
144 	int retval = 0;							\
145 	if (try_module_get(ops->owner)) {				\
146 		if (ops->get_##name)					\
147 			retval = ops->get_##name (slot, value);		\
148 		else							\
149 			*value = slot->info->name;			\
150 		module_put(ops->owner);					\
151 	}								\
152 	return retval;							\
153 }
154 
155 GET_STATUS(power_status, u8)
156 GET_STATUS(attention_status, u8)
157 GET_STATUS(latch_status, u8)
158 GET_STATUS(adapter_status, u8)
159 GET_STATUS(address, u32)
160 GET_STATUS(max_bus_speed, enum pci_bus_speed)
161 GET_STATUS(cur_bus_speed, enum pci_bus_speed)
162 
163 static ssize_t power_read_file (struct hotplug_slot *slot, char *buf)
164 {
165 	int retval;
166 	u8 value;
167 
168 	retval = get_power_status (slot, &value);
169 	if (retval)
170 		goto exit;
171 	retval = sprintf (buf, "%d\n", value);
172 exit:
173 	return retval;
174 }
175 
176 static ssize_t power_write_file (struct hotplug_slot *slot, const char *buf,
177 		size_t count)
178 {
179 	unsigned long lpower;
180 	u8 power;
181 	int retval = 0;
182 
183 	lpower = simple_strtoul (buf, NULL, 10);
184 	power = (u8)(lpower & 0xff);
185 	dbg ("power = %d\n", power);
186 
187 	if (!try_module_get(slot->ops->owner)) {
188 		retval = -ENODEV;
189 		goto exit;
190 	}
191 	switch (power) {
192 		case 0:
193 			if (slot->ops->disable_slot)
194 				retval = slot->ops->disable_slot(slot);
195 			break;
196 
197 		case 1:
198 			if (slot->ops->enable_slot)
199 				retval = slot->ops->enable_slot(slot);
200 			break;
201 
202 		default:
203 			err ("Illegal value specified for power\n");
204 			retval = -EINVAL;
205 	}
206 	module_put(slot->ops->owner);
207 
208 exit:
209 	if (retval)
210 		return retval;
211 	return count;
212 }
213 
214 static struct hotplug_slot_attribute hotplug_slot_attr_power = {
215 	.attr = {.name = "power", .mode = S_IFREG | S_IRUGO | S_IWUSR},
216 	.show = power_read_file,
217 	.store = power_write_file
218 };
219 
220 static ssize_t attention_read_file (struct hotplug_slot *slot, char *buf)
221 {
222 	int retval;
223 	u8 value;
224 
225 	retval = get_attention_status (slot, &value);
226 	if (retval)
227 		goto exit;
228 	retval = sprintf (buf, "%d\n", value);
229 
230 exit:
231 	return retval;
232 }
233 
234 static ssize_t attention_write_file (struct hotplug_slot *slot, const char *buf,
235 		size_t count)
236 {
237 	unsigned long lattention;
238 	u8 attention;
239 	int retval = 0;
240 
241 	lattention = simple_strtoul (buf, NULL, 10);
242 	attention = (u8)(lattention & 0xff);
243 	dbg (" - attention = %d\n", attention);
244 
245 	if (!try_module_get(slot->ops->owner)) {
246 		retval = -ENODEV;
247 		goto exit;
248 	}
249 	if (slot->ops->set_attention_status)
250 		retval = slot->ops->set_attention_status(slot, attention);
251 	module_put(slot->ops->owner);
252 
253 exit:
254 	if (retval)
255 		return retval;
256 	return count;
257 }
258 
259 static struct hotplug_slot_attribute hotplug_slot_attr_attention = {
260 	.attr = {.name = "attention", .mode = S_IFREG | S_IRUGO | S_IWUSR},
261 	.show = attention_read_file,
262 	.store = attention_write_file
263 };
264 
265 static ssize_t latch_read_file (struct hotplug_slot *slot, char *buf)
266 {
267 	int retval;
268 	u8 value;
269 
270 	retval = get_latch_status (slot, &value);
271 	if (retval)
272 		goto exit;
273 	retval = sprintf (buf, "%d\n", value);
274 
275 exit:
276 	return retval;
277 }
278 
279 static struct hotplug_slot_attribute hotplug_slot_attr_latch = {
280 	.attr = {.name = "latch", .mode = S_IFREG | S_IRUGO},
281 	.show = latch_read_file,
282 };
283 
284 static ssize_t presence_read_file (struct hotplug_slot *slot, char *buf)
285 {
286 	int retval;
287 	u8 value;
288 
289 	retval = get_adapter_status (slot, &value);
290 	if (retval)
291 		goto exit;
292 	retval = sprintf (buf, "%d\n", value);
293 
294 exit:
295 	return retval;
296 }
297 
298 static struct hotplug_slot_attribute hotplug_slot_attr_presence = {
299 	.attr = {.name = "adapter", .mode = S_IFREG | S_IRUGO},
300 	.show = presence_read_file,
301 };
302 
303 static ssize_t address_read_file (struct hotplug_slot *slot, char *buf)
304 {
305 	int retval;
306 	u32 address;
307 
308 	retval = get_address (slot, &address);
309 	if (retval)
310 		goto exit;
311 	retval = sprintf (buf, "%04x:%02x:%02x\n",
312 			  (address >> 16) & 0xffff,
313 			  (address >> 8) & 0xff,
314 			  address & 0xff);
315 
316 exit:
317 	return retval;
318 }
319 
320 static struct hotplug_slot_attribute hotplug_slot_attr_address = {
321 	.attr = {.name = "address", .mode = S_IFREG | S_IRUGO},
322 	.show = address_read_file,
323 };
324 
325 static char *unknown_speed = "Unknown bus speed";
326 
327 static ssize_t max_bus_speed_read_file (struct hotplug_slot *slot, char *buf)
328 {
329 	char *speed_string;
330 	int retval;
331 	enum pci_bus_speed value;
332 
333 	retval = get_max_bus_speed (slot, &value);
334 	if (retval)
335 		goto exit;
336 
337 	if (value == PCI_SPEED_UNKNOWN)
338 		speed_string = unknown_speed;
339 	else
340 		speed_string = pci_bus_speed_strings[value];
341 
342 	retval = sprintf (buf, "%s\n", speed_string);
343 
344 exit:
345 	return retval;
346 }
347 
348 static struct hotplug_slot_attribute hotplug_slot_attr_max_bus_speed = {
349 	.attr = {.name = "max_bus_speed", .mode = S_IFREG | S_IRUGO},
350 	.show = max_bus_speed_read_file,
351 };
352 
353 static ssize_t cur_bus_speed_read_file (struct hotplug_slot *slot, char *buf)
354 {
355 	char *speed_string;
356 	int retval;
357 	enum pci_bus_speed value;
358 
359 	retval = get_cur_bus_speed (slot, &value);
360 	if (retval)
361 		goto exit;
362 
363 	if (value == PCI_SPEED_UNKNOWN)
364 		speed_string = unknown_speed;
365 	else
366 		speed_string = pci_bus_speed_strings[value];
367 
368 	retval = sprintf (buf, "%s\n", speed_string);
369 
370 exit:
371 	return retval;
372 }
373 
374 static struct hotplug_slot_attribute hotplug_slot_attr_cur_bus_speed = {
375 	.attr = {.name = "cur_bus_speed", .mode = S_IFREG | S_IRUGO},
376 	.show = cur_bus_speed_read_file,
377 };
378 
379 static ssize_t test_write_file (struct hotplug_slot *slot, const char *buf,
380 		size_t count)
381 {
382 	unsigned long ltest;
383 	u32 test;
384 	int retval = 0;
385 
386 	ltest = simple_strtoul (buf, NULL, 10);
387 	test = (u32)(ltest & 0xffffffff);
388 	dbg ("test = %d\n", test);
389 
390 	if (!try_module_get(slot->ops->owner)) {
391 		retval = -ENODEV;
392 		goto exit;
393 	}
394 	if (slot->ops->hardware_test)
395 		retval = slot->ops->hardware_test(slot, test);
396 	module_put(slot->ops->owner);
397 
398 exit:
399 	if (retval)
400 		return retval;
401 	return count;
402 }
403 
404 static struct hotplug_slot_attribute hotplug_slot_attr_test = {
405 	.attr = {.name = "test", .mode = S_IFREG | S_IRUGO | S_IWUSR},
406 	.store = test_write_file
407 };
408 
409 static int has_power_file (struct hotplug_slot *slot)
410 {
411 	if ((!slot) || (!slot->ops))
412 		return -ENODEV;
413 	if ((slot->ops->enable_slot) ||
414 	    (slot->ops->disable_slot) ||
415 	    (slot->ops->get_power_status))
416 		return 0;
417 	return -ENOENT;
418 }
419 
420 static int has_attention_file (struct hotplug_slot *slot)
421 {
422 	if ((!slot) || (!slot->ops))
423 		return -ENODEV;
424 	if ((slot->ops->set_attention_status) ||
425 	    (slot->ops->get_attention_status))
426 		return 0;
427 	return -ENOENT;
428 }
429 
430 static int has_latch_file (struct hotplug_slot *slot)
431 {
432 	if ((!slot) || (!slot->ops))
433 		return -ENODEV;
434 	if (slot->ops->get_latch_status)
435 		return 0;
436 	return -ENOENT;
437 }
438 
439 static int has_adapter_file (struct hotplug_slot *slot)
440 {
441 	if ((!slot) || (!slot->ops))
442 		return -ENODEV;
443 	if (slot->ops->get_adapter_status)
444 		return 0;
445 	return -ENOENT;
446 }
447 
448 static int has_address_file (struct hotplug_slot *slot)
449 {
450 	if ((!slot) || (!slot->ops))
451 		return -ENODEV;
452 	if (slot->ops->get_address)
453 		return 0;
454 	return -ENOENT;
455 }
456 
457 static int has_max_bus_speed_file (struct hotplug_slot *slot)
458 {
459 	if ((!slot) || (!slot->ops))
460 		return -ENODEV;
461 	if (slot->ops->get_max_bus_speed)
462 		return 0;
463 	return -ENOENT;
464 }
465 
466 static int has_cur_bus_speed_file (struct hotplug_slot *slot)
467 {
468 	if ((!slot) || (!slot->ops))
469 		return -ENODEV;
470 	if (slot->ops->get_cur_bus_speed)
471 		return 0;
472 	return -ENOENT;
473 }
474 
475 static int has_test_file (struct hotplug_slot *slot)
476 {
477 	if ((!slot) || (!slot->ops))
478 		return -ENODEV;
479 	if (slot->ops->hardware_test)
480 		return 0;
481 	return -ENOENT;
482 }
483 
484 static int fs_add_slot (struct hotplug_slot *slot)
485 {
486 	if (has_power_file(slot) == 0)
487 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_power.attr);
488 
489 	if (has_attention_file(slot) == 0)
490 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_attention.attr);
491 
492 	if (has_latch_file(slot) == 0)
493 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
494 
495 	if (has_adapter_file(slot) == 0)
496 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_presence.attr);
497 
498 	if (has_address_file(slot) == 0)
499 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_address.attr);
500 
501 	if (has_max_bus_speed_file(slot) == 0)
502 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr);
503 
504 	if (has_cur_bus_speed_file(slot) == 0)
505 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr);
506 
507 	if (has_test_file(slot) == 0)
508 		sysfs_create_file(&slot->kobj, &hotplug_slot_attr_test.attr);
509 
510 	return 0;
511 }
512 
513 static void fs_remove_slot (struct hotplug_slot *slot)
514 {
515 	if (has_power_file(slot) == 0)
516 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_power.attr);
517 
518 	if (has_attention_file(slot) == 0)
519 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_attention.attr);
520 
521 	if (has_latch_file(slot) == 0)
522 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
523 
524 	if (has_adapter_file(slot) == 0)
525 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_presence.attr);
526 
527 	if (has_address_file(slot) == 0)
528 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_address.attr);
529 
530 	if (has_max_bus_speed_file(slot) == 0)
531 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr);
532 
533 	if (has_cur_bus_speed_file(slot) == 0)
534 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr);
535 
536 	if (has_test_file(slot) == 0)
537 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_test.attr);
538 }
539 
540 static struct hotplug_slot *get_slot_from_name (const char *name)
541 {
542 	struct hotplug_slot *slot;
543 	struct list_head *tmp;
544 
545 	list_for_each (tmp, &pci_hotplug_slot_list) {
546 		slot = list_entry (tmp, struct hotplug_slot, slot_list);
547 		if (strcmp(slot->name, name) == 0)
548 			return slot;
549 	}
550 	return NULL;
551 }
552 
553 /**
554  * pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem
555  * @slot: pointer to the &struct hotplug_slot to register
556  *
557  * Registers a hotplug slot with the pci hotplug subsystem, which will allow
558  * userspace interaction to the slot.
559  *
560  * Returns 0 if successful, anything else for an error.
561  */
562 int pci_hp_register (struct hotplug_slot *slot)
563 {
564 	int result;
565 
566 	if (slot == NULL)
567 		return -ENODEV;
568 	if ((slot->info == NULL) || (slot->ops == NULL))
569 		return -EINVAL;
570 	if (slot->release == NULL) {
571 		dbg("Why are you trying to register a hotplug slot"
572 		    "without a proper release function?\n");
573 		return -EINVAL;
574 	}
575 
576 	kobject_set_name(&slot->kobj, "%s", slot->name);
577 	kobj_set_kset_s(slot, pci_hotplug_slots_subsys);
578 
579 	/* this can fail if we have already registered a slot with the same name */
580 	if (kobject_register(&slot->kobj)) {
581 		err("Unable to register kobject");
582 		return -EINVAL;
583 	}
584 
585 	list_add (&slot->slot_list, &pci_hotplug_slot_list);
586 
587 	result = fs_add_slot (slot);
588 	dbg ("Added slot %s to the list\n", slot->name);
589 	return result;
590 }
591 
592 /**
593  * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem
594  * @slot: pointer to the &struct hotplug_slot to deregister
595  *
596  * The @slot must have been registered with the pci hotplug subsystem
597  * previously with a call to pci_hp_register().
598  *
599  * Returns 0 if successful, anything else for an error.
600  */
601 int pci_hp_deregister (struct hotplug_slot *slot)
602 {
603 	struct hotplug_slot *temp;
604 
605 	if (slot == NULL)
606 		return -ENODEV;
607 
608 	temp = get_slot_from_name (slot->name);
609 	if (temp != slot) {
610 		return -ENODEV;
611 	}
612 	list_del (&slot->slot_list);
613 
614 	fs_remove_slot (slot);
615 	dbg ("Removed slot %s from the list\n", slot->name);
616 	kobject_unregister(&slot->kobj);
617 	return 0;
618 }
619 
620 /**
621  * pci_hp_change_slot_info - changes the slot's information structure in the core
622  * @slot: pointer to the slot whose info has changed
623  * @info: pointer to the info copy into the slot's info structure
624  *
625  * @slot must have been registered with the pci
626  * hotplug subsystem previously with a call to pci_hp_register().
627  *
628  * Returns 0 if successful, anything else for an error.
629  */
630 int pci_hp_change_slot_info (struct hotplug_slot *slot, struct hotplug_slot_info *info)
631 {
632 	if ((slot == NULL) || (info == NULL))
633 		return -ENODEV;
634 
635 	/*
636 	* check all fields in the info structure, and update timestamps
637 	* for the files referring to the fields that have now changed.
638 	*/
639 	if ((has_power_file(slot) == 0) &&
640 	    (slot->info->power_status != info->power_status))
641 		sysfs_update_file(&slot->kobj, &hotplug_slot_attr_power.attr);
642 
643 	if ((has_attention_file(slot) == 0) &&
644 	    (slot->info->attention_status != info->attention_status))
645 		sysfs_update_file(&slot->kobj, &hotplug_slot_attr_attention.attr);
646 
647 	if ((has_latch_file(slot) == 0) &&
648 	    (slot->info->latch_status != info->latch_status))
649 		sysfs_update_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
650 
651 	if ((has_adapter_file(slot) == 0) &&
652 	    (slot->info->adapter_status != info->adapter_status))
653 		sysfs_update_file(&slot->kobj, &hotplug_slot_attr_presence.attr);
654 
655 	if ((has_address_file(slot) == 0) &&
656 	    (slot->info->address != info->address))
657 		sysfs_update_file(&slot->kobj, &hotplug_slot_attr_address.attr);
658 
659 	if ((has_max_bus_speed_file(slot) == 0) &&
660 	    (slot->info->max_bus_speed != info->max_bus_speed))
661 		sysfs_update_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr);
662 
663 	if ((has_cur_bus_speed_file(slot) == 0) &&
664 	    (slot->info->cur_bus_speed != info->cur_bus_speed))
665 		sysfs_update_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr);
666 
667 	memcpy (slot->info, info, sizeof (struct hotplug_slot_info));
668 
669 	return 0;
670 }
671 
672 static int __init pci_hotplug_init (void)
673 {
674 	int result;
675 
676 	kset_set_kset_s(&pci_hotplug_slots_subsys, pci_bus_type.subsys);
677 	result = subsystem_register(&pci_hotplug_slots_subsys);
678 	if (result) {
679 		err("Register subsys with error %d\n", result);
680 		goto exit;
681 	}
682 	result = cpci_hotplug_init(debug);
683 	if (result) {
684 		err ("cpci_hotplug_init with error %d\n", result);
685 		goto err_subsys;
686 	}
687 
688 	info (DRIVER_DESC " version: " DRIVER_VERSION "\n");
689 	goto exit;
690 
691 err_subsys:
692 	subsystem_unregister(&pci_hotplug_slots_subsys);
693 exit:
694 	return result;
695 }
696 
697 static void __exit pci_hotplug_exit (void)
698 {
699 	cpci_hotplug_exit();
700 	subsystem_unregister(&pci_hotplug_slots_subsys);
701 }
702 
703 module_init(pci_hotplug_init);
704 module_exit(pci_hotplug_exit);
705 
706 MODULE_AUTHOR(DRIVER_AUTHOR);
707 MODULE_DESCRIPTION(DRIVER_DESC);
708 MODULE_LICENSE("GPL");
709 module_param(debug, bool, 0644);
710 MODULE_PARM_DESC(debug, "Debugging mode enabled or not");
711 
712 EXPORT_SYMBOL_GPL(pci_hotplug_slots_subsys);
713 EXPORT_SYMBOL_GPL(pci_hp_register);
714 EXPORT_SYMBOL_GPL(pci_hp_deregister);
715 EXPORT_SYMBOL_GPL(pci_hp_change_slot_info);
716