diff options
Diffstat (limited to 'drivers/pci/pci-driver.c')
-rw-r--r-- | drivers/pci/pci-driver.c | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c new file mode 100644 index 00000000000..37b7961efc4 --- /dev/null +++ b/drivers/pci/pci-driver.c @@ -0,0 +1,531 @@ +/* + * drivers/pci/pci-driver.c + * + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/pci-dynids.h> +#include "pci.h" + +/* + * Registration of PCI drivers and handling of hot-pluggable devices. + */ + +/* + * Dynamic device IDs are disabled for !CONFIG_HOTPLUG + */ + +#ifdef CONFIG_HOTPLUG +/** + * pci_device_probe_dynamic() + * + * Walk the dynamic ID list looking for a match. + * returns 0 and sets pci_dev->driver when drv claims pci_dev, else error. + */ +static int +pci_device_probe_dynamic(struct pci_driver *drv, struct pci_dev *pci_dev) +{ + int error = -ENODEV; + struct list_head *pos; + struct dynid *dynid; + + spin_lock(&drv->dynids.lock); + list_for_each(pos, &drv->dynids.list) { + dynid = list_entry(pos, struct dynid, node); + if (pci_match_one_device(&dynid->id, pci_dev)) { + spin_unlock(&drv->dynids.lock); + error = drv->probe(pci_dev, &dynid->id); + if (error >= 0) { + pci_dev->driver = drv; + return 0; + } + return error; + } + } + spin_unlock(&drv->dynids.lock); + return error; +} + +/** + * store_new_id + * + * Adds a new dynamic pci device ID to this driver, + * and causes the driver to probe for all devices again. + */ +static inline ssize_t +store_new_id(struct device_driver *driver, const char *buf, size_t count) +{ + struct dynid *dynid; + struct bus_type * bus; + struct pci_driver *pdrv = to_pci_driver(driver); + __u32 vendor=PCI_ANY_ID, device=PCI_ANY_ID, subvendor=PCI_ANY_ID, + subdevice=PCI_ANY_ID, class=0, class_mask=0; + unsigned long driver_data=0; + int fields=0; + + fields = sscanf(buf, "%x %x %x %x %x %x %lux", + &vendor, &device, &subvendor, &subdevice, + &class, &class_mask, &driver_data); + if (fields < 0) + return -EINVAL; + + dynid = kmalloc(sizeof(*dynid), GFP_KERNEL); + if (!dynid) + return -ENOMEM; + + memset(dynid, 0, sizeof(*dynid)); + INIT_LIST_HEAD(&dynid->node); + dynid->id.vendor = vendor; + dynid->id.device = device; + dynid->id.subvendor = subvendor; + dynid->id.subdevice = subdevice; + dynid->id.class = class; + dynid->id.class_mask = class_mask; + dynid->id.driver_data = pdrv->dynids.use_driver_data ? + driver_data : 0UL; + + spin_lock(&pdrv->dynids.lock); + list_add_tail(&pdrv->dynids.list, &dynid->node); + spin_unlock(&pdrv->dynids.lock); + + bus = get_bus(pdrv->driver.bus); + if (bus) { + if (get_driver(&pdrv->driver)) { + down_write(&bus->subsys.rwsem); + driver_attach(&pdrv->driver); + up_write(&bus->subsys.rwsem); + put_driver(&pdrv->driver); + } + put_bus(bus); + } + + return count; +} + +static DRIVER_ATTR(new_id, S_IWUSR, NULL, store_new_id); +static inline void +pci_init_dynids(struct pci_dynids *dynids) +{ + spin_lock_init(&dynids->lock); + INIT_LIST_HEAD(&dynids->list); +} + +static void +pci_free_dynids(struct pci_driver *drv) +{ + struct list_head *pos, *n; + struct dynid *dynid; + + spin_lock(&drv->dynids.lock); + list_for_each_safe(pos, n, &drv->dynids.list) { + dynid = list_entry(pos, struct dynid, node); + list_del(&dynid->node); + kfree(dynid); + } + spin_unlock(&drv->dynids.lock); +} + +static int +pci_create_newid_file(struct pci_driver *drv) +{ + int error = 0; + if (drv->probe != NULL) + error = sysfs_create_file(&drv->driver.kobj, + &driver_attr_new_id.attr); + return error; +} + +static int +pci_bus_match_dynids(const struct pci_dev *pci_dev, struct pci_driver *pci_drv) +{ + struct list_head *pos; + struct dynid *dynid; + + spin_lock(&pci_drv->dynids.lock); + list_for_each(pos, &pci_drv->dynids.list) { + dynid = list_entry(pos, struct dynid, node); + if (pci_match_one_device(&dynid->id, pci_dev)) { + spin_unlock(&pci_drv->dynids.lock); + return 1; + } + } + spin_unlock(&pci_drv->dynids.lock); + return 0; +} + +#else /* !CONFIG_HOTPLUG */ +static inline int pci_device_probe_dynamic(struct pci_driver *drv, struct pci_dev *pci_dev) +{ + return -ENODEV; +} +static inline void pci_init_dynids(struct pci_dynids *dynids) {} +static inline void pci_free_dynids(struct pci_driver *drv) {} +static inline int pci_create_newid_file(struct pci_driver *drv) +{ + return 0; +} +static inline int pci_bus_match_dynids(const struct pci_dev *pci_dev, struct pci_driver *pci_drv) +{ + return 0; +} +#endif + +/** + * pci_match_device - Tell if a PCI device structure has a matching + * PCI device id structure + * @ids: array of PCI device id structures to search in + * @dev: the PCI device structure to match against + * + * Used by a driver to check whether a PCI device present in the + * system is in its list of supported devices.Returns the matching + * pci_device_id structure or %NULL if there is no match. + */ +const struct pci_device_id * +pci_match_device(const struct pci_device_id *ids, const struct pci_dev *dev) +{ + while (ids->vendor || ids->subvendor || ids->class_mask) { + if (pci_match_one_device(ids, dev)) + return ids; + ids++; + } + return NULL; +} + +/** + * pci_device_probe_static() + * + * returns 0 and sets pci_dev->driver when drv claims pci_dev, else error. + */ +static int +pci_device_probe_static(struct pci_driver *drv, struct pci_dev *pci_dev) +{ + int error = -ENODEV; + const struct pci_device_id *id; + + if (!drv->id_table) + return error; + id = pci_match_device(drv->id_table, pci_dev); + if (id) + error = drv->probe(pci_dev, id); + if (error >= 0) { + pci_dev->driver = drv; + error = 0; + } + return error; +} + +/** + * __pci_device_probe() + * + * returns 0 on success, else error. + * side-effect: pci_dev->driver is set to drv when drv claims pci_dev. + */ +static int +__pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev) +{ + int error = 0; + + if (!pci_dev->driver && drv->probe) { + error = pci_device_probe_static(drv, pci_dev); + if (error == -ENODEV) + error = pci_device_probe_dynamic(drv, pci_dev); + } + return error; +} + +static int pci_device_probe(struct device * dev) +{ + int error = 0; + struct pci_driver *drv; + struct pci_dev *pci_dev; + + drv = to_pci_driver(dev->driver); + pci_dev = to_pci_dev(dev); + pci_dev_get(pci_dev); + error = __pci_device_probe(drv, pci_dev); + if (error) + pci_dev_put(pci_dev); + + return error; +} + +static int pci_device_remove(struct device * dev) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + + if (drv) { + if (drv->remove) + drv->remove(pci_dev); + pci_dev->driver = NULL; + } + + /* + * We would love to complain here if pci_dev->is_enabled is set, that + * the driver should have called pci_disable_device(), but the + * unfortunate fact is there are too many odd BIOS and bridge setups + * that don't like drivers doing that all of the time. + * Oh well, we can dream of sane hardware when we sleep, no matter how + * horrible the crap we have to deal with is when we are awake... + */ + + pci_dev_put(pci_dev); + return 0; +} + +static int pci_device_suspend(struct device * dev, pm_message_t state) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + int i = 0; + + if (drv && drv->suspend) + i = drv->suspend(pci_dev, state); + else + pci_save_state(pci_dev); + return i; +} + + +/* + * Default resume method for devices that have no driver provided resume, + * or not even a driver at all. + */ +static void pci_default_resume(struct pci_dev *pci_dev) +{ + /* restore the PCI config space */ + pci_restore_state(pci_dev); + /* if the device was enabled before suspend, reenable */ + if (pci_dev->is_enabled) + pci_enable_device(pci_dev); + /* if the device was busmaster before the suspend, make it busmaster again */ + if (pci_dev->is_busmaster) + pci_set_master(pci_dev); +} + +static int pci_device_resume(struct device * dev) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + + if (drv && drv->resume) + drv->resume(pci_dev); + else + pci_default_resume(pci_dev); + return 0; +} + + +#define kobj_to_pci_driver(obj) container_of(obj, struct device_driver, kobj) +#define attr_to_driver_attribute(obj) container_of(obj, struct driver_attribute, attr) + +static ssize_t +pci_driver_attr_show(struct kobject * kobj, struct attribute *attr, char *buf) +{ + struct device_driver *driver = kobj_to_pci_driver(kobj); + struct driver_attribute *dattr = attr_to_driver_attribute(attr); + ssize_t ret = 0; + + if (get_driver(driver)) { + if (dattr->show) + ret = dattr->show(driver, buf); + put_driver(driver); + } + return ret; +} + +static ssize_t +pci_driver_attr_store(struct kobject * kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct device_driver *driver = kobj_to_pci_driver(kobj); + struct driver_attribute *dattr = attr_to_driver_attribute(attr); + ssize_t ret = 0; + + if (get_driver(driver)) { + if (dattr->store) + ret = dattr->store(driver, buf, count); + put_driver(driver); + } + return ret; +} + +static struct sysfs_ops pci_driver_sysfs_ops = { + .show = pci_driver_attr_show, + .store = pci_driver_attr_store, +}; +static struct kobj_type pci_driver_kobj_type = { + .sysfs_ops = &pci_driver_sysfs_ops, +}; + +static int +pci_populate_driver_dir(struct pci_driver *drv) +{ + return pci_create_newid_file(drv); +} + +/** + * pci_register_driver - register a new pci driver + * @drv: the driver structure to register + * + * Adds the driver structure to the list of registered drivers. + * Returns a negative value on error, otherwise 0. + * If no error occured, the driver remains registered even if + * no device was claimed during registration. + */ +int pci_register_driver(struct pci_driver *drv) +{ + int error; + + /* initialize common driver fields */ + drv->driver.name = drv->name; + drv->driver.bus = &pci_bus_type; + drv->driver.probe = pci_device_probe; + drv->driver.remove = pci_device_remove; + drv->driver.owner = drv->owner; + drv->driver.kobj.ktype = &pci_driver_kobj_type; + pci_init_dynids(&drv->dynids); + + /* register with core */ + error = driver_register(&drv->driver); + + if (!error) + pci_populate_driver_dir(drv); + + return error; +} + +/** + * pci_unregister_driver - unregister a pci driver + * @drv: the driver structure to unregister + * + * Deletes the driver structure from the list of registered PCI drivers, + * gives it a chance to clean up by calling its remove() function for + * each device it was responsible for, and marks those devices as + * driverless. + */ + +void +pci_unregister_driver(struct pci_driver *drv) +{ + driver_unregister(&drv->driver); + pci_free_dynids(drv); +} + +static struct pci_driver pci_compat_driver = { + .name = "compat" +}; + +/** + * pci_dev_driver - get the pci_driver of a device + * @dev: the device to query + * + * Returns the appropriate pci_driver structure or %NULL if there is no + * registered driver for the device. + */ +struct pci_driver * +pci_dev_driver(const struct pci_dev *dev) +{ + if (dev->driver) + return dev->driver; + else { + int i; + for(i=0; i<=PCI_ROM_RESOURCE; i++) + if (dev->resource[i].flags & IORESOURCE_BUSY) + return &pci_compat_driver; + } + return NULL; +} + +/** + * pci_bus_match - Tell if a PCI device structure has a matching PCI device id structure + * @ids: array of PCI device id structures to search in + * @dev: the PCI device structure to match against + * + * Used by a driver to check whether a PCI device present in the + * system is in its list of supported devices.Returns the matching + * pci_device_id structure or %NULL if there is no match. + */ +static int pci_bus_match(struct device * dev, struct device_driver * drv) +{ + const struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * pci_drv = to_pci_driver(drv); + const struct pci_device_id * ids = pci_drv->id_table; + const struct pci_device_id *found_id; + + if (!ids) + return 0; + + found_id = pci_match_device(ids, pci_dev); + if (found_id) + return 1; + + return pci_bus_match_dynids(pci_dev, pci_drv); +} + +/** + * pci_dev_get - increments the reference count of the pci device structure + * @dev: the device being referenced + * + * Each live reference to a device should be refcounted. + * + * Drivers for PCI devices should normally record such references in + * their probe() methods, when they bind to a device, and release + * them by calling pci_dev_put(), in their disconnect() methods. + * + * A pointer to the device with the incremented reference counter is returned. + */ +struct pci_dev *pci_dev_get(struct pci_dev *dev) +{ + if (dev) + get_device(&dev->dev); + return dev; +} + +/** + * pci_dev_put - release a use of the pci device structure + * @dev: device that's been disconnected + * + * Must be called when a user of a device is finished with it. When the last + * user of the device calls this function, the memory of the device is freed. + */ +void pci_dev_put(struct pci_dev *dev) +{ + if (dev) + put_device(&dev->dev); +} + +#ifndef CONFIG_HOTPLUG +int pci_hotplug (struct device *dev, char **envp, int num_envp, + char *buffer, int buffer_size) +{ + return -ENODEV; +} +#endif + +struct bus_type pci_bus_type = { + .name = "pci", + .match = pci_bus_match, + .hotplug = pci_hotplug, + .suspend = pci_device_suspend, + .resume = pci_device_resume, + .dev_attrs = pci_dev_attrs, +}; + +static int __init pci_driver_init(void) +{ + return bus_register(&pci_bus_type); +} + +postcore_initcall(pci_driver_init); + +EXPORT_SYMBOL(pci_match_device); +EXPORT_SYMBOL(pci_register_driver); +EXPORT_SYMBOL(pci_unregister_driver); +EXPORT_SYMBOL(pci_dev_driver); +EXPORT_SYMBOL(pci_bus_type); +EXPORT_SYMBOL(pci_dev_get); +EXPORT_SYMBOL(pci_dev_put); |