165 lines
3.7 KiB
C
165 lines
3.7 KiB
C
/* 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");
|