aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci/hotplug/legacy_fakephp.c
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 /drivers/pci/hotplug/legacy_fakephp.c
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>
Diffstat (limited to 'drivers/pci/hotplug/legacy_fakephp.c')
-rw-r--r--drivers/pci/hotplug/legacy_fakephp.c162
1 files changed, 162 insertions, 0 deletions
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");