| /* Works like the fakephp driver used to, except a little better. |
| * |
| * - It's possible to remove devices with subordinate busses. |
| * - New PCI devices that appear via any method, not just a fakephp triggered |
| * rescan, will be noticed. |
| * - Devices that are removed via any method, not just a fakephp triggered |
| * removal, will also be noticed. |
| * |
| * Uses nothing from the pci-hotplug subsystem. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/list.h> |
| #include <linux/kobject.h> |
| #include <linux/sysfs.h> |
| #include <linux/init.h> |
| #include <linux/pci.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include "../pci.h" |
| |
| struct legacy_slot { |
| struct kobject kobj; |
| struct pci_dev *dev; |
| struct list_head list; |
| }; |
| |
| static LIST_HEAD(legacy_list); |
| |
| static ssize_t legacy_show(struct kobject *kobj, struct attribute *attr, |
| char *buf) |
| { |
| struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj); |
| strcpy(buf, "1\n"); |
| return 2; |
| } |
| |
| static void remove_callback(void *data) |
| { |
| pci_stop_and_remove_bus_device((struct pci_dev *)data); |
| } |
| |
| static ssize_t legacy_store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t len) |
| { |
| struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj); |
| unsigned long val; |
| |
| if (strict_strtoul(buf, 0, &val) < 0) |
| return -EINVAL; |
| |
| if (val) |
| pci_rescan_bus(slot->dev->bus); |
| else |
| sysfs_schedule_callback(&slot->dev->dev.kobj, remove_callback, |
| slot->dev, THIS_MODULE); |
| return len; |
| } |
| |
| static struct attribute *legacy_attrs[] = { |
| &(struct attribute){ .name = "power", .mode = 0644 }, |
| NULL, |
| }; |
| |
| static void legacy_release(struct kobject *kobj) |
| { |
| struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj); |
| |
| pci_dev_put(slot->dev); |
| kfree(slot); |
| } |
| |
| static struct kobj_type legacy_ktype = { |
| .sysfs_ops = &(const struct sysfs_ops){ |
| .store = legacy_store, .show = legacy_show |
| }, |
| .release = &legacy_release, |
| .default_attrs = legacy_attrs, |
| }; |
| |
| static int legacy_add_slot(struct pci_dev *pdev) |
| { |
| struct legacy_slot *slot = kzalloc(sizeof(*slot), GFP_KERNEL); |
| |
| if (!slot) |
| return -ENOMEM; |
| |
| if (kobject_init_and_add(&slot->kobj, &legacy_ktype, |
| &pci_slots_kset->kobj, "%s", |
| dev_name(&pdev->dev))) { |
| dev_warn(&pdev->dev, "Failed to created legacy fake slot\n"); |
| return -EINVAL; |
| } |
| slot->dev = pci_dev_get(pdev); |
| |
| list_add(&slot->list, &legacy_list); |
| |
| return 0; |
| } |
| |
| static int legacy_notify(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct pci_dev *pdev = to_pci_dev(data); |
| |
| if (action == BUS_NOTIFY_ADD_DEVICE) { |
| legacy_add_slot(pdev); |
| } else if (action == BUS_NOTIFY_DEL_DEVICE) { |
| struct legacy_slot *slot; |
| |
| list_for_each_entry(slot, &legacy_list, list) |
| if (slot->dev == pdev) |
| goto found; |
| |
| dev_warn(&pdev->dev, "Missing legacy fake slot?"); |
| return -ENODEV; |
| found: |
| kobject_del(&slot->kobj); |
| list_del(&slot->list); |
| kobject_put(&slot->kobj); |
| } |
| |
| return 0; |
| } |
| |
| static struct notifier_block legacy_notifier = { |
| .notifier_call = legacy_notify |
| }; |
| |
| static int __init init_legacy(void) |
| { |
| struct pci_dev *pdev = NULL; |
| |
| /* Add existing devices */ |
| for_each_pci_dev(pdev) |
| legacy_add_slot(pdev); |
| |
| /* Be alerted of any new ones */ |
| bus_register_notifier(&pci_bus_type, &legacy_notifier); |
| return 0; |
| } |
| module_init(init_legacy); |
| |
| static void __exit remove_legacy(void) |
| { |
| struct legacy_slot *slot, *tmp; |
| |
| bus_unregister_notifier(&pci_bus_type, &legacy_notifier); |
| |
| list_for_each_entry_safe(slot, tmp, &legacy_list, list) { |
| list_del(&slot->list); |
| kobject_del(&slot->kobj); |
| kobject_put(&slot->kobj); |
| } |
| } |
| module_exit(remove_legacy); |
| |
| |
| MODULE_AUTHOR("Trent Piepho <xyzzy@speakeasy.org>"); |
| MODULE_DESCRIPTION("Legacy version of the fakephp interface"); |
| MODULE_LICENSE("GPL"); |