1
1
kolibrios-fun/drivers/devman/scan.c
Sergey Semyonov (Serge) c0a2886159 devman: create device list
git-svn-id: svn://kolibrios.org@1867 a494cfbc-eb01-0410-851d-a64ba20cac60
2011-02-15 17:45:12 +00:00

788 lines
22 KiB
C

#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;
};