164 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			164 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 "../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_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 = &(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 */
 | 
						|
	while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, 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");
 |