1 /* Works like the fakephp driver used to, except a little better.
2  *
3  * - It's possible to remove devices with subordinate busses.
4  * - New PCI devices that appear via any method, not just a fakephp triggered
5  *   rescan, will be noticed.
6  * - Devices that are removed via any method, not just a fakephp triggered
7  *   removal, will also be noticed.
8  *
9  * Uses nothing from the pci-hotplug subsystem.
10  *
11  */
12 
13 #include <linux/module.h>
14 #include <linux/kernel.h>
15 #include <linux/types.h>
16 #include <linux/list.h>
17 #include <linux/kobject.h>
18 #include <linux/sysfs.h>
19 #include <linux/init.h>
20 #include <linux/pci.h>
21 #include <linux/device.h>
22 #include <linux/slab.h>
23 #include "../pci.h"
24 
25 struct legacy_slot {
26 	struct kobject		kobj;
27 	struct pci_dev		*dev;
28 	struct list_head	list;
29 };
30 
31 static LIST_HEAD(legacy_list);
32 
legacy_show(struct kobject * kobj,struct attribute * attr,char * buf)33 static ssize_t legacy_show(struct kobject *kobj, struct attribute *attr,
34 			   char *buf)
35 {
36 	struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
37 	strcpy(buf, "1\n");
38 	return 2;
39 }
40 
remove_callback(void * data)41 static void remove_callback(void *data)
42 {
43 	pci_stop_and_remove_bus_device((struct pci_dev *)data);
44 }
45 
legacy_store(struct kobject * kobj,struct attribute * attr,const char * buf,size_t len)46 static ssize_t legacy_store(struct kobject *kobj, struct attribute *attr,
47 			    const char *buf, size_t len)
48 {
49 	struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
50 	unsigned long val;
51 
52 	if (strict_strtoul(buf, 0, &val) < 0)
53 		return -EINVAL;
54 
55 	if (val)
56 		pci_rescan_bus(slot->dev->bus);
57 	else
58 		sysfs_schedule_callback(&slot->dev->dev.kobj, remove_callback,
59 					slot->dev, THIS_MODULE);
60 	return len;
61 }
62 
63 static struct attribute *legacy_attrs[] = {
64 	&(struct attribute){ .name = "power", .mode = 0644 },
65 	NULL,
66 };
67 
legacy_release(struct kobject * kobj)68 static void legacy_release(struct kobject *kobj)
69 {
70 	struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
71 
72 	pci_dev_put(slot->dev);
73 	kfree(slot);
74 }
75 
76 static struct kobj_type legacy_ktype = {
77 	.sysfs_ops = &(const struct sysfs_ops){
78 		.store = legacy_store, .show = legacy_show
79 	},
80 	.release = &legacy_release,
81 	.default_attrs = legacy_attrs,
82 };
83 
legacy_add_slot(struct pci_dev * pdev)84 static int legacy_add_slot(struct pci_dev *pdev)
85 {
86 	struct legacy_slot *slot = kzalloc(sizeof(*slot), GFP_KERNEL);
87 
88 	if (!slot)
89 		return -ENOMEM;
90 
91 	if (kobject_init_and_add(&slot->kobj, &legacy_ktype,
92 				 &pci_slots_kset->kobj, "%s",
93 				 dev_name(&pdev->dev))) {
94 		dev_warn(&pdev->dev, "Failed to created legacy fake slot\n");
95 		return -EINVAL;
96 	}
97 	slot->dev = pci_dev_get(pdev);
98 
99 	list_add(&slot->list, &legacy_list);
100 
101 	return 0;
102 }
103 
legacy_notify(struct notifier_block * nb,unsigned long action,void * data)104 static int legacy_notify(struct notifier_block *nb,
105 			 unsigned long action, void *data)
106 {
107 	struct pci_dev *pdev = to_pci_dev(data);
108 
109 	if (action == BUS_NOTIFY_ADD_DEVICE) {
110 		legacy_add_slot(pdev);
111 	} else if (action == BUS_NOTIFY_DEL_DEVICE) {
112 		struct legacy_slot *slot;
113 
114 		list_for_each_entry(slot, &legacy_list, list)
115 			if (slot->dev == pdev)
116 				goto found;
117 
118 		dev_warn(&pdev->dev, "Missing legacy fake slot?");
119 		return -ENODEV;
120 found:
121 		kobject_del(&slot->kobj);
122 		list_del(&slot->list);
123 		kobject_put(&slot->kobj);
124 	}
125 
126 	return 0;
127 }
128 
129 static struct notifier_block legacy_notifier = {
130 	.notifier_call = legacy_notify
131 };
132 
init_legacy(void)133 static int __init init_legacy(void)
134 {
135 	struct pci_dev *pdev = NULL;
136 
137 	/* Add existing devices */
138 	for_each_pci_dev(pdev)
139 		legacy_add_slot(pdev);
140 
141 	/* Be alerted of any new ones */
142 	bus_register_notifier(&pci_bus_type, &legacy_notifier);
143 	return 0;
144 }
145 module_init(init_legacy);
146 
remove_legacy(void)147 static void __exit remove_legacy(void)
148 {
149 	struct legacy_slot *slot, *tmp;
150 
151 	bus_unregister_notifier(&pci_bus_type, &legacy_notifier);
152 
153 	list_for_each_entry_safe(slot, tmp, &legacy_list, list) {
154 		list_del(&slot->list);
155 		kobject_del(&slot->kobj);
156 		kobject_put(&slot->kobj);
157 	}
158 }
159 module_exit(remove_legacy);
160 
161 
162 MODULE_AUTHOR("Trent Piepho <xyzzy@speakeasy.org>");
163 MODULE_DESCRIPTION("Legacy version of the fakephp interface");
164 MODULE_LICENSE("GPL");
165