#include <ddk.h> #include <linux/errno.h> #include <mutex.h> #include <pci.h> #include <syscall.h> #include "acpi.h" #include "acpi_bus.h" #define PREFIX "ACPI: " #define ACPI_BUS_CLASS "system_bus" #define ACPI_BUS_HID "KLBSYBUS" #define ACPI_BUS_DEVICE_NAME "System Bus" #define ACPI_IS_ROOT_DEVICE(device) (!(device)->parent) #define STRUCT_TO_INT(s) (*((int*)&s)) extern struct acpi_device *acpi_root; extern struct list_head acpi_device_list; extern struct list_head acpi_bus_id_list; DEFINE_MUTEX(acpi_device_lock); struct acpi_device_bus_id{ char bus_id[15]; unsigned int instance_no; struct list_head node; }; struct acpi_hardware_id { struct list_head list; char *id; }; #define acpi_device_name(d) ((d)->pnp.device_name) #define acpi_device_class(d) ((d)->pnp.device_class) static void acpi_util_eval_error(ACPI_HANDLE h, ACPI_STRING p, ACPI_STATUS s) { #ifdef ACPI_DEBUG_OUTPUT char prefix[80] = {'\0'}; ACPI_BUFFER buffer = {sizeof(prefix), prefix}; AcpiGetName(h, ACPI_FULL_PATHNAME, &buffer); ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluate [%s.%s]: %s\n", (char *) prefix, p, AcpiFormatException(s))); #else return; #endif } ACPI_STATUS acpi_evaluate_integer(ACPI_HANDLE handle, ACPI_STRING pathname, ACPI_OBJECT_LIST *arguments, unsigned long long *data) { ACPI_STATUS status = AE_OK; ACPI_OBJECT element; ACPI_BUFFER buffer = { 0, NULL }; if (!data) return AE_BAD_PARAMETER; buffer.Length = sizeof(ACPI_OBJECT); buffer.Pointer = &element; status = AcpiEvaluateObject(handle, pathname, arguments, &buffer); if (ACPI_FAILURE(status)) { acpi_util_eval_error(handle, pathname, status); return status; } if (element.Type != ACPI_TYPE_INTEGER) { acpi_util_eval_error(handle, pathname, AE_BAD_DATA); return AE_BAD_DATA; } *data = element.Integer.Value; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Return value [%llu]\n", *data)); return AE_OK; } void acpi_bus_data_handler(ACPI_HANDLE handle, void *context) { /* TBD */ return; } int acpi_bus_get_device(ACPI_HANDLE handle, struct acpi_device **device) { ACPI_STATUS status = AE_OK; if (!device) { return -EINVAL; }; /* TBD: Support fixed-feature devices */ status = AcpiGetData(handle, acpi_bus_data_handler, (void **)device); if (ACPI_FAILURE(status) || !*device) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No context for object [%p]\n", handle)); return -ENODEV; } return 0; } ACPI_STATUS acpi_bus_get_status_handle(ACPI_HANDLE handle, unsigned long long *sta) { ACPI_STATUS status; status = acpi_evaluate_integer(handle, "_STA", NULL, sta); if (ACPI_SUCCESS(status)) { return AE_OK; }; if (status == AE_NOT_FOUND) { *sta = ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_ENABLED | ACPI_STA_DEVICE_UI | ACPI_STA_DEVICE_FUNCTIONING; return AE_OK; } return status; } /* -------------------------------------------------------------------------- ACPI Bus operations -------------------------------------------------------------------------- */ int acpi_match_device_ids(struct acpi_device *device, const struct acpi_device_ids *ids) { const struct acpi_device_ids *id; struct acpi_hardware_id *hwid; /* * If the device is not present, it is unnecessary to load device * driver for it. */ // if (!device->status.present) // return -ENODEV; for (id = ids; id->id[0]; id++) list_for_each_entry(hwid, &device->pnp.ids, list) if (!strcmp((char *) id->id, hwid->id)) return 0; return -ENOENT; } static int acpi_device_register(struct acpi_device *device) { int result; struct acpi_device_bus_id *acpi_device_bus_id, *new_bus_id; int found = 0; /* * Linkage * ------- * Link this device to its parent and siblings. */ INIT_LIST_HEAD(&device->children); INIT_LIST_HEAD(&device->node); new_bus_id = kzalloc(sizeof(struct acpi_device_bus_id), GFP_KERNEL); if (!new_bus_id) { printk(KERN_ERR PREFIX "Memory allocation error\n"); return -ENOMEM; } mutex_lock(&acpi_device_lock); /* * Find suitable bus_id and instance number in acpi_bus_id_list * If failed, create one and link it into acpi_bus_id_list */ list_for_each_entry(acpi_device_bus_id, &acpi_bus_id_list, node) { if (!strcmp(acpi_device_bus_id->bus_id, acpi_device_hid(device))) { acpi_device_bus_id->instance_no++; found = 1; kfree(new_bus_id); break; } }; if (!found) { acpi_device_bus_id = new_bus_id; strcpy(acpi_device_bus_id->bus_id, acpi_device_hid(device)); acpi_device_bus_id->instance_no = 0; list_add_tail(&acpi_device_bus_id->node, &acpi_bus_id_list); } // dev_set_name(&device->dev, "%s:%02x", acpi_device_bus_id->bus_id, acpi_device_bus_id->instance_no); if (device->parent) list_add_tail(&device->node, &device->parent->children); mutex_unlock(&acpi_device_lock); // device->dev.bus = &acpi_bus_type; // device->dev.release = &acpi_device_release; // result = device_register(&device->dev); // if (result) { // dev_err(&device->dev, "Error registering device\n"); // goto end; // } // device->removal_type = ACPI_BUS_REMOVAL_NORMAL; return 0; end: mutex_lock(&acpi_device_lock); if (device->parent) list_del(&device->node); mutex_unlock(&acpi_device_lock); return result; } static struct acpi_device *acpi_bus_get_parent(ACPI_HANDLE handle) { ACPI_STATUS status; struct acpi_device *device; int ret; /* * Fixed hardware devices do not appear in the namespace and do not * have handles, but we fabricate acpi_devices for them, so we have * to deal with them specially. */ if (handle == NULL) return acpi_root; do { status = AcpiGetParent(handle, &handle); if (status == AE_NULL_ENTRY) return NULL; if (ACPI_FAILURE(status)) return acpi_root; ret = acpi_bus_get_device(handle, &device); if (ret == 0) return device; } while (1); } static int acpi_bus_get_flags(struct acpi_device *device) { ACPI_STATUS status = AE_OK; ACPI_HANDLE temp = NULL; /* Presence of _STA indicates 'dynamic_status' */ status = AcpiGetHandle(device->handle, "_STA", &temp); if (ACPI_SUCCESS(status)) device->flags.dynamic_status = 1; /* Presence of _RMV indicates 'removable' */ status = AcpiGetHandle(device->handle, "_RMV", &temp); if (ACPI_SUCCESS(status)) device->flags.removable = 1; /* Presence of _EJD|_EJ0 indicates 'ejectable' */ status = AcpiGetHandle(device->handle, "_EJD", &temp); if (ACPI_SUCCESS(status)) device->flags.ejectable = 1; else { status = AcpiGetHandle(device->handle, "_EJ0", &temp); if (ACPI_SUCCESS(status)) device->flags.ejectable = 1; } /* Presence of _LCK indicates 'lockable' */ status = AcpiGetHandle(device->handle, "_LCK", &temp); if (ACPI_SUCCESS(status)) device->flags.lockable = 1; /* Presence of _PS0|_PR0 indicates 'power manageable' */ status = AcpiGetHandle(device->handle, "_PS0", &temp); if (ACPI_FAILURE(status)) status = AcpiGetHandle(device->handle, "_PR0", &temp); if (ACPI_SUCCESS(status)) device->flags.power_manageable = 1; /* Presence of _PRW indicates wake capable */ status = AcpiGetHandle(device->handle, "_PRW", &temp); if (ACPI_SUCCESS(status)) device->flags.wake_capable = 1; /* TBD: Performance management */ return 0; } static void acpi_device_get_busid(struct acpi_device *device) { 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)? */ if (ACPI_IS_ROOT_DEVICE(device)) { strcpy(device->pnp.bus_id, "ACPI"); return; } switch (device->device_type) { 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: AcpiGetName(device->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; } } #define ACPI_VIDEO_OUTPUT_SWITCHING 0x0001 #define ACPI_VIDEO_DEVICE_POSTING 0x0002 #define ACPI_VIDEO_ROM_AVAILABLE 0x0004 #define ACPI_VIDEO_BACKLIGHT 0x0008 #define ACPI_VIDEO_BACKLIGHT_FORCE_VENDOR 0x0010 #define ACPI_VIDEO_BACKLIGHT_FORCE_VIDEO 0x0020 #define ACPI_VIDEO_OUTPUT_SWITCHING_FORCE_VENDOR 0x0040 #define ACPI_VIDEO_OUTPUT_SWITCHING_FORCE_VIDEO 0x0080 #define ACPI_VIDEO_BACKLIGHT_DMI_VENDOR 0x0100 #define ACPI_VIDEO_BACKLIGHT_DMI_VIDEO 0x0200 #define ACPI_VIDEO_OUTPUT_SWITCHING_DMI_VENDOR 0x0400 #define ACPI_VIDEO_OUTPUT_SWITCHING_DMI_VIDEO 0x0800 long acpi_is_video_device(struct acpi_device *device) { ACPI_HANDLE h_dummy; long video_caps = 0; if (!device) return 0; /* Is this device able to support video switching ? */ if (ACPI_SUCCESS(AcpiGetHandle(device->handle, "_DOD", &h_dummy)) || ACPI_SUCCESS(AcpiGetHandle(device->handle, "_DOS", &h_dummy))) video_caps |= ACPI_VIDEO_OUTPUT_SWITCHING; /* Is this device able to retrieve a video ROM ? */ if (ACPI_SUCCESS(AcpiGetHandle(device->handle, "_ROM", &h_dummy))) video_caps |= ACPI_VIDEO_ROM_AVAILABLE; /* Is this device able to configure which video head to be POSTed ? */ if (ACPI_SUCCESS(AcpiGetHandle(device->handle, "_VPO", &h_dummy)) && ACPI_SUCCESS(AcpiGetHandle(device->handle, "_GPD", &h_dummy)) && ACPI_SUCCESS(AcpiGetHandle(device->handle, "_SPD", &h_dummy))) video_caps |= ACPI_VIDEO_DEVICE_POSTING; return video_caps; } /* * acpi_bay_match - see if a device is an ejectable driver bay * * If an acpi object is ejectable and has one of the ACPI ATA methods defined, * then we can safely call it an ejectable drive bay */ static int acpi_bay_match(struct acpi_device *device){ ACPI_STATUS status; ACPI_HANDLE handle; ACPI_HANDLE tmp; ACPI_HANDLE phandle; handle = device->handle; status = AcpiGetHandle(handle, "_EJ0", &tmp); if (ACPI_FAILURE(status)) return -ENODEV; if ((ACPI_SUCCESS(AcpiGetHandle(handle, "_GTF", &tmp))) || (ACPI_SUCCESS(AcpiGetHandle(handle, "_GTM", &tmp))) || (ACPI_SUCCESS(AcpiGetHandle(handle, "_STM", &tmp))) || (ACPI_SUCCESS(AcpiGetHandle(handle, "_SDD", &tmp)))) return 0; if (AcpiGetParent(handle, &phandle)) return -ENODEV; if ((ACPI_SUCCESS(AcpiGetHandle(phandle, "_GTF", &tmp))) || (ACPI_SUCCESS(AcpiGetHandle(phandle, "_GTM", &tmp))) || (ACPI_SUCCESS(AcpiGetHandle(phandle, "_STM", &tmp))) || (ACPI_SUCCESS(AcpiGetHandle(phandle, "_SDD", &tmp)))) return 0; return -ENODEV; } /* * acpi_dock_match - see if a device has a _DCK method */ static int acpi_dock_match(struct acpi_device *device) { ACPI_HANDLE tmp; return AcpiGetHandle(device->handle, "_DCK", &tmp); } char *acpi_device_hid(struct acpi_device *device) { struct acpi_hardware_id *hid; hid = list_first_entry(&device->pnp.ids, struct acpi_hardware_id, list); return hid->id; } static void acpi_add_id(struct acpi_device *device, const char *dev_id) { struct acpi_hardware_id *id; id = kmalloc(sizeof(*id), GFP_KERNEL); if (!id) return; INIT_LIST_HEAD(&id->list); id->id = kmalloc(strlen(dev_id) + 1, GFP_KERNEL); if (!id->id) { kfree(id); return; } strcpy(id->id, dev_id); list_add_tail(&id->list, &device->pnp.ids); } static void acpi_device_set_id(struct acpi_device *device) { ACPI_STATUS status; ACPI_DEVICE_INFO *info; ACPI_DEVICE_ID_LIST *cid_list; int i; switch (device->device_type) { case ACPI_BUS_TYPE_DEVICE: if (ACPI_IS_ROOT_DEVICE(device)) { acpi_add_id(device, ACPI_SYSTEM_HID); break; } status = AcpiGetObjectInfo(device->handle, &info); if (ACPI_FAILURE(status)) { printk(KERN_ERR PREFIX "%s: Error reading device info\n", __func__); return; } if (info->Valid & ACPI_VALID_HID) acpi_add_id(device, info->HardwareId.String); if (info->Valid & ACPI_VALID_CID) { cid_list = &info->CompatibleIdList; for (i = 0; i < cid_list->Count; i++) acpi_add_id(device, cid_list->Ids[i].String); } if (info->Valid & ACPI_VALID_ADR) { device->pnp.bus_address = info->Address; device->flags.bus_address = 1; } kfree(info); /* * Some devices don't reliably have _HIDs & _CIDs, so add * synthetic HIDs to make sure drivers can find them. */ if (acpi_is_video_device(device)) acpi_add_id(device, ACPI_VIDEO_HID); else if (ACPI_SUCCESS(acpi_bay_match(device))) acpi_add_id(device, ACPI_BAY_HID); else if (ACPI_SUCCESS(acpi_dock_match(device))) acpi_add_id(device, ACPI_DOCK_HID); else if (!acpi_device_hid(device) && ACPI_IS_ROOT_DEVICE(device->parent)) { acpi_add_id(device, ACPI_BUS_HID); /* \_SB, LNXSYBUS */ strcpy(device->pnp.device_name, ACPI_BUS_DEVICE_NAME); strcpy(device->pnp.device_class, ACPI_BUS_CLASS); } break; case ACPI_BUS_TYPE_POWER: acpi_add_id(device, ACPI_POWER_HID); break; case ACPI_BUS_TYPE_PROCESSOR: acpi_add_id(device, ACPI_PROCESSOR_OBJECT_HID); break; case ACPI_BUS_TYPE_THERMAL: acpi_add_id(device, ACPI_THERMAL_HID); break; case ACPI_BUS_TYPE_POWER_BUTTON: acpi_add_id(device, ACPI_BUTTON_HID_POWERF); break; case ACPI_BUS_TYPE_SLEEP_BUTTON: acpi_add_id(device, ACPI_BUTTON_HID_SLEEPF); break; } /* * We build acpi_devices for some objects that don't have _HID or _CID, * e.g., PCI bridges and slots. Drivers can't bind to these objects, * but we do use them indirectly by traversing the acpi_device tree. * This generic ID isn't useful for driver binding, but it provides * the useful property that "every acpi_device has an ID." */ if (list_empty(&device->pnp.ids)) acpi_add_id(device, "device"); } static int acpi_device_set_context(struct acpi_device *device) { ACPI_STATUS status; /* * Context * ------- * Attach this 'struct acpi_device' to the ACPI object. This makes * resolutions from handle->device very efficient. Fixed hardware * devices have no handles, so we skip them. */ if (!device->handle) return 0; status = AcpiAttachData(device->handle, acpi_bus_data_handler, device); if (ACPI_SUCCESS(status)) return 0; printk(KERN_ERR PREFIX "Error attaching device data\n"); return -ENODEV; } static int acpi_add_single_object(struct acpi_device **child, ACPI_HANDLE handle, int type, unsigned long long sta, struct acpi_bus_ops *ops) { int result; struct acpi_device *device; ACPI_BUFFER buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ACPI_DEVICE_INFO *info = NULL; ACPI_DEVICE_ID_LIST *cid_list; int i; device = kzalloc(sizeof(struct acpi_device), GFP_KERNEL); if (!device) { printk(KERN_ERR PREFIX "Memory allocation error\n"); return -ENOMEM; } INIT_LIST_HEAD(&device->pnp.ids); device->device_type = type; device->handle = handle; device->parent = acpi_bus_get_parent(handle); device->bus_ops = *ops; /* workround for not call .start */ STRUCT_TO_INT(device->status) = sta; acpi_device_get_busid(device); /* * Flags * ----- * 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; /* * Initialize Device * ----------------- * TBD: Synch with Core's enumeration/initialization process. */ acpi_device_set_id(device); if ((result = acpi_device_set_context(device))) goto end; result = acpi_device_register(device); /* * Bind _ADR-Based Devices when hot add */ if (device->flags.bus_address) { if (device->parent && device->parent->ops.bind) device->parent->ops.bind(device); } end: AcpiGetName(handle, ACPI_FULL_PATHNAME, &buffer); dbgprintf(PREFIX "Adding [%s]", (char *)buffer.Pointer); kfree(buffer.Pointer); AcpiGetObjectInfo(handle, &info); if (info->Valid & ACPI_VALID_HID) dbgprintf (" HID: %s", info->HardwareId.String); if (info->Valid & ACPI_VALID_CID) { cid_list = &info->CompatibleIdList; for (i = 0; i < cid_list->Count; i++) dbgprintf(" CID: %s\n", cid_list->Ids[i].String); } dbgprintf("\n"); kfree(info); if (!result) { *child = device; }; return result; } #define ACPI_STA_DEFAULT (ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_ENABLED | \ ACPI_STA_DEVICE_UI | ACPI_STA_DEVICE_FUNCTIONING) static int acpi_bus_type_and_status(ACPI_HANDLE handle, int *type, unsigned long long *sta) { ACPI_STATUS status; ACPI_OBJECT_TYPE acpi_type; status = AcpiGetType(handle, &acpi_type); if (ACPI_FAILURE(status)) return -ENODEV; switch (acpi_type) { case ACPI_TYPE_ANY: /* for ACPI_ROOT_OBJECT */ case ACPI_TYPE_DEVICE: *type = ACPI_BUS_TYPE_DEVICE; status = acpi_bus_get_status_handle(handle, sta); if (ACPI_FAILURE(status)) return -ENODEV; break; case ACPI_TYPE_PROCESSOR: *type = ACPI_BUS_TYPE_PROCESSOR; status = acpi_bus_get_status_handle(handle, sta); if (ACPI_FAILURE(status)) return -ENODEV; break; case ACPI_TYPE_THERMAL: *type = ACPI_BUS_TYPE_THERMAL; *sta = ACPI_STA_DEFAULT; break; case ACPI_TYPE_POWER: *type = ACPI_BUS_TYPE_POWER; *sta = ACPI_STA_DEFAULT; break; default: return -ENODEV; } return 0; } static ACPI_STATUS acpi_bus_check_add(ACPI_HANDLE handle, u32 lvl, void *context, void **return_value) { struct acpi_bus_ops *ops = context; int type; unsigned long long sta; struct acpi_device *device; ACPI_STATUS status; int result; result = acpi_bus_type_and_status(handle, &type, &sta); if (result) return AE_OK; if (!(sta & ACPI_STA_DEVICE_PRESENT) && !(sta & ACPI_STA_DEVICE_FUNCTIONING)) return AE_CTRL_DEPTH; /* * We may already have an acpi_device from a previous enumeration. If * so, we needn't add it again, but we may still have to start it. */ device = NULL; acpi_bus_get_device(handle, &device); if (ops->acpi_op_add && !device) acpi_add_single_object(&device, handle, type, sta, ops); if (!device) return AE_CTRL_DEPTH; /* if (ops->acpi_op_start && !(ops->acpi_op_add)) { status = acpi_start_single_object(device); if (ACPI_FAILURE(status)) return AE_CTRL_DEPTH; } */ if (!*return_value) *return_value = device; return AE_OK; } static int acpi_bus_scan(ACPI_HANDLE handle, struct acpi_bus_ops *ops, struct acpi_device **child) { ACPI_STATUS status; void *device = NULL; status = acpi_bus_check_add(handle, 0, ops, &device); if (ACPI_SUCCESS(status)) AcpiWalkNamespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX, acpi_bus_check_add, NULL, ops, &device); if (child) *child = device; if (device) return 0; else return -ENODEV; } int acpi_scan() { int err; struct acpi_bus_ops ops; memset(&ops, 0, sizeof(ops)); ops.acpi_op_add = 1; ops.acpi_op_start = 1; err = acpi_bus_scan(ACPI_ROOT_OBJECT, &ops, &acpi_root); return err; };