aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrent Piepho <xyzzy@speakeasy.org>2009-03-20 16:56:46 -0400
committerJesse Barnes <jbarnes@virtuousgeek.org>2009-03-20 17:59:25 -0400
commit83dbf66f04b96e65c6c18436c16d40f9cf8630aa (patch)
tree0c3ffcc7d34aea8cd8983f3863bae67ef7f72029
parent738a6396c223b486304dda778119dbbca563f019 (diff)
PCI Hotplug: restore fakephp interface with complete reimplementation
A complete re-implementation of fakephp is necessary if it is to present its former interface (pre-2.6.27, when it broke). The reason is that PCI hotplug drivers call pci_hp_register(), which enforces the rule that only one /sys/bus/pci/slots/ file may be created per physical slot. The change breaks the old fakephp's assumption that it could create a file per function. So we re-implement fakephp to avoid using the standard PCI hotplug API so that we can restore the old fakephp user interface. It puts entries in /sys/bus/pci/slots with the names of all PCI devices/functions, exactly symmetrical to what is shown in /sys/bus/pci/devices. Each slots/ entry has a "power" attribute, which works the same way as the fakephp driver's power attribute has worked. There are a few improvements over old fakephp, which couldn't handle PCI devices being added or removed via a means outside of fakephp's knowledge. If a device was added another way, old fakephp didn't notice and didn't create the fake slot for it. If a device was removed another way, old fakephp didn't delete the fake slot for it (and accessing the stale slot caused an oops). The new implementation overcomes these limitations. As a consequence, removing a bridge with other devices behind it now works as well, which is something else old fakephp couldn't do previously. This duplicates a tiny bit of the code in the PCI core that does this same function. Re-using that code ends up being more complex than duplicating it, and it makes code in the PCI core more ugly just to support this legacy fakephp interface compatibility layer. Reviewed-by: James Cameron <qz@hp.com> Signed-off-by: Trent Piepho <xyzzy@speakeasy.org> Signed-off-by: Alex Chiang <achiang@hp.com> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
-rw-r--r--drivers/pci/hotplug/Makefile2
-rw-r--r--drivers/pci/hotplug/fakephp.c395
-rw-r--r--drivers/pci/hotplug/legacy_fakephp.c162
3 files changed, 163 insertions, 396 deletions
diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile
index 2aa117c8cd87..3314fe881509 100644
--- a/drivers/pci/hotplug/Makefile
+++ b/drivers/pci/hotplug/Makefile
@@ -20,7 +20,7 @@ obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o
20obj-$(CONFIG_HOTPLUG_PCI_SGI) += sgi_hotplug.o 20obj-$(CONFIG_HOTPLUG_PCI_SGI) += sgi_hotplug.o
21 21
22# Link this last so it doesn't claim devices that have a real hotplug driver 22# Link this last so it doesn't claim devices that have a real hotplug driver
23obj-$(CONFIG_HOTPLUG_PCI_FAKE) += fakephp.o 23obj-$(CONFIG_HOTPLUG_PCI_FAKE) += legacy_fakephp.o
24 24
25pci_hotplug-objs := pci_hotplug_core.o 25pci_hotplug-objs := pci_hotplug_core.o
26 26
diff --git a/drivers/pci/hotplug/fakephp.c b/drivers/pci/hotplug/fakephp.c
deleted file mode 100644
index 16063745766e..000000000000
--- a/drivers/pci/hotplug/fakephp.c
+++ /dev/null
@@ -1,395 +0,0 @@
1/*
2 * Fake PCI Hot Plug Controller Driver
3 *
4 * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
5 * Copyright (C) 2003 IBM Corp.
6 * Copyright (C) 2003 Rolf Eike Beer <eike-kernel@sf-tec.de>
7 *
8 * Based on ideas and code from:
9 * Vladimir Kondratiev <vladimir.kondratiev@intel.com>
10 * Rolf Eike Beer <eike-kernel@sf-tec.de>
11 *
12 * All rights reserved.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, version 2 of the License.
17 *
18 * Send feedback to <greg@kroah.com>
19 */
20
21/*
22 *
23 * This driver will "emulate" removing PCI devices from the system. If
24 * the "power" file is written to with "0" then the specified PCI device
25 * will be completely removed from the kernel.
26 *
27 * WARNING, this does NOT turn off the power to the PCI device. This is
28 * a "logical" removal, not a physical or electrical removal.
29 *
30 * Use this module at your own risk, you have been warned!
31 *
32 * Enabling PCI devices is left as an exercise for the reader...
33 *
34 */
35#include <linux/kernel.h>
36#include <linux/module.h>
37#include <linux/pci.h>
38#include <linux/pci_hotplug.h>
39#include <linux/init.h>
40#include <linux/string.h>
41#include <linux/slab.h>
42#include <linux/workqueue.h>
43#include "../pci.h"
44
45#if !defined(MODULE)
46 #define MY_NAME "fakephp"
47#else
48 #define MY_NAME THIS_MODULE->name
49#endif
50
51#define dbg(format, arg...) \
52 do { \
53 if (debug) \
54 printk(KERN_DEBUG "%s: " format, \
55 MY_NAME , ## arg); \
56 } while (0)
57#define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME , ## arg)
58#define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME , ## arg)
59
60#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
61#define DRIVER_DESC "Fake PCI Hot Plug Controller Driver"
62
63struct dummy_slot {
64 struct list_head node;
65 struct hotplug_slot *slot;
66 struct pci_dev *dev;
67 struct work_struct remove_work;
68 unsigned long removed;
69};
70
71static int debug;
72static int dup_slots;
73static LIST_HEAD(slot_list);
74static struct workqueue_struct *dummyphp_wq;
75
76static void pci_rescan_worker(struct work_struct *work);
77static DECLARE_WORK(pci_rescan_work, pci_rescan_worker);
78
79static int enable_slot (struct hotplug_slot *slot);
80static int disable_slot (struct hotplug_slot *slot);
81
82static struct hotplug_slot_ops dummy_hotplug_slot_ops = {
83 .owner = THIS_MODULE,
84 .enable_slot = enable_slot,
85 .disable_slot = disable_slot,
86};
87
88static void dummy_release(struct hotplug_slot *slot)
89{
90 struct dummy_slot *dslot = slot->private;
91
92 list_del(&dslot->node);
93 kfree(dslot->slot->info);
94 kfree(dslot->slot);
95 pci_dev_put(dslot->dev);
96 kfree(dslot);
97}
98
99#define SLOT_NAME_SIZE 8
100
101static int add_slot(struct pci_dev *dev)
102{
103 struct dummy_slot *dslot;
104 struct hotplug_slot *slot;
105 char name[SLOT_NAME_SIZE];
106 int retval = -ENOMEM;
107 static int count = 1;
108
109 slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
110 if (!slot)
111 goto error;
112
113 slot->info = kzalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL);
114 if (!slot->info)
115 goto error_slot;
116
117 slot->info->power_status = 1;
118 slot->info->max_bus_speed = PCI_SPEED_UNKNOWN;
119 slot->info->cur_bus_speed = PCI_SPEED_UNKNOWN;
120
121 dslot = kzalloc(sizeof(struct dummy_slot), GFP_KERNEL);
122 if (!dslot)
123 goto error_info;
124
125 if (dup_slots)
126 snprintf(name, SLOT_NAME_SIZE, "fake");
127 else
128 snprintf(name, SLOT_NAME_SIZE, "fake%d", count++);
129 dbg("slot->name = %s\n", name);
130 slot->ops = &dummy_hotplug_slot_ops;
131 slot->release = &dummy_release;
132 slot->private = dslot;
133
134 retval = pci_hp_register(slot, dev->bus, PCI_SLOT(dev->devfn), name);
135 if (retval) {
136 err("pci_hp_register failed with error %d\n", retval);
137 goto error_dslot;
138 }
139
140 dbg("slot->name = %s\n", hotplug_slot_name(slot));
141 dslot->slot = slot;
142 dslot->dev = pci_dev_get(dev);
143 list_add (&dslot->node, &slot_list);
144 return retval;
145
146error_dslot:
147 kfree(dslot);
148error_info:
149 kfree(slot->info);
150error_slot:
151 kfree(slot);
152error:
153 return retval;
154}
155
156static int __init pci_scan_buses(void)
157{
158 struct pci_dev *dev = NULL;
159 int lastslot = 0;
160
161 while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
162 if (PCI_FUNC(dev->devfn) > 0 &&
163 lastslot == PCI_SLOT(dev->devfn))
164 continue;
165 lastslot = PCI_SLOT(dev->devfn);
166 add_slot(dev);
167 }
168
169 return 0;
170}
171
172static void remove_slot(struct dummy_slot *dslot)
173{
174 int retval;
175
176 dbg("removing slot %s\n", hotplug_slot_name(dslot->slot));
177 retval = pci_hp_deregister(dslot->slot);
178 if (retval)
179 err("Problem unregistering a slot %s\n",
180 hotplug_slot_name(dslot->slot));
181}
182
183/* called from the single-threaded workqueue handler to remove a slot */
184static void remove_slot_worker(struct work_struct *work)
185{
186 struct dummy_slot *dslot =
187 container_of(work, struct dummy_slot, remove_work);
188 remove_slot(dslot);
189}
190
191/**
192 * pci_rescan_slot - Rescan slot
193 * @temp: Device template. Should be set: bus and devfn.
194 *
195 * Tries hard not to re-enable already existing devices;
196 * also handles scanning of subfunctions.
197 */
198static int pci_rescan_slot(struct pci_dev *temp)
199{
200 struct pci_bus *bus = temp->bus;
201 struct pci_dev *dev;
202 int func;
203 u8 hdr_type;
204 int count = 0;
205
206 if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) {
207 temp->hdr_type = hdr_type & 0x7f;
208 if ((dev = pci_get_slot(bus, temp->devfn)) != NULL)
209 pci_dev_put(dev);
210 else {
211 dev = pci_scan_single_device(bus, temp->devfn);
212 if (dev) {
213 dbg("New device on %s function %x:%x\n",
214 bus->name, temp->devfn >> 3,
215 temp->devfn & 7);
216 count++;
217 }
218 }
219 /* multifunction device? */
220 if (!(hdr_type & 0x80))
221 return count;
222
223 /* continue scanning for other functions */
224 for (func = 1, temp->devfn++; func < 8; func++, temp->devfn++) {
225 if (pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type))
226 continue;
227 temp->hdr_type = hdr_type & 0x7f;
228
229 if ((dev = pci_get_slot(bus, temp->devfn)) != NULL)
230 pci_dev_put(dev);
231 else {
232 dev = pci_scan_single_device(bus, temp->devfn);
233 if (dev) {
234 dbg("New device on %s function %x:%x\n",
235 bus->name, temp->devfn >> 3,
236 temp->devfn & 7);
237 count++;
238 }
239 }
240 }
241 }
242
243 return count;
244}
245
246
247/**
248 * pci_rescan_bus_local - fakephp version of rescan PCI bus
249 * @bus: the PCI bus to rescan
250 *
251 * Call pci_rescan_slot for each possible function of the bus.
252 */
253static void pci_rescan_bus_local(const struct pci_bus *bus)
254{
255 unsigned int devfn;
256 struct pci_dev *dev;
257 int retval;
258 int found = 0;
259 dev = alloc_pci_dev();
260 if (!dev)
261 return;
262
263 dev->bus = (struct pci_bus*)bus;
264 dev->sysdata = bus->sysdata;
265 for (devfn = 0; devfn < 0x100; devfn += 8) {
266 dev->devfn = devfn;
267 found += pci_rescan_slot(dev);
268 }
269
270 if (found) {
271 pci_bus_assign_resources(bus);
272 list_for_each_entry(dev, &bus->devices, bus_list) {
273 /* Skip already-added devices */
274 if (dev->is_added)
275 continue;
276 retval = pci_bus_add_device(dev);
277 if (retval)
278 dev_err(&dev->dev,
279 "Error adding device, continuing\n");
280 else
281 add_slot(dev);
282 }
283 pci_bus_add_devices(bus);
284 }
285 kfree(dev);
286}
287
288/* recursively scan all buses */
289static void pci_rescan_buses(const struct list_head *list)
290{
291 const struct list_head *l;
292 list_for_each(l,list) {
293 const struct pci_bus *b = pci_bus_b(l);
294 pci_rescan_bus_local(b);
295 pci_rescan_buses(&b->children);
296 }
297}
298
299/* initiate rescan of all pci buses */
300static inline void pci_rescan(void) {
301 pci_rescan_buses(&pci_root_buses);
302}
303
304/* called from the single-threaded workqueue handler to rescan all pci buses */
305static void pci_rescan_worker(struct work_struct *work)
306{
307 pci_rescan();
308}
309
310static int enable_slot(struct hotplug_slot *hotplug_slot)
311{
312 /* mis-use enable_slot for rescanning of the pci bus */
313 cancel_work_sync(&pci_rescan_work);
314 queue_work(dummyphp_wq, &pci_rescan_work);
315 return 0;
316}
317
318static int disable_slot(struct hotplug_slot *slot)
319{
320 struct dummy_slot *dslot;
321 struct pci_dev *dev;
322 int func;
323
324 if (!slot)
325 return -ENODEV;
326 dslot = slot->private;
327
328 dbg("%s - physical_slot = %s\n", __func__, hotplug_slot_name(slot));
329
330 for (func = 7; func >= 0; func--) {
331 dev = pci_get_slot(dslot->dev->bus, dslot->dev->devfn + func);
332 if (!dev)
333 continue;
334
335 if (test_and_set_bit(0, &dslot->removed)) {
336 dbg("Slot already scheduled for removal\n");
337 pci_dev_put(dev);
338 return -ENODEV;
339 }
340
341 /* remove the device from the pci core */
342 pci_remove_bus_device(dev);
343
344 /* queue work item to blow away this sysfs entry and other
345 * parts.
346 */
347 INIT_WORK(&dslot->remove_work, remove_slot_worker);
348 queue_work(dummyphp_wq, &dslot->remove_work);
349
350 pci_dev_put(dev);
351 }
352 return 0;
353}
354
355static void cleanup_slots (void)
356{
357 struct list_head *tmp;
358 struct list_head *next;
359 struct dummy_slot *dslot;
360
361 destroy_workqueue(dummyphp_wq);
362 list_for_each_safe (tmp, next, &slot_list) {
363 dslot = list_entry (tmp, struct dummy_slot, node);
364 remove_slot(dslot);
365 }
366
367}
368
369static int __init dummyphp_init(void)
370{
371 info(DRIVER_DESC "\n");
372
373 dummyphp_wq = create_singlethread_workqueue(MY_NAME);
374 if (!dummyphp_wq)
375 return -ENOMEM;
376
377 return pci_scan_buses();
378}
379
380
381static void __exit dummyphp_exit(void)
382{
383 cleanup_slots();
384}
385
386module_init(dummyphp_init);
387module_exit(dummyphp_exit);
388
389MODULE_AUTHOR(DRIVER_AUTHOR);
390MODULE_DESCRIPTION(DRIVER_DESC);
391MODULE_LICENSE("GPL");
392module_param(debug, bool, S_IRUGO | S_IWUSR);
393MODULE_PARM_DESC(debug, "Debugging mode enabled or not");
394module_param(dup_slots, bool, S_IRUGO | S_IWUSR);
395MODULE_PARM_DESC(dup_slots, "Force duplicate slot names for debugging");
diff --git a/drivers/pci/hotplug/legacy_fakephp.c b/drivers/pci/hotplug/legacy_fakephp.c
new file mode 100644
index 000000000000..2dc7828df480
--- /dev/null
+++ b/drivers/pci/hotplug/legacy_fakephp.c
@@ -0,0 +1,162 @@
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 "../pci.h"
22
23struct legacy_slot {
24 struct kobject kobj;
25 struct pci_dev *dev;
26 struct list_head list;
27};
28
29static LIST_HEAD(legacy_list);
30
31static ssize_t legacy_show(struct kobject *kobj, struct attribute *attr,
32 char *buf)
33{
34 struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
35 strcpy(buf, "1\n");
36 return 2;
37}
38
39static void remove_callback(void *data)
40{
41 pci_remove_bus_device((struct pci_dev *)data);
42}
43
44static ssize_t legacy_store(struct kobject *kobj, struct attribute *attr,
45 const char *buf, size_t len)
46{
47 struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
48 unsigned long val;
49
50 if (strict_strtoul(buf, 0, &val) < 0)
51 return -EINVAL;
52
53 if (val)
54 pci_rescan_bus(slot->dev->bus);
55 else
56 sysfs_schedule_callback(&slot->dev->dev.kobj, remove_callback,
57 slot->dev, THIS_MODULE);
58 return len;
59}
60
61static struct attribute *legacy_attrs[] = {
62 &(struct attribute){ .name = "power", .mode = 0644 },
63 NULL,
64};
65
66static void legacy_release(struct kobject *kobj)
67{
68 struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
69
70 pci_dev_put(slot->dev);
71 kfree(slot);
72}
73
74static struct kobj_type legacy_ktype = {
75 .sysfs_ops = &(struct sysfs_ops){
76 .store = legacy_store, .show = legacy_show
77 },
78 .release = &legacy_release,
79 .default_attrs = legacy_attrs,
80};
81
82static int legacy_add_slot(struct pci_dev *pdev)
83{
84 struct legacy_slot *slot = kzalloc(sizeof(*slot), GFP_KERNEL);
85
86 if (!slot)
87 return -ENOMEM;
88
89 if (kobject_init_and_add(&slot->kobj, &legacy_ktype,
90 &pci_slots_kset->kobj, "%s",
91 pdev->dev.bus_id)) {
92 dev_warn(&pdev->dev, "Failed to created legacy fake slot\n");
93 return -EINVAL;
94 }
95 slot->dev = pci_dev_get(pdev);
96
97 list_add(&slot->list, &legacy_list);
98
99 return 0;
100}
101
102static int legacy_notify(struct notifier_block *nb,
103 unsigned long action, void *data)
104{
105 struct pci_dev *pdev = to_pci_dev(data);
106
107 if (action == BUS_NOTIFY_ADD_DEVICE) {
108 legacy_add_slot(pdev);
109 } else if (action == BUS_NOTIFY_DEL_DEVICE) {
110 struct legacy_slot *slot;
111
112 list_for_each_entry(slot, &legacy_list, list)
113 if (slot->dev == pdev)
114 goto found;
115
116 dev_warn(&pdev->dev, "Missing legacy fake slot?");
117 return -ENODEV;
118found:
119 kobject_del(&slot->kobj);
120 list_del(&slot->list);
121 kobject_put(&slot->kobj);
122 }
123
124 return 0;
125}
126
127static struct notifier_block legacy_notifier = {
128 .notifier_call = legacy_notify
129};
130
131static int __init init_legacy(void)
132{
133 struct pci_dev *pdev = NULL;
134
135 /* Add existing devices */
136 while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)))
137 legacy_add_slot(pdev);
138
139 /* Be alerted of any new ones */
140 bus_register_notifier(&pci_bus_type, &legacy_notifier);
141 return 0;
142}
143module_init(init_legacy);
144
145static void __exit remove_legacy(void)
146{
147 struct legacy_slot *slot, *tmp;
148
149 bus_unregister_notifier(&pci_bus_type, &legacy_notifier);
150
151 list_for_each_entry_safe(slot, tmp, &legacy_list, list) {
152 list_del(&slot->list);
153 kobject_del(&slot->kobj);
154 kobject_put(&slot->kobj);
155 }
156}
157module_exit(remove_legacy);
158
159
160MODULE_AUTHOR("Trent Piepho <xyzzy@speakeasy.org>");
161MODULE_DESCRIPTION("Legacy version of the fakephp interface");
162MODULE_LICENSE("GPL");