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 25struct legacy_slot { 26 struct kobject kobj; 27 struct pci_dev *dev; 28 struct list_head list; 29}; 30 31static LIST_HEAD(legacy_list); 32 33static 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 41static void remove_callback(void *data) 42{ 43 pci_stop_and_remove_bus_device((struct pci_dev *)data); 44} 45 46static 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 63static struct attribute *legacy_attrs[] = { 64 &(struct attribute){ .name = "power", .mode = 0644 }, 65 NULL, 66}; 67 68static 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 76static 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 84static 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 104static 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; 120found: 121 kobject_del(&slot->kobj); 122 list_del(&slot->list); 123 kobject_put(&slot->kobj); 124 } 125 126 return 0; 127} 128 129static struct notifier_block legacy_notifier = { 130 .notifier_call = legacy_notify 131}; 132 133static 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} 145module_init(init_legacy); 146 147static 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} 159module_exit(remove_legacy); 160 161 162MODULE_AUTHOR("Trent Piepho <xyzzy@speakeasy.org>"); 163MODULE_DESCRIPTION("Legacy version of the fakephp interface"); 164MODULE_LICENSE("GPL"); 165