diff options
Diffstat (limited to 'drivers/acpi/scan.c')
-rw-r--r-- | drivers/acpi/scan.c | 1379 |
1 files changed, 1379 insertions, 0 deletions
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c new file mode 100644 index 000000000000..e7ca06626566 --- /dev/null +++ b/drivers/acpi/scan.c @@ -0,0 +1,1379 @@ +/* + * scan.c - support for transforming the ACPI namespace into individual objects + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/acpi.h> + +#include <acpi/acpi_drivers.h> +#include <acpi/acinterp.h> /* for acpi_ex_eisa_id_to_string() */ + + +#define _COMPONENT ACPI_BUS_COMPONENT +ACPI_MODULE_NAME ("scan") + +#define STRUCT_TO_INT(s) (*((int*)&s)) + +extern struct acpi_device *acpi_root; + + +#define ACPI_BUS_CLASS "system_bus" +#define ACPI_BUS_HID "ACPI_BUS" +#define ACPI_BUS_DRIVER_NAME "ACPI Bus Driver" +#define ACPI_BUS_DEVICE_NAME "System Bus" + +static LIST_HEAD(acpi_device_list); +DEFINE_SPINLOCK(acpi_device_lock); +LIST_HEAD(acpi_wakeup_device_list); + +static int +acpi_bus_trim(struct acpi_device *start, + int rmdevice); + +static void acpi_device_release(struct kobject * kobj) +{ + struct acpi_device * dev = container_of(kobj,struct acpi_device,kobj); + if (dev->pnp.cid_list) + kfree(dev->pnp.cid_list); + kfree(dev); +} + +struct acpi_device_attribute { + struct attribute attr; + ssize_t (*show)(struct acpi_device *, char *); + ssize_t (*store)(struct acpi_device *, const char *, size_t); +}; + +typedef void acpi_device_sysfs_files(struct kobject *, + const struct attribute *); + +static void setup_sys_fs_device_files(struct acpi_device *dev, + acpi_device_sysfs_files *func); + +#define create_sysfs_device_files(dev) \ + setup_sys_fs_device_files(dev, (acpi_device_sysfs_files *)&sysfs_create_file) +#define remove_sysfs_device_files(dev) \ + setup_sys_fs_device_files(dev, (acpi_device_sysfs_files *)&sysfs_remove_file) + + +#define to_acpi_device(n) container_of(n, struct acpi_device, kobj) +#define to_handle_attr(n) container_of(n, struct acpi_device_attribute, attr); + +static ssize_t acpi_device_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct acpi_device *device = to_acpi_device(kobj); + struct acpi_device_attribute *attribute = to_handle_attr(attr); + return attribute->show ? attribute->show(device, buf) : 0; +} +static ssize_t acpi_device_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t len) +{ + struct acpi_device *device = to_acpi_device(kobj); + struct acpi_device_attribute *attribute = to_handle_attr(attr); + return attribute->store ? attribute->store(device, buf, len) : len; +} + +static struct sysfs_ops acpi_device_sysfs_ops = { + .show = acpi_device_attr_show, + .store = acpi_device_attr_store, +}; + +static struct kobj_type ktype_acpi_ns = { + .sysfs_ops = &acpi_device_sysfs_ops, + .release = acpi_device_release, +}; + +static int namespace_hotplug(struct kset *kset, struct kobject *kobj, + char **envp, int num_envp, char *buffer, + int buffer_size) +{ + struct acpi_device *dev = to_acpi_device(kobj); + int i = 0; + int len = 0; + + if (!dev->driver) + return 0; + + if (add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size, &len, + "PHYSDEVDRIVER=%s", dev->driver->name)) + return -ENOMEM; + + envp[i] = NULL; + + return 0; +} + +static struct kset_hotplug_ops namespace_hotplug_ops = { + .hotplug = &namespace_hotplug, +}; + +static struct kset acpi_namespace_kset = { + .kobj = { + .name = "namespace", + }, + .subsys = &acpi_subsys, + .ktype = &ktype_acpi_ns, + .hotplug_ops = &namespace_hotplug_ops, +}; + + +static void acpi_device_register(struct acpi_device * device, struct acpi_device * parent) +{ + /* + * Linkage + * ------- + * Link this device to its parent and siblings. + */ + INIT_LIST_HEAD(&device->children); + INIT_LIST_HEAD(&device->node); + INIT_LIST_HEAD(&device->g_list); + INIT_LIST_HEAD(&device->wakeup_list); + + spin_lock(&acpi_device_lock); + if (device->parent) { + list_add_tail(&device->node, &device->parent->children); + list_add_tail(&device->g_list,&device->parent->g_list); + } else + list_add_tail(&device->g_list,&acpi_device_list); + if (device->wakeup.flags.valid) + list_add_tail(&device->wakeup_list,&acpi_wakeup_device_list); + spin_unlock(&acpi_device_lock); + + strlcpy(device->kobj.name,device->pnp.bus_id,KOBJ_NAME_LEN); + if (parent) + device->kobj.parent = &parent->kobj; + device->kobj.ktype = &ktype_acpi_ns; + device->kobj.kset = &acpi_namespace_kset; + kobject_register(&device->kobj); + create_sysfs_device_files(device); +} + +static int +acpi_device_unregister ( + struct acpi_device *device, + int type) +{ + spin_lock(&acpi_device_lock); + if (device->parent) { + list_del(&device->node); + list_del(&device->g_list); + } else + list_del(&device->g_list); + + list_del(&device->wakeup_list); + + spin_unlock(&acpi_device_lock); + + acpi_detach_data(device->handle, acpi_bus_data_handler); + remove_sysfs_device_files(device); + kobject_unregister(&device->kobj); + return 0; +} + +void +acpi_bus_data_handler ( + acpi_handle handle, + u32 function, + void *context) +{ + ACPI_FUNCTION_TRACE("acpi_bus_data_handler"); + + /* TBD */ + + return_VOID; +} + +static int +acpi_bus_get_power_flags ( + struct acpi_device *device) +{ + acpi_status status = 0; + acpi_handle handle = NULL; + u32 i = 0; + + ACPI_FUNCTION_TRACE("acpi_bus_get_power_flags"); + + /* + * Power Management Flags + */ + status = acpi_get_handle(device->handle, "_PSC", &handle); + if (ACPI_SUCCESS(status)) + device->power.flags.explicit_get = 1; + status = acpi_get_handle(device->handle, "_IRC", &handle); + if (ACPI_SUCCESS(status)) + device->power.flags.inrush_current = 1; + + /* + * Enumerate supported power management states + */ + for (i = ACPI_STATE_D0; i <= ACPI_STATE_D3; i++) { + struct acpi_device_power_state *ps = &device->power.states[i]; + char object_name[5] = {'_','P','R','0'+i,'\0'}; + + /* Evaluate "_PRx" to se if power resources are referenced */ + acpi_evaluate_reference(device->handle, object_name, NULL, + &ps->resources); + if (ps->resources.count) { + device->power.flags.power_resources = 1; + ps->flags.valid = 1; + } + + /* Evaluate "_PSx" to see if we can do explicit sets */ + object_name[2] = 'S'; + status = acpi_get_handle(device->handle, object_name, &handle); + if (ACPI_SUCCESS(status)) { + ps->flags.explicit_set = 1; + ps->flags.valid = 1; + } + + /* State is valid if we have some power control */ + if (ps->resources.count || ps->flags.explicit_set) + ps->flags.valid = 1; + + ps->power = -1; /* Unknown - driver assigned */ + ps->latency = -1; /* Unknown - driver assigned */ + } + + /* Set defaults for D0 and D3 states (always valid) */ + device->power.states[ACPI_STATE_D0].flags.valid = 1; + device->power.states[ACPI_STATE_D0].power = 100; + device->power.states[ACPI_STATE_D3].flags.valid = 1; + device->power.states[ACPI_STATE_D3].power = 0; + + /* TBD: System wake support and resource requirements. */ + + device->power.state = ACPI_STATE_UNKNOWN; + + return_VALUE(0); +} + +int +acpi_match_ids ( + struct acpi_device *device, + char *ids) +{ + int error = 0; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + + if (device->flags.hardware_id) + if (strstr(ids, device->pnp.hardware_id)) + goto Done; + + if (device->flags.compatible_ids) { + struct acpi_compatible_id_list *cid_list = device->pnp.cid_list; + int i; + + /* compare multiple _CID entries against driver ids */ + for (i = 0; i < cid_list->count; i++) + { + if (strstr(ids, cid_list->id[i].value)) + goto Done; + } + } + error = -ENOENT; + + Done: + if (buffer.pointer) + acpi_os_free(buffer.pointer); + return error; +} + +static acpi_status +acpi_bus_extract_wakeup_device_power_package ( + struct acpi_device *device, + union acpi_object *package) +{ + int i = 0; + union acpi_object *element = NULL; + + if (!device || !package || (package->package.count < 2)) + return AE_BAD_PARAMETER; + + element = &(package->package.elements[0]); + if (!element) + return AE_BAD_PARAMETER; + if (element->type == ACPI_TYPE_PACKAGE) { + if ((element->package.count < 2) || + (element->package.elements[0].type != ACPI_TYPE_LOCAL_REFERENCE) || + (element->package.elements[1].type != ACPI_TYPE_INTEGER)) + return AE_BAD_DATA; + device->wakeup.gpe_device = element->package.elements[0].reference.handle; + device->wakeup.gpe_number = (u32)element->package.elements[1].integer.value; + }else if (element->type == ACPI_TYPE_INTEGER) { + device->wakeup.gpe_number = element->integer.value; + }else + return AE_BAD_DATA; + + element = &(package->package.elements[1]); + if (element->type != ACPI_TYPE_INTEGER) { + return AE_BAD_DATA; + } + device->wakeup.sleep_state = element->integer.value; + + if ((package->package.count - 2) > ACPI_MAX_HANDLES) { + return AE_NO_MEMORY; + } + device->wakeup.resources.count = package->package.count - 2; + for (i=0; i < device->wakeup.resources.count; i++) { + element = &(package->package.elements[i + 2]); + if (element->type != ACPI_TYPE_ANY ) { + return AE_BAD_DATA; + } + + device->wakeup.resources.handles[i] = element->reference.handle; + } + + return AE_OK; +} + +static int +acpi_bus_get_wakeup_device_flags ( + struct acpi_device *device) +{ + acpi_status status = 0; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *package = NULL; + + ACPI_FUNCTION_TRACE("acpi_bus_get_wakeup_flags"); + + /* _PRW */ + status = acpi_evaluate_object(device->handle, "_PRW", NULL, &buffer); + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error evaluating _PRW\n")); + goto end; + } + + package = (union acpi_object *) buffer.pointer; + status = acpi_bus_extract_wakeup_device_power_package(device, package); + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error extracting _PRW package\n")); + goto end; + } + + acpi_os_free(buffer.pointer); + + device->wakeup.flags.valid = 1; + /* Power button, Lid switch always enable wakeup*/ + if (!acpi_match_ids(device, "PNP0C0D,PNP0C0C,PNP0C0E")) + device->wakeup.flags.run_wake = 1; + +end: + if (ACPI_FAILURE(status)) + device->flags.wake_capable = 0; + return_VALUE(0); +} + +/* -------------------------------------------------------------------------- + ACPI hotplug sysfs device file support + -------------------------------------------------------------------------- */ +static ssize_t acpi_eject_store(struct acpi_device *device, + const char *buf, size_t count); + +#define ACPI_DEVICE_ATTR(_name,_mode,_show,_store) \ +static struct acpi_device_attribute acpi_device_attr_##_name = \ + __ATTR(_name, _mode, _show, _store) + +ACPI_DEVICE_ATTR(eject, 0200, NULL, acpi_eject_store); + +/** + * setup_sys_fs_device_files - sets up the device files under device namespace + * @@dev: acpi_device object + * @@func: function pointer to create or destroy the device file + */ +static void +setup_sys_fs_device_files ( + struct acpi_device *dev, + acpi_device_sysfs_files *func) +{ + acpi_status status; + acpi_handle temp = NULL; + + /* + * If device has _EJ0, 'eject' file is created that is used to trigger + * hot-removal function from userland. + */ + status = acpi_get_handle(dev->handle, "_EJ0", &temp); + if (ACPI_SUCCESS(status)) + (*(func))(&dev->kobj,&acpi_device_attr_eject.attr); +} + +static int +acpi_eject_operation(acpi_handle handle, int lockable) +{ + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status = AE_OK; + + /* + * TBD: evaluate _PS3? + */ + + if (lockable) { + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = 0; + acpi_evaluate_object(handle, "_LCK", &arg_list, NULL); + } + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = 1; + + /* + * TBD: _EJD support. + */ + + status = acpi_evaluate_object(handle, "_EJ0", &arg_list, NULL); + if (ACPI_FAILURE(status)) { + return(-ENODEV); + } + + return(0); +} + + +static ssize_t +acpi_eject_store(struct acpi_device *device, const char *buf, size_t count) +{ + int result; + int ret = count; + int islockable; + acpi_status status; + acpi_handle handle; + acpi_object_type type = 0; + + if ((!count) || (buf[0] != '1')) { + return -EINVAL; + } + +#ifndef FORCE_EJECT + if (device->driver == NULL) { + ret = -ENODEV; + goto err; + } +#endif + status = acpi_get_type(device->handle, &type); + if (ACPI_FAILURE(status) || (!device->flags.ejectable) ) { + ret = -ENODEV; + goto err; + } + + islockable = device->flags.lockable; + handle = device->handle; + + if (type == ACPI_TYPE_PROCESSOR) + result = acpi_bus_trim(device, 0); + else + result = acpi_bus_trim(device, 1); + + if (!result) + result = acpi_eject_operation(handle, islockable); + + if (result) { + ret = -EBUSY; + } +err: + return ret; +} + + +/* -------------------------------------------------------------------------- + Performance Management + -------------------------------------------------------------------------- */ + +static int +acpi_bus_get_perf_flags ( + struct acpi_device *device) +{ + device->performance.state = ACPI_STATE_UNKNOWN; + return 0; +} + +/* -------------------------------------------------------------------------- + Driver Management + -------------------------------------------------------------------------- */ + +static LIST_HEAD(acpi_bus_drivers); +static DECLARE_MUTEX(acpi_bus_drivers_lock); + + +/** + * acpi_bus_match + * -------------- + * Checks the device's hardware (_HID) or compatible (_CID) ids to see if it + * matches the specified driver's criteria. + */ +static int +acpi_bus_match ( + struct acpi_device *device, + struct acpi_driver *driver) +{ + if (driver && driver->ops.match) + return driver->ops.match(device, driver); + return acpi_match_ids(device, driver->ids); +} + + +/** + * acpi_bus_driver_init + * -------------------- + * Used to initialize a device via its device driver. Called whenever a + * driver is bound to a device. Invokes the driver's add() and start() ops. + */ +static int +acpi_bus_driver_init ( + struct acpi_device *device, + struct acpi_driver *driver) +{ + int result = 0; + + ACPI_FUNCTION_TRACE("acpi_bus_driver_init"); + + if (!device || !driver) + return_VALUE(-EINVAL); + + if (!driver->ops.add) + return_VALUE(-ENOSYS); + + result = driver->ops.add(device); + if (result) { + device->driver = NULL; + acpi_driver_data(device) = NULL; + return_VALUE(result); + } + + device->driver = driver; + + /* + * TBD - Configuration Management: Assign resources to device based + * upon possible configuration and currently allocated resources. + */ + + if (driver->ops.start) { + result = driver->ops.start(device); + if (result && driver->ops.remove) + driver->ops.remove(device, ACPI_BUS_REMOVAL_NORMAL); + return_VALUE(result); + } + + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Driver successfully bound to device\n")); + + if (driver->ops.scan) { + driver->ops.scan(device); + } + + return_VALUE(0); +} + +static int acpi_driver_attach(struct acpi_driver * drv) +{ + struct list_head * node, * next; + int count = 0; + + ACPI_FUNCTION_TRACE("acpi_driver_attach"); + + spin_lock(&acpi_device_lock); + list_for_each_safe(node, next, &acpi_device_list) { + struct acpi_device * dev = container_of(node, struct acpi_device, g_list); + + if (dev->driver || !dev->status.present) + continue; + spin_unlock(&acpi_device_lock); + + if (!acpi_bus_match(dev, drv)) { + if (!acpi_bus_driver_init(dev, drv)) { + atomic_inc(&drv->references); + count++; + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found driver [%s] for device [%s]\n", + drv->name, dev->pnp.bus_id)); + } + } + spin_lock(&acpi_device_lock); + } + spin_unlock(&acpi_device_lock); + return_VALUE(count); +} + +static int acpi_driver_detach(struct acpi_driver * drv) +{ + struct list_head * node, * next; + + ACPI_FUNCTION_TRACE("acpi_driver_detach"); + + spin_lock(&acpi_device_lock); + list_for_each_safe(node,next,&acpi_device_list) { + struct acpi_device * dev = container_of(node,struct acpi_device,g_list); + + if (dev->driver == drv) { + spin_unlock(&acpi_device_lock); + if (drv->ops.remove) + drv->ops.remove(dev,ACPI_BUS_REMOVAL_NORMAL); + spin_lock(&acpi_device_lock); + dev->driver = NULL; + dev->driver_data = NULL; + atomic_dec(&drv->references); + } + } + spin_unlock(&acpi_device_lock); + return_VALUE(0); +} + +/** + * acpi_bus_register_driver + * ------------------------ + * Registers a driver with the ACPI bus. Searches the namespace for all + * devices that match the driver's criteria and binds. Returns the + * number of devices that were claimed by the driver, or a negative + * error status for failure. + */ +int +acpi_bus_register_driver ( + struct acpi_driver *driver) +{ + int count; + + ACPI_FUNCTION_TRACE("acpi_bus_register_driver"); + + if (acpi_disabled) + return_VALUE(-ENODEV); + + if (!driver) + return_VALUE(-EINVAL); + + spin_lock(&acpi_device_lock); + list_add_tail(&driver->node, &acpi_bus_drivers); + spin_unlock(&acpi_device_lock); + count = acpi_driver_attach(driver); + + return_VALUE(count); +} +EXPORT_SYMBOL(acpi_bus_register_driver); + + +/** + * acpi_bus_unregister_driver + * -------------------------- + * Unregisters a driver with the ACPI bus. Searches the namespace for all + * devices that match the driver's criteria and unbinds. + */ +int +acpi_bus_unregister_driver ( + struct acpi_driver *driver) +{ + int error = 0; + + ACPI_FUNCTION_TRACE("acpi_bus_unregister_driver"); + + if (driver) { + acpi_driver_detach(driver); + + if (!atomic_read(&driver->references)) { + spin_lock(&acpi_device_lock); + list_del_init(&driver->node); + spin_unlock(&acpi_device_lock); + } + } else + error = -EINVAL; + return_VALUE(error); +} +EXPORT_SYMBOL(acpi_bus_unregister_driver); + +/** + * acpi_bus_find_driver + * -------------------- + * Parses the list of registered drivers looking for a driver applicable for + * the specified device. + */ +static int +acpi_bus_find_driver ( + struct acpi_device *device) +{ + int result = 0; + struct list_head * node, *next; + + ACPI_FUNCTION_TRACE("acpi_bus_find_driver"); + + spin_lock(&acpi_device_lock); + list_for_each_safe(node,next,&acpi_bus_drivers) { + struct acpi_driver * driver = container_of(node,struct acpi_driver,node); + + atomic_inc(&driver->references); + spin_unlock(&acpi_device_lock); + if (!acpi_bus_match(device, driver)) { + result = acpi_bus_driver_init(device, driver); + if (!result) + goto Done; + } + atomic_dec(&driver->references); + spin_lock(&acpi_device_lock); + } + spin_unlock(&acpi_device_lock); + + Done: + return_VALUE(result); +} + + +/* -------------------------------------------------------------------------- + Device Enumeration + -------------------------------------------------------------------------- */ + +static int +acpi_bus_get_flags ( + struct acpi_device *device) +{ + acpi_status status = AE_OK; + acpi_handle temp = NULL; + + ACPI_FUNCTION_TRACE("acpi_bus_get_flags"); + + /* Presence of _STA indicates 'dynamic_status' */ + status = acpi_get_handle(device->handle, "_STA", &temp); + if (ACPI_SUCCESS(status)) + device->flags.dynamic_status = 1; + + /* Presence of _CID indicates 'compatible_ids' */ + status = acpi_get_handle(device->handle, "_CID", &temp); + if (ACPI_SUCCESS(status)) + device->flags.compatible_ids = 1; + + /* Presence of _RMV indicates 'removable' */ + status = acpi_get_handle(device->handle, "_RMV", &temp); + if (ACPI_SUCCESS(status)) + device->flags.removable = 1; + + /* Presence of _EJD|_EJ0 indicates 'ejectable' */ + status = acpi_get_handle(device->handle, "_EJD", &temp); + if (ACPI_SUCCESS(status)) + device->flags.ejectable = 1; + else { + status = acpi_get_handle(device->handle, "_EJ0", &temp); + if (ACPI_SUCCESS(status)) + device->flags.ejectable = 1; + } + + /* Presence of _LCK indicates 'lockable' */ + status = acpi_get_handle(device->handle, "_LCK", &temp); + if (ACPI_SUCCESS(status)) + device->flags.lockable = 1; + + /* Presence of _PS0|_PR0 indicates 'power manageable' */ + status = acpi_get_handle(device->handle, "_PS0", &temp); + if (ACPI_FAILURE(status)) + status = acpi_get_handle(device->handle, "_PR0", &temp); + if (ACPI_SUCCESS(status)) + device->flags.power_manageable = 1; + + /* Presence of _PRW indicates wake capable */ + status = acpi_get_handle(device->handle, "_PRW", &temp); + if (ACPI_SUCCESS(status)) + device->flags.wake_capable = 1; + + /* TBD: Peformance management */ + + return_VALUE(0); +} + +static void acpi_device_get_busid(struct acpi_device * device, acpi_handle handle, int type) +{ + char bus_id[5] = {'?',0}; + struct acpi_buffer buffer = {sizeof(bus_id), bus_id}; + int i = 0; + + /* + * Bus ID + * ------ + * The device's Bus ID is simply the object name. + * TBD: Shouldn't this value be unique (within the ACPI namespace)? + */ + switch (type) { + case ACPI_BUS_TYPE_SYSTEM: + strcpy(device->pnp.bus_id, "ACPI"); + break; + case ACPI_BUS_TYPE_POWER_BUTTON: + strcpy(device->pnp.bus_id, "PWRF"); + break; + case ACPI_BUS_TYPE_SLEEP_BUTTON: + strcpy(device->pnp.bus_id, "SLPF"); + break; + default: + acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer); + /* Clean up trailing underscores (if any) */ + for (i = 3; i > 1; i--) { + if (bus_id[i] == '_') + bus_id[i] = '\0'; + else + break; + } + strcpy(device->pnp.bus_id, bus_id); + break; + } +} + +static void acpi_device_set_id(struct acpi_device * device, struct acpi_device * parent, + acpi_handle handle, int type) +{ + struct acpi_device_info *info; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + char *hid = NULL; + char *uid = NULL; + struct acpi_compatible_id_list *cid_list = NULL; + acpi_status status; + + switch (type) { + case ACPI_BUS_TYPE_DEVICE: + status = acpi_get_object_info(handle, &buffer); + if (ACPI_FAILURE(status)) { + printk("%s: Error reading device info\n",__FUNCTION__); + return; + } + + info = buffer.pointer; + if (info->valid & ACPI_VALID_HID) + hid = info->hardware_id.value; + if (info->valid & ACPI_VALID_UID) + uid = info->unique_id.value; + if (info->valid & ACPI_VALID_CID) + cid_list = &info->compatibility_id; + if (info->valid & ACPI_VALID_ADR) { + device->pnp.bus_address = info->address; + device->flags.bus_address = 1; + } + break; + case ACPI_BUS_TYPE_POWER: + hid = ACPI_POWER_HID; + break; + case ACPI_BUS_TYPE_PROCESSOR: + hid = ACPI_PROCESSOR_HID; + break; + case ACPI_BUS_TYPE_SYSTEM: + hid = ACPI_SYSTEM_HID; + break; + case ACPI_BUS_TYPE_THERMAL: + hid = ACPI_THERMAL_HID; + break; + case ACPI_BUS_TYPE_POWER_BUTTON: + hid = ACPI_BUTTON_HID_POWERF; + break; + case ACPI_BUS_TYPE_SLEEP_BUTTON: + hid = ACPI_BUTTON_HID_SLEEPF; + break; + } + + /* + * \_SB + * ---- + * Fix for the system root bus device -- the only root-level device. + */ + if ((parent == ACPI_ROOT_OBJECT) && (type == ACPI_BUS_TYPE_DEVICE)) { + hid = ACPI_BUS_HID; + strcpy(device->pnp.device_name, ACPI_BUS_DEVICE_NAME); + strcpy(device->pnp.device_class, ACPI_BUS_CLASS); + } + + if (hid) { + strcpy(device->pnp.hardware_id, hid); + device->flags.hardware_id = 1; + } + if (uid) { + strcpy(device->pnp.unique_id, uid); + device->flags.unique_id = 1; + } + if (cid_list) { + device->pnp.cid_list = kmalloc(cid_list->size, GFP_KERNEL); + if (device->pnp.cid_list) + memcpy(device->pnp.cid_list, cid_list, cid_list->size); + else + printk(KERN_ERR "Memory allocation error\n"); + } + + acpi_os_free(buffer.pointer); +} + +static int acpi_device_set_context(struct acpi_device * device, int type) +{ + acpi_status status = AE_OK; + int result = 0; + /* + * Context + * ------- + * Attach this 'struct acpi_device' to the ACPI object. This makes + * resolutions from handle->device very efficient. Note that we need + * to be careful with fixed-feature devices as they all attach to the + * root object. + */ + if (type != ACPI_BUS_TYPE_POWER_BUTTON && + type != ACPI_BUS_TYPE_SLEEP_BUTTON) { + status = acpi_attach_data(device->handle, + acpi_bus_data_handler, device); + + if (ACPI_FAILURE(status)) { + printk("Error attaching device data\n"); + result = -ENODEV; + } + } + return result; +} + +static void acpi_device_get_debug_info(struct acpi_device * device, acpi_handle handle, int type) +{ +#ifdef CONFIG_ACPI_DEBUG_OUTPUT + char *type_string = NULL; + char name[80] = {'?','\0'}; + struct acpi_buffer buffer = {sizeof(name), name}; + + switch (type) { + case ACPI_BUS_TYPE_DEVICE: + type_string = "Device"; + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + break; + case ACPI_BUS_TYPE_POWER: + type_string = "Power Resource"; + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + break; + case ACPI_BUS_TYPE_PROCESSOR: + type_string = "Processor"; + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + break; + case ACPI_BUS_TYPE_SYSTEM: + type_string = "System"; + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + break; + case ACPI_BUS_TYPE_THERMAL: + type_string = "Thermal Zone"; + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); + break; + case ACPI_BUS_TYPE_POWER_BUTTON: + type_string = "Power Button"; + sprintf(name, "PWRB"); + break; + case ACPI_BUS_TYPE_SLEEP_BUTTON: + type_string = "Sleep Button"; + sprintf(name, "SLPB"); + break; + } + + printk(KERN_DEBUG "Found %s %s [%p]\n", type_string, name, handle); +#endif /*CONFIG_ACPI_DEBUG_OUTPUT*/ +} + + +static int +acpi_bus_remove ( + struct acpi_device *dev, + int rmdevice) +{ + int result = 0; + struct acpi_driver *driver; + + ACPI_FUNCTION_TRACE("acpi_bus_remove"); + + if (!dev) + return_VALUE(-EINVAL); + + driver = dev->driver; + + if ((driver) && (driver->ops.remove)) { + + if (driver->ops.stop) { + result = driver->ops.stop(dev, ACPI_BUS_REMOVAL_EJECT); + if (result) + return_VALUE(result); + } + + result = dev->driver->ops.remove(dev, ACPI_BUS_REMOVAL_EJECT); + if (result) { + return_VALUE(result); + } + + atomic_dec(&dev->driver->references); + dev->driver = NULL; + acpi_driver_data(dev) = NULL; + } + + if (!rmdevice) + return_VALUE(0); + + if (dev->flags.bus_address) { + if ((dev->parent) && (dev->parent->ops.unbind)) + dev->parent->ops.unbind(dev); + } + + acpi_device_unregister(dev, ACPI_BUS_REMOVAL_EJECT); + + return_VALUE(0); +} + + +int +acpi_bus_add ( + struct acpi_device **child, + struct acpi_device *parent, + acpi_handle handle, + int type) +{ + int result = 0; + struct acpi_device *device = NULL; + + ACPI_FUNCTION_TRACE("acpi_bus_add"); + + if (!child) + return_VALUE(-EINVAL); + + device = kmalloc(sizeof(struct acpi_device), GFP_KERNEL); + if (!device) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Memory allocation error\n")); + return_VALUE(-ENOMEM); + } + memset(device, 0, sizeof(struct acpi_device)); + + device->handle = handle; + device->parent = parent; + + acpi_device_get_busid(device,handle,type); + + /* + * Flags + * ----- + * Get prior to calling acpi_bus_get_status() so we know whether + * or not _STA is present. Note that we only look for object + * handles -- cannot evaluate objects until we know the device is + * present and properly initialized. + */ + result = acpi_bus_get_flags(device); + if (result) + goto end; + + /* + * Status + * ------ + * See if the device is present. We always assume that non-Device() + * objects (e.g. thermal zones, power resources, processors, etc.) are + * present, functioning, etc. (at least when parent object is present). + * Note that _STA has a different meaning for some objects (e.g. + * power resources) so we need to be careful how we use it. + */ + switch (type) { + case ACPI_BUS_TYPE_DEVICE: + result = acpi_bus_get_status(device); + if (ACPI_FAILURE(result) || !device->status.present) { + result = -ENOENT; + goto end; + } + break; + default: + STRUCT_TO_INT(device->status) = 0x0F; + break; + } + + /* + * Initialize Device + * ----------------- + * TBD: Synch with Core's enumeration/initialization process. + */ + + /* + * Hardware ID, Unique ID, & Bus Address + * ------------------------------------- + */ + acpi_device_set_id(device,parent,handle,type); + + /* + * Power Management + * ---------------- + */ + if (device->flags.power_manageable) { + result = acpi_bus_get_power_flags(device); + if (result) + goto end; + } + + /* + * Wakeup device management + *----------------------- + */ + if (device->flags.wake_capable) { + result = acpi_bus_get_wakeup_device_flags(device); + if (result) + goto end; + } + + /* + * Performance Management + * ---------------------- + */ + if (device->flags.performance_manageable) { + result = acpi_bus_get_perf_flags(device); + if (result) + goto end; + } + + if ((result = acpi_device_set_context(device,type))) + goto end; + + acpi_device_get_debug_info(device,handle,type); + + acpi_device_register(device,parent); + + /* + * Bind _ADR-Based Devices + * ----------------------- + * If there's a a bus address (_ADR) then we utilize the parent's + * 'bind' function (if exists) to bind the ACPI- and natively- + * enumerated device representations. + */ + if (device->flags.bus_address) { + if (device->parent && device->parent->ops.bind) + device->parent->ops.bind(device); + } + + /* + * Locate & Attach Driver + * ---------------------- + * If there's a hardware id (_HID) or compatible ids (_CID) we check + * to see if there's a driver installed for this kind of device. Note + * that drivers can install before or after a device is enumerated. + * + * TBD: Assumes LDM provides driver hot-plug capability. + */ + acpi_bus_find_driver(device); + +end: + if (!result) + *child = device; + else { + if (device->pnp.cid_list) + kfree(device->pnp.cid_list); + kfree(device); + } + + return_VALUE(result); +} +EXPORT_SYMBOL(acpi_bus_add); + + +int acpi_bus_scan (struct acpi_device *start) +{ + acpi_status status = AE_OK; + struct acpi_device *parent = NULL; + struct acpi_device *child = NULL; + acpi_handle phandle = NULL; + acpi_handle chandle = NULL; + acpi_object_type type = 0; + u32 level = 1; + + ACPI_FUNCTION_TRACE("acpi_bus_scan"); + + if (!start) + return_VALUE(-EINVAL); + + parent = start; + phandle = start->handle; + + /* + * Parse through the ACPI namespace, identify all 'devices', and + * create a new 'struct acpi_device' for each. + */ + while ((level > 0) && parent) { + + status = acpi_get_next_object(ACPI_TYPE_ANY, phandle, + chandle, &chandle); + + /* + * If this scope is exhausted then move our way back up. + */ + if (ACPI_FAILURE(status)) { + level--; + chandle = phandle; + acpi_get_parent(phandle, &phandle); + if (parent->parent) + parent = parent->parent; + continue; + } + + status = acpi_get_type(chandle, &type); + if (ACPI_FAILURE(status)) + continue; + + /* + * If this is a scope object then parse it (depth-first). + */ + if (type == ACPI_TYPE_LOCAL_SCOPE) { + level++; + phandle = chandle; + chandle = NULL; + continue; + } + + /* + * We're only interested in objects that we consider 'devices'. + */ + switch (type) { + case ACPI_TYPE_DEVICE: + type = ACPI_BUS_TYPE_DEVICE; + break; + case ACPI_TYPE_PROCESSOR: + type = ACPI_BUS_TYPE_PROCESSOR; + break; + case ACPI_TYPE_THERMAL: + type = ACPI_BUS_TYPE_THERMAL; + break; + case ACPI_TYPE_POWER: + type = ACPI_BUS_TYPE_POWER; + break; + default: + continue; + } + + status = acpi_bus_add(&child, parent, chandle, type); + if (ACPI_FAILURE(status)) + continue; + + /* + * If the device is present, enabled, and functioning then + * parse its scope (depth-first). Note that we need to + * represent absent devices to facilitate PnP notifications + * -- but only the subtree head (not all of its children, + * which will be enumerated when the parent is inserted). + * + * TBD: Need notifications and other detection mechanisms + * in place before we can fully implement this. + */ + if (child->status.present) { + status = acpi_get_next_object(ACPI_TYPE_ANY, chandle, + NULL, NULL); + if (ACPI_SUCCESS(status)) { + level++; + phandle = chandle; + chandle = NULL; + parent = child; + } + } + } + + return_VALUE(0); +} +EXPORT_SYMBOL(acpi_bus_scan); + + +static int +acpi_bus_trim(struct acpi_device *start, + int rmdevice) +{ + acpi_status status; + struct acpi_device *parent, *child; + acpi_handle phandle, chandle; + acpi_object_type type; + u32 level = 1; + int err = 0; + + parent = start; + phandle = start->handle; + child = chandle = NULL; + + while ((level > 0) && parent && (!err)) { + status = acpi_get_next_object(ACPI_TYPE_ANY, phandle, + chandle, &chandle); + + /* + * If this scope is exhausted then move our way back up. + */ + if (ACPI_FAILURE(status)) { + level--; + chandle = phandle; + acpi_get_parent(phandle, &phandle); + child = parent; + parent = parent->parent; + + if (level == 0) + err = acpi_bus_remove(child, rmdevice); + else + err = acpi_bus_remove(child, 1); + + continue; + } + + status = acpi_get_type(chandle, &type); + if (ACPI_FAILURE(status)) { + continue; + } + /* + * If there is a device corresponding to chandle then + * parse it (depth-first). + */ + if (acpi_bus_get_device(chandle, &child) == 0) { + level++; + phandle = chandle; + chandle = NULL; + parent = child; + } + continue; + } + return err; +} + +static int +acpi_bus_scan_fixed ( + struct acpi_device *root) +{ + int result = 0; + struct acpi_device *device = NULL; + + ACPI_FUNCTION_TRACE("acpi_bus_scan_fixed"); + + if (!root) + return_VALUE(-ENODEV); + + /* + * Enumerate all fixed-feature devices. + */ + if (acpi_fadt.pwr_button == 0) + result = acpi_bus_add(&device, acpi_root, + NULL, ACPI_BUS_TYPE_POWER_BUTTON); + + if (acpi_fadt.sleep_button == 0) + result = acpi_bus_add(&device, acpi_root, + NULL, ACPI_BUS_TYPE_SLEEP_BUTTON); + + return_VALUE(result); +} + + +static int __init acpi_scan_init(void) +{ + int result; + + ACPI_FUNCTION_TRACE("acpi_scan_init"); + + if (acpi_disabled) + return_VALUE(0); + + kset_register(&acpi_namespace_kset); + + /* + * Create the root device in the bus's device tree + */ + result = acpi_bus_add(&acpi_root, NULL, ACPI_ROOT_OBJECT, + ACPI_BUS_TYPE_SYSTEM); + if (result) + goto Done; + + /* + * Enumerate devices in the ACPI namespace. + */ + result = acpi_bus_scan_fixed(acpi_root); + if (!result) + result = acpi_bus_scan(acpi_root); + + if (result) + acpi_device_unregister(acpi_root, ACPI_BUS_REMOVAL_NORMAL); + + Done: + return_VALUE(result); +} + +subsys_initcall(acpi_scan_init); |