Kulikov VasiliyPCI: use for_each_pci_dev()4e344b116 years agoHistory
164 lines · 3.7 KB · C
RawDownload
/* 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_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");