mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
![Rafael J. Wysocki](/assets/img/avatar_default.png)
The PNP ACPI driver squirrels the ACPI handles of PNP devices' ACPI companions, but this isn't correct, because those handles should be accessed using the DEVICE_ACPI_HANDLE() macro operating on struct device objects. Using DEVICE_ACPI_HANDLE() in the PNP ACPI driver instead of the driver's own copies of the ACPI handles allows us to avoid a problem with docking stations where a machine docked before suspend to RAM and undocked while suspended crashes during the subsequent resume (in that case the ACPI companion of the PNP device in question doesn't exist any more while the device is being resumed). It also allows us to avoid the problem where suspend to RAM fails when the machine was undocked while suspended before (again, the ACPI companion of the PNP device is not present any more while it is being suspended). This change doesn't fix all of the the PNP ACPI driver's problems with PNP devices in docking stations (generally speaking, the driver has no idea that devices can come and go and doesn't even attempt to handle such events), but at least it makes suspend work for the users of docking stations who don't use the PNP devices located in there. References: https://bugzilla.kernel.org/show_bug.cgi?id=15100 Reported-and-tested-by: Toralf Förster <toralf.foerster@gmx.de> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Bjorn Helgaas <bjorn.helgaas@hp.com> Signed-off-by: Len Brown <len.brown@intel.com>
274 lines
5.4 KiB
C
274 lines
5.4 KiB
C
/*
|
|
* driver.c - device id matching, driver model, etc.
|
|
*
|
|
* Copyright 2002 Adam Belay <ambx1@neo.rr.com>
|
|
*/
|
|
|
|
#include <linux/string.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pnp.h>
|
|
#include "base.h"
|
|
|
|
static int compare_func(const char *ida, const char *idb)
|
|
{
|
|
int i;
|
|
|
|
/* we only need to compare the last 4 chars */
|
|
for (i = 3; i < 7; i++) {
|
|
if (ida[i] != 'X' &&
|
|
idb[i] != 'X' && toupper(ida[i]) != toupper(idb[i]))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int compare_pnp_id(struct pnp_id *pos, const char *id)
|
|
{
|
|
if (!pos || !id || (strlen(id) != 7))
|
|
return 0;
|
|
if (memcmp(id, "ANYDEVS", 7) == 0)
|
|
return 1;
|
|
while (pos) {
|
|
if (memcmp(pos->id, id, 3) == 0)
|
|
if (compare_func(pos->id, id) == 1)
|
|
return 1;
|
|
pos = pos->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct pnp_device_id *match_device(struct pnp_driver *drv,
|
|
struct pnp_dev *dev)
|
|
{
|
|
const struct pnp_device_id *drv_id = drv->id_table;
|
|
|
|
if (!drv_id)
|
|
return NULL;
|
|
|
|
while (*drv_id->id) {
|
|
if (compare_pnp_id(dev->id, drv_id->id))
|
|
return drv_id;
|
|
drv_id++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int pnp_device_attach(struct pnp_dev *pnp_dev)
|
|
{
|
|
spin_lock(&pnp_lock);
|
|
if (pnp_dev->status != PNP_READY) {
|
|
spin_unlock(&pnp_lock);
|
|
return -EBUSY;
|
|
}
|
|
pnp_dev->status = PNP_ATTACHED;
|
|
spin_unlock(&pnp_lock);
|
|
return 0;
|
|
}
|
|
|
|
void pnp_device_detach(struct pnp_dev *pnp_dev)
|
|
{
|
|
spin_lock(&pnp_lock);
|
|
if (pnp_dev->status == PNP_ATTACHED)
|
|
pnp_dev->status = PNP_READY;
|
|
spin_unlock(&pnp_lock);
|
|
pnp_disable_dev(pnp_dev);
|
|
}
|
|
|
|
static int pnp_device_probe(struct device *dev)
|
|
{
|
|
int error;
|
|
struct pnp_driver *pnp_drv;
|
|
struct pnp_dev *pnp_dev;
|
|
const struct pnp_device_id *dev_id = NULL;
|
|
pnp_dev = to_pnp_dev(dev);
|
|
pnp_drv = to_pnp_driver(dev->driver);
|
|
|
|
error = pnp_device_attach(pnp_dev);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
if (pnp_dev->active == 0) {
|
|
if (!(pnp_drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)) {
|
|
error = pnp_activate_dev(pnp_dev);
|
|
if (error < 0)
|
|
return error;
|
|
}
|
|
} else if ((pnp_drv->flags & PNP_DRIVER_RES_DISABLE)
|
|
== PNP_DRIVER_RES_DISABLE) {
|
|
error = pnp_disable_dev(pnp_dev);
|
|
if (error < 0)
|
|
return error;
|
|
}
|
|
error = 0;
|
|
if (pnp_drv->probe) {
|
|
dev_id = match_device(pnp_drv, pnp_dev);
|
|
if (dev_id != NULL)
|
|
error = pnp_drv->probe(pnp_dev, dev_id);
|
|
}
|
|
if (error >= 0) {
|
|
pnp_dev->driver = pnp_drv;
|
|
error = 0;
|
|
} else
|
|
goto fail;
|
|
|
|
return error;
|
|
|
|
fail:
|
|
pnp_device_detach(pnp_dev);
|
|
return error;
|
|
}
|
|
|
|
static int pnp_device_remove(struct device *dev)
|
|
{
|
|
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
|
|
struct pnp_driver *drv = pnp_dev->driver;
|
|
|
|
if (drv) {
|
|
if (drv->remove)
|
|
drv->remove(pnp_dev);
|
|
pnp_dev->driver = NULL;
|
|
}
|
|
pnp_device_detach(pnp_dev);
|
|
return 0;
|
|
}
|
|
|
|
static void pnp_device_shutdown(struct device *dev)
|
|
{
|
|
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
|
|
struct pnp_driver *drv = pnp_dev->driver;
|
|
|
|
if (drv && drv->shutdown)
|
|
drv->shutdown(pnp_dev);
|
|
}
|
|
|
|
static int pnp_bus_match(struct device *dev, struct device_driver *drv)
|
|
{
|
|
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
|
|
struct pnp_driver *pnp_drv = to_pnp_driver(drv);
|
|
|
|
if (match_device(pnp_drv, pnp_dev) == NULL)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int pnp_bus_suspend(struct device *dev, pm_message_t state)
|
|
{
|
|
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
|
|
struct pnp_driver *pnp_drv = pnp_dev->driver;
|
|
int error;
|
|
|
|
if (!pnp_drv)
|
|
return 0;
|
|
|
|
if (pnp_drv->suspend) {
|
|
error = pnp_drv->suspend(pnp_dev, state);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
if (pnp_can_disable(pnp_dev)) {
|
|
error = pnp_stop_dev(pnp_dev);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
if (pnp_dev->protocol->suspend)
|
|
pnp_dev->protocol->suspend(pnp_dev, state);
|
|
return 0;
|
|
}
|
|
|
|
static int pnp_bus_resume(struct device *dev)
|
|
{
|
|
struct pnp_dev *pnp_dev = to_pnp_dev(dev);
|
|
struct pnp_driver *pnp_drv = pnp_dev->driver;
|
|
int error;
|
|
|
|
if (!pnp_drv)
|
|
return 0;
|
|
|
|
if (pnp_dev->protocol->resume) {
|
|
error = pnp_dev->protocol->resume(pnp_dev);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
if (pnp_can_write(pnp_dev)) {
|
|
error = pnp_start_dev(pnp_dev);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
if (pnp_drv->resume) {
|
|
error = pnp_drv->resume(pnp_dev);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct bus_type pnp_bus_type = {
|
|
.name = "pnp",
|
|
.match = pnp_bus_match,
|
|
.probe = pnp_device_probe,
|
|
.remove = pnp_device_remove,
|
|
.shutdown = pnp_device_shutdown,
|
|
.suspend = pnp_bus_suspend,
|
|
.resume = pnp_bus_resume,
|
|
.dev_attrs = pnp_interface_attrs,
|
|
};
|
|
|
|
int pnp_register_driver(struct pnp_driver *drv)
|
|
{
|
|
drv->driver.name = drv->name;
|
|
drv->driver.bus = &pnp_bus_type;
|
|
|
|
return driver_register(&drv->driver);
|
|
}
|
|
|
|
void pnp_unregister_driver(struct pnp_driver *drv)
|
|
{
|
|
driver_unregister(&drv->driver);
|
|
}
|
|
|
|
/**
|
|
* pnp_add_id - adds an EISA id to the specified device
|
|
* @dev: pointer to the desired device
|
|
* @id: pointer to an EISA id string
|
|
*/
|
|
struct pnp_id *pnp_add_id(struct pnp_dev *dev, const char *id)
|
|
{
|
|
struct pnp_id *dev_id, *ptr;
|
|
|
|
dev_id = kzalloc(sizeof(struct pnp_id), GFP_KERNEL);
|
|
if (!dev_id)
|
|
return NULL;
|
|
|
|
dev_id->id[0] = id[0];
|
|
dev_id->id[1] = id[1];
|
|
dev_id->id[2] = id[2];
|
|
dev_id->id[3] = tolower(id[3]);
|
|
dev_id->id[4] = tolower(id[4]);
|
|
dev_id->id[5] = tolower(id[5]);
|
|
dev_id->id[6] = tolower(id[6]);
|
|
dev_id->id[7] = '\0';
|
|
|
|
dev_id->next = NULL;
|
|
ptr = dev->id;
|
|
while (ptr && ptr->next)
|
|
ptr = ptr->next;
|
|
if (ptr)
|
|
ptr->next = dev_id;
|
|
else
|
|
dev->id = dev_id;
|
|
|
|
return dev_id;
|
|
}
|
|
|
|
EXPORT_SYMBOL(pnp_register_driver);
|
|
EXPORT_SYMBOL(pnp_unregister_driver);
|
|
EXPORT_SYMBOL(pnp_device_attach);
|
|
EXPORT_SYMBOL(pnp_device_detach);
|