linux/drivers/pci/hotplug/fakephp.c
<<
>>
Prefs
   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 "../pci.h"
  23
  24struct legacy_slot {
  25        struct kobject          kobj;
  26        struct pci_dev          *dev;
  27        struct list_head        list;
  28};
  29
  30static LIST_HEAD(legacy_list);
  31
  32static ssize_t legacy_show(struct kobject *kobj, struct attribute *attr,
  33                           char *buf)
  34{
  35        struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
  36        strcpy(buf, "1\n");
  37        return 2;
  38}
  39
  40static void remove_callback(void *data)
  41{
  42        pci_remove_bus_device((struct pci_dev *)data);
  43}
  44
  45static ssize_t legacy_store(struct kobject *kobj, struct attribute *attr,
  46                            const char *buf, size_t len)
  47{
  48        struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
  49        unsigned long val;
  50
  51        if (strict_strtoul(buf, 0, &val) < 0)
  52                return -EINVAL;
  53
  54        if (val)
  55                pci_rescan_bus(slot->dev->bus);
  56        else
  57                sysfs_schedule_callback(&slot->dev->dev.kobj, remove_callback,
  58                                        slot->dev, THIS_MODULE);
  59        return len;
  60}
  61
  62static struct attribute *legacy_attrs[] = {
  63        &(struct attribute){ .name = "power", .mode = 0644 },
  64        NULL,
  65};
  66
  67static void legacy_release(struct kobject *kobj)
  68{
  69        struct legacy_slot *slot = container_of(kobj, typeof(*slot), kobj);
  70
  71        pci_dev_put(slot->dev);
  72        kfree(slot);
  73}
  74
  75static struct kobj_type legacy_ktype = {
  76        .sysfs_ops = &(struct sysfs_ops){
  77                .store = legacy_store, .show = legacy_show
  78        },
  79        .release = &legacy_release,
  80        .default_attrs = legacy_attrs,
  81};
  82
  83static int legacy_add_slot(struct pci_dev *pdev)
  84{
  85        struct legacy_slot *slot = kzalloc(sizeof(*slot), GFP_KERNEL);
  86
  87        if (!slot)
  88                return -ENOMEM;
  89
  90        if (kobject_init_and_add(&slot->kobj, &legacy_ktype,
  91                                 &pci_slots_kset->kobj, "%s",
  92                                 dev_name(&pdev->dev))) {
  93                dev_warn(&pdev->dev, "Failed to created legacy fake slot\n");
  94                return -EINVAL;
  95        }
  96        slot->dev = pci_dev_get(pdev);
  97
  98        list_add(&slot->list, &legacy_list);
  99
 100        return 0;
 101}
 102
 103static int legacy_notify(struct notifier_block *nb,
 104                         unsigned long action, void *data)
 105{
 106        struct pci_dev *pdev = to_pci_dev(data);
 107
 108        if (action == BUS_NOTIFY_ADD_DEVICE) {
 109                legacy_add_slot(pdev);
 110        } else if (action == BUS_NOTIFY_DEL_DEVICE) {
 111                struct legacy_slot *slot;
 112
 113                list_for_each_entry(slot, &legacy_list, list)
 114                        if (slot->dev == pdev)
 115                                goto found;
 116
 117                dev_warn(&pdev->dev, "Missing legacy fake slot?");
 118                return -ENODEV;
 119found:
 120                kobject_del(&slot->kobj);
 121                list_del(&slot->list);
 122                kobject_put(&slot->kobj);
 123        }
 124
 125        return 0;
 126}
 127
 128static struct notifier_block legacy_notifier = {
 129        .notifier_call = legacy_notify
 130};
 131
 132static int __init init_legacy(void)
 133{
 134        struct pci_dev *pdev = NULL;
 135
 136        /* Add existing devices */
 137        while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)))
 138                legacy_add_slot(pdev);
 139
 140        /* Be alerted of any new ones */
 141        bus_register_notifier(&pci_bus_type, &legacy_notifier);
 142        return 0;
 143}
 144module_init(init_legacy);
 145
 146static void __exit remove_legacy(void)
 147{
 148        struct legacy_slot *slot, *tmp;
 149
 150        bus_unregister_notifier(&pci_bus_type, &legacy_notifier);
 151
 152        list_for_each_entry_safe(slot, tmp, &legacy_list, list) {
 153                list_del(&slot->list);
 154                kobject_del(&slot->kobj);
 155                kobject_put(&slot->kobj);
 156        }
 157}
 158module_exit(remove_legacy);
 159
 160
 161MODULE_AUTHOR("Trent Piepho <xyzzy@speakeasy.org>");
 162MODULE_DESCRIPTION("Legacy version of the fakephp interface");
 163MODULE_LICENSE("GPL");
 164