Commit 40c05507 authored by Andy Grover's avatar Andy Grover Committed by Linus Torvalds

[PATCH] ACPI patch 2/9

This patch adds in the new drivers.

- Support for driverfs
- File/code layout more in the Linux style
- improvements to battery, processor, and thermal support
parent 62c7aea9
/*
* acpi_ac.c - ACPI AC Adapter Driver ($Revision: 22 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_AC_COMPONENT
ACPI_MODULE_NAME ("acpi_ac")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_AC_DRIVER_NAME);
MODULE_LICENSE("GPL");
#define PREFIX "ACPI: "
int acpi_ac_add (struct acpi_device *device);
int acpi_ac_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_ac_driver = {
name: ACPI_AC_DRIVER_NAME,
class: ACPI_AC_CLASS,
ids: ACPI_AC_HID,
ops: {
add: acpi_ac_add,
remove: acpi_ac_remove,
},
};
struct acpi_ac {
acpi_handle handle;
unsigned long state;
};
/* --------------------------------------------------------------------------
AC Adapter Management
-------------------------------------------------------------------------- */
static int
acpi_ac_get_state (
struct acpi_ac *ac)
{
acpi_status status = AE_OK;
ACPI_FUNCTION_TRACE("acpi_ac_get_state");
if (!ac)
return_VALUE(-EINVAL);
status = acpi_evaluate_integer(ac->handle, "_PSR", NULL, &ac->state);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error reading AC Adapter state\n"));
ac->state = ACPI_AC_STATUS_UNKNOWN;
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_ac_dir = NULL;
static int
acpi_ac_read_state (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_ac *ac = (struct acpi_ac *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_ac_read_state");
if (!ac || (off != 0))
goto end;
if (0 != acpi_ac_get_state(ac)) {
p += sprintf(p, "ERROR: Unable to read AC Adapter state\n");
goto end;
}
p += sprintf(p, "state: ");
switch (ac->state) {
case ACPI_AC_STATUS_OFFLINE:
p += sprintf(p, "off-line\n");
break;
case ACPI_AC_STATUS_ONLINE:
p += sprintf(p, "on-line\n");
break;
default:
p += sprintf(p, "unknown\n");
break;
}
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_ac_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_ac_add_fs");
if (!acpi_ac_dir) {
acpi_ac_dir = proc_mkdir(ACPI_AC_CLASS, acpi_root_dir);
if (!acpi_ac_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_ac_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
/* 'state' [R] */
entry = create_proc_entry(ACPI_AC_FILE_STATE,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_AC_FILE_STATE));
else {
entry->read_proc = acpi_ac_read_state;
entry->data = acpi_driver_data(device);
}
return_VALUE(0);
}
static int
acpi_ac_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_ac_remove_fs");
if (!acpi_ac_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_ac_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Model
-------------------------------------------------------------------------- */
void
acpi_ac_notify (
acpi_handle handle,
u32 event,
void *data)
{
struct acpi_ac *ac = (struct acpi_ac *) data;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_ac_notify");
if (!ac)
return;
if (0 != acpi_bus_get_device(ac->handle, &device))
return_VOID;
switch (event) {
case ACPI_AC_NOTIFY_STATUS:
acpi_ac_get_state(ac);
acpi_bus_generate_event(device, event, (u32) ac->state);
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Unsupported event [0x%x]\n", event));
break;
}
return_VOID;
}
int
acpi_ac_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_ac *ac = NULL;
ACPI_FUNCTION_TRACE("acpi_ac_add");
if (!device)
return_VALUE(-EINVAL);
ac = kmalloc(sizeof(struct acpi_ac), GFP_KERNEL);
if (!ac)
return_VALUE(-ENOMEM);
memset(ac, 0, sizeof(struct acpi_ac));
ac->handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_AC_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_AC_CLASS);
acpi_driver_data(device) = ac;
result = acpi_ac_get_state(ac);
if (0 != result)
goto end;
result = acpi_ac_add_fs(device);
if (0 != result)
goto end;
status = acpi_install_notify_handler(ac->handle,
ACPI_DEVICE_NOTIFY, acpi_ac_notify, ac);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error installing notify handler\n"));
result = -ENODEV;
goto end;
}
printk(KERN_INFO PREFIX "%s [%s] (%s)\n",
acpi_device_name(device), acpi_device_bid(device),
ac->state?"on-line":"off-line");
end:
if (0 != result) {
acpi_ac_remove_fs(device);
kfree(ac);
}
return_VALUE(result);
}
int
acpi_ac_remove (
struct acpi_device *device,
int type)
{
acpi_status status = AE_OK;
struct acpi_ac *ac = NULL;
ACPI_FUNCTION_TRACE("acpi_ac_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
ac = (struct acpi_ac *) acpi_driver_data(device);
status = acpi_remove_notify_handler(ac->handle,
ACPI_DEVICE_NOTIFY, acpi_ac_notify);
if (ACPI_FAILURE(status))
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error removing notify handler\n"));
acpi_ac_remove_fs(device);
kfree(ac);
return_VALUE(0);
}
int __init
acpi_ac_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_ac_init");
result = acpi_bus_register_driver(&acpi_ac_driver);
if (0 > result) {
remove_proc_entry(ACPI_AC_CLASS, acpi_root_dir);
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
void __exit
acpi_ac_exit (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_ac_exit");
result = acpi_bus_unregister_driver(&acpi_ac_driver);
if (0 == result)
remove_proc_entry(ACPI_AC_CLASS, acpi_root_dir);
return_VOID;
}
module_init(acpi_ac_init);
module_exit(acpi_ac_exit);
/*
* acpi_battery.c - ACPI Battery Driver ($Revision: 31 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_BATTERY_COMPONENT
ACPI_MODULE_NAME ("acpi_battery")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_BATTERY_DRIVER_NAME);
MODULE_LICENSE("GPL");
#define PREFIX "ACPI: "
#define ACPI_BATTERY_VALUE_UNKNOWN 0xFFFFFFFF
#define ACPI_BATTERY_FORMAT_BIF "NNNNNNNNNSSSS"
#define ACPI_BATTERY_FORMAT_BST "NNNN"
static int acpi_battery_add (struct acpi_device *device);
static int acpi_battery_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_battery_driver = {
name: ACPI_BATTERY_DRIVER_NAME,
class: ACPI_BATTERY_CLASS,
ids: ACPI_BATTERY_HID,
ops: {
add: acpi_battery_add,
remove: acpi_battery_remove,
},
};
struct acpi_battery_status {
acpi_integer state;
acpi_integer present_rate;
acpi_integer remaining_capacity;
acpi_integer present_voltage;
};
struct acpi_battery_info {
acpi_integer power_unit;
acpi_integer design_capacity;
acpi_integer last_full_capacity;
acpi_integer battery_technology;
acpi_integer design_voltage;
acpi_integer design_capacity_warning;
acpi_integer design_capacity_low;
acpi_integer battery_capacity_granularity_1;
acpi_integer battery_capacity_granularity_2;
acpi_string model_number;
acpi_string serial_number;
acpi_string battery_type;
acpi_string oem_info;
};
struct acpi_battery_flags {
u8 present:1; /* Bay occupied? */
u8 power_unit:1; /* 0=watts, 1=apms */
u8 alarm:1; /* _BTP present? */
u8 reserved:5;
};
struct acpi_battery_trips {
unsigned long warning;
unsigned long low;
};
struct acpi_battery {
acpi_handle handle;
struct acpi_battery_flags flags;
struct acpi_battery_trips trips;
unsigned long alarm;
struct acpi_battery_info *info;
};
/* --------------------------------------------------------------------------
Battery Management
-------------------------------------------------------------------------- */
static int
acpi_battery_get_info (
struct acpi_battery *battery,
struct acpi_battery_info **bif)
{
acpi_status status = 0;
acpi_buffer buffer = {0, NULL};
acpi_buffer format = {sizeof(ACPI_BATTERY_FORMAT_BIF),
ACPI_BATTERY_FORMAT_BIF};
acpi_buffer data = {0, NULL};
acpi_object *package = NULL;
ACPI_FUNCTION_TRACE("acpi_battery_get_info");
if (!battery || !bif)
return_VALUE(-EINVAL);
/* Evalute _BIF */
status = acpi_evaluate(battery->handle, "_BIF", NULL, &buffer);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
package = (acpi_object *) buffer.pointer;
/* Extract Package Data */
status = acpi_extract_package(package, &format, &data);
if (status != AE_BUFFER_OVERFLOW) {
kfree(buffer.pointer);
return_VALUE(-ENODEV);
}
data.pointer = kmalloc(data.length, GFP_KERNEL);
if (!data.pointer) {
kfree(buffer.pointer);
return_VALUE(-ENOMEM);
}
memset(data.pointer, 0, data.length);
status = acpi_extract_package(package, &format, &data);
if (ACPI_FAILURE(status)) {
kfree(buffer.pointer);
kfree(data.pointer);
return_VALUE(-ENODEV);
}
kfree(buffer.pointer);
(*bif) = data.pointer;
return_VALUE(0);
}
static int
acpi_battery_get_status (
struct acpi_battery *battery,
struct acpi_battery_status **bst)
{
acpi_status status = 0;
acpi_buffer buffer = {0, NULL};
acpi_buffer format = {sizeof(ACPI_BATTERY_FORMAT_BST),
ACPI_BATTERY_FORMAT_BST};
acpi_buffer data = {0, NULL};
acpi_object *package = NULL;
ACPI_FUNCTION_TRACE("acpi_battery_get_status");
if (!battery || !bst)
return_VALUE(-EINVAL);
/* Evalute _BST */
status = acpi_evaluate(battery->handle, "_BST", NULL, &buffer);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
package = (acpi_object *) buffer.pointer;
/* Extract Package Data */
status = acpi_extract_package(package, &format, &data);
if (status != AE_BUFFER_OVERFLOW) {
kfree(buffer.pointer);
return_VALUE(-ENODEV);
}
data.pointer = kmalloc(data.length, GFP_KERNEL);
if (!data.pointer) {
kfree(buffer.pointer);
return_VALUE(-ENOMEM);
}
memset(data.pointer, 0, data.length);
status = acpi_extract_package(package, &format, &data);
if (ACPI_FAILURE(status)) {
kfree(buffer.pointer);
kfree(data.pointer);
return_VALUE(-ENODEV);
}
kfree(buffer.pointer);
(*bst) = data.pointer;
return_VALUE(0);
}
static int
acpi_battery_set_alarm (
struct acpi_battery *battery,
unsigned long alarm)
{
acpi_status status = 0;
acpi_object arg0 = {ACPI_TYPE_INTEGER};
acpi_object_list arg_list = {1, &arg0};
ACPI_FUNCTION_TRACE("acpi_battery_set_alarm");
if (!battery)
return_VALUE(-EINVAL);
if (!battery->flags.alarm)
return_VALUE(-ENODEV);
arg0.integer.value = alarm;
status = acpi_evaluate(battery->handle, "_BTP", &arg_list, NULL);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Alarm set to %d\n", (u32) alarm));
battery->alarm = alarm;
return_VALUE(0);
}
static int
acpi_battery_check (
struct acpi_battery *battery)
{
int result = 0;
acpi_status status = AE_OK;
acpi_handle handle = NULL;
struct acpi_device *device = NULL;
struct acpi_battery_info *bif = NULL;
ACPI_FUNCTION_TRACE("acpi_battery_check");
if (!battery)
return_VALUE(-EINVAL);
result = acpi_bus_get_device(battery->handle, &device);
if (0 != result)
return_VALUE(result);
result = acpi_bus_get_status(device);
if (0 != result)
return_VALUE(result);
/* Insertion? */
if (!battery->flags.present && device->status.battery_present) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Battery inserted\n"));
/* Evalute _BIF to get certain static information */
result = acpi_battery_get_info(battery, &bif);
if (0 != result)
return_VALUE(result);
battery->flags.power_unit = bif->power_unit;
battery->trips.warning = bif->design_capacity_warning;
battery->trips.low = bif->design_capacity_low;
kfree(bif);
/* See if alarms are supported, and if so, set default */
status = acpi_get_handle(battery->handle, "_BTP", &handle);
if (ACPI_SUCCESS(status)) {
battery->flags.alarm = 1;
acpi_battery_set_alarm(battery, battery->trips.warning);
}
}
/* Removal? */
else if (battery->flags.present && !device->status.battery_present) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Battery removed\n"));
}
battery->flags.present = device->status.battery_present;
return_VALUE(result);
}
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_battery_dir = NULL;
static int
acpi_battery_read_info (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
int result = 0;
struct acpi_battery *battery = (struct acpi_battery *) data;
struct acpi_battery_info *bif = 0;
char *units = "?";
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_battery_read_info");
if (!battery)
goto end;
if (battery->flags.present)
p += sprintf(p, "present: yes\n");
else {
p += sprintf(p, "present: no\n");
goto end;
}
/* Battery Info (_BIF) */
result = acpi_battery_get_info(battery, &bif);
if ((0 != result) || !bif) {
p += sprintf(p, "ERROR: Unable to read battery information\n");
goto end;
}
units = bif->power_unit ? ACPI_BATTERY_UNITS_AMPS : ACPI_BATTERY_UNITS_WATTS;
if (bif->design_capacity == ACPI_BATTERY_VALUE_UNKNOWN)
p += sprintf(p, "design capacity: unknown\n");
else
p += sprintf(p, "design capacity: %d %sh\n",
(u32) bif->design_capacity, units);
if (bif->last_full_capacity == ACPI_BATTERY_VALUE_UNKNOWN)
p += sprintf(p, "last full capacity: unknown\n");
else
p += sprintf(p, "last full capacity: %d %sh\n",
(u32) bif->last_full_capacity, units);
switch ((u32) bif->battery_technology) {
case 0:
p += sprintf(p, "battery technology: non-rechargeable\n");
break;
case 1:
p += sprintf(p, "battery technology: rechargeable\n");
break;
default:
p += sprintf(p, "battery technology: unknown\n");
break;
}
if (bif->design_voltage == ACPI_BATTERY_VALUE_UNKNOWN)
p += sprintf(p, "design voltage: unknown\n");
else
p += sprintf(p, "design voltage: %d mV\n",
(u32) bif->design_voltage);
p += sprintf(p, "design capacity warning: %d %sh\n",
(u32) bif->design_capacity_warning, units);
p += sprintf(p, "design capacity low: %d %sh\n",
(u32) bif->design_capacity_low, units);
p += sprintf(p, "capacity granularity 1: %d %sh\n",
(u32) bif->battery_capacity_granularity_1, units);
p += sprintf(p, "capacity granularity 2: %d %sh\n",
(u32) bif->battery_capacity_granularity_2, units);
p += sprintf(p, "model number: %s\n",
bif->model_number);
p += sprintf(p, "serial number: %s\n",
bif->serial_number);
p += sprintf(p, "battery type: %s\n",
bif->battery_type);
p += sprintf(p, "OEM info: %s\n",
bif->oem_info);
end:
if (bif)
kfree(bif);
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_battery_read_state (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
int result = 0;
struct acpi_battery *battery = (struct acpi_battery *) data;
struct acpi_battery_status *bst = NULL;
char *units = "?";
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_battery_read_state");
if (!battery)
goto end;
if (battery->flags.present)
p += sprintf(p, "present: yes\n");
else {
p += sprintf(p, "present: no\n");
goto end;
}
/* Battery Units */
units = battery->flags.power_unit ? ACPI_BATTERY_UNITS_AMPS : ACPI_BATTERY_UNITS_WATTS;
/* Battery Status (_BST) */
result = acpi_battery_get_status(battery, &bst);
if ((0 != result) || !bst) {
p += sprintf(p, "ERROR: Unable to read battery status\n");
goto end;
}
if (!(bst->state & 0x04))
p += sprintf(p, "capacity state: ok\n");
else
p += sprintf(p, "capacity state: critical\n");
if ((bst->state & 0x01) && (bst->state & 0x02))
p += sprintf(p, "charging state: charging/discharging\n");
else if (bst->state & 0x01)
p += sprintf(p, "charging state: discharging\n");
else if (bst->state & 0x02)
p += sprintf(p, "charging state: charging\n");
else
p += sprintf(p, "charging state: unknown\n");
if (bst->present_rate == ACPI_BATTERY_VALUE_UNKNOWN)
p += sprintf(p, "present rate: unknown\n");
else
p += sprintf(p, "present rate: %d %s\n",
(u32) bst->present_rate, units);
if (bst->remaining_capacity == ACPI_BATTERY_VALUE_UNKNOWN)
p += sprintf(p, "remaining capacity: unknown\n");
else
p += sprintf(p, "remaining capacity: %d %sh\n",
(u32) bst->remaining_capacity, units);
if (bst->present_voltage == ACPI_BATTERY_VALUE_UNKNOWN)
p += sprintf(p, "present voltage: unknown\n");
else
p += sprintf(p, "present voltage: %d mV\n",
(u32) bst->present_voltage);
end:
if (bst)
kfree(bst);
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_battery_read_alarm (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_battery *battery = (struct acpi_battery *) data;
char *units = "?";
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_battery_read_alarm");
if (!battery)
goto end;
if (!battery->flags.present) {
p += sprintf(p, "present: no\n");
goto end;
}
/* Battery Units */
units = battery->flags.power_unit ? ACPI_BATTERY_UNITS_AMPS : ACPI_BATTERY_UNITS_WATTS;
/* Battery Alarm */
p += sprintf(p, "alarm: ");
if (!battery->alarm)
p += sprintf(p, "unsupported\n");
else
p += sprintf(p, "%d %sh\n", (u32) battery->alarm, units);
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_battery_write_alarm (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
struct acpi_battery *battery = (struct acpi_battery *) data;
char alarm_string[12] = {'\0'};
ACPI_FUNCTION_TRACE("acpi_battery_write_alarm");
if (!battery || (count > sizeof(alarm_string) - 1))
return_VALUE(-EINVAL);
if (!battery->flags.present)
return_VALUE(-ENODEV);
if (copy_from_user(alarm_string, buffer, count))
return_VALUE(-EFAULT);
alarm_string[count] = '\0';
result = acpi_battery_set_alarm(battery,
simple_strtoul(alarm_string, NULL, 0));
if (0 != result)
return_VALUE(result);
return_VALUE(count);
}
static int
acpi_battery_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_battery_add_fs");
if (!acpi_battery_dir) {
acpi_battery_dir = proc_mkdir(ACPI_BATTERY_CLASS, acpi_root_dir);
if (!acpi_battery_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_battery_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
/* 'info' [R] */
entry = create_proc_entry(ACPI_BATTERY_FILE_INFO,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_BATTERY_FILE_INFO));
else {
entry->read_proc = acpi_battery_read_info;
entry->data = acpi_driver_data(device);
}
/* 'status' [R] */
entry = create_proc_entry(ACPI_BATTERY_FILE_STATUS,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_BATTERY_FILE_STATUS));
else {
entry->read_proc = acpi_battery_read_state;
entry->data = acpi_driver_data(device);
}
/* 'alarm' [R/W] */
entry = create_proc_entry(ACPI_BATTERY_FILE_ALARM,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_BATTERY_FILE_ALARM));
else {
entry->read_proc = acpi_battery_read_alarm;
entry->write_proc = acpi_battery_write_alarm;
entry->data = acpi_driver_data(device);
}
return_VALUE(0);
}
static int
acpi_battery_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_battery_remove_fs");
if (!acpi_battery_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_battery_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
static void
acpi_battery_notify (
acpi_handle handle,
u32 event,
void *data)
{
struct acpi_battery *battery = (struct acpi_battery *) data;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_battery_notify");
if (!battery)
return_VOID;
if (0 != acpi_bus_get_device(handle, &device))
return_VOID;
switch (event) {
case ACPI_BATTERY_NOTIFY_STATUS:
case ACPI_BATTERY_NOTIFY_INFO:
acpi_battery_check(battery);
acpi_bus_generate_event(device, event, battery->flags.present);
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Unsupported event [0x%x]\n", event));
break;
}
return_VOID;
}
static int
acpi_battery_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = 0;
struct acpi_battery *battery = NULL;
ACPI_FUNCTION_TRACE("acpi_battery_add");
if (!device)
return_VALUE(-EINVAL);
battery = kmalloc(sizeof(struct acpi_battery), GFP_KERNEL);
if (!battery)
return_VALUE(-ENOMEM);
memset(battery, 0, sizeof(struct acpi_battery));
battery->handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_BATTERY_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_BATTERY_CLASS);
acpi_driver_data(device) = battery;
result = acpi_battery_check(battery);
if (0 != result)
goto end;
result = acpi_battery_add_fs(device);
if (0 != result)
goto end;
status = acpi_install_notify_handler(battery->handle,
ACPI_DEVICE_NOTIFY, acpi_battery_notify, battery);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error installing notify handler\n"));
result = -ENODEV;
goto end;
}
printk(KERN_INFO PREFIX "%s Slot [%s] (battery %s)\n",
ACPI_BATTERY_DEVICE_NAME, acpi_device_bid(device),
device->status.battery_present?"present":"absent");
end:
if (0 != result) {
acpi_battery_remove_fs(device);
kfree(battery);
}
return_VALUE(result);
}
static int
acpi_battery_remove (
struct acpi_device *device,
int type)
{
acpi_status status = 0;
struct acpi_battery *battery = NULL;
ACPI_FUNCTION_TRACE("acpi_battery_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
battery = (struct acpi_battery *) acpi_driver_data(device);
status = acpi_remove_notify_handler(battery->handle,
ACPI_DEVICE_NOTIFY, acpi_battery_notify);
if (ACPI_FAILURE(status))
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error removing notify handler\n"));
acpi_battery_remove_fs(device);
kfree(battery);
return_VALUE(0);
}
static int __init
acpi_battery_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_battery_init");
result = acpi_bus_register_driver(&acpi_battery_driver);
if (0 > result) {
remove_proc_entry(ACPI_BATTERY_CLASS, acpi_root_dir);
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
static void __exit
acpi_battery_exit (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_battery_exit");
result = acpi_bus_unregister_driver(&acpi_battery_driver);
if (0 == result)
remove_proc_entry(ACPI_BATTERY_CLASS, acpi_root_dir);
return_VOID;
}
module_init(acpi_battery_init);
module_exit(acpi_battery_exit);
/*
* acpi_bus.c - ACPI Bus Driver ($Revision: 56 $)
*
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/ioport.h>
#include <linux/list.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/proc_fs.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#include "include/acinterp.h" /* for acpi_ex_eisa_id_to_string() */
#define _COMPONENT ACPI_BUS_COMPONENT
ACPI_MODULE_NAME ("acpi_bus")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_BUS_DRIVER_NAME);
MODULE_LICENSE("GPL");
#define PREFIX "ACPI: "
FADT_DESCRIPTOR acpi_fadt;
static u8 acpi_disabled = 0;
struct acpi_device *acpi_root = NULL;
struct proc_dir_entry *acpi_root_dir = NULL;
#define STRUCT_TO_INT(s) (*((int*)&s))
/*
* POLICY: If *anything* doesn't work, put it on the blacklist.
* If they are critical errors, mark it critical, and abort driver load.
*/
static struct acpi_blacklist_item acpi_blacklist[] __initdata =
{
/* Portege 7020, BIOS 8.10 */
{"TOSHIB", "7020CT ", 0x19991112, ACPI_TABLE_DSDT, all_versions, "Implicit Return", 0},
/* Portege 4030 */
{"TOSHIB", "4030 ", 0x19991112, ACPI_TABLE_DSDT, all_versions, "Implicit Return", 0},
/* Portege 310/320, BIOS 7.1 */
{"TOSHIB", "310 ", 0x19990511, ACPI_TABLE_DSDT, all_versions, "Implicit Return", 0},
/* Seattle 2, old bios rev. */
{"INTEL ", "440BX ", 0x00001000, ACPI_TABLE_DSDT, less_than_or_equal, "Field beyond end of region", 0},
/* ASUS K7M */
{"ASUS ", "K7M ", 0x00001000, ACPI_TABLE_DSDT, less_than_or_equal, "Field beyond end of region", 0},
/* Intel 810 Motherboard? */
{"MNTRAL", "MO81010A", 0x00000012, ACPI_TABLE_DSDT, less_than_or_equal, "Field beyond end of region", 0},
/* Compaq Presario 1700 */
{"PTLTD ", " DSDT ", 0x06040000, ACPI_TABLE_DSDT, less_than_or_equal, "Multiple problems", 1},
/* Sony FX120, FX140, FX150? */
{"SONY ", "U0 ", 0x20010313, ACPI_TABLE_DSDT, less_than_or_equal, "ACPI driver problem", 1},
/* Compaq Presario 800, Insyde BIOS */
{"INT440", "SYSFexxx", 0x00001001, ACPI_TABLE_DSDT, less_than_or_equal, "Does not use _REG to protect EC OpRegions", 1},
{""}
};
/* --------------------------------------------------------------------------
Linux Driver Model (LDM) Support
-------------------------------------------------------------------------- */
#ifdef CONFIG_LDM
static int acpi_device_probe(struct device *dev);
static int acpi_device_remove(struct device *dev, u32 flags);
static int acpi_device_suspend(struct device *dev, u32 state, u32 stage);
static int acpi_device_resume(struct device *dev, u32 stage);
static struct device_driver acpi_bus_driver = {
probe: acpi_device_probe,
remove: acpi_device_remove,
suspend: acpi_device_suspend,
resume: acpi_device_resume,
};
static int
acpi_device_probe (
struct device *dev)
{
ACPI_FUNCTION_TRACE("acpi_device_probe");
if (!dev)
return_VALUE(-EINVAL);
/* TBD */
return_VALUE(0);
}
static int
acpi_device_remove (
struct device *dev,
u32 flags)
{
ACPI_FUNCTION_TRACE("acpi_device_remove");
if (!dev)
return_VALUE(-EINVAL);
/* TBD */
return_VALUE(0);
}
static int
acpi_device_suspend (
struct device *dev,
u32 state,
u32 stage)
{
ACPI_FUNCTION_TRACE("acpi_device_suspend");
if (!dev)
return_VALUE(-EINVAL);
/* TBD */
return_VALUE(0);
}
static int
acpi_device_resume (
struct device *dev,
u32 stage)
{
ACPI_FUNCTION_TRACE("acpi_device_resume");
if (!dev)
return_VALUE(-EINVAL);
/* TBD */
return_VALUE(0);
}
#if 0 /* not used ATM */
static int
acpi_platform_add (
struct device *dev)
{
ACPI_FUNCTION_TRACE("acpi_platform_add");
if (!dev)
return -EINVAL;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device %s (%s) added\n",
dev->name, dev->bus_id));
/* TBD */
return_VALUE(0);
}
static int
acpi_platform_remove (
struct device *dev)
{
ACPI_FUNCTION_TRACE("acpi_platform_add");
if (!dev)
return -EINVAL;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device %s (%s) removed\n",
dev->name, dev->bus_id));
/* TBD */
return_VALUE(0);
}
#endif /* unused */
#endif /*CONFIG_LDM*/
static int
acpi_device_register (
struct acpi_device *device,
struct acpi_device *parent)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_device_register");
if (!device)
return_VALUE(-EINVAL);
#ifdef CONFIG_LDM
sprintf(device->dev.name, "ACPI device %s:%s",
device->pnp.hardware_id, device->pnp.unique_id);
strncpy(device->dev.bus_id, device->pnp.bus_id, sizeof(acpi_bus_id));
if (parent)
device->dev.parent = &parent->dev;
device->dev.driver = &acpi_bus_driver;
result = device_register(&device->dev);
#endif /*CONFIG_LDM*/
return_VALUE(result);
}
static int
acpi_device_unregister (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_device_unregister");
if (!device)
return_VALUE(-EINVAL);
#ifdef CONFIG_LDM
put_device(&device->dev);
#endif /*CONFIG_LDM*/
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Device Management
-------------------------------------------------------------------------- */
static void
acpi_bus_data_handler (
acpi_handle handle,
u32 function,
void *context)
{
ACPI_FUNCTION_TRACE("acpi_bus_data_handler");
/* TBD */
return_VOID;
}
static void
acpi_bus_data_handler_powerf (
acpi_handle handle,
u32 function,
void *context)
{
ACPI_FUNCTION_TRACE("acpi_bus_data_handler_powerf");
/* TBD */
return_VOID;
}
static void
acpi_bus_data_handler_sleepf (
acpi_handle handle,
u32 function,
void *context)
{
ACPI_FUNCTION_TRACE("acpi_bus_data_handler_sleepf");
/* TBD */
return_VOID;
}
int
acpi_bus_get_device (
acpi_handle handle,
struct acpi_device **device)
{
acpi_status status = AE_OK;
ACPI_FUNCTION_TRACE("acpi_bus_get_device");
if (!device)
return_VALUE(-EINVAL);
/* TBD: Support fixed-feature devices */
status = acpi_get_data(handle, acpi_bus_data_handler, (void**) device);
if (ACPI_FAILURE(status) || !*device) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error getting context for object [%p]\n",
handle));
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
int
acpi_bus_get_status (
struct acpi_device *device)
{
acpi_status status = AE_OK;
unsigned long sta = 0;
ACPI_FUNCTION_TRACE("acpi_bus_get_status");
if (!device)
return_VALUE(-EINVAL);
/*
* Evaluate _STA if present.
*/
if (device->flags.dynamic_status) {
status = acpi_evaluate_integer(device->handle, "_STA", NULL, &sta);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
STRUCT_TO_INT(device->status) = (int) sta;
}
/*
* Otherwise we assume the status of our parent (unless we don't
* have one, in which case status is implied).
*/
else if (device->parent)
device->status = device->parent->status;
else
STRUCT_TO_INT(device->status) = 0x0F;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] status [%08x]\n",
device->pnp.bus_id, (u32) STRUCT_TO_INT(device->status)));
return_VALUE(0);
}
/*
static int
acpi_bus_create_device_fs (struct device *device)
{
ACPI_FUNCTION_TRACE("acpi_bus_create_device_fs");
if (!device)
return_VALUE(-EINVAL);
if (device->dir.entry)
return_VALUE(-EEXIST);
if (!device->parent)
device->dir.entry = proc_mkdir(device->pnp.bus_id, NULL);
else
device->dir.entry = proc_mkdir(device->pnp.bus_id,
device->parent->fs.entry);
if (!device->dir.entry) {
printk(KERN_ERR PREFIX "Unable to create fs entry '%s'\n",
device->pnp.bus_id);
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
static int
acpi_bus_remove_device_fs (struct device *device)
{
ACPI_FUNCTION_TRACE("acpi_bus_create_device_fs");
if (!device)
return_VALUE(-EINVAL);
if (!device->dir.entry)
return_VALUE(-ENODEV);
if (!device->parent)
remove_proc_entry(device->pnp_bus_id, NULL);
else
remove_proc_entry(device->pnp.bus_id, device->parent->fs.entry);
device->dir.entry = NULL;
return_VALUE(0);
}
*/
/* --------------------------------------------------------------------------
Power Management
-------------------------------------------------------------------------- */
int
acpi_bus_get_power (
acpi_handle handle,
int *state)
{
int result = 0;
acpi_status status = 0;
struct acpi_device *device = NULL;
unsigned long psc = 0;
ACPI_FUNCTION_TRACE("acpi_bus_get_power");
result = acpi_bus_get_device(handle, &device);
if (0 != result)
return_VALUE(result);
*state = ACPI_STATE_UNKNOWN;
if (!device->flags.power_manageable) {
/* TBD: Non-recursive algorithm for walking up hierarchy */
if (device->parent)
*state = device->parent->power.state;
else
*state = ACPI_STATE_D0;
}
else {
/*
* Get the device's power state either directly (via _PSC) or
* indirectly (via power resources).
*/
if (device->power.flags.explicit_get) {
status = acpi_evaluate_integer(device->handle, "_PSC",
NULL, &psc);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
device->power.state = (int) psc;
}
else if (device->power.flags.power_resources) {
result = acpi_power_get_inferred_state(device);
if (0 != result)
return_VALUE(result);
}
*state = device->power.state;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] power state is D%d\n",
device->pnp.bus_id, device->power.state));
return_VALUE(0);
}
int
acpi_bus_set_power (
acpi_handle handle,
int state)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_device *device = NULL;
char object_name[5] = {'_','P','S','0'+state,'\0'};
ACPI_FUNCTION_TRACE("acpi_bus_set_power");
result = acpi_bus_get_device(handle, &device);
if (0 != result)
return_VALUE(result);
if ((state < ACPI_STATE_D0) || (state > ACPI_STATE_D3))
return_VALUE(-EINVAL);
/* Make sure this is a valid target state */
if (!device->flags.power_manageable) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Device is not power manageable\n"));
return_VALUE(-ENODEV);
}
if (state == device->power.state) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device is already at D%d\n", state));
return_VALUE(0);
}
if (!device->power.states[state].flags.valid) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Device does not support D%d\n", state));
return_VALUE(-ENODEV);
}
if (device->parent && (state < device->parent->power.state)) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Cannot set device to a higher-powered state than parent\n"));
return_VALUE(-ENODEV);
}
/*
* Transition Power
* ----------------
* On transitions to a high-powered state we first apply power (via
* power resources) then evalute _PSx. Conversly for transitions to
* a lower-powered state.
*/
if (state < device->power.state) {
if (device->power.flags.power_resources) {
result = acpi_power_transition(device, state);
if (0 != result)
goto end;
}
if (device->power.states[state].flags.explicit_set) {
status = acpi_evaluate_object(device->handle,
object_name, NULL, NULL);
if (ACPI_FAILURE(status)) {
result = -ENODEV;
goto end;
}
}
}
else {
if (device->power.states[state].flags.explicit_set) {
status = acpi_evaluate_object(device->handle,
object_name, NULL, NULL);
if (ACPI_FAILURE(status)) {
result = -ENODEV;
goto end;
}
}
if (device->power.flags.power_resources) {
result = acpi_power_transition(device, state);
if (0 != result)
goto end;
}
}
end:
if (0 != result)
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error transitioning device [%s] to D%d\n",
device->pnp.bus_id, state));
else
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] transitioned to D%d\n",
device->pnp.bus_id, state));
return_VALUE(result);
}
static int
acpi_bus_get_power_flags (
struct acpi_device *device)
{
acpi_status status = 0;
acpi_handle handle = 0;
u32 i = 0;
ACPI_FUNCTION_TRACE("acpi_bus_get_power_flags");
if (!device)
return -ENODEV;
/*
* 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;
status = acpi_get_handle(device->handle, "_PRW", &handle);
if (ACPI_SUCCESS(status))
device->power.flags.wake_capable = 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;
/*
* System Power States
* -------------------
*/
/* TBD: S1-S4 power state support and resource requirements. */
/*
for (i=ACPI_STATE_S1; i<ACPI_STATE_S5; i++) {
char name[5] = {'_','S',('0'+i),'D','\0'};
status = acpi_evaluate_integer(device->handle, name, NULL,
&state);
if (ACPI_FAILURE(status))
continue;
}
*/
/* TBD: System wake support and resource requirements. */
device->power.state = ACPI_STATE_UNKNOWN;
return 0;
}
/* --------------------------------------------------------------------------
Performance Management
-------------------------------------------------------------------------- */
static int
acpi_bus_get_perf_flags (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_bus_get_perf_flags");
if (!device)
return -ENODEV;
device->performance.state = ACPI_STATE_UNKNOWN;
return 0;
}
/* --------------------------------------------------------------------------
Event Management
-------------------------------------------------------------------------- */
static spinlock_t acpi_bus_event_lock = SPIN_LOCK_UNLOCKED;
LIST_HEAD(acpi_bus_event_list);
DECLARE_WAIT_QUEUE_HEAD(acpi_bus_event_queue);
extern int event_is_open;
int
acpi_bus_generate_event (
struct acpi_device *device,
u8 type,
int data)
{
struct acpi_bus_event *event = NULL;
u32 flags = 0;
ACPI_FUNCTION_TRACE("acpi_bus_generate_event");
if (!device)
return_VALUE(-EINVAL);
/* drop event on the floor if no one's listening */
if (!event_is_open)
return_VALUE(0);
event = kmalloc(sizeof(struct acpi_bus_event), GFP_KERNEL);
if (!event)
return_VALUE(-ENOMEM);
sprintf(event->device_class, "%s", device->pnp.device_class);
sprintf(event->bus_id, "%s", device->pnp.bus_id);
event->type = type;
event->data = data;
spin_lock_irqsave(&acpi_bus_event_lock, flags);
list_add_tail(&event->node, &acpi_bus_event_list);
spin_unlock_irqrestore(&acpi_bus_event_lock, flags);
wake_up_interruptible(&acpi_bus_event_queue);
return_VALUE(0);
}
int
acpi_bus_receive_event (
struct acpi_bus_event *event)
{
u32 flags = 0;
struct acpi_bus_event *entry = NULL;
DECLARE_WAITQUEUE(wait, current);
ACPI_FUNCTION_TRACE("acpi_bus_receive_event");
if (!event)
return -EINVAL;
if (list_empty(&acpi_bus_event_list)) {
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&acpi_bus_event_queue, &wait);
if (list_empty(&acpi_bus_event_list))
schedule();
remove_wait_queue(&acpi_bus_event_queue, &wait);
set_current_state(TASK_RUNNING);
if (signal_pending(current))
return_VALUE(-ERESTARTSYS);
}
spin_lock_irqsave(&acpi_bus_event_lock, flags);
entry = list_entry(acpi_bus_event_list.next, struct acpi_bus_event, node);
if (entry)
list_del(&entry->node);
spin_unlock_irqrestore(&acpi_bus_event_lock, flags);
if (!entry)
return_VALUE(-ENODEV);
memcpy(event, entry, sizeof(struct acpi_bus_event));
kfree(entry);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Namespace Management
-------------------------------------------------------------------------- */
#define WALK_UP 0
#define WALK_DOWN 1
typedef int (*acpi_bus_walk_callback)(struct acpi_device*, int, void*);
#define HAS_CHILDREN(d) ((d)->children.next != &((d)->children))
#define HAS_SIBLINGS(d) (((d)->parent) && ((d)->node.next != &(d)->parent->children))
#define NODE_TO_DEVICE(n) (list_entry(n, struct acpi_device, node))
/**
* acpi_bus_walk
* -------------
* Used to walk the ACPI Bus's device namespace. Can walk down (depth-first)
* or up. Able to parse starting at any node in the namespace. Note that a
* callback return value of -ELOOP will terminate the walk.
*
* @start: starting point
* callback: function to call for every device encountered while parsing
* direction: direction to parse (up or down)
* @data: context for this search operation
*/
static int
acpi_bus_walk (
struct acpi_device *start,
acpi_bus_walk_callback callback,
int direction,
void *data)
{
int result = 0;
int level = 0;
struct acpi_device *device = NULL;
if (!start || !callback)
return -EINVAL;
device = start;
/*
* Parse Namespace
* ---------------
* Parse a given subtree (specified by start) in the given direction.
* Walking 'up' simply means that we execute the callback on leaf
* devices prior to their parents (useful for things like removing
* or powering down a subtree).
*/
while (device) {
if (direction == WALK_DOWN)
if (-ELOOP == callback(device, level, data))
break;
/* Depth First */
if (HAS_CHILDREN(device)) {
device = NODE_TO_DEVICE(device->children.next);
++level;
continue;
}
if (direction == WALK_UP)
if (-ELOOP == callback(device, level, data))
break;
/* Now Breadth */
if (HAS_SIBLINGS(device)) {
device = NODE_TO_DEVICE(device->node.next);
continue;
}
/* Scope Exhausted - Find Next */
while ((device = device->parent)) {
--level;
if (HAS_SIBLINGS(device)) {
device = NODE_TO_DEVICE(device->node.next);
break;
}
}
}
if ((direction == WALK_UP) && (result == 0))
callback(start, level, data);
return result;
}
/* --------------------------------------------------------------------------
Notification Handling
-------------------------------------------------------------------------- */
static int
acpi_bus_check_device (
struct acpi_device *device,
int *status_changed)
{
acpi_status status = 0;
struct acpi_device_status old_status;
ACPI_FUNCTION_TRACE("acpi_bus_check_device");
if (!device)
return_VALUE(-EINVAL);
if (status_changed)
*status_changed = 0;
old_status = device->status;
/*
* Make sure this device's parent is present before we go about
* messing with the device.
*/
if (device->parent && !device->parent->status.present) {
device->status = device->parent->status;
if (STRUCT_TO_INT(old_status) != STRUCT_TO_INT(device->status)) {
if (status_changed)
*status_changed = 1;
}
return_VALUE(0);
}
status = acpi_bus_get_status(device);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
if (STRUCT_TO_INT(old_status) == STRUCT_TO_INT(device->status))
return_VALUE(0);
if (status_changed)
*status_changed = 1;
/*
* Device Insertion/Removal
*/
if ((device->status.present) && !(old_status.present)) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device insertion detected\n"));
/* TBD: Handle device insertion */
}
else if (!(device->status.present) && (old_status.present)) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device removal detected\n"));
/* TBD: Handle device removal */
}
return_VALUE(0);
}
static int
acpi_bus_check_scope (
struct acpi_device *device)
{
int result = 0;
int status_changed = 0;
ACPI_FUNCTION_TRACE("acpi_bus_check_scope");
if (!device)
return_VALUE(-EINVAL);
/* Status Change? */
result = acpi_bus_check_device(device, &status_changed);
if (0 != result)
return_VALUE(result);
if (!status_changed)
return_VALUE(0);
/*
* TBD: Enumerate child devices within this device's scope and
* run acpi_bus_check_device()'s on them.
*/
return_VALUE(0);
}
/**
* acpi_bus_notify
* ---------------
* Callback for all 'system-level' device notifications (values 0x00-0x7F).
*/
static void
acpi_bus_notify (
acpi_handle handle,
u32 type,
void *data)
{
int result = 0;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_bus_notify");
if (0 != acpi_bus_get_device(handle, &device))
return_VOID;
switch (type) {
case ACPI_NOTIFY_BUS_CHECK:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received BUS CHECK notification for device [%s]\n",
device->pnp.bus_id));
result = acpi_bus_check_scope(device);
/*
* TBD: We'll need to outsource certain events to non-ACPI
* drivers via the device manager (device.c).
*/
break;
case ACPI_NOTIFY_DEVICE_CHECK:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received DEVICE CHECK notification for device [%s]\n",
device->pnp.bus_id));
result = acpi_bus_check_device(device, NULL);
/*
* TBD: We'll need to outsource certain events to non-ACPI
* drivers via the device manager (device.c).
*/
break;
case ACPI_NOTIFY_DEVICE_WAKE:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received DEVICE WAKE notification for device [%s]\n",
device->pnp.bus_id));
/* TBD */
break;
case ACPI_NOTIFY_EJECT_REQUEST:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received EJECT REQUEST notification for device [%s]\n",
device->pnp.bus_id));
/* TBD */
break;
case ACPI_NOTIFY_DEVICE_CHECK_LIGHT:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received DEVICE CHECK LIGHT notification for device [%s]\n",
device->pnp.bus_id));
/* TBD: Exactly what does 'light' mean? */
break;
case ACPI_NOTIFY_FREQUENCY_MISMATCH:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received FREQUENCY MISMATCH notification for device [%s]\n",
device->pnp.bus_id));
/* TBD */
break;
case ACPI_NOTIFY_BUS_MODE_MISMATCH:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received BUS MODE MISMATCH notification for device [%s]\n",
device->pnp.bus_id));
/* TBD */
break;
case ACPI_NOTIFY_POWER_FAULT:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received POWER FAULT notification for device [%s]\n",
device->pnp.bus_id));
/* TBD */
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Received unknown/unsupported notification [%08x]\n",
type));
break;
}
return_VOID;
}
/* --------------------------------------------------------------------------
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 (!device || !driver)
return -EINVAL;
if (0 != strstr(driver->ids, device->pnp.hardware_id))
return 0;
if (device->flags.compatible_ids) {
acpi_status status = AE_OK;
acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
acpi_object *object = NULL;
char cid[256];
memset(cid, 0, sizeof(cid));
status = acpi_evaluate_object(device->handle, "_CID",
NULL, &buffer);
if (ACPI_FAILURE(status) || !buffer.pointer)
return -ENOENT;
object = (acpi_object *) buffer.pointer;
switch (object->type) {
case ACPI_TYPE_INTEGER:
acpi_ex_eisa_id_to_string((u32) object->integer.value,
cid);
break;
case ACPI_TYPE_STRING:
strncpy(cid, object->string.pointer, sizeof(cid) - 1);
break;
case ACPI_TYPE_PACKAGE:
/* TBD: Support CID packages */
default:
return -ENOENT;
}
if (0 != strstr(cid, device->pnp.hardware_id))
return 0;
}
return -ENOENT;
}
/**
* 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 (0 != result) {
device->driver = NULL;
acpi_driver_data(device) = NULL;
return_VALUE(result);
}
/*
* 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 ((0 != 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"));
#ifdef CONFIG_LDM
/*
* Update the device information (in the global device hierarchy) now
* that there's a driver bound to it.
*/
strncpy(device->dev.name, device->pnp.device_name,
sizeof(device->dev.name));
#endif
if (driver->ops.scan) {
driver->ops.scan(device);
}
return_VALUE(0);
}
/**
* acpi_bus_bind
* -------------
* Callback for acpi_bus_walk() used to find devices that match a specific
* driver's criteria and bind the driver to the device.
*/
static int
acpi_bus_bind (
struct acpi_device *device,
int level,
void *data)
{
int result = 0;
struct acpi_driver *driver = NULL;
ACPI_FUNCTION_TRACE("acpi_bus_bind");
if (!device || !data)
return_VALUE(-EINVAL);
driver = (struct acpi_driver *) data;
if (device->driver)
return_VALUE(-EEXIST);
if (!device->status.present)
return_VALUE(-ENODEV);
result = acpi_bus_match(device, driver);
if (0 != result)
return_VALUE(result);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found driver [%s] for device [%s]\n",
driver->name, device->pnp.bus_id));
result = acpi_bus_driver_init(device, driver);
if (0 != result)
return_VALUE(result);
down(&acpi_bus_drivers_lock);
++driver->references;
up(&acpi_bus_drivers_lock);
return_VALUE(0);
}
/**
* acpi_bus_unbind
* ---------------
* Callback for acpi_bus_walk() used to find devices that match a specific
* driver's criteria and unbind the driver from the device.
*/
static int
acpi_bus_unbind (
struct acpi_device *device,
int level,
void *data)
{
int result = 0;
struct acpi_driver *driver = (struct acpi_driver *) data;
ACPI_FUNCTION_TRACE("acpi_bus_unbind");
if (!device || !driver)
return_VALUE(-EINVAL);
if (device->driver != driver)
return_VALUE(-ENOENT);
if (!driver->ops.remove)
return_VALUE(-ENOSYS);
result = driver->ops.remove(device, ACPI_BUS_REMOVAL_NORMAL);
if (0 != result)
return_VALUE(result);
device->driver = NULL;
acpi_driver_data(device) = NULL;
down(&acpi_bus_drivers_lock);
driver->references--;
up(&acpi_bus_drivers_lock);
return_VALUE(0);
}
/**
* 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 = -ENODEV;
struct list_head *entry = NULL;
struct acpi_driver *driver = NULL;
ACPI_FUNCTION_TRACE("acpi_bus_find_driver");
if (!device || device->driver)
return_VALUE(-EINVAL);
down(&acpi_bus_drivers_lock);
list_for_each(entry, &acpi_bus_drivers) {
driver = list_entry(entry, struct acpi_driver, node);
if (0 != acpi_bus_match(device, driver))
continue;
result = acpi_bus_driver_init(device, driver);
if (0 == result)
++driver->references;
break;
}
up(&acpi_bus_drivers_lock);
return_VALUE(result);
}
/**
* 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.
*/
int
acpi_bus_register_driver (
struct acpi_driver *driver)
{
ACPI_FUNCTION_TRACE("acpi_bus_register_driver");
if (!driver)
return_VALUE(-EINVAL);
down(&acpi_bus_drivers_lock);
list_add_tail(&driver->node, &acpi_bus_drivers);
up(&acpi_bus_drivers_lock);
acpi_bus_walk(acpi_root, acpi_bus_bind,
WALK_DOWN, driver);
return_VALUE(driver->references);
}
/**
* 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)
{
ACPI_FUNCTION_TRACE("acpi_bus_unregister_driver");
if (!driver)
return_VALUE(-EINVAL);
acpi_bus_walk(acpi_root, acpi_bus_unbind, WALK_UP, driver);
if (driver->references)
return_VALUE(driver->references);
down(&acpi_bus_drivers_lock);
list_del(&driver->node);
up(&acpi_bus_drivers_lock);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
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;
/* TBD: Peformance management */
return_VALUE(0);
}
static int
acpi_bus_add (
struct acpi_device **child,
struct acpi_device *parent,
acpi_handle handle,
int type)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_device *device = NULL;
char bus_id[5] = {'?',0};
acpi_buffer buffer = {sizeof(bus_id), bus_id};
acpi_device_info info;
char *hid = "?";
char *uid = "0";
int i = 0;
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;
/*
* 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 (0 != 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 (0 != result)
goto end;
break;
default:
STRUCT_TO_INT(device->status) = 0x0F;
break;
}
if (!device->status.present) {
result = -ENOENT;
goto end;
}
/*
* Initialize Device
* -----------------
* TBD: Synch with Core's enumeration/initialization process.
*/
/*
* 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:
sprintf(device->pnp.bus_id, "%s", "root");
break;
case ACPI_BUS_TYPE_POWER_BUTTON:
sprintf(device->pnp.bus_id, "%s", "PWRF");
break;
case ACPI_BUS_TYPE_SLEEP_BUTTON:
sprintf(device->pnp.bus_id, "%s", "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;
}
sprintf(device->pnp.bus_id, "%s", bus_id);
break;
}
/*
* Hardware ID, Unique ID, & Bus Address
* -------------------------------------
*/
switch (type) {
case ACPI_BUS_TYPE_DEVICE:
status = acpi_get_object_info(handle, &info);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error reading device info\n"));
result = -ENODEV;
goto end;
}
/* Clean up info strings (not NULL terminated) */
info.hardware_id[sizeof(info.hardware_id)-1] = '\0';
info.unique_id[sizeof(info.unique_id)-1] = '\0';
if (info.valid & ACPI_VALID_HID)
hid = info.hardware_id;
if (info.valid & ACPI_VALID_UID)
uid = info.unique_id;
if (info.valid & ACPI_VALID_ADR)
device->pnp.bus_address = info.address;
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;
}
sprintf(device->pnp.hardware_id, "%s", hid);
sprintf(device->pnp.unique_id, "%s", uid);
/*
* Power Management
* ----------------
*/
if (device->flags.power_manageable) {
result = acpi_bus_get_power_flags(device);
if (0 != result)
goto end;
}
/*
* Performance Management
* ----------------------
*/
if (device->flags.performance_manageable) {
result = acpi_bus_get_perf_flags(device);
if (0 != result)
goto end;
}
/*
* Context
* -------
* Attach this 'struct acpi_device' to the ACPI object. This makes
* resolutions from handle->device very efficient. Note that since we
* attach multiple context to the root object (system device plus all
* fixed-feature devices) we must use alternate handlers.
*/
switch (type) {
case ACPI_BUS_TYPE_POWER_BUTTON:
status = acpi_attach_data(device->handle,
acpi_bus_data_handler_powerf, device);
break;
case ACPI_BUS_TYPE_SLEEP_BUTTON:
status = acpi_attach_data(device->handle,
acpi_bus_data_handler_sleepf, device);
break;
default:
status = acpi_attach_data(device->handle,
acpi_bus_data_handler, device);
break;
}
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error attaching device data\n"));
result = -ENODEV;
goto end;
}
/*
* Linkage
* -------
* Link this device to its parent and siblings.
*/
INIT_LIST_HEAD(&device->children);
if (!device->parent)
INIT_LIST_HEAD(&device->node);
else
list_add_tail(&device->node, &device->parent->children);
/*
* \_SB
* ----
* Fix for the system root bus device -- the only root-level device.
*/
if ((type == ACPI_BUS_TYPE_DEVICE) && (parent == ACPI_ROOT_OBJECT)) {
sprintf(device->pnp.hardware_id, "%s", ACPI_BUS_HID);
sprintf(device->pnp.device_name, "%s", ACPI_BUS_DEVICE_NAME);
sprintf(device->pnp.device_class, "%s", ACPI_BUS_CLASS);
}
#ifdef CONFIG_ACPI_DEBUG
{
char *type_string = NULL;
char *message = NULL;
char name[80] = {'?','\0'};
acpi_buffer buffer = {sizeof(name), name};
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
switch (type) {
case ACPI_BUS_TYPE_DEVICE:
type_string = "Device";
break;
case ACPI_BUS_TYPE_POWER:
type_string = "Power Resource";
break;
case ACPI_BUS_TYPE_PROCESSOR:
type_string = "Processor";
break;
case ACPI_BUS_TYPE_SYSTEM:
type_string = "System";
break;
case ACPI_BUS_TYPE_THERMAL:
type_string = "Thermal Zone";
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;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %s %s [%p]\n",
type_string, name, handle));
}
#endif
/*
* Global Device Hierarchy:
* ------------------------
* Register this device with the global device hierarchy.
*/
acpi_device_register(device, parent);
/*
* Find Driver
* -----------
* Check to see if there's a driver installed for this kind of device.
*/
acpi_bus_find_driver(device);
end:
if (0 != result) {
kfree(device);
return_VALUE(result);
}
*child = device;
return_VALUE(0);
}
static int
acpi_bus_remove (
struct acpi_device *device,
int type)
{
ACPI_FUNCTION_TRACE("acpi_bus_remove");
if (!device)
return_VALUE(-ENODEV);
acpi_device_unregister(device);
kfree(device);
return_VALUE(0);
}
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 = 0;
acpi_handle chandle = 0;
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_ANY) {
/* Hack to get around scope identity problem */
status = acpi_get_next_object(ACPI_TYPE_ANY, chandle, 0, NULL);
if (ACPI_SUCCESS(status)) {
level++;
phandle = chandle;
chandle = 0;
}
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 we 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 (STRUCT_TO_INT(child->status) & 0x0B) {
*/
if (child->status.present) {
status = acpi_get_next_object(ACPI_TYPE_ANY, chandle,
0, NULL);
if (ACPI_SUCCESS(status)) {
level++;
phandle = chandle;
chandle = 0;
parent = child;
}
}
}
return_VALUE(0);
}
static int
acpi_bus_scan_fixed (
struct acpi_device *root)
{
int result = 0;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_bus_scan");
if (!root)
return_VALUE(-ENODEV);
/*
* Enumerate all fixed-feature devices.
*/
if (acpi_fadt.pwr_button == 0)
result = acpi_bus_add(&device, acpi_root,
ACPI_ROOT_OBJECT, ACPI_BUS_TYPE_POWER_BUTTON);
if (acpi_fadt.sleep_button == 0)
result = acpi_bus_add(&device, acpi_root,
ACPI_ROOT_OBJECT, ACPI_BUS_TYPE_SLEEP_BUTTON);
return_VALUE(result);
}
/* --------------------------------------------------------------------------
Initialization/Cleanup
-------------------------------------------------------------------------- */
int __init
acpi_blacklisted(void)
{
int i = 0;
int blacklisted = 0;
acpi_table_header table_header;
while (acpi_blacklist[i].oem_id[0] != '\0')
{
if (!ACPI_SUCCESS(acpi_get_table_header(acpi_blacklist[i].table, 1, &table_header))) {
i++;
continue;
}
if (strncmp(acpi_blacklist[i].oem_id, table_header.oem_id, 6)) {
i++;
continue;
}
if (strncmp(acpi_blacklist[i].oem_table_id, table_header.oem_table_id, 8)) {
i++;
continue;
}
if ((acpi_blacklist[i].oem_revision_predicate == all_versions)
|| (acpi_blacklist[i].oem_revision_predicate == less_than_or_equal
&& table_header.oem_revision <= acpi_blacklist[i].oem_revision)
|| (acpi_blacklist[i].oem_revision_predicate == greater_than_or_equal
&& table_header.oem_revision >= acpi_blacklist[i].oem_revision)
|| (acpi_blacklist[i].oem_revision_predicate == equal
&& table_header.oem_revision == acpi_blacklist[i].oem_revision)) {
printk(KERN_ERR PREFIX "Vendor \"%6.6s\" System \"%8.8s\" "
"Revision 0x%x has a known ACPI BIOS problem.\n",
acpi_blacklist[i].oem_id,
acpi_blacklist[i].oem_table_id,
acpi_blacklist[i].oem_revision);
printk(KERN_ERR PREFIX "Reason: %s. This is a %s error\n",
acpi_blacklist[i].reason,
(acpi_blacklist[i].is_critical_error ? "non-recoverable" : "recoverable"));
blacklisted = acpi_blacklist[i].is_critical_error;
break;
}
else {
i++;
}
}
return blacklisted;
}
static int __init
acpi_bus_init (void)
{
int result = 0;
acpi_status status = AE_OK;
acpi_buffer buffer = {sizeof(acpi_fadt), &acpi_fadt};
int progress = 0;
ACPI_FUNCTION_TRACE("acpi_bus_init");
/*
* [0] Initailize the ACPI Core Subsystem.
*/
status = acpi_initialize_subsystem();
if (ACPI_FAILURE(status)) {
printk(KERN_ERR PREFIX "Unable to initialize the ACPI Interpreter\n");
result = -ENODEV;
goto end;
}
progress++;
/*
* [1] Load the ACPI tables.
*/
status = acpi_load_tables();
if (ACPI_FAILURE(status)) {
printk(KERN_ERR PREFIX "Unable to load the System Description Tables\n");
result = -ENODEV;
goto end;
}
progress++;
/*
* [2] Check the blacklist
*/
if (acpi_blacklisted()) {
result = -ENODEV;
goto end;
}
progress++;
/*
* [2] Get a separate copy of the FADT for use by other drivers.
*/
status = acpi_get_table(ACPI_TABLE_FADT, 1, &buffer);
if (ACPI_FAILURE(status)) {
printk(KERN_ERR PREFIX "Unable to get the FADT\n");
result = -ENODEV;
goto end;
}
progress++;
/*
* [3] Enable the ACPI Core Subsystem.
*/
status = acpi_enable_subsystem(ACPI_FULL_INITIALIZATION);
if (ACPI_FAILURE(status)) {
printk(KERN_ERR PREFIX "Unable to start the ACPI Interpreter\n");
result = -ENODEV;
goto end;
}
printk(KERN_INFO PREFIX "Interpreter enabled\n");
progress++;
/*
* [4] Register for all standard device notifications.
*/
status = acpi_install_notify_handler(ACPI_ROOT_OBJECT, ACPI_SYSTEM_NOTIFY, &acpi_bus_notify, NULL);
if (ACPI_FAILURE(status)) {
printk(KERN_ERR PREFIX "Unable to register for device notifications\n");
result = -ENODEV;
goto end;
}
progress++;
/*
* [5] Create the root device.
*/
result = acpi_bus_add(&acpi_root, NULL, ACPI_ROOT_OBJECT,
ACPI_BUS_TYPE_SYSTEM);
if (0 != result)
goto end;
progress++;
/*
* [6] Create the root file system.
*/
acpi_device_dir(acpi_root) = proc_mkdir(ACPI_BUS_FILE_ROOT, NULL);
if (!acpi_root) {
result = -ENODEV;
goto end;
}
acpi_root_dir = acpi_device_dir(acpi_root);
progress++;
/*
* [7] Install drivers required for proper enumeration of the
* ACPI namespace.
*/
acpi_system_init(); /* ACPI System */
acpi_power_init(); /* ACPI Bus Power Management */
#ifdef CONFIG_ACPI_EC
acpi_ec_init(); /* ACPI Embedded Controller */
#endif
#ifdef CONFIG_ACPI_PCI
acpi_pci_link_init(); /* ACPI PCI Interrupt Link */
acpi_pci_root_init(); /* ACPI PCI Root Bridge */
#endif
progress++;
/*
* [8] Enumerate devices in the ACPI namespace.
*/
result = acpi_bus_scan_fixed(acpi_root);
if (0 != result)
goto end;
result = acpi_bus_scan(acpi_root);
if (0 != result)
goto end;
end:
if (0 != result) {
switch (progress) {
case 9:
case 8: remove_proc_entry("ACPI", NULL);
case 7: acpi_bus_remove(acpi_root, ACPI_BUS_REMOVAL_NORMAL);
case 6: acpi_remove_notify_handler(ACPI_ROOT_OBJECT,
ACPI_SYSTEM_NOTIFY, &acpi_bus_notify);
case 5:
case 4:
case 3:
case 2: acpi_terminate();
case 1:
case 0:
default: return_VALUE(-ENODEV);
}
}
return_VALUE(0);
}
static void __exit
acpi_bus_exit (void)
{
acpi_status status = AE_OK;
ACPI_FUNCTION_TRACE("acpi_bus_exit");
status = acpi_remove_notify_handler(ACPI_ROOT_OBJECT,
ACPI_SYSTEM_NOTIFY, acpi_bus_notify);
if (ACPI_FAILURE(status))
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error removing notify handler\n"));
#ifdef CONFIG_ACPI_PCI
acpi_pci_root_exit();
acpi_pci_link_exit();
#endif
#ifdef CONFIG_ACPI_EC
acpi_ec_exit();
#endif
acpi_power_exit();
acpi_system_exit();
acpi_bus_remove(acpi_root, ACPI_BUS_REMOVAL_NORMAL);
remove_proc_entry(ACPI_BUS_FILE_ROOT, NULL);
status = acpi_terminate();
if (ACPI_FAILURE(status))
printk(KERN_ERR PREFIX "Unable to terminate the ACPI Interpreter\n");
else
printk(KERN_ERR PREFIX "Interpreter disabled\n");
return_VOID;
}
int __init
acpi_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_init");
printk(KERN_INFO PREFIX "Bus Driver revision %08x\n",
ACPI_DRIVER_VERSION);
printk(KERN_INFO PREFIX "Core Subsystem revision %08x\n",
ACPI_CA_VERSION);
if (acpi_disabled) {
printk(KERN_INFO PREFIX "Disabled via command line (acpi=off)\n");
return -ENODEV;
}
#ifdef CONFIG_PM
if (PM_IS_ACTIVE()) {
printk(KERN_INFO PREFIX "APM is already active, exiting\n");
return -ENODEV;
}
#endif
result = acpi_bus_init();
if (0 != result)
return_VALUE(result);
#ifdef CONFIG_PM
pm_active = 1;
#endif
return_VALUE(0);
}
void __exit
acpi_exit (void)
{
ACPI_FUNCTION_TRACE("acpi_exit");
#ifdef CONFIG_PM
pm_active = 0;
#endif
acpi_bus_exit();
return_VOID;
}
int __init
acpi_setup(char *str)
{
while (str && *str) {
if (strncmp(str, "off", 3) == 0)
acpi_disabled = 1;
str = strchr(str, ',');
if (str)
str += strspn(str, ", \t");
}
return 1;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
subsys_initcall(acpi_init);
#endif
__setup("acpi=", acpi_setup);
/*
* acpi_bus.h - ACPI Bus Driver ($Revision: 17 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#ifndef __ACPI_BUS_H__
#define __ACPI_BUS_H__
#include <linux/version.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,4))
#include <linux/device.h>
#define CONFIG_LDM
#endif
#include "include/acpi.h"
/* TBD: Make dynamic */
#define ACPI_MAX_HANDLES 10
struct acpi_handle_list {
u32 count;
acpi_handle handles[ACPI_MAX_HANDLES];
};
/* acpi_utils.h */
acpi_status acpi_extract_package (acpi_object *, acpi_buffer *, acpi_buffer *);
acpi_status acpi_evaluate (acpi_handle, acpi_string, acpi_object_list *, acpi_buffer *);
acpi_status acpi_evaluate_integer (acpi_handle, acpi_string, acpi_object_list *, unsigned long *);
acpi_status acpi_evaluate_reference (acpi_handle, acpi_string, acpi_object_list *, struct acpi_handle_list *);
#ifdef CONFIG_ACPI_BUS
#include <linux/proc_fs.h>
#define ACPI_BUS_FILE_ROOT "acpi"
extern struct proc_dir_entry *acpi_root_dir;
extern FADT_DESCRIPTOR acpi_fadt;
enum acpi_bus_removal_type {
ACPI_BUS_REMOVAL_NORMAL = 0,
ACPI_BUS_REMOVAL_EJECT,
ACPI_BUS_REMOVAL_SUPRISE,
ACPI_BUS_REMOVAL_TYPE_COUNT
};
enum acpi_bus_device_type {
ACPI_BUS_TYPE_DEVICE = 0,
ACPI_BUS_TYPE_POWER,
ACPI_BUS_TYPE_PROCESSOR,
ACPI_BUS_TYPE_THERMAL,
ACPI_BUS_TYPE_SYSTEM,
ACPI_BUS_TYPE_POWER_BUTTON,
ACPI_BUS_TYPE_SLEEP_BUTTON,
ACPI_BUS_DEVICE_TYPE_COUNT
};
struct acpi_driver;
struct acpi_device;
/*
* ACPI Driver
* -----------
*/
typedef int (*acpi_op_add) (struct acpi_device *device);
typedef int (*acpi_op_remove) (struct acpi_device *device, int type);
typedef int (*acpi_op_lock) (struct acpi_device *device, int type);
typedef int (*acpi_op_start) (struct acpi_device *device);
typedef int (*acpi_op_stop) (struct acpi_device *device, int type);
typedef int (*acpi_op_suspend) (struct acpi_device *device, int state);
typedef int (*acpi_op_resume) (struct acpi_device *device, int state);
typedef int (*acpi_op_scan) (struct acpi_device *device);
struct acpi_driver_ops {
acpi_op_add add;
acpi_op_remove remove;
acpi_op_lock lock;
acpi_op_start start;
acpi_op_stop stop;
acpi_op_suspend suspend;
acpi_op_resume resume;
acpi_op_scan scan;
};
struct acpi_driver {
struct list_head node;
char name[80];
char class[80];
int references;
char *ids; /* Supported Hardware IDs */
struct acpi_driver_ops ops;
};
enum acpi_blacklist_predicates
{
all_versions,
less_than_or_equal,
equal,
greater_than_or_equal,
};
struct acpi_blacklist_item
{
char oem_id[7];
char oem_table_id[9];
u32 oem_revision;
acpi_table_type table;
enum acpi_blacklist_predicates oem_revision_predicate;
char *reason;
u32 is_critical_error;
};
/*
* ACPI Device
* -----------
*/
/* Status (_STA) */
struct acpi_device_status {
u32 present:1;
u32 enabled:1;
u32 show_in_ui:1;
u32 functional:1;
u32 battery_present:1;
u32 reserved:27;
};
/* Flags */
struct acpi_device_flags {
u8 dynamic_status:1;
u8 compatible_ids:1;
u8 removable:1;
u8 ejectable:1;
u8 lockable:1;
u8 suprise_removal_ok:1;
u8 power_manageable:1;
u8 performance_manageable:1;
};
/* File System */
struct acpi_device_dir {
struct proc_dir_entry *entry;
};
#define acpi_device_dir(d) ((d)->dir.entry)
/* Plug and Play */
typedef char acpi_bus_id[5];
typedef unsigned long acpi_bus_address;
typedef char acpi_hardware_id[9];
typedef char acpi_unique_id[9];
typedef char acpi_device_name[40];
typedef char acpi_device_class[20];
struct acpi_device_pnp {
acpi_bus_id bus_id; /* Object name */
acpi_bus_address bus_address; /* _ADR */
acpi_hardware_id hardware_id; /* _HID */
acpi_unique_id unique_id; /* _UID */
acpi_device_name device_name; /* Driver-determined */
acpi_device_class device_class; /* " */
};
#define acpi_device_bid(d) ((d)->pnp.bus_id)
#define acpi_device_adr(d) ((d)->pnp.bus_address)
#define acpi_device_hid(d) ((d)->pnp.hardware_id)
#define acpi_device_uid(d) ((d)->pnp.unique_id)
#define acpi_device_name(d) ((d)->pnp.device_name)
#define acpi_device_class(d) ((d)->pnp.device_class)
/* Power Management */
struct acpi_device_power_flags {
u8 explicit_get:1; /* _PSC present? */
u8 power_resources:1; /* Power resources */
u8 inrush_current:1; /* Serialize Dx->D0 */
u8 wake_capable:1; /* Wakeup supported? */
u8 wake_enabled:1; /* Enabled for wakeup */
u8 power_removed:1; /* Optimize Dx->D0 */
u8 reserved:2;
};
struct acpi_device_power_state {
struct {
u8 valid:1;
u8 explicit_set:1; /* _PSx present? */
u8 reserved:6;
} flags;
int power; /* % Power (compared to D0) */
int latency; /* Dx->D0 time (microseconds) */
struct acpi_handle_list resources; /* Power resources referenced */
};
struct acpi_device_power {
int state; /* Current state */
struct acpi_device_power_flags flags;
struct acpi_device_power_state states[4]; /* Power states (D0-D3) */
};
/* Performance Management */
struct acpi_device_perf_flags {
u8 reserved:8;
};
struct acpi_device_perf_state {
struct {
u8 valid:1;
u8 reserved:7;
} flags;
u8 power; /* % Power (compared to P0) */
u8 performance; /* % Performance ( " ) */
int latency; /* Px->P0 time (microseconds) */
};
struct acpi_device_perf {
int state;
struct acpi_device_perf_flags flags;
int state_count;
struct acpi_device_perf_state *states;
};
/* Device */
struct acpi_device {
acpi_handle handle;
struct acpi_device *parent;
struct list_head children;
struct list_head node;
struct acpi_device_status status;
struct acpi_device_flags flags;
struct acpi_device_pnp pnp;
struct acpi_device_power power;
struct acpi_device_perf performance;
struct acpi_device_dir dir;
struct acpi_driver *driver;
void *driver_data;
#ifdef CONFIG_LDM
struct device dev;
#endif
};
#define acpi_driver_data(d) ((d)->driver_data)
/*
* Events
* ------
*/
struct acpi_bus_event {
struct list_head node;
acpi_device_class device_class;
acpi_bus_id bus_id;
u32 type;
u32 data;
};
/*
* External Functions
*/
int acpi_bus_get_device(acpi_handle, struct acpi_device **device);
int acpi_bus_get_status (struct acpi_device *device);
int acpi_bus_get_power (acpi_handle handle, int *state);
int acpi_bus_set_power (acpi_handle handle, int state);
int acpi_bus_generate_event (struct acpi_device *device, u8 type, int data);
int acpi_bus_receive_event (struct acpi_bus_event *event);
int acpi_bus_register_driver (struct acpi_driver *driver);
int acpi_bus_unregister_driver (struct acpi_driver *driver);
int acpi_bus_scan (struct acpi_device *device);
int acpi_init (void);
void acpi_exit (void);
#endif /*CONFIG_ACPI_BUS*/
#endif /*__ACPI_BUS_H__*/
/*
* acpi_button.c - ACPI Button Driver ($Revision: 22 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_BUTTON_COMPONENT
ACPI_MODULE_NAME ("acpi_button")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_BUTTON_DRIVER_NAME);
MODULE_LICENSE("GPL");
#define PREFIX "ACPI: "
int acpi_button_add (struct acpi_device *device);
int acpi_button_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_button_driver = {
name: ACPI_BUTTON_DRIVER_NAME,
class: ACPI_BUTTON_CLASS,
ids: "ACPI_FPB,ACPI_FSB,PNP0C0D,PNP0C0C,PNP0C0E",
ops: {
add: acpi_button_add,
remove: acpi_button_remove,
},
};
struct acpi_button {
acpi_handle handle;
struct acpi_device *device; /* Fixed button kludge */
u8 type;
unsigned long pushed;
};
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_button_dir = NULL;
static int
acpi_button_read_info (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
int result = 0;
struct acpi_button *button = (struct acpi_button *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_button_read_info");
if (!button || !button->device)
goto end;
p += sprintf(p, "type: %s\n",
acpi_device_name(button->device));
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_button_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_button_add_fs");
if (!acpi_button_dir) {
acpi_button_dir = proc_mkdir(ACPI_BUTTON_CLASS, acpi_root_dir);
if (!acpi_button_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_button_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
/* 'info' [R] */
entry = create_proc_entry(ACPI_BUTTON_FILE_INFO,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_BUTTON_FILE_INFO));
else {
entry->read_proc = acpi_button_read_info;
entry->data = acpi_driver_data(device);
}
return_VALUE(0);
}
static int
acpi_button_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_button_remove_fs");
if (!acpi_button_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_button_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
void
acpi_button_notify (
acpi_handle handle,
u32 event,
void *data)
{
struct acpi_button *button = (struct acpi_button *) data;
ACPI_FUNCTION_TRACE("acpi_button_notify");
if (!button || !button->device)
return_VOID;
switch (event) {
case ACPI_BUTTON_NOTIFY_STATUS:
acpi_bus_generate_event(button->device, event, ++button->pushed);
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Unsupported event [0x%x]\n", event));
break;
}
return_VOID;
}
acpi_status
acpi_button_notify_fixed (
void *data)
{
struct acpi_button *button = (struct acpi_button *) data;
ACPI_FUNCTION_TRACE("acpi_button_notify_fixed");
if (!button)
return_ACPI_STATUS(AE_BAD_PARAMETER);
acpi_button_notify(button->handle, ACPI_BUTTON_NOTIFY_STATUS, button);
return_ACPI_STATUS(AE_OK);
}
int
acpi_button_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_button *button = NULL;
ACPI_FUNCTION_TRACE("acpi_button_add");
if (!device)
return_VALUE(-EINVAL);
button = kmalloc(sizeof(struct acpi_button), GFP_KERNEL);
if (!button)
return_VALUE(-ENOMEM);
memset(button, 0, sizeof(struct acpi_button));
button->device = device;
button->handle = device->handle;
sprintf(acpi_device_class(device), "%s", ACPI_BUTTON_CLASS);
acpi_driver_data(device) = button;
/*
* Determine the button type (via hid), as fixed-feature buttons
* need to be handled a bit differently than generic-space.
*/
if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_POWERF)) {
button->type = ACPI_BUTTON_TYPE_POWERF;
sprintf(acpi_device_name(device), "%s",
ACPI_BUTTON_DEVICE_NAME_POWERF);
}
else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_SLEEPF)) {
button->type = ACPI_BUTTON_TYPE_SLEEPF;
sprintf(acpi_device_name(device), "%s",
ACPI_BUTTON_DEVICE_NAME_SLEEPF);
}
else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_POWER)) {
button->type = ACPI_BUTTON_TYPE_POWER;
sprintf(acpi_device_name(device), "%s",
ACPI_BUTTON_DEVICE_NAME_POWER);
}
else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_SLEEP)) {
button->type = ACPI_BUTTON_TYPE_SLEEP;
sprintf(acpi_device_name(device), "%s",
ACPI_BUTTON_DEVICE_NAME_SLEEP);
}
else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_LID)) {
button->type = ACPI_BUTTON_TYPE_LID;
sprintf(acpi_device_name(device), "%s",
ACPI_BUTTON_DEVICE_NAME_LID);
}
else {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unsupported hid [%s]\n",
acpi_device_hid(device)));
result = -ENODEV;
goto end;
}
result = acpi_button_add_fs(device);
if (0 != result)
goto end;
switch (button->type) {
case ACPI_BUTTON_TYPE_POWERF:
status = acpi_install_fixed_event_handler (
ACPI_EVENT_POWER_BUTTON,
acpi_button_notify_fixed,
button);
break;
case ACPI_BUTTON_TYPE_SLEEPF:
status = acpi_install_fixed_event_handler (
ACPI_EVENT_SLEEP_BUTTON,
acpi_button_notify_fixed,
button);
break;
default:
status = acpi_install_notify_handler (
button->handle,
ACPI_DEVICE_NOTIFY,
acpi_button_notify,
button);
break;
}
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error installing notify handler\n"));
result = -ENODEV;
goto end;
}
printk(KERN_INFO PREFIX "%s [%s]\n",
acpi_device_name(device), acpi_device_bid(device));
end:
if (0 != result) {
acpi_button_remove_fs(device);
kfree(button);
}
return_VALUE(result);
}
int
acpi_button_remove (struct acpi_device *device, int type)
{
acpi_status status = 0;
struct acpi_button *button = NULL;
ACPI_FUNCTION_TRACE("acpi_button_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
button = acpi_driver_data(device);
/* Unregister for device notifications. */
switch (button->type) {
case ACPI_BUTTON_TYPE_POWERF:
status = acpi_remove_fixed_event_handler(
ACPI_EVENT_POWER_BUTTON, acpi_button_notify_fixed);
break;
case ACPI_BUTTON_TYPE_SLEEPF:
status = acpi_remove_fixed_event_handler(
ACPI_EVENT_SLEEP_BUTTON, acpi_button_notify_fixed);
break;
default:
status = acpi_remove_notify_handler(button->handle,
ACPI_DEVICE_NOTIFY, acpi_button_notify);
break;
}
if (ACPI_FAILURE(status))
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error removing notify handler\n"));
acpi_button_remove_fs(device);
kfree(button);
return_VALUE(0);
}
static int __init
acpi_button_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_button_init");
result = acpi_bus_register_driver(&acpi_button_driver);
if (0 > result)
return_VALUE(-ENODEV);
return_VALUE(0);
}
static void __exit
acpi_button_exit (void)
{
ACPI_FUNCTION_TRACE("acpi_button_exit");
acpi_bus_unregister_driver(&acpi_button_driver);
return_VOID;
}
module_init(acpi_button_init);
module_exit(acpi_button_exit);
/*
* acpi_drivers.h ($Revision: 17 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#ifndef __ACPI_DRIVERS_H__
#define __ACPI_DRIVERS_H__
#include <linux/acpi.h>
#include "acpi_bus.h"
#define ACPI_DRIVER_VERSION 0x20020308
#define ACPI_MAX_STRING 80
/* --------------------------------------------------------------------------
ACPI Bus
-------------------------------------------------------------------------- */
#define ACPI_BUS_COMPONENT 0x00010000
#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"
/* --------------------------------------------------------------------------
AC Adapter
-------------------------------------------------------------------------- */
#define ACPI_AC_COMPONENT 0x00020000
#define ACPI_AC_CLASS "ac_adapter"
#define ACPI_AC_HID "ACPI0003"
#define ACPI_AC_DRIVER_NAME "ACPI AC Adapter Driver"
#define ACPI_AC_DEVICE_NAME "AC Adapter"
#define ACPI_AC_FILE_STATE "state"
#define ACPI_AC_NOTIFY_STATUS 0x80
#define ACPI_AC_STATUS_OFFLINE 0x00
#define ACPI_AC_STATUS_ONLINE 0x01
#define ACPI_AC_STATUS_UNKNOWN 0xFF
/* --------------------------------------------------------------------------
Battery
-------------------------------------------------------------------------- */
#define ACPI_BATTERY_COMPONENT 0x00040000
#define ACPI_BATTERY_CLASS "battery"
#define ACPI_BATTERY_HID "PNP0C0A"
#define ACPI_BATTERY_DRIVER_NAME "ACPI Battery Driver"
#define ACPI_BATTERY_DEVICE_NAME "Battery"
#define ACPI_BATTERY_FILE_INFO "info"
#define ACPI_BATTERY_FILE_STATUS "state"
#define ACPI_BATTERY_FILE_ALARM "alarm"
#define ACPI_BATTERY_NOTIFY_STATUS 0x80
#define ACPI_BATTERY_NOTIFY_INFO 0x81
#define ACPI_BATTERY_UNITS_WATTS "mW"
#define ACPI_BATTERY_UNITS_AMPS "mA"
/* --------------------------------------------------------------------------
Button
-------------------------------------------------------------------------- */
#define ACPI_BUTTON_COMPONENT 0x00080000
#define ACPI_BUTTON_CLASS "button"
#define ACPI_BUTTON_HID_LID "PNP0C0D"
#define ACPI_BUTTON_HID_POWER "PNP0C0C"
#define ACPI_BUTTON_HID_POWERF "ACPI_FPB"
#define ACPI_BUTTON_HID_SLEEP "PNP0C0E"
#define ACPI_BUTTON_HID_SLEEPF "ACPI_FSB"
#define ACPI_BUTTON_DRIVER_NAME "ACPI Button Driver"
#define ACPI_BUTTON_DEVICE_NAME_POWER "Power Button"
#define ACPI_BUTTON_DEVICE_NAME_POWERF "Power Button"
#define ACPI_BUTTON_DEVICE_NAME_SLEEP "Sleep Button"
#define ACPI_BUTTON_DEVICE_NAME_SLEEPF "Sleep Button"
#define ACPI_BUTTON_DEVICE_NAME_LID "Lid Switch"
#define ACPI_BUTTON_FILE_INFO "info"
#define ACPI_BUTTON_TYPE_UNKNOWN 0x00
#define ACPI_BUTTON_TYPE_POWER 0x01
#define ACPI_BUTTON_TYPE_POWERF 0x02
#define ACPI_BUTTON_TYPE_SLEEP 0x03
#define ACPI_BUTTON_TYPE_SLEEPF 0x04
#define ACPI_BUTTON_TYPE_LID 0x05
#define ACPI_BUTTON_NOTIFY_STATUS 0x80
/* --------------------------------------------------------------------------
Embedded Controller
-------------------------------------------------------------------------- */
#define ACPI_EC_COMPONENT 0x00100000
#define ACPI_EC_CLASS "embedded_controller"
#define ACPI_EC_HID "PNP0C09"
#define ACPI_EC_DRIVER_NAME "ACPI Embedded Controller Driver"
#define ACPI_EC_DEVICE_NAME "Embedded Controller"
#define ACPI_EC_FILE_INFO "info"
#ifdef CONFIG_ACPI_EC
int acpi_ec_init (void);
void acpi_ec_exit (void);
#endif
/* --------------------------------------------------------------------------
Fan
-------------------------------------------------------------------------- */
#define ACPI_FAN_COMPONENT 0x00200000
#define ACPI_FAN_CLASS "fan"
#define ACPI_FAN_HID "PNP0C0B"
#define ACPI_FAN_DRIVER_NAME "ACPI Fan Driver"
#define ACPI_FAN_DEVICE_NAME "Fan"
#define ACPI_FAN_FILE_STATE "state"
#define ACPI_FAN_NOTIFY_STATUS 0x80
/* --------------------------------------------------------------------------
PCI
-------------------------------------------------------------------------- */
#define ACPI_PCI_LINK_COMPONENT 0x00400000
#define ACPI_PCI_LINK_CLASS "irq_routing"
#define ACPI_PCI_LINK_HID "PNP0C0F"
#define ACPI_PCI_LINK_DRIVER_NAME "ACPI PCI Interrupt Link Driver"
#define ACPI_PCI_LINK_DEVICE_NAME "PCI Interrupt Link"
#define ACPI_PCI_LINK_FILE_INFO "info"
#define ACPI_PCI_LINK_FILE_STATUS "state"
#define ACPI_PCI_ROOT_COMPONENT 0x00800000
#define ACPI_PCI_ROOT_CLASS "bridge"
#define ACPI_PCI_ROOT_HID "PNP0A03"
#define ACPI_PCI_ROOT_DRIVER_NAME "ACPI PCI Root Bridge Driver"
#define ACPI_PCI_ROOT_DEVICE_NAME "PCI Root Bridge"
#define ACPI_PCI_PRT_DEVICE_NAME "PCI Interrupt Routing Table"
#ifdef CONFIG_ACPI_PCI
int acpi_pci_link_get_irq (struct acpi_prt_entry *entry, int *irq);
int acpi_pci_link_set_irq (struct acpi_prt_entry *entry, int irq);
int acpi_pci_link_init (void);
void acpi_pci_link_exit (void);
int acpi_pci_root_init (void);
void acpi_pci_root_exit (void);
#endif
/* --------------------------------------------------------------------------
Power Resource
-------------------------------------------------------------------------- */
#define ACPI_POWER_COMPONENT 0x01000000
#define ACPI_POWER_CLASS "power_resource"
#define ACPI_POWER_HID "ACPI_PWR"
#define ACPI_POWER_DRIVER_NAME "ACPI Power Resource Driver"
#define ACPI_POWER_DEVICE_NAME "Power Resource"
#define ACPI_POWER_FILE_INFO "info"
#define ACPI_POWER_FILE_STATUS "state"
#define ACPI_POWER_RESOURCE_STATE_OFF 0x00
#define ACPI_POWER_RESOURCE_STATE_ON 0x01
#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
#ifdef CONFIG_ACPI_POWER
int acpi_power_get_inferred_state (struct acpi_device *device);
int acpi_power_transition (struct acpi_device *device, int state);
int acpi_power_init (void);
void acpi_power_exit (void);
#endif
/* --------------------------------------------------------------------------
Processor
-------------------------------------------------------------------------- */
#define ACPI_PROCESSOR_COMPONENT 0x02000000
#define ACPI_PROCESSOR_CLASS "processor"
#define ACPI_PROCESSOR_HID "ACPI_CPU"
#define ACPI_PROCESSOR_DRIVER_NAME "ACPI Processor Driver"
#define ACPI_PROCESSOR_DEVICE_NAME "Processor"
#define ACPI_PROCESSOR_FILE_INFO "info"
#define ACPI_PROCESSOR_FILE_POWER "power"
#define ACPI_PROCESSOR_FILE_PERFORMANCE "performance"
#define ACPI_PROCESSOR_FILE_THROTTLING "throttling"
#define ACPI_PROCESSOR_FILE_LIMIT "limit"
#define ACPI_PROCESSOR_NOTIFY_PERFORMANCE 0x80
#define ACPI_PROCESSOR_NOTIFY_POWER 0x81
#define ACPI_PROCESSOR_LIMIT_NONE 0x00
#define ACPI_PROCESSOR_LIMIT_INCREMENT 0x01
#define ACPI_PROCESSOR_LIMIT_DECREMENT 0x02
int acpi_processor_set_limit(acpi_handle handle, int flags, int *state);
/* --------------------------------------------------------------------------
System
-------------------------------------------------------------------------- */
#define ACPI_SYSTEM_COMPONENT 0x04000000
#define ACPI_SYSTEM_CLASS "system"
#define ACPI_SYSTEM_HID "ACPI_SYS"
#define ACPI_SYSTEM_DRIVER_NAME "ACPI System Driver"
#define ACPI_SYSTEM_DEVICE_NAME "System"
#define ACPI_SYSTEM_FILE_INFO "info"
#define ACPI_SYSTEM_FILE_EVENT "event"
#define ACPI_SYSTEM_FILE_ALARM "alarm"
#define ACPI_SYSTEM_FILE_DSDT "dsdt"
#define ACPI_SYSTEM_FILE_FADT "fadt"
#define ACPI_SYSTEM_FILE_SLEEP "sleep"
#define ACPI_SYSTEM_FILE_DEBUG_LAYER "debug_layer"
#define ACPI_SYSTEM_FILE_DEBUG_LEVEL "debug_level"
#ifdef CONFIG_ACPI_SYSTEM
int acpi_system_init (void);
void acpi_system_exit (void);
#endif
/* --------------------------------------------------------------------------
Thermal Zone
-------------------------------------------------------------------------- */
#define ACPI_THERMAL_COMPONENT 0x08000000
#define ACPI_THERMAL_CLASS "thermal_zone"
#define ACPI_THERMAL_HID "ACPI_THM"
#define ACPI_THERMAL_DRIVER_NAME "ACPI Thermal Zone Driver"
#define ACPI_THERMAL_DEVICE_NAME "Thermal Zone"
#define ACPI_THERMAL_FILE_STATE "state"
#define ACPI_THERMAL_FILE_TEMPERATURE "temperature"
#define ACPI_THERMAL_FILE_TRIP_POINTS "trip_points"
#define ACPI_THERMAL_FILE_COOLING_MODE "cooling_mode"
#define ACPI_THERMAL_FILE_POLLING_FREQ "polling_frequency"
#define ACPI_THERMAL_NOTIFY_TEMPERATURE 0x80
#define ACPI_THERMAL_NOTIFY_THRESHOLDS 0x81
#define ACPI_THERMAL_NOTIFY_DEVICES 0x82
#define ACPI_THERMAL_NOTIFY_CRITICAL 0xF0
#define ACPI_THERMAL_NOTIFY_HOT 0xF1
#define ACPI_THERMAL_MODE_ACTIVE 0x00
#define ACPI_THERMAL_MODE_PASSIVE 0x01
#define ACPI_THERMAL_PATH_POWEROFF "/sbin/poweroff"
/* --------------------------------------------------------------------------
Debug Support
-------------------------------------------------------------------------- */
#define ACPI_DEBUG_RESTORE 0
#define ACPI_DEBUG_LOW 1
#define ACPI_DEBUG_MEDIUM 2
#define ACPI_DEBUG_HIGH 3
#define ACPI_DEBUG_DRIVERS 4
extern u32 acpi_dbg_level;
extern u32 acpi_dbg_layer;
static inline void
acpi_set_debug (
u32 flag)
{
static u32 layer_save;
static u32 level_save;
switch (flag) {
case ACPI_DEBUG_RESTORE:
acpi_dbg_layer = layer_save;
acpi_dbg_level = level_save;
break;
case ACPI_DEBUG_LOW:
case ACPI_DEBUG_MEDIUM:
case ACPI_DEBUG_HIGH:
case ACPI_DEBUG_DRIVERS:
layer_save = acpi_dbg_layer;
level_save = acpi_dbg_level;
break;
}
switch (flag) {
case ACPI_DEBUG_LOW:
acpi_dbg_layer = ACPI_COMPONENT_DEFAULT | ACPI_ALL_DRIVERS;
acpi_dbg_level = DEBUG_DEFAULT;
break;
case ACPI_DEBUG_MEDIUM:
acpi_dbg_layer = ACPI_COMPONENT_DEFAULT | ACPI_ALL_DRIVERS;
acpi_dbg_level = ACPI_LV_FUNCTIONS | ACPI_LV_ALL_EXCEPTIONS;
break;
case ACPI_DEBUG_HIGH:
acpi_dbg_layer = 0xFFFFFFFF;
acpi_dbg_level = 0xFFFFFFFF;
break;
case ACPI_DEBUG_DRIVERS:
acpi_dbg_layer = ACPI_ALL_DRIVERS;
acpi_dbg_level = 0xFFFFFFFF;
break;
}
}
#endif /*__ACPI_DRIVERS_H__*/
/*
* acpi_ec.c - ACPI Embedded Controller Driver ($Revision: 27 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_EC_COMPONENT
ACPI_MODULE_NAME ("acpi_ec")
#define PREFIX "ACPI: "
#define ACPI_EC_FLAG_OBF 0x01 /* Output buffer full */
#define ACPI_EC_FLAG_IBF 0x02 /* Input buffer full */
#define ACPI_EC_FLAG_SCI 0x20 /* EC-SCI occurred */
#define ACPI_EC_EVENT_OBF 0x01 /* Output buffer full */
#define ACPI_EC_EVENT_IBE 0x02 /* Input buffer empty */
#define ACPI_EC_UDELAY 100 /* Poll @ 100us increments */
#define ACPI_EC_UDELAY_COUNT 1000 /* Wait 10ms max. during EC ops */
#define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */
#define ACPI_EC_COMMAND_READ 0x80
#define ACPI_EC_COMMAND_WRITE 0x81
#define ACPI_EC_COMMAND_QUERY 0x84
static int acpi_ec_add (struct acpi_device *device);
static int acpi_ec_remove (struct acpi_device *device, int type);
static int acpi_ec_start (struct acpi_device *device);
static int acpi_ec_stop (struct acpi_device *device, int type);
static struct acpi_driver acpi_ec_driver = {
name: ACPI_EC_DRIVER_NAME,
class: ACPI_EC_CLASS,
ids: ACPI_EC_HID,
ops: {
add: acpi_ec_add,
remove: acpi_ec_remove,
start: acpi_ec_start,
stop: acpi_ec_stop,
},
};
struct acpi_ec {
acpi_handle handle;
unsigned long gpe_bit;
unsigned long status_port;
unsigned long command_port;
unsigned long data_port;
unsigned long global_lock;
spinlock_t lock;
};
/* --------------------------------------------------------------------------
Transaction Management
-------------------------------------------------------------------------- */
static int
acpi_ec_wait (
struct acpi_ec *ec,
u8 event)
{
u8 acpi_ec_status = 0;
u32 i = ACPI_EC_UDELAY_COUNT;
if (!ec)
return -EINVAL;
/* Poll the EC status register waiting for the event to occur. */
switch (event) {
case ACPI_EC_EVENT_OBF:
do {
acpi_ec_status = inb(ec->status_port);
if (acpi_ec_status & ACPI_EC_FLAG_OBF)
return 0;
udelay(ACPI_EC_UDELAY);
} while (--i>0);
break;
case ACPI_EC_EVENT_IBE:
do {
acpi_ec_status = inb(ec->status_port);
if (!(acpi_ec_status & ACPI_EC_FLAG_IBF))
return 0;
udelay(ACPI_EC_UDELAY);
} while (--i>0);
break;
default:
return -EINVAL;
}
return -ETIME;
}
static int
acpi_ec_read (
struct acpi_ec *ec,
u8 address,
u8 *data)
{
acpi_status status = AE_OK;
int result = 0;
unsigned long flags = 0;
u32 glk = 0;
ACPI_FUNCTION_TRACE("acpi_ec_read");
if (!ec || !data)
return_VALUE(-EINVAL);
*data = 0;
if (ec->global_lock) {
status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
}
spin_lock_irqsave(&ec->lock, flags);
outb(ACPI_EC_COMMAND_READ, ec->command_port);
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (0 != result)
goto end;
outb(address, ec->data_port);
result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF);
if (0 != result)
goto end;
*data = inb(ec->data_port);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Read [%02x] from address [%02x]\n",
*data, address));
end:
spin_unlock_irqrestore(&ec->lock, flags);
if (ec->global_lock)
acpi_release_global_lock(glk);
return_VALUE(result);
}
static int
acpi_ec_write (
struct acpi_ec *ec,
u8 address,
u8 data)
{
int result = 0;
acpi_status status = AE_OK;
unsigned long flags = 0;
u32 glk = 0;
ACPI_FUNCTION_TRACE("acpi_ec_write");
if (!ec)
return_VALUE(-EINVAL);
if (ec->global_lock) {
status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
}
spin_lock_irqsave(&ec->lock, flags);
outb(ACPI_EC_COMMAND_WRITE, ec->command_port);
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (0 != result)
goto end;
outb(address, ec->data_port);
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (0 != result)
goto end;
outb(data, ec->data_port);
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (0 != result)
goto end;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Wrote [%02x] to address [%02x]\n",
data, address));
end:
spin_unlock_irqrestore(&ec->lock, flags);
if (ec->global_lock)
acpi_release_global_lock(glk);
return_VALUE(result);
}
static int
acpi_ec_query (
struct acpi_ec *ec,
u8 *data)
{
int result = 0;
acpi_status status = AE_OK;
unsigned long flags = 0;
u32 glk = 0;
ACPI_FUNCTION_TRACE("acpi_ec_query");
if (!ec || !data)
return_VALUE(-EINVAL);
*data = 0;
if (ec->global_lock) {
status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
}
/*
* Query the EC to find out which _Qxx method we need to evaluate.
* Note that successful completion of the query causes the ACPI_EC_SCI
* bit to be cleared (and thus clearing the interrupt source).
*/
spin_lock_irqsave(&ec->lock, flags);
outb(ACPI_EC_COMMAND_QUERY, ec->command_port);
result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF);
if (0 != result)
goto end;
*data = inb(ec->data_port);
if (!*data)
result = -ENODATA;
end:
spin_unlock_irqrestore(&ec->lock, flags);
if (ec->global_lock)
acpi_release_global_lock(glk);
return_VALUE(result);
}
/* --------------------------------------------------------------------------
Event Management
-------------------------------------------------------------------------- */
struct acpi_ec_query_data {
acpi_handle handle;
u8 data;
};
static void
acpi_ec_gpe_query (
void *data)
{
struct acpi_ec_query_data *query_data = NULL;
static char object_name[5] = {'_','Q','0','0','\0'};
const char hex[] = {'0','1','2','3','4','5','6','7',
'8','9','A','B','C','D','E','F'};
ACPI_FUNCTION_TRACE("acpi_ec_gpe_query");
if (!data)
return;
query_data = (struct acpi_ec_query_data *) data;
object_name[2] = hex[((query_data->data >> 4) & 0x0F)];
object_name[3] = hex[(query_data->data & 0x0F)];
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluating %s\n", object_name));
acpi_evaluate(query_data->handle, object_name, NULL, NULL);
kfree(query_data);
return;
}
static void
acpi_ec_gpe_handler (
void *data)
{
acpi_status status = AE_OK;
struct acpi_ec *ec = (struct acpi_ec *) data;
u8 value = 0;
unsigned long flags = 0;
struct acpi_ec_query_data *query_data = NULL;
if (!ec)
return;
spin_lock_irqsave(&ec->lock, flags);
value = inb(ec->command_port);
spin_unlock_irqrestore(&ec->lock, flags);
/* TBD: Implement asynch events!
* NOTE: All we care about are EC-SCI's. Other EC events are
* handled via polling (yuck!). This is because some systems
* treat EC-SCIs as level (versus EDGE!) triggered, preventing
* a purely interrupt-driven approach (grumble, grumble).
*/
if (!(value & ACPI_EC_FLAG_SCI))
return;
if (0 != acpi_ec_query(ec, &value))
return;
query_data = kmalloc(sizeof(struct acpi_ec_query_data), GFP_KERNEL);
if (!query_data)
return;
query_data->handle = ec->handle;
query_data->data = value;
status = acpi_os_queue_for_execution(OSD_PRIORITY_GPE,
acpi_ec_gpe_query, query_data);
if (ACPI_FAILURE(status))
kfree(query_data);
return;
}
/* --------------------------------------------------------------------------
Address Space Management
-------------------------------------------------------------------------- */
static acpi_status
acpi_ec_space_setup (
acpi_handle region_handle,
u32 function,
void *handler_context,
void **return_context)
{
/*
* The EC object is in the handler context and is needed
* when calling the acpi_ec_space_handler.
*/
*return_context = handler_context;
return AE_OK;
}
static acpi_status
acpi_ec_space_handler (
u32 function,
ACPI_PHYSICAL_ADDRESS address,
u32 bit_width,
acpi_integer *value,
void *handler_context,
void *region_context)
{
int result = 0;
struct acpi_ec *ec = NULL;
ACPI_FUNCTION_TRACE("acpi_ec_space_handler");
if ((address > 0xFF) || (bit_width != 8) || !value || !handler_context)
return_VALUE(AE_BAD_PARAMETER);
ec = (struct acpi_ec *) handler_context;
switch (function) {
case ACPI_READ:
result = acpi_ec_read(ec, (u8) address, (u8*) value);
break;
case ACPI_WRITE:
result = acpi_ec_write(ec, (u8) address, (u8) *value);
break;
default:
result = -EINVAL;
break;
}
switch (result) {
case -EINVAL:
return_VALUE(AE_BAD_PARAMETER);
break;
case -ENODEV:
return_VALUE(AE_NOT_FOUND);
break;
case -ETIME:
return_VALUE(AE_TIME);
break;
default:
return_VALUE(AE_OK);
}
}
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_ec_dir = NULL;
static int
acpi_ec_read_info (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_ec *ec = (struct acpi_ec *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_ec_read_info");
if (!ec || (off != 0))
goto end;
p += sprintf(p, "gpe bit: 0x%02x\n",
(u32) ec->gpe_bit);
p += sprintf(p, "ports: 0x%02x, 0x%02x\n",
(u32) ec->status_port, (u32) ec->data_port);
p += sprintf(p, "use global lock: %s\n",
ec->global_lock?"yes":"no");
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_ec_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_ec_add_fs");
if (!acpi_ec_dir) {
acpi_ec_dir = proc_mkdir(ACPI_EC_CLASS, acpi_root_dir);
if (!acpi_ec_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_ec_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
entry = create_proc_read_entry(ACPI_EC_FILE_INFO, S_IRUGO,
acpi_device_dir(device), acpi_ec_read_info,
acpi_driver_data(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_WARN,
"Unable to create '%s' fs entry\n",
ACPI_EC_FILE_INFO));
return_VALUE(0);
}
static int
acpi_ec_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_ec_remove_fs");
if (!acpi_ec_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_ec_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
static int
acpi_ec_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_ec *ec = NULL;
ACPI_FUNCTION_TRACE("acpi_ec_add");
if (!device)
return_VALUE(-EINVAL);
ec = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
if (!ec)
return_VALUE(-ENOMEM);
memset(ec, 0, sizeof(struct acpi_ec));
ec->handle = device->handle;
ec->lock = SPIN_LOCK_UNLOCKED;
sprintf(acpi_device_name(device), "%s", ACPI_EC_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_EC_CLASS);
acpi_driver_data(device) = ec;
/* Use the global lock for all EC transactions? */
acpi_evaluate_integer(ec->handle, "_GLK", NULL, &ec->global_lock);
/* Get GPE bit assignment (EC events). */
status = acpi_evaluate_integer(ec->handle, "_GPE", NULL, &ec->gpe_bit);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error obtaining GPE bit assignment\n"));
result = -ENODEV;
goto end;
}
result = acpi_ec_add_fs(device);
if (0 != result)
goto end;
printk(KERN_INFO PREFIX "%s [%s] (gpe %d)\n",
acpi_device_name(device), acpi_device_bid(device),
(u32) ec->gpe_bit);
end:
if (0 != result)
kfree(ec);
return_VALUE(result);
}
static int
acpi_ec_remove (
struct acpi_device *device,
int type)
{
struct acpi_ec *ec = NULL;
ACPI_FUNCTION_TRACE("acpi_ec_remove");
if (!device)
return_VALUE(-EINVAL);
ec = (struct acpi_ec *) acpi_driver_data(device);
acpi_ec_remove_fs(device);
kfree(ec);
return_VALUE(0);
}
static int
acpi_ec_start (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_ec *ec = NULL;
acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
acpi_resource *resource = NULL;
ACPI_FUNCTION_TRACE("acpi_ec_start");
if (!device)
return_VALUE(-EINVAL);
ec = (struct acpi_ec *) acpi_driver_data(device);
if (!ec)
return_VALUE(-EINVAL);
/*
* Get I/O port addresses
*/
status = acpi_get_current_resources(ec->handle, &buffer);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error getting I/O port addresses"));
return_VALUE(-ENODEV);
}
resource = (acpi_resource *) buffer.pointer;
if (!resource || (resource->id != ACPI_RSTYPE_IO)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid or missing resource\n"));
result = -ENODEV;
goto end;
}
ec->data_port = resource->data.io.min_base_address;
resource = ACPI_NEXT_RESOURCE(resource);
if (!resource || (resource->id != ACPI_RSTYPE_IO)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid or missing resource\n"));
result = -ENODEV;
goto end;
}
ec->command_port = ec->status_port = resource->data.io.min_base_address;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02x, ports=0x%2x,0x%2x\n",
(u32) ec->gpe_bit, (u32) ec->command_port, (u32) ec->data_port));
/*
* Install GPE handler
*/
status = acpi_install_gpe_handler(ec->gpe_bit,
ACPI_EVENT_EDGE_TRIGGERED, &acpi_ec_gpe_handler, ec);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
status = acpi_install_address_space_handler (ec->handle,
ACPI_ADR_SPACE_EC, &acpi_ec_space_handler,
&acpi_ec_space_setup, ec);
if (ACPI_FAILURE(status)) {
acpi_remove_address_space_handler(ec->handle,
ACPI_ADR_SPACE_EC, &acpi_ec_space_handler);
return_VALUE(-ENODEV);
}
end:
kfree(buffer.pointer);
return_VALUE(result);
}
static int
acpi_ec_stop (
struct acpi_device *device,
int type)
{
acpi_status status = AE_OK;
struct acpi_ec *ec = NULL;
ACPI_FUNCTION_TRACE("acpi_ec_stop");
if (!device)
return_VALUE(-EINVAL);
ec = (struct acpi_ec *) acpi_driver_data(device);
status = acpi_remove_address_space_handler(ec->handle,
ACPI_ADR_SPACE_EC, &acpi_ec_space_handler);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
status = acpi_remove_gpe_handler(ec->gpe_bit, &acpi_ec_gpe_handler);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
return_VALUE(0);
}
int __init
acpi_ec_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_ec_init");
result = acpi_bus_register_driver(&acpi_ec_driver);
if (0 > result) {
remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
void __exit
acpi_ec_exit (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_ec_exit");
result = acpi_bus_unregister_driver(&acpi_ec_driver);
if (0 == result)
remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
return_VOID;
}
/*
* acpi_fan.c - ACPI Fan Driver ($Revision: 25 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_FAN_COMPONENT
ACPI_MODULE_NAME ("acpi_fan")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_FAN_DRIVER_NAME);
MODULE_LICENSE("GPL");
#define PREFIX "ACPI: "
int acpi_fan_add (struct acpi_device *device);
int acpi_fan_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_fan_driver = {
name: ACPI_FAN_DRIVER_NAME,
class: ACPI_FAN_CLASS,
ids: ACPI_FAN_HID,
ops: {
add: acpi_fan_add,
remove: acpi_fan_remove,
},
};
struct acpi_fan {
acpi_handle handle;
};
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_fan_dir = NULL;
static int
acpi_fan_read_state (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_fan *fan = (struct acpi_fan *) data;
char *p = page;
int len = 0;
int state = 0;
ACPI_FUNCTION_TRACE("acpi_fan_read_state");
if (!fan || (off != 0))
goto end;
if (0 != acpi_bus_get_power(fan->handle, &state))
goto end;
p += sprintf(p, "status: %s\n",
!state?"on":"off");
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_fan_write_state (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
struct acpi_fan *fan = (struct acpi_fan *) data;
char state_string[12] = {'\0'};
ACPI_FUNCTION_TRACE("acpi_fan_write_state");
if (!fan || (count > sizeof(state_string) - 1))
return_VALUE(-EINVAL);
if (copy_from_user(state_string, buffer, count))
return_VALUE(-EFAULT);
state_string[count] = '\0';
result = acpi_bus_set_power(fan->handle,
simple_strtoul(state_string, NULL, 0));
if (0 != result)
return_VALUE(result);
return_VALUE(count);
}
static int
acpi_fan_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_fan_add_fs");
if (!device)
return_VALUE(-EINVAL);
if (!acpi_fan_dir) {
acpi_fan_dir = proc_mkdir(ACPI_FAN_CLASS, acpi_root_dir);
if (!acpi_fan_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_fan_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
/* 'status' [R/W] */
entry = create_proc_entry(ACPI_FAN_FILE_STATE,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_FAN_FILE_STATE));
else {
entry->read_proc = acpi_fan_read_state;
entry->write_proc = acpi_fan_write_state;
entry->data = acpi_driver_data(device);
}
return_VALUE(0);
}
static int
acpi_fan_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_fan_remove_fs");
if (!acpi_fan_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_fan_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
int
acpi_fan_add (
struct acpi_device *device)
{
int result = 0;
struct acpi_fan *fan = NULL;
int state = 0;
ACPI_FUNCTION_TRACE("acpi_fan_add");
if (!device)
return_VALUE(-EINVAL);
fan = kmalloc(sizeof(struct acpi_fan), GFP_KERNEL);
if (!fan)
return_VALUE(-ENOMEM);
memset(fan, 0, sizeof(struct acpi_fan));
fan->handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_FAN_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_FAN_CLASS);
acpi_driver_data(device) = fan;
result = acpi_bus_get_power(fan->handle, &state);
if (0 != result) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error reading power state\n"));
goto end;
}
result = acpi_fan_add_fs(device);
if (0 != result)
goto end;
printk(KERN_INFO PREFIX "%s [%s] (%s)\n",
acpi_device_name(device), acpi_device_bid(device),
!device->power.state?"on":"off");
end:
if (0 != result)
kfree(fan);
return_VALUE(result);
}
int
acpi_fan_remove (
struct acpi_device *device,
int type)
{
struct acpi_fan *fan = NULL;
ACPI_FUNCTION_TRACE("acpi_fan_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
fan = (struct acpi_fan *) acpi_driver_data(device);
acpi_fan_remove_fs(device);
kfree(fan);
return_VALUE(0);
}
int __init
acpi_fan_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_fan_init");
result = acpi_bus_register_driver(&acpi_fan_driver);
if (0 > result)
return_VALUE(-ENODEV);
return_VALUE(0);
}
void __exit
acpi_fan_exit (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_fan_exit");
result = acpi_bus_unregister_driver(&acpi_fan_driver);
if (0 == result)
remove_proc_entry(ACPI_FAN_CLASS, acpi_root_dir);
return_VOID;
}
module_init(acpi_fan_init);
module_exit(acpi_fan_exit);
/*
* ksyms.c - ACPI exported symbols
* acpi_ksyms.c - ACPI Kernel Symbols ($Revision: 13 $)
*
* Copyright (C) 2000 Andrew Grover
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/acpi.h>
#include "acpi.h"
#include "acdebug.h"
#include "include/acpi.h"
#include "acpi_bus.h"
extern int acpi_in_debugger;
extern FADT_DESCRIPTOR acpi_fadt;
#ifdef CONFIG_ACPI_INTERPRETER
#define _COMPONENT OS_DEPENDENT
MODULE_NAME ("symbols")
/* ACPI Debugger */
#ifdef ENABLE_DEBUGGER
extern int acpi_in_debugger;
EXPORT_SYMBOL(acpi_in_debugger);
EXPORT_SYMBOL(acpi_db_user_commands);
#endif
#endif /* ENABLE_DEBUGGER */
/* ACPI Core Subsystem */
#ifdef ACPI_DEBUG
EXPORT_SYMBOL(acpi_dbg_layer);
EXPORT_SYMBOL(acpi_dbg_level);
EXPORT_SYMBOL(acpi_ut_debug_print_raw);
EXPORT_SYMBOL(acpi_ut_debug_print);
EXPORT_SYMBOL(acpi_ut_status_exit);
EXPORT_SYMBOL(acpi_ut_value_exit);
EXPORT_SYMBOL(acpi_ut_exit);
EXPORT_SYMBOL(acpi_ut_trace);
#endif
EXPORT_SYMBOL(acpi_gbl_FADT);
EXPORT_SYMBOL(acpi_os_free);
EXPORT_SYMBOL(acpi_os_printf);
EXPORT_SYMBOL(acpi_os_callocate);
EXPORT_SYMBOL(acpi_os_sleep);
EXPORT_SYMBOL(acpi_os_stall);
EXPORT_SYMBOL(acpi_os_queue_for_execution);
EXPORT_SYMBOL(acpi_dbg_layer);
EXPORT_SYMBOL(acpi_dbg_level);
EXPORT_SYMBOL(acpi_format_exception);
#endif /*ACPI_DEBUG*/
EXPORT_SYMBOL(acpi_get_handle);
EXPORT_SYMBOL(acpi_get_parent);
......@@ -68,7 +62,6 @@ EXPORT_SYMBOL(acpi_get_object_info);
EXPORT_SYMBOL(acpi_get_next_object);
EXPORT_SYMBOL(acpi_evaluate_object);
EXPORT_SYMBOL(acpi_get_table);
EXPORT_SYMBOL(acpi_install_notify_handler);
EXPORT_SYMBOL(acpi_remove_notify_handler);
EXPORT_SYMBOL(acpi_install_gpe_handler);
......@@ -77,39 +70,61 @@ EXPORT_SYMBOL(acpi_install_address_space_handler);
EXPORT_SYMBOL(acpi_remove_address_space_handler);
EXPORT_SYMBOL(acpi_install_fixed_event_handler);
EXPORT_SYMBOL(acpi_remove_fixed_event_handler);
EXPORT_SYMBOL(acpi_acquire_global_lock);
EXPORT_SYMBOL(acpi_release_global_lock);
EXPORT_SYMBOL(acpi_get_current_resources);
EXPORT_SYMBOL(acpi_get_possible_resources);
EXPORT_SYMBOL(acpi_set_current_resources);
EXPORT_SYMBOL(acpi_enable_event);
EXPORT_SYMBOL(acpi_disable_event);
EXPORT_SYMBOL(acpi_clear_event);
EXPORT_SYMBOL(acpi_get_timer_duration);
EXPORT_SYMBOL(acpi_get_timer);
EXPORT_SYMBOL(acpi_hw_get_sleep_type_data);
EXPORT_SYMBOL(acpi_hw_bit_register_read);
EXPORT_SYMBOL(acpi_hw_bit_register_write);
EXPORT_SYMBOL(acpi_enter_sleep_state);
EXPORT_SYMBOL(acpi_get_system_info);
/* ACPI OS Services Layer (acpi_osl.c) */
EXPORT_SYMBOL(acpi_os_free);
EXPORT_SYMBOL(acpi_os_printf);
EXPORT_SYMBOL(acpi_os_sleep);
EXPORT_SYMBOL(acpi_os_stall);
EXPORT_SYMBOL(acpi_os_signal);
EXPORT_SYMBOL(acpi_os_queue_for_execution);
EXPORT_SYMBOL(acpi_os_signal_semaphore);
EXPORT_SYMBOL(acpi_os_create_semaphore);
EXPORT_SYMBOL(acpi_os_delete_semaphore);
EXPORT_SYMBOL(acpi_os_wait_semaphore);
EXPORT_SYMBOL(acpi_os_read_port);
EXPORT_SYMBOL(acpi_os_write_port);
/* ACPI Utilities (acpi_utils.c) */
EXPORT_SYMBOL(acpi_extract_package);
EXPORT_SYMBOL(acpi_evaluate);
EXPORT_SYMBOL(acpi_evaluate_integer);
EXPORT_SYMBOL(acpi_evaluate_reference);
#endif /*CONFIG_ACPI_INTERPRETER*/
/* ACPI Bus Driver (acpi_bus.c) */
#ifdef CONFIG_ACPI_BUS
EXPORT_SYMBOL(acpi_fadt);
EXPORT_SYMBOL(acpi_hw_register_bit_access);
EXPORT_SYMBOL(acpi_hw_obtain_sleep_type_register_data);
EXPORT_SYMBOL(acpi_enter_sleep_state);
EXPORT_SYMBOL(acpi_get_system_info);
EXPORT_SYMBOL(acpi_leave_sleep_state);
/*EXPORT_SYMBOL(acpi_save_state_mem);*/
/*EXPORT_SYMBOL(acpi_save_state_disk);*/
EXPORT_SYMBOL(acpi_hw_register_read);
EXPORT_SYMBOL(acpi_set_firmware_waking_vector);
EXPORT_SYMBOL(acpi_subsystem_status);
EXPORT_SYMBOL(acpi_root_dir);
EXPORT_SYMBOL(acpi_bus_get_device);
EXPORT_SYMBOL(acpi_bus_get_status);
EXPORT_SYMBOL(acpi_bus_get_power);
EXPORT_SYMBOL(acpi_bus_set_power);
EXPORT_SYMBOL(acpi_bus_generate_event);
EXPORT_SYMBOL(acpi_bus_receive_event);
EXPORT_SYMBOL(acpi_bus_register_driver);
EXPORT_SYMBOL(acpi_bus_unregister_driver);
EXPORT_SYMBOL(acpi_bus_scan);
EXPORT_SYMBOL(acpi_init);
#endif /*CONFIG_ACPI_BUS*/
EXPORT_SYMBOL(acpi_os_signal);
/*
* acpi_osl.c - OS-dependent functions ($Revision: 65 $)
*
* Copyright (C) 2000 Andrew Henroid
* Copyright (C) 2001 Andrew Grover
* Copyright (C) 2001 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/kmod.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "acpi.h"
#ifdef CONFIG_ACPI_EFI
#include <asm/efi.h>
#endif
#ifdef _IA64
#include <asm/hw_irq.h>
#include <asm/delay.h>
#endif
#define _COMPONENT ACPI_OS_SERVICES
ACPI_MODULE_NAME ("osl")
#define PREFIX "ACPI: "
typedef struct
{
OSD_EXECUTION_CALLBACK function;
void *context;
} ACPI_OS_DPC;
#ifdef ENABLE_DEBUGGER
#include <linux/kdb.h>
/* stuff for debugger support */
int acpi_in_debugger = 0;
extern NATIVE_CHAR line_buf[80];
#endif /*ENABLE_DEBUGGER*/
static int acpi_irq_irq = 0;
static OSD_HANDLER acpi_irq_handler = NULL;
static void *acpi_irq_context = NULL;
acpi_status
acpi_os_initialize(void)
{
/*
* Initialize PCI configuration space access, as we'll need to access
* it while walking the namespace (bus 0 and root bridges w/ _BBNs).
*/
pcibios_config_init();
if (!pci_config_read || !pci_config_write) {
printk(KERN_ERR PREFIX "Access to PCI configuration space unavailable\n");
return AE_NULL_ENTRY;
}
return AE_OK;
}
acpi_status
acpi_os_terminate(void)
{
if (acpi_irq_handler) {
acpi_os_remove_interrupt_handler(acpi_irq_irq,
acpi_irq_handler);
}
return AE_OK;
}
void
acpi_os_printf(const NATIVE_CHAR *fmt,...)
{
va_list args;
va_start(args, fmt);
acpi_os_vprintf(fmt, args);
va_end(args);
}
void
acpi_os_vprintf(const NATIVE_CHAR *fmt, va_list args)
{
static char buffer[512];
vsprintf(buffer, fmt, args);
#ifdef ENABLE_DEBUGGER
if (acpi_in_debugger) {
kdb_printf("%s", buffer);
} else {
printk("%s", buffer);
}
#else
printk("%s", buffer);
#endif
}
void *
acpi_os_allocate(u32 size)
{
return kmalloc(size, GFP_KERNEL);
}
void *
acpi_os_callocate(u32 size)
{
void *ptr = acpi_os_allocate(size);
if (ptr)
memset(ptr, 0, size);
return ptr;
}
void
acpi_os_free(void *ptr)
{
kfree(ptr);
}
acpi_status
acpi_os_get_root_pointer(u32 flags, ACPI_PHYSICAL_ADDRESS *phys_addr)
{
#ifndef CONFIG_ACPI_EFI
if (ACPI_FAILURE(acpi_find_root_pointer(flags, phys_addr))) {
printk(KERN_ERR PREFIX "System description tables not found\n");
return AE_NOT_FOUND;
}
#else /*CONFIG_ACPI_EFI*/
if (efi.acpi20)
*phys_addr = (ACPI_PHYSICAL_ADDRESS) efi.acpi20;
else if (efi.acpi)
*phys_addr = (ACPI_PHYSICAL_ADDRESS) efi.acpi;
else {
printk(KERN_ERR PREFIX "System description tables not found\n");
*phys_addr = 0;
return AE_NOT_FOUND;
}
#endif /*CONFIG_ACPI_EFI*/
return AE_OK;
}
acpi_status
acpi_os_map_memory(ACPI_PHYSICAL_ADDRESS phys, u32 size, void **virt)
{
if (phys > ULONG_MAX) {
printk(KERN_ERR PREFIX "Cannot map memory that high\n");
return AE_BAD_PARAMETER;
}
/*
* ioremap already checks to ensure this is in reserved space
*/
*virt = ioremap((unsigned long) phys, size);
if (!*virt)
return AE_NO_MEMORY;
return AE_OK;
}
void
acpi_os_unmap_memory(void *virt, u32 size)
{
iounmap(virt);
}
acpi_status
acpi_os_get_physical_address(void *virt, ACPI_PHYSICAL_ADDRESS *phys)
{
if(!phys || !virt)
return AE_BAD_PARAMETER;
*phys = virt_to_phys(virt);
return AE_OK;
}
static void
acpi_irq(int irq, void *dev_id, struct pt_regs *regs)
{
(*acpi_irq_handler)(acpi_irq_context);
}
acpi_status
acpi_os_install_interrupt_handler(u32 irq, OSD_HANDLER handler, void *context)
{
#ifdef _IA64
irq = isa_irq_to_vector(irq);
#endif /*_IA64*/
acpi_irq_irq = irq;
acpi_irq_handler = handler;
acpi_irq_context = context;
if (request_irq(irq, acpi_irq, SA_SHIRQ, "acpi", acpi_irq)) {
printk(KERN_ERR PREFIX "SCI (IRQ%d) allocation failed\n", irq);
return AE_NOT_ACQUIRED;
}
return AE_OK;
}
acpi_status
acpi_os_remove_interrupt_handler(u32 irq, OSD_HANDLER handler)
{
if (acpi_irq_handler) {
#ifdef _IA64
irq = isa_irq_to_vector(irq);
#endif /*_IA64*/
free_irq(irq, acpi_irq);
acpi_irq_handler = NULL;
}
return AE_OK;
}
/*
* Running in interpreter thread context, safe to sleep
*/
void
acpi_os_sleep(u32 sec, u32 ms)
{
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(HZ * sec + (ms * HZ) / 1000);
}
void
acpi_os_stall(u32 us)
{
if (us > 10000) {
mdelay(us / 1000);
}
else {
udelay(us);
}
}
acpi_status
acpi_os_read_port(
ACPI_IO_ADDRESS port,
void *value,
u32 width)
{
u32 dummy;
if (!value)
value = &dummy;
switch (width)
{
case 8:
*(u8*) value = inb(port);
break;
case 16:
*(u16*) value = inw(port);
break;
case 32:
*(u32*) value = inl(port);
break;
default:
BUG();
}
return AE_OK;
}
acpi_status
acpi_os_write_port(
ACPI_IO_ADDRESS port,
acpi_integer value,
u32 width)
{
switch (width)
{
case 8:
outb(value, port);
break;
case 16:
outw(value, port);
break;
case 32:
outl(value, port);
break;
default:
BUG();
}
return AE_OK;
}
acpi_status
acpi_os_read_memory(
ACPI_PHYSICAL_ADDRESS phys_addr,
void *value,
u32 width)
{
u32 dummy;
if (!value)
value = &dummy;
switch (width)
{
case 8:
*(u8*) value = *(u8*) phys_to_virt(phys_addr);
break;
case 16:
*(u16*) value = *(u16*) phys_to_virt(phys_addr);
break;
case 32:
*(u32*) value = *(u32*) phys_to_virt(phys_addr);
break;
default:
BUG();
}
return AE_OK;
}
acpi_status
acpi_os_write_memory(
ACPI_PHYSICAL_ADDRESS phys_addr,
acpi_integer value,
u32 width)
{
switch (width)
{
case 8:
*(u8*) phys_to_virt(phys_addr) = value;
break;
case 16:
*(u16*) phys_to_virt(phys_addr) = value;
break;
case 32:
*(u32*) phys_to_virt(phys_addr) = value;
break;
default:
BUG();
}
return AE_OK;
}
acpi_status
acpi_os_read_pci_configuration (
acpi_pci_id *pci_id,
u32 reg,
void *value,
u32 width)
{
int result = 0;
if (!value)
return AE_BAD_PARAMETER;
switch (width)
{
case 8:
result = pci_config_read(pci_id->segment, pci_id->bus,
pci_id->device, pci_id->function, reg, 1, value);
break;
case 16:
result = pci_config_read(pci_id->segment, pci_id->bus,
pci_id->device, pci_id->function, reg, 2, value);
break;
case 32:
result = pci_config_read(pci_id->segment, pci_id->bus,
pci_id->device, pci_id->function, reg, 4, value);
break;
default:
BUG();
}
return (result ? AE_ERROR : AE_OK);
}
acpi_status
acpi_os_write_pci_configuration (
acpi_pci_id *pci_id,
u32 reg,
acpi_integer value,
u32 width)
{
int result = 0;
switch (width)
{
case 8:
result = pci_config_write(pci_id->segment, pci_id->bus,
pci_id->device, pci_id->function, reg, 1, value);
break;
case 16:
result = pci_config_write(pci_id->segment, pci_id->bus,
pci_id->device, pci_id->function, reg, 2, value);
break;
case 32:
result = pci_config_write(pci_id->segment, pci_id->bus,
pci_id->device, pci_id->function, reg, 4, value);
break;
default:
BUG();
}
return (result ? AE_ERROR : AE_OK);
}
acpi_status
acpi_os_load_module (
char *module_name)
{
ACPI_FUNCTION_TRACE ("os_load_module");
if (!module_name)
return AE_BAD_PARAMETER;
if (0 > request_module(module_name)) {
ACPI_DEBUG_PRINT ((ACPI_DB_WARN, "Unable to load module [%s].\n", module_name));
return AE_ERROR;
}
return AE_OK;
}
acpi_status
acpi_os_unload_module (
char *module_name)
{
if (!module_name)
return AE_BAD_PARAMETER;
/* TODO: How on Linux? */
/* this is done automatically for all modules with
use_count = 0, I think. see: MOD_INC_USE_COUNT -ASG */
return AE_OK;
}
/*
* See acpi_os_queue_for_execution()
*/
static int
acpi_os_queue_exec (
void *context)
{
ACPI_OS_DPC *dpc = (ACPI_OS_DPC*)context;
ACPI_FUNCTION_TRACE ("os_queue_exec");
daemonize();
strcpy(current->comm, "kacpidpc");
if (!dpc || !dpc->function)
return AE_BAD_PARAMETER;
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Executing function [%p(%p)].\n", dpc->function, dpc->context));
dpc->function(dpc->context);
kfree(dpc);
return 1;
}
static void
acpi_os_schedule_exec (
void *context)
{
ACPI_OS_DPC *dpc = NULL;
int thread_pid = -1;
ACPI_FUNCTION_TRACE ("os_schedule_exec");
dpc = (ACPI_OS_DPC*)context;
if (!dpc) {
ACPI_DEBUG_PRINT ((ACPI_DB_ERROR, "Invalid (NULL) context.\n"));
return;
}
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Creating new thread to run function [%p(%p)].\n", dpc->function, dpc->context));
thread_pid = kernel_thread(acpi_os_queue_exec, dpc,
(CLONE_FS | CLONE_FILES | SIGCHLD));
if (thread_pid < 0) {
ACPI_DEBUG_PRINT ((ACPI_DB_ERROR, "Call to kernel_thread() failed.\n"));
acpi_os_free(dpc);
}
}
acpi_status
acpi_os_queue_for_execution(
u32 priority,
OSD_EXECUTION_CALLBACK function,
void *context)
{
acpi_status status = AE_OK;
ACPI_OS_DPC *dpc = NULL;
ACPI_FUNCTION_TRACE ("os_queue_for_execution");
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Scheduling function [%p(%p)] for deferred execution.\n", function, context));
if (!function)
return AE_BAD_PARAMETER;
/*
* Queue via DPC:
* --------------
* Note that we have to use two different processes for queuing DPCs:
* Interrupt-Level: Use schedule_task; can't spawn a new thread.
* Kernel-Level: Spawn a new kernel thread, as schedule_task has
* its limitations (e.g. single-threaded model), and
* all other task queues run at interrupt-level.
*/
switch (priority) {
case OSD_PRIORITY_GPE:
{
static struct tq_struct task;
/*
* Allocate/initialize DPC structure. Note that this memory will be
* freed by the callee.
*/
dpc = kmalloc(sizeof(ACPI_OS_DPC), GFP_ATOMIC);
if (!dpc)
return AE_NO_MEMORY;
dpc->function = function;
dpc->context = context;
memset(&task, 0, sizeof(struct tq_struct));
task.routine = acpi_os_schedule_exec;
task.data = (void*)dpc;
if (schedule_task(&task) < 0) {
ACPI_DEBUG_PRINT ((ACPI_DB_ERROR, "Call to schedule_task() failed.\n"));
status = AE_ERROR;
}
}
break;
default:
/*
* Allocate/initialize DPC structure. Note that this memory will be
* freed by the callee.
*/
dpc = kmalloc(sizeof(ACPI_OS_DPC), GFP_KERNEL);
if (!dpc)
return AE_NO_MEMORY;
dpc->function = function;
dpc->context = context;
acpi_os_schedule_exec(dpc);
break;
}
return status;
}
acpi_status
acpi_os_create_semaphore(
u32 max_units,
u32 initial_units,
acpi_handle *handle)
{
struct semaphore *sem = NULL;
ACPI_FUNCTION_TRACE ("os_create_semaphore");
sem = acpi_os_callocate(sizeof(struct semaphore));
if (!sem)
return AE_NO_MEMORY;
sema_init(sem, initial_units);
*handle = (acpi_handle*)sem;
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Creating semaphore[%p|%d].\n", *handle, initial_units));
return AE_OK;
}
/*
* TODO: A better way to delete semaphores? Linux doesn't have a
* 'delete_semaphore()' function -- may result in an invalid
* pointer dereference for non-synchronized consumers. Should
* we at least check for blocked threads and signal/cancel them?
*/
acpi_status
acpi_os_delete_semaphore(
acpi_handle handle)
{
struct semaphore *sem = (struct semaphore*) handle;
ACPI_FUNCTION_TRACE ("os_delete_semaphore");
if (!sem)
return AE_BAD_PARAMETER;
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Deleting semaphore[%p].\n", handle));
acpi_os_free(sem); sem = NULL;
return AE_OK;
}
/*
* TODO: The kernel doesn't have a 'down_timeout' function -- had to
* improvise. The process is to sleep for one scheduler quantum
* until the semaphore becomes available. Downside is that this
* may result in starvation for timeout-based waits when there's
* lots of semaphore activity.
*
* TODO: Support for units > 1?
*/
acpi_status
acpi_os_wait_semaphore(
acpi_handle handle,
u32 units,
u32 timeout)
{
acpi_status status = AE_OK;
struct semaphore *sem = (struct semaphore*)handle;
int ret = 0;
ACPI_FUNCTION_TRACE ("os_wait_semaphore");
if (!sem || (units < 1))
return AE_BAD_PARAMETER;
if (units > 1)
return AE_SUPPORT;
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Waiting for semaphore[%p|%d|%d]\n", handle, units, timeout));
switch (timeout)
{
/*
* No Wait:
* --------
* A zero timeout value indicates that we shouldn't wait - just
* acquire the semaphore if available otherwise return AE_TIME
* (a.k.a. 'would block').
*/
case 0:
if(down_trylock(sem))
status = AE_TIME;
break;
/*
* Wait Indefinitely:
* ------------------
*/
case WAIT_FOREVER:
ret = down_interruptible(sem);
if (ret < 0)
status = AE_ERROR;
break;
/*
* Wait w/ Timeout:
* ----------------
*/
default:
// TODO: A better timeout algorithm?
{
int i = 0;
static const int quantum_ms = 1000/HZ;
ret = down_trylock(sem);
for (i = timeout; (i > 0 && ret < 0); i -= quantum_ms) {
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(1);
ret = down_trylock(sem);
}
if (ret != 0)
status = AE_TIME;
}
break;
}
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Failed to acquire semaphore[%p|%d|%d]\n", handle, units, timeout));
}
else {
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Acquired semaphore[%p|%d|%d]\n", handle, units, timeout));
}
return status;
}
/*
* TODO: Support for units > 1?
*/
acpi_status
acpi_os_signal_semaphore(
acpi_handle handle,
u32 units)
{
struct semaphore *sem = (struct semaphore *) handle;
ACPI_FUNCTION_TRACE ("os_signal_semaphore");
if (!sem || (units < 1))
return AE_BAD_PARAMETER;
if (units > 1)
return AE_SUPPORT;
ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Signaling semaphore[%p|%d]\n", handle, units));
up(sem);
return AE_OK;
}
u32
acpi_os_get_line(NATIVE_CHAR *buffer)
{
#ifdef ENABLE_DEBUGGER
if (acpi_in_debugger) {
u32 chars;
kdb_read(buffer, sizeof(line_buf));
/* remove the CR kdb includes */
chars = strlen(buffer) - 1;
buffer[chars] = '\0';
}
#endif
return 0;
}
/*
* We just have to assume we're dealing with valid memory
*/
BOOLEAN
acpi_os_readable(void *ptr, u32 len)
{
return 1;
}
BOOLEAN
acpi_os_writable(void *ptr, u32 len)
{
return 1;
}
u32
acpi_os_get_thread_id (void)
{
if (!in_interrupt())
return current->pid;
return 0;
}
acpi_status
acpi_os_signal (
u32 function,
void *info)
{
switch (function)
{
case ACPI_SIGNAL_FATAL:
printk(KERN_ERR PREFIX "Fatal opcode executed\n");
break;
case ACPI_SIGNAL_BREAKPOINT:
{
char *bp_info = (char*) info;
printk(KERN_ERR "ACPI breakpoint: %s\n", bp_info);
}
default:
break;
}
return AE_OK;
}
acpi_status
acpi_os_breakpoint(NATIVE_CHAR *msg)
{
acpi_os_printf("breakpoint: %s", msg);
return AE_OK;
}
/*
* acpi_pci_link.c - ACPI PCI Interrupt Link Device Driver ($Revision: 19 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* TBD:
* 1. Support more than one IRQ resource entry per link device.
* 2. Implement start/stop mechanism and use ACPI Bus Driver facilities
* for IRQ management (e.g. start()->_SRS).
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/pm.h>
#include <linux/pci.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_PCI_LINK_COMPONENT
ACPI_MODULE_NAME ("acpi_pci_link")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_PCI_DRIVER_NAME);
MODULE_LICENSE("GPL");
#define PREFIX "ACPI: "
#define ACPI_PCI_LINK_MAX_IRQS 16
static int acpi_pci_link_add (struct acpi_device *device);
static int acpi_pci_link_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_pci_link_driver = {
name: ACPI_PCI_LINK_DRIVER_NAME,
class: ACPI_PCI_LINK_CLASS,
ids: ACPI_PCI_LINK_HID,
ops: {
add: acpi_pci_link_add,
remove: acpi_pci_link_remove,
},
};
struct acpi_pci_link_irq {
u8 active; /* Current IRQ */
u8 possible_count;
u8 possible[ACPI_PCI_LINK_MAX_IRQS];
struct {
u8 valid:1;
u8 enabled:1;
u8 shareable:1; /* 0 = Exclusive */
u8 polarity:1; /* 0 = Active-High */
u8 trigger:1; /* 0 = Level-Triggered */
u8 producer:1; /* 0 = Consumer-Only */
u8 reserved:2;
} flags;
};
struct acpi_pci_link {
acpi_handle handle;
struct acpi_pci_link_irq irq;
};
/* --------------------------------------------------------------------------
PCI Link Device Management
-------------------------------------------------------------------------- */
static int
acpi_pci_link_get_current (
struct acpi_pci_link *link)
{
int result = 0;
acpi_status status = AE_OK;
acpi_buffer buffer = {0, NULL};
acpi_resource *resource = NULL;
ACPI_FUNCTION_TRACE("acpi_pci_link_get_current");
if (!link || !link->handle)
return_VALUE(-EINVAL);
link->irq.active = 0;
status = acpi_get_current_resources(link->handle, &buffer);
if (status != AE_BUFFER_OVERFLOW) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error evaluating _CRS\n"));
return_VALUE(-ENODEV);
}
buffer.pointer = kmalloc(buffer.length, GFP_KERNEL);
if (!buffer.pointer)
return_VALUE(-ENOMEM);
memset(buffer.pointer, 0, buffer.length);
status = acpi_get_current_resources(link->handle, &buffer);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error evaluating _CRS\n"));
result = -ENODEV;
goto end;
}
resource = (acpi_resource *) buffer.pointer;
switch (resource->id) {
case ACPI_RSTYPE_IRQ:
{
acpi_resource_irq *p = &resource->data.irq;
if (p->number_of_interrupts > 0)
link->irq.active = p->interrupts[0];
else {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Blank IRQ resource\n"));
result = -ENODEV;
goto end;
}
break;
}
case ACPI_RSTYPE_EXT_IRQ:
{
acpi_resource_ext_irq *p = &resource->data.extended_irq;
if (p->number_of_interrupts > 0)
link->irq.active = p->interrupts[0];
else {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Blank IRQ resource\n"));
result = -ENODEV;
goto end;
}
break;
}
default:
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"First resource is not an IRQ entry\n"));
break;
}
end:
kfree(buffer.pointer);
return_VALUE(0);
}
static int
acpi_pci_link_get_possible (
struct acpi_pci_link *link)
{
int result = 0;
acpi_status status = AE_OK;
acpi_buffer buffer = {0, NULL};
acpi_resource *resource = NULL;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_pci_link_get_possible");
if (!link)
return_VALUE(-EINVAL);
status = acpi_get_possible_resources(link->handle, &buffer);
if (status != AE_BUFFER_OVERFLOW) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error evaluating _PRS\n"));
return_VALUE(-ENODEV);
}
buffer.pointer = kmalloc(buffer.length, GFP_KERNEL);
if (!buffer.pointer)
return_VALUE(-ENOMEM);
memset(buffer.pointer, 0, buffer.length);
status = acpi_get_possible_resources(link->handle, &buffer);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error evaluating _PRS\n"));
result = -ENODEV;
goto end;
}
resource = (acpi_resource *) buffer.pointer;
switch (resource->id) {
case ACPI_RSTYPE_IRQ:
{
acpi_resource_irq *p = &resource->data.irq;
for (i = 0; (i<p->number_of_interrupts && i<ACPI_PCI_LINK_MAX_IRQS); i++) {
link->irq.possible[i] = p->interrupts[i];
link->irq.possible_count++;
}
if (!p->number_of_interrupts) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Blank IRQ resource\n"));
result = -ENODEV;
goto end;
}
link->irq.flags.trigger = p->edge_level;
link->irq.flags.polarity = p->active_high_low;
link->irq.flags.shareable = p->shared_exclusive;
break;
}
case ACPI_RSTYPE_EXT_IRQ:
{
acpi_resource_ext_irq *p = &resource->data.extended_irq;
for (i = 0; (i<p->number_of_interrupts && i<ACPI_PCI_LINK_MAX_IRQS); i++) {
link->irq.possible[i] = p->interrupts[i];
link->irq.possible_count++;
}
if (!p->number_of_interrupts) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Blank IRQ resource\n"));
result = -ENODEV;
goto end;
}
link->irq.flags.trigger = p->edge_level;
link->irq.flags.polarity = p->active_high_low;
link->irq.flags.shareable = p->shared_exclusive;
break;
}
default:
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"First resource is not an IRQ entry\n"));
break;
}
end:
kfree(buffer.pointer);
return_VALUE(result);
}
static int
acpi_pci_link_set (
struct acpi_pci_link *link,
u32 irq)
{
acpi_status status = AE_OK;
struct {
acpi_resource res;
acpi_resource end;
} resource;
acpi_buffer buffer = {sizeof(resource)+1, &resource};
ACPI_FUNCTION_TRACE("acpi_pci_link_set");
if (!link || !irq)
return_VALUE(-EINVAL);
memset(&resource, 0, sizeof(resource));
resource.res.id = ACPI_RSTYPE_IRQ;
resource.res.length = sizeof(acpi_resource);
resource.res.data.irq.edge_level = link->irq.flags.trigger;
resource.res.data.irq.active_high_low = link->irq.flags.polarity;
resource.res.data.irq.shared_exclusive = link->irq.flags.shareable;
resource.res.data.irq.number_of_interrupts = 1;
resource.res.data.irq.interrupts[0] = irq;
resource.end.id = ACPI_RSTYPE_END_TAG;
status = acpi_set_current_resources(link->handle, &buffer);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error evaluating _SRS\n"));
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
int
acpi_pci_link_get_irq (
struct acpi_prt_entry *entry,
int *irq)
{
int result = -ENODEV;
struct acpi_device *device = NULL;
struct acpi_pci_link *link = NULL;
ACPI_FUNCTION_TRACE("acpi_pci_link_get_irq");
if (!entry || !entry->source.handle || !irq)
return_VALUE(-EINVAL);
/* TBD: Support multiple index values (not just first). */
if (0 != entry->source.index) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unsupported resource index [%d]\n",
entry->source.index));
return_VALUE(-ENODEV);
}
result = acpi_bus_get_device(entry->source.handle, &device);
if (0 != result) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Link IRQ invalid\n"));
return_VALUE(-ENODEV);
}
link = (struct acpi_pci_link *) acpi_driver_data(device);
if (!link) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid link context\n"));
return_VALUE(-ENODEV);
}
if (!link->irq.flags.valid)
return_VALUE(-ENODEV);
/* TBD: Support multiple index (IRQ) entries per Link Device */
if (0 != entry->source.index) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unsupported IRQ resource index [%d]\n",
entry->source.index));
return_VALUE(-EFAULT);
}
*irq = link->irq.active;
return_VALUE(0);
}
int
acpi_pci_link_set_irq (
struct acpi_prt_entry *entry,
int irq)
{
int result = 0;
int i = 0;
int valid = 0;
struct acpi_device *device = NULL;
struct acpi_pci_link *link = NULL;
ACPI_FUNCTION_TRACE("acpi_pci_link_set_irq");
if (!entry || !entry->source.handle || !irq)
return_VALUE(-EINVAL);
/* TBD: Support multiple index values (not just first). */
if (0 != entry->source.index) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unsupported resource index [%d]\n",
entry->source.index));
return_VALUE(-ENODEV);
}
result = acpi_bus_get_device(entry->source.handle, &device);
if (0 != result)
return_VALUE(result);
link = (struct acpi_pci_link *) acpi_driver_data(device);
if (!link) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid link context\n"));
return_VALUE(-ENODEV);
}
if (!link->irq.flags.valid) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Link IRQ invalid\n"));
return_VALUE(-ENODEV);
}
/* Is the target IRQ the same as the currently enabled IRQ? */
if (link->irq.flags.enabled && (irq == link->irq.active)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Link IRQ already at target\n"));
return_VALUE(0);
}
/* Is the target IRQ in the list of possible IRQs? */
for (i=0; i<link->irq.possible_count; i++) {
if (irq == link->irq.possible[i])
valid = 1;
}
if (!valid) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Target IRQ invalid\n"));
return_VALUE(-EINVAL);
}
/* TBD: Do we need to disable this link device before resetting? */
/* Set the new IRQ */
result = acpi_pci_link_set(link, irq);
if (0 != result)
return_VALUE(result);
link->irq.active = irq;
return_VALUE(result);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
static int
acpi_pci_link_add (
struct acpi_device *device)
{
int result = 0;
struct acpi_pci_link *link = NULL;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_pci_link_add");
if (!device)
return_VALUE(-EINVAL);
link = kmalloc(sizeof(struct acpi_pci_link), GFP_KERNEL);
if (!link)
return_VALUE(-ENOMEM);
memset(link, 0, sizeof(struct acpi_pci_link));
link->handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_PCI_LINK_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_PCI_LINK_CLASS);
acpi_driver_data(device) = link;
result = acpi_pci_link_get_possible(link);
if (0 != result)
return_VALUE(result);
if (!device->status.enabled) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Enabling at IRQ [%d]\n",
link->irq.possible[0]));
result = acpi_pci_link_set(link, link->irq.possible[0]);
if (0 != result)
return_VALUE(result);
result = acpi_bus_get_status(device);
if (0 != result)
return_VALUE(result);
if (!device->status.enabled) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Enable failed\n"));
return_VALUE(-EFAULT);
}
}
result = acpi_pci_link_get_current(link);
if (0 != result)
return_VALUE(result);
link->irq.flags.valid = 1;
printk(PREFIX "%s [%s] (IRQs",
acpi_device_name(device), acpi_device_bid(device));
for (i = 0; i < link->irq.possible_count; i++)
printk("%s%d",
(link->irq.active==link->irq.possible[i])?" *":" ",
link->irq.possible[i]);
printk(")\n");
return_VALUE(0);
}
static int
acpi_pci_link_remove (
struct acpi_device *device,
int type)
{
struct acpi_pci_link *link = NULL;
ACPI_FUNCTION_TRACE("acpi_pci_link_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
link = (struct acpi_pci_link *) acpi_driver_data(device);
kfree(link);
return_VALUE(0);
}
int __init
acpi_pci_link_init (void)
{
ACPI_FUNCTION_TRACE("acpi_pci_link_init");
if (0 > acpi_bus_register_driver(&acpi_pci_link_driver))
return_VALUE(-ENODEV);
return_VALUE(0);
}
void __exit
acpi_pci_link_exit (void)
{
ACPI_FUNCTION_TRACE("acpi_pci_link_init");
acpi_bus_unregister_driver(&acpi_pci_link_driver);
return_VOID;
}
/*
* acpi_pci_root.c - ACPI PCI Root Bridge Driver ($Revision: 22 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/pm.h>
#include <linux/pci.h>
#include <linux/acpi.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_PCI_ROOT_COMPONENT
ACPI_MODULE_NAME ("pci_root")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_PCI_ROOT_DRIVER_NAME);
extern struct pci_ops *pci_root_ops;
#define PREFIX "ACPI: "
static int acpi_pci_root_add (struct acpi_device *device);
static int acpi_pci_root_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_pci_root_driver = {
name: ACPI_PCI_ROOT_DRIVER_NAME,
class: ACPI_PCI_ROOT_CLASS,
ids: ACPI_PCI_ROOT_HID,
ops: {
add: acpi_pci_root_add,
remove: acpi_pci_root_remove,
},
};
struct acpi_pci_root_context {
acpi_handle handle;
struct {
u8 seg; /* Root's segment number */
u8 bus; /* Root's bus number */
u8 sub; /* Max subordinate bus */
} id;
struct pci_bus *bus;
struct pci_dev *dev;
};
struct acpi_prt_list acpi_prts;
/* --------------------------------------------------------------------------
PCI Routing Table (PRT) Support
-------------------------------------------------------------------------- */
static struct acpi_prt_entry *
acpi_prt_find_entry (
struct pci_dev *dev,
u8 pin)
{
struct acpi_prt_entry *entry = NULL;
struct list_head *node = NULL;
ACPI_FUNCTION_TRACE("acpi_prt_find_entry");
/* TBD: Locking */
list_for_each(node, &acpi_prts.entries) {
entry = list_entry(node, struct acpi_prt_entry, node);
if ((entry->id.dev == PCI_SLOT(dev->devfn))
&& (entry->id.pin == pin))
return_PTR(entry);
}
return_PTR(NULL);
}
int
acpi_prt_get_irq (
struct pci_dev *dev,
u8 pin,
int *irq)
{
int result = 0;
struct acpi_prt_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_pci_get_current_irq");
if (!dev || !irq)
return_VALUE(-ENODEV);
entry = acpi_prt_find_entry(dev, pin);
if (!entry)
return_VALUE(-ENODEV);
/* Type 1: Dynamic (e.g. PCI Link Device) */
if (entry->source.handle)
result = acpi_pci_link_get_irq(entry, irq);
/* Type 2: Static (e.g. I/O [S]APIC Direct) */
else
*irq = entry->source.index;
return_VALUE(0);
}
int
acpi_prt_set_irq (
struct pci_dev *dev,
u8 pin,
int irq)
{
int result = 0;
int i = 0;
int valid = 0;
struct acpi_prt_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_pci_set_current_irq");
if (!dev || !irq)
return_VALUE(-EINVAL);
entry = acpi_prt_find_entry(dev, pin);
if (!entry)
return_VALUE(-ENODEV);
/* Type 1: Dynamic (e.g. PCI Link Device) */
if (entry->source.handle)
result = acpi_pci_link_set_irq(entry, irq);
/* Type 2: Static (e.g. I/O [S]APIC Direct) */
else
result = -EFAULT;
return_VALUE(result);
}
static int
acpi_prt_add_entry (
acpi_handle handle,
u8 seg,
u8 bus,
acpi_pci_routing_table *prt)
{
struct acpi_prt_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_prt_add_entry");
if (!prt)
return_VALUE(-EINVAL);
entry = kmalloc(sizeof(struct acpi_prt_entry), GFP_KERNEL);
if (!entry)
return_VALUE(-ENOMEM);
memset(entry, 0, sizeof(struct acpi_prt_entry));
entry->id.seg = seg;
entry->id.bus = bus;
entry->id.dev = prt->address >> 16;
entry->id.pin = prt->pin;
/*
* Type 1: Dynamic
* ---------------
* The 'source' field specifies the PCI interrupt link device used to
* configure the IRQ assigned to this slot|dev|pin. The 'source_index'
* indicates which resource descriptor in the resource template (of
* the link device) this interrupt is allocated from.
*/
if (prt->source)
acpi_get_handle(handle, prt->source, &entry->source.handle);
/*
* Type 2: Static
* --------------
* The 'source' field is NULL, and the 'source_index' field specifies
* the IRQ value, which is hardwired to specific interrupt inputs on
* the interrupt controller.
*/
else
entry->source.handle = NULL;
entry->source.index = prt->source_index;
/*
* NOTE: Don't query the Link Device for IRQ information at this time
* because Link Device enumeration may not have occurred yet
* (e.g. exists somewhere 'below' this _PRT entry in the ACPI
* namespace).
*/
printk(KERN_INFO " %02X:%02X:%02X[%c] -> %s[%d]\n",
entry->id.seg, entry->id.bus, entry->id.dev,
('A' + entry->id.pin), prt->source, entry->source.index);
/* TBD: Acquire/release lock */
list_add_tail(&entry->node, &acpi_prts.entries);
acpi_prts.count++;
return_VALUE(0);
}
static acpi_status
acpi_prt_callback (
acpi_handle handle,
u32 nesting_level,
void *context,
void **return_value)
{
acpi_status status = AE_OK;
acpi_handle prt_handle = NULL;
char pathname[PATHNAME_MAX] = {0};
acpi_buffer buffer = {0, NULL};
acpi_pci_routing_table *prt = NULL;
unsigned long sta = 0;
struct acpi_pci_root_context *root = (struct acpi_pci_root_context *) context;
u8 bus_number = 0;
ACPI_FUNCTION_TRACE("acpi_prt_callback");
if (!root)
return_VALUE(AE_BAD_PARAMETER);
status = acpi_get_handle(handle, METHOD_NAME__PRT, &prt_handle);
if (ACPI_FAILURE(status))
return_VALUE(AE_OK);
buffer.length = sizeof(pathname);
buffer.pointer = pathname;
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
/*
* Evalute _STA to see if the device containing this _PRT is present.
*/
status = acpi_evaluate_integer(handle, METHOD_NAME__STA, NULL, &sta);
switch (status) {
case AE_OK:
if (!(sta & 0x01)) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Found _PRT but device [%s] not present\n",
pathname));
return_VALUE(AE_OK);
}
break;
case AE_NOT_FOUND:
/* assume present */
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Error evaluating %s._STA [%s]\n",
pathname, acpi_format_exception(status)));
return_VALUE(status);
}
printk(KERN_INFO PREFIX "%s [%s._PRT]\n", ACPI_PCI_PRT_DEVICE_NAME,
pathname);
/*
* Evaluate this _PRT and add all entries to our global list.
*/
buffer.length = 0;
buffer.pointer = NULL;
status = acpi_get_irq_routing_table(handle, &buffer);
if (status != AE_BUFFER_OVERFLOW) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Error evaluating _PRT [%s]\n",
acpi_format_exception(status)));
return_VALUE(status);
}
prt = kmalloc(buffer.length, GFP_KERNEL);
if (!prt)
return_VALUE(-ENOMEM);
memset(prt, 0, buffer.length);
buffer.pointer = prt;
status = acpi_get_irq_routing_table(handle, &buffer);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Error evaluating _PRT [%s]\n",
acpi_format_exception(status)));
kfree(buffer.pointer);
return_VALUE(status);
}
if (root->handle == handle)
bus_number = root->id.bus;
else {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Need to get subordinate bus number!\n"));
/* get the bus number for this device */
}
while (prt && (prt->length > 0)) {
acpi_prt_add_entry(handle, root->id.seg, bus_number, prt);
prt = (acpi_pci_routing_table*)((unsigned long)prt + prt->length);
}
return_VALUE(AE_OK);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
int
acpi_pci_root_add (
struct acpi_device *device)
{
acpi_status status = AE_OK;
struct acpi_pci_root_context *context = NULL;
unsigned long temp = 0;
ACPI_FUNCTION_TRACE("acpi_pci_root_add");
if (!device)
return_VALUE(-EINVAL);
context = kmalloc(sizeof(struct acpi_pci_root_context), GFP_KERNEL);
if (!context)
return_VALUE(-ENOMEM);
memset(context, 0, sizeof(struct acpi_pci_root_context));
context->handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_PCI_ROOT_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_PCI_ROOT_CLASS);
acpi_driver_data(device) = context;
status = acpi_evaluate_integer(context->handle, METHOD_NAME__SEG,
NULL, &temp);
if (ACPI_SUCCESS(status))
context->id.seg = temp;
else
context->id.seg = 0;
status = acpi_evaluate_integer(context->handle, METHOD_NAME__BBN,
NULL, &temp);
if (ACPI_SUCCESS(status))
context->id.bus = temp;
else
context->id.bus = 0;
/* TBD: Evaluate _CRS for bus range of child P2P (bus min/max/len) */
printk(KERN_INFO PREFIX "%s [%s] (%02x:%02x)\n",
acpi_device_name(device), acpi_device_bid(device),
context->id.seg, context->id.bus);
/*
* Scan all devices on this root bridge. Note that this must occur
* now to get the correct bus number assignments for subordinate
* PCI-PCI bridges.
*/
pci_scan_bus(context->id.bus, pci_root_ops, NULL);
/* Evaluate _PRT for this root bridge. */
acpi_prt_callback(context->handle, 0, context, NULL);
/* Evaluate all subordinate _PRTs. */
acpi_walk_namespace(ACPI_TYPE_DEVICE, context->handle, ACPI_UINT32_MAX,
acpi_prt_callback, context, NULL);
return_VALUE(0);
}
int
acpi_pci_root_remove (
struct acpi_device *device,
int type)
{
struct acpi_pci_dev_context *context = NULL;
if (!device)
return -EINVAL;
if (device->driver_data)
/* Root bridge */
kfree(device->driver_data);
else {
/* Standard PCI device */
context = acpi_driver_data(device);
if (context)
kfree(context);
}
return 0;
}
int __init
acpi_pci_irq_init (void)
{
int result = 0;
acpi_status status = AE_OK;
acpi_object arg = {ACPI_TYPE_INTEGER};
acpi_object_list arg_list = {1, &arg};
int irq_model = 0;
ACPI_FUNCTION_TRACE("acpi_pci_irq_init");
/*
* Let the system know what interrupt model we are using by
* evaluating the \_PIC object, if exists.
*/
result = acpi_get_interrupt_model(&irq_model);
if (0 != result)
return_VALUE(result);
arg.integer.value = irq_model;
status = acpi_evaluate_object(NULL, "\\_PIC", &arg_list, NULL);
if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error evaluating _PIC\n"));
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
int __init
acpi_pci_root_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_pci_root_init");
acpi_prts.count = 0;
INIT_LIST_HEAD(&acpi_prts.entries);
result = acpi_pci_irq_init();
if (0 != result)
return_VALUE(result);
if (0 > acpi_bus_register_driver(&acpi_pci_root_driver))
return_VALUE(-ENODEV);
return_VALUE(0);
}
void __exit
acpi_pci_root_exit (void)
{
ACPI_FUNCTION_TRACE("acpi_pci_root_exit");
acpi_bus_unregister_driver(&acpi_pci_root_driver);
}
/*
* acpi_power.c - ACPI Bus Power Management ($Revision: 34 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_POWER_COMPONENT
ACPI_MODULE_NAME ("acpi_power")
#define PREFIX "ACPI: "
int acpi_power_add (struct acpi_device *device);
int acpi_power_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_power_driver = {
name: ACPI_POWER_DRIVER_NAME,
class: ACPI_POWER_CLASS,
ids: ACPI_POWER_HID,
ops: {
add: acpi_power_add,
remove: acpi_power_remove,
},
};
struct acpi_power_resource
{
acpi_handle handle;
acpi_bus_id name;
u32 system_level;
u32 order;
int state;
int references;
};
static struct list_head acpi_power_resource_list;
/* --------------------------------------------------------------------------
Power Resource Management
-------------------------------------------------------------------------- */
static int
acpi_power_get_context (
acpi_handle handle,
struct acpi_power_resource **resource)
{
int result = 0;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_power_get_context");
if (!resource)
return_VALUE(-ENODEV);
result = acpi_bus_get_device(handle, &device);
if (0 != result) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error getting context [%p]\n",
handle));
return_VALUE(result);
}
*resource = (struct acpi_power_resource *) acpi_driver_data(device);
if (!resource)
return_VALUE(-ENODEV);
return_VALUE(0);
}
static int
acpi_power_get_state (
struct acpi_power_resource *resource)
{
acpi_status status = AE_OK;
unsigned long sta = 0;
ACPI_FUNCTION_TRACE("acpi_power_get_state");
if (!resource)
return_VALUE(-EINVAL);
status = acpi_evaluate_integer(resource->handle, "_STA", NULL, &sta);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
if (sta & 0x01)
resource->state = ACPI_POWER_RESOURCE_STATE_ON;
else
resource->state = ACPI_POWER_RESOURCE_STATE_OFF;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] is %s\n",
resource->name, resource->state?"on":"off"));
return_VALUE(0);
}
static int
acpi_power_get_list_state (
struct acpi_handle_list *list,
int *state)
{
int result = 0;
struct acpi_power_resource *resource = NULL;
u32 i = 0;
ACPI_FUNCTION_TRACE("acpi_power_get_list_state");
if (!list || !state)
return_VALUE(-EINVAL);
/* The state of the list is 'on' IFF all resources are 'on'. */
for (i=0; i<list->count; i++) {
result = acpi_power_get_context(list->handles[i], &resource);
if (0 != result)
return_VALUE(result);
result = acpi_power_get_state(resource);
if (0 != result)
return_VALUE(result);
*state = resource->state;
if (*state != ACPI_POWER_RESOURCE_STATE_ON)
break;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource list is %s\n",
*state?"on":"off"));
return_VALUE(result);
}
static int
acpi_power_on (
acpi_handle handle)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_device *device = NULL;
struct acpi_power_resource *resource = NULL;
ACPI_FUNCTION_TRACE("acpi_power_on");
result = acpi_power_get_context(handle, &resource);
if (0 != result)
return_VALUE(result);
resource->references++;
if ((resource->references > 1)
|| (resource->state == ACPI_POWER_RESOURCE_STATE_ON)) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already on\n",
resource->name));
return_VALUE(0);
}
status = acpi_evaluate(resource->handle, "_ON", NULL, NULL);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
result = acpi_power_get_state(resource);
if (0 != result)
return_VALUE(result);
if (resource->state != ACPI_POWER_RESOURCE_STATE_ON)
return_VALUE(-ENOEXEC);
/* Update the power resource's _device_ power state */
result = acpi_bus_get_device(resource->handle, &device);
if (0 != result)
return_VALUE(result);
device->power.state = ACPI_STATE_D0;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned on\n",
resource->name));
return_VALUE(0);
}
static int
acpi_power_off (
acpi_handle handle)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_device *device = NULL;
struct acpi_power_resource *resource = NULL;
ACPI_FUNCTION_TRACE("acpi_power_off");
result = acpi_power_get_context(handle, &resource);
if (0 != result)
return_VALUE(result);
if (resource->references)
resource->references--;
if (resource->references) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Resource [%s] is still in use, dereferencing\n",
device->pnp.bus_id));
return_VALUE(0);
}
if (resource->state == ACPI_POWER_RESOURCE_STATE_OFF) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already off\n",
device->pnp.bus_id));
return_VALUE(0);
}
status = acpi_evaluate(resource->handle, "_OFF", NULL, NULL);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
result = acpi_power_get_state(resource);
if (0 != result)
return_VALUE(result);
if (resource->state != ACPI_POWER_RESOURCE_STATE_OFF)
return_VALUE(-ENOEXEC);
/* Update the power resource's _device_ power state */
result = acpi_bus_get_device(resource->handle, &device);
if (0 != result)
return_VALUE(result);
device->power.state = ACPI_STATE_D3;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned off\n",
resource->name));
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Device Power Management
-------------------------------------------------------------------------- */
int
acpi_power_get_inferred_state (
struct acpi_device *device)
{
int result = 0;
struct acpi_handle_list *list = NULL;
int list_state = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_power_get_inferred_state");
if (!device)
return_VALUE(-EINVAL);
device->power.state = ACPI_STATE_UNKNOWN;
/*
* We know a device's inferred power state when all the resources
* required for a given D-state are 'on'.
*/
for (i=ACPI_STATE_D0; i<ACPI_STATE_D3; i++) {
list = &device->power.states[i].resources;
if (list->count < 1)
continue;
result = acpi_power_get_list_state(list, &list_state);
if (0 != result)
return_VALUE(result);
if (list_state == ACPI_POWER_RESOURCE_STATE_ON) {
device->power.state = i;
return_VALUE(0);
}
}
device->power.state = ACPI_STATE_D3;
return_VALUE(0);
}
int
acpi_power_transition (
struct acpi_device *device,
int state)
{
int result = 0;
struct acpi_handle_list *cl = NULL; /* Current Resources */
struct acpi_handle_list *tl = NULL; /* Target Resources */
int i = 0;
ACPI_FUNCTION_TRACE("acpi_power_transition");
if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3))
return_VALUE(-EINVAL);
cl = &device->power.states[device->power.state].resources;
tl = &device->power.states[state].resources;
device->power.state = ACPI_STATE_UNKNOWN;
if (!cl->count && !tl->count) {
result = -ENODEV;
goto end;
}
/* TBD: Resources must be ordered. */
/*
* First we reference all power resources required in the target list
* (e.g. so the device doesn't loose power while transitioning).
*/
for (i=0; i<tl->count; i++) {
result = acpi_power_on(tl->handles[i]);
if (0 != result)
goto end;
}
device->power.state = state;
/*
* Then we dereference all power resources used in the current list.
*/
for (i=0; i<cl->count; i++) {
result = acpi_power_off(cl->handles[i]);
if (0 != result)
goto end;
}
end:
if (0 != result)
ACPI_DEBUG_PRINT((ACPI_DB_WARN,
"Error transitioning device [%s] to D%d\n",
device->pnp.bus_id, state));
return_VALUE(result);
}
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_power_dir = NULL;
static int
acpi_power_read_status (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_power_resource *resource = NULL;
char *p = page;
int len;
ACPI_FUNCTION_TRACE("acpi_power_read_status");
if (!data || (off != 0))
goto end;
resource = (struct acpi_power_resource *) data;
p += sprintf(p, "state: ");
switch (resource->state) {
case ACPI_POWER_RESOURCE_STATE_ON:
p += sprintf(p, "on\n");
break;
case ACPI_POWER_RESOURCE_STATE_OFF:
p += sprintf(p, "off\n");
break;
default:
p += sprintf(p, "unknown\n");
break;
}
p += sprintf(p, "system level: S%d\n",
resource->system_level);
p += sprintf(p, "order: %d\n",
resource->order);
p += sprintf(p, "reference count: %d\n",
resource->references);
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_power_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_power_add_fs");
if (!device)
return_VALUE(-EINVAL);
if (!acpi_power_dir) {
acpi_power_dir = proc_mkdir(ACPI_POWER_CLASS, acpi_root_dir);
if (!acpi_power_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_power_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
/* 'status' [R] */
entry = create_proc_entry(ACPI_POWER_FILE_STATUS,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_POWER_FILE_STATUS));
else {
entry->read_proc = acpi_power_read_status;
entry->data = acpi_driver_data(device);
}
return_VALUE(0);
}
static int
acpi_power_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_power_remove_fs");
if (!acpi_power_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_power_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
int
acpi_power_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_power_resource *resource = NULL;
acpi_object acpi_object;
acpi_buffer buffer = {sizeof(acpi_object), &acpi_object};
ACPI_FUNCTION_TRACE("acpi_power_add");
if (!device)
return_VALUE(-EINVAL);
resource = kmalloc(sizeof(struct acpi_power_resource), GFP_KERNEL);
if (!resource)
return_VALUE(-ENOMEM);
memset(resource, 0, sizeof(struct acpi_power_resource));
resource->handle = device->handle;
sprintf(resource->name, "%s", device->pnp.bus_id);
sprintf(acpi_device_name(device), "%s", ACPI_POWER_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_POWER_CLASS);
acpi_driver_data(device) = resource;
/* Evalute the object to get the system level and resource order. */
status = acpi_evaluate_object(resource->handle, NULL, NULL, &buffer);
if (ACPI_FAILURE(status)) {
result = -ENODEV;
goto end;
}
resource->system_level = acpi_object.power_resource.system_level;
resource->order = acpi_object.power_resource.resource_order;
result = acpi_power_get_state(resource);
if (0 != result)
goto end;
switch (resource->state) {
case ACPI_POWER_RESOURCE_STATE_ON:
device->power.state = ACPI_STATE_D0;
break;
case ACPI_POWER_RESOURCE_STATE_OFF:
device->power.state = ACPI_STATE_D3;
break;
default:
device->power.state = ACPI_STATE_UNKNOWN;
break;
}
result = acpi_power_add_fs(device);
if (0 != result)
goto end;
printk(KERN_INFO PREFIX "%s [%s] (%s)\n", acpi_device_name(device),
acpi_device_bid(device), resource->state?"on":"off");
end:
if (0 != result)
kfree(resource);
return_VALUE(result);
}
int
acpi_power_remove (
struct acpi_device *device,
int type)
{
struct acpi_power_resource *resource = NULL;
ACPI_FUNCTION_TRACE("acpi_power_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
resource = (struct acpi_power_resource *) acpi_driver_data(device);
acpi_power_remove_fs(device);
kfree(resource);
return_VALUE(0);
}
int __init
acpi_power_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_power_init");
INIT_LIST_HEAD(&acpi_power_resource_list);
result = acpi_bus_register_driver(&acpi_power_driver);
if (0 > result) {
remove_proc_entry(ACPI_POWER_CLASS, acpi_root_dir);
return_VALUE(-ENODEV);
}
return_VALUE(0);
}
void __exit
acpi_power_exit (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_power_exit");
/* TBD: Empty acpi_power_resource_list */
result = acpi_bus_unregister_driver(&acpi_power_driver);
if (0 == result)
remove_proc_entry(ACPI_POWER_CLASS, acpi_root_dir);
return_VOID;
}
/*
* acpi_processor.c - ACPI Processor Driver ($Revision: 50 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* TBD:
* 1. Make # power/performance states dynamic.
* 2. Includes support for _real_ performance states (not just throttle).
* 3. Support duty_cycle values that span bit 4.
* 4. Optimize by having scheduler determine business instead of
* having us try to calculate it here.
* 5. Need C1 timing -- must modify kernel (IRQ handler) to get this.
* 6. Convert time values to ticks (initially) to avoid having to do
* the math (acpi_get_timer_duration).
* 7. What is a good default value for the OS busy_metric?
* 8. Support both thermal and power limits.
* 9. Resolve PIIX4 BMISX errata issue (getting an I/O port value of 0).
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <asm/io.h>
#include <asm/system.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_PROCESSOR_COMPONENT
ACPI_MODULE_NAME ("acpi_processor")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_PROCESSOR_DRIVER_NAME);
MODULE_LICENSE("GPL");
#define PREFIX "ACPI: "
#define ACPI_PROCESSOR_MAX_POWER ACPI_C_STATE_COUNT
#define ACPI_PROCESSOR_MAX_C2_LATENCY 100
#define ACPI_PROCESSOR_MAX_C3_LATENCY 1000
#define ACPI_PROCESSOR_MAX_PERFORMANCE 4
#define ACPI_PROCESSOR_MAX_THROTTLING 16
#define ACPI_PROCESSOR_MAX_THROTTLE 500 /* 50% */
#define ACPI_PROCESSOR_MAX_DUTY_WIDTH 4
const u32 POWER_OF_2[] = {1,2,4,8,16,32,64};
#define ACPI_PROCESSOR_MAX_LIMIT 20
static int acpi_processor_add (struct acpi_device *device);
static int acpi_processor_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_processor_driver = {
name: ACPI_PROCESSOR_DRIVER_NAME,
class: ACPI_PROCESSOR_CLASS,
ids: ACPI_PROCESSOR_HID,
ops: {
add: acpi_processor_add,
remove: acpi_processor_remove,
},
};
/* Power Management */
struct acpi_processor_cx_policy {
u32 count;
int state;
struct {
u32 time;
u32 count;
u32 bm;
} threshold;
};
struct acpi_processor_cx {
u8 valid;
u32 address;
u32 latency;
u32 power;
u32 usage;
struct acpi_processor_cx_policy promotion;
struct acpi_processor_cx_policy demotion;
};
struct acpi_processor_power {
int state;
int default_state;
u32 bm_activity;
u32 busy_metric;
struct acpi_processor_cx states[ACPI_PROCESSOR_MAX_POWER];
};
/* Performance Management */
struct acpi_processor_px {
u8 valid;
u32 core_frequency;
u32 power;
u32 transition_latency;
u32 bus_master_latency;
u32 control;
u32 status;
};
struct acpi_processor_performance {
int state;
int state_count;
struct acpi_processor_px states[ACPI_PROCESSOR_MAX_PERFORMANCE];
};
/* Throttling Control */
struct acpi_processor_tx {
u8 valid;
u16 power;
u16 performance;
};
struct acpi_processor_throttling {
int state;
u32 address;
u8 duty_offset;
u8 duty_width;
int state_count;
struct acpi_processor_tx states[ACPI_PROCESSOR_MAX_THROTTLING];
};
/* Limit Interface */
struct acpi_processor_lx {
u8 valid;
u16 performance;
int px;
int tx;
};
struct acpi_processor_limit {
int state;
int state_count;
struct {
u8 valid;
u16 performance;
int px;
int tx;
} states[ACPI_PROCESSOR_MAX_LIMIT];
};
struct acpi_processor_flags {
u8 bm_control:1;
u8 power:1;
u8 performance:1;
u8 throttling:1;
u8 limit:1;
u8 reserved:3;
};
struct acpi_processor_errata {
struct {
u8 reverse_throttle;
u32 bmisx;
} piix4;
};
struct acpi_processor {
acpi_handle handle;
u32 acpi_id;
u32 id;
struct acpi_processor_flags flags;
struct acpi_processor_errata errata;
struct acpi_processor_power power;
struct acpi_processor_performance performance;
struct acpi_processor_throttling throttling;
struct acpi_processor_limit limit;
};
static u8 acpi_processor_smp = 0;
/* --------------------------------------------------------------------------
Errata Handling
-------------------------------------------------------------------------- */
int
acpi_processor_get_errata (
struct acpi_processor *pr)
{
struct pci_dev *dev = NULL;
ACPI_FUNCTION_TRACE("acpi_processor_get_errata");
if (!pr)
return_VALUE(-EINVAL);
/*
* PIIX4
* -----
*/
dev = pci_find_subsys(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_82371AB_3, PCI_ANY_ID, PCI_ANY_ID, dev);
if (dev) {
u8 rev = 0;
pci_read_config_byte(dev, PCI_REVISION_ID, &rev);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "PIIX4 ACPI rev %d\n", rev));
switch (rev) {
case 0: /* PIIX4 A-step */
case 1: /* PIIX4 B-step */
/*
* Workaround for reverse-notation on throttling states
* used by early PIIX4 models.
*/
pr->errata.piix4.reverse_throttle = 1;
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Reverse-throttle errata enabled\n"));
case 2: /* PIIX4E */
case 3: /* PIIX4M */
/*
* Workaround for errata #18 "C3 Power State/BMIDE and
* Type-F DMA Livelock" from the July 2001 PIIX4
* specification update. Applies to all PIIX4 models.
*/
/* TBD: Why is the bmisx value always ZERO? */
pr->errata.piix4.bmisx = pci_resource_start(dev, 4);
if (pr->errata.piix4.bmisx)
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"BM-IDE errata enabled\n"));
break;
}
}
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Power Management
-------------------------------------------------------------------------- */
static struct acpi_processor *acpi_processor_list[NR_CPUS];
static void (*pm_idle_save)(void) = NULL;
static void
acpi_processor_power_activate (
struct acpi_processor *pr,
int state)
{
if (!pr)
return;
pr->power.states[pr->power.state].promotion.count = 0;
pr->power.states[pr->power.state].demotion.count = 0;
/* Cleanup from old state. */
switch (pr->power.state) {
case ACPI_STATE_C3:
/* Disable bus master reload */
acpi_hw_bit_register_write(ACPI_BITREG_BUS_MASTER_RLD, 0, ACPI_MTX_DO_NOT_LOCK);
break;
}
/* Prepare to use new state. */
switch (state) {
case ACPI_STATE_C3:
/* Enable bus master reload */
acpi_hw_bit_register_write(ACPI_BITREG_BUS_MASTER_RLD, 1, ACPI_MTX_DO_NOT_LOCK);
break;
}
pr->power.state = state;
return;
}
static void
acpi_processor_idle (void)
{
struct acpi_processor *pr = NULL;
struct acpi_processor_cx *cx = NULL;
int next_state = 0;
u32 start_ticks = 0;
u32 end_ticks = 0;
u32 time_elapsed = 0;
static unsigned long last_idle_jiffies = 0;
pr = acpi_processor_list[smp_processor_id()];
if (!pr)
return;
/*
* Interrupts must be disabled during bus mastering calculations and
* for C2/C3 transitions.
*/
__cli();
next_state = pr->power.state;
/*
* Check OS Idleness:
* ------------------
* If the OS has been busy (hasn't called the idle handler in a while)
* then automatically demote to the default power state (e.g. C1).
*
* TBD: Optimize by having scheduler determine business instead
* of having us try to calculate it here.
*/
if (pr->power.state != pr->power.default_state) {
if ((jiffies - last_idle_jiffies) >= pr->power.busy_metric) {
next_state = pr->power.default_state;
if (next_state != pr->power.state)
acpi_processor_power_activate(pr, next_state);
}
}
/*
* Log BM Activity:
* ----------------
* Read BM_STS and record its value for later use by C3 policy.
* (Note that we save the BM_STS values for the last 32 cycles).
*/
if (pr->flags.bm_control) {
pr->power.bm_activity <<= 1;
if (acpi_hw_bit_register_read(ACPI_BITREG_BUS_MASTER_STATUS, ACPI_MTX_DO_NOT_LOCK)) {
pr->power.bm_activity |= 1;
acpi_hw_bit_register_write(ACPI_BITREG_BUS_MASTER_STATUS,
1, ACPI_MTX_DO_NOT_LOCK);
}
/*
* PIIX4 Errata:
* -------------
* This code is a workaround for errata #18 "C3 Power State/
* BMIDE and Type-F DMA Livelock" from the July '01 PIIX4
* specification update. Note that BM_STS doesn't always
* reflect the true state of bus mastering activity; forcing
* us to manually check the BMIDEA bit of each IDE channel.
*/
else if (pr->errata.piix4.bmisx) {
if ((inb_p(pr->errata.piix4.bmisx + 0x02) & 0x01) ||
(inb_p(pr->errata.piix4.bmisx + 0x0A) & 0x01))
pr->power.bm_activity |= 1;
}
}
cx = &(pr->power.states[pr->power.state]);
cx->usage++;
/*
* Sleep:
* ------
* Invoke the current Cx state to put the processor to sleep.
*/
switch (pr->power.state) {
case ACPI_STATE_C1:
/* Invoke C1. */
safe_halt();
/*
* TBD: Can't get time duration while in C1, as resumes
* go to an ISR rather than here. Need to instrument
* base interrupt handler.
*/
time_elapsed = 0xFFFFFFFF;
break;
case ACPI_STATE_C2:
/* See how long we're asleep for */
start_ticks = inl(acpi_fadt.Xpm_tmr_blk.address);
/* Invoke C2 */
inb(pr->power.states[ACPI_STATE_C2].address);
/* Dummy op - must do something useless after P_LVL2 read */
end_ticks = inl(acpi_fadt.Xpm_tmr_blk.address);
/* Compute time elapsed */
end_ticks = inl(acpi_fadt.Xpm_tmr_blk.address);
/* Re-enable interrupts */
__sti();
/*
* Compute the amount of time asleep (in the Cx state).
* TBD: Convert to PM timer ticks initially to avoid having
* to do the math (acpi_get_timer_duration).
*/
acpi_get_timer_duration(start_ticks, end_ticks, &time_elapsed);
break;
case ACPI_STATE_C3:
/* Disable bus master arbitration */
acpi_hw_bit_register_write(ACPI_BITREG_ARB_DISABLE, 1, ACPI_MTX_DO_NOT_LOCK);
/* See how long we're asleep for */
start_ticks = inl(acpi_fadt.Xpm_tmr_blk.address);
/* Invoke C2 */
inb(pr->power.states[ACPI_STATE_C3].address);
/* Dummy op - must do something useless after P_LVL3 read */
end_ticks = inl(acpi_fadt.Xpm_tmr_blk.address);
/* Compute time elapsed */
end_ticks = inl(acpi_fadt.Xpm_tmr_blk.address);
/* Enable bus master arbitration */
acpi_hw_bit_register_write(ACPI_BITREG_ARB_DISABLE, 0, ACPI_MTX_DO_NOT_LOCK);
/* Re-enable interrupts */
__sti();
/*
* Compute the amount of time asleep (in the Cx state).
* TBD: Convert to PM timer ticks initially to avoid having
* to do the math (acpi_get_timer_duration).
*/
acpi_get_timer_duration(start_ticks, end_ticks, &time_elapsed);
break;
default:
__sti();
return;
}
/*
* Promotion?
* ----------
* Track the number of longs (time asleep is greater than threshold)
* and promote when the count threshold is reached. Note that bus
* mastering activity may prevent promotions.
*/
if (cx->promotion.state) {
if (time_elapsed >= cx->promotion.threshold.time) {
cx->promotion.count++;
cx->demotion.count = 0;
if (cx->promotion.count >= cx->promotion.threshold.count) {
if (pr->flags.bm_control) {
if (!(pr->power.bm_activity & cx->promotion.threshold.bm))
next_state = cx->promotion.state;
}
else
next_state = cx->promotion.state;
}
}
}
/*
* Demotion?
* ---------
* Track the number of shorts (time asleep is less than time threshold)
* and demote when the usage threshold is reached. Note that bus
* mastering activity may cause immediate demotions.
*/
if (cx->demotion.state) {
if (time_elapsed < cx->demotion.threshold.time) {
cx->demotion.count++;
cx->promotion.count = 0;
if (cx->demotion.count >= cx->demotion.threshold.count)
next_state = cx->demotion.state;
}
if (pr->flags.bm_control) {
if (pr->power.bm_activity & cx->demotion.threshold.bm)
next_state = cx->demotion.state;
}
}
/*
* New Cx State?
* -------------
* If we're going to start using a new Cx state we must clean up
* from the previous and prepare to use the new.
*/
if (next_state != pr->power.state)
acpi_processor_power_activate(pr, next_state);
/*
* Track OS Idleness:
* ------------------
* Record a jiffies timestamp to compute time elapsed between calls
* to the idle handler.
*/
last_idle_jiffies = jiffies;
return;
}
static int
acpi_processor_set_power_policy (
struct acpi_processor *pr)
{
ACPI_FUNCTION_TRACE("acpi_processor_set_power_policy");
/*
* This function sets the default Cx state policy (OS idle handler).
* Our scheme is to promote quickly to C2 but more conservatively
* to C3. We're favoring C2 for its characteristics of low latency
* (quick response), good power savings, and ability to allow bus
* mastering activity. Note that the Cx state policy is completely
* customizable and can be altered dynamically.
*/
if (!pr)
return_VALUE(-EINVAL);
/*
* The Busy Metric is used to determine when the OS has been busy
* and thus when policy should return to using the default Cx state
* (e.g. C1). On Linux we use the number of jiffies (scheduler
* quantums) that transpire between calls to the idle handler.
*
* TBD: What is a good value for the OS busy_metric?
*/
pr->power.busy_metric = 2;
/*
* C0/C1
* -----
*/
if (pr->power.states[ACPI_STATE_C1].valid) {
pr->power.state = ACPI_STATE_C1;
pr->power.default_state = ACPI_STATE_C1;
}
else {
pr->power.state = ACPI_STATE_C0;
pr->power.default_state = ACPI_STATE_C0;
return_VALUE(0);
}
/*
* C1/C2
* -----
* Set the default C1 promotion and C2 demotion policies, where we
* promote from C1 to C2 anytime we're asleep in C1 for longer than
* two times the C2 latency (to amortize cost of transitions). Demote
* from C2 to C1 anytime we're asleep in C2 for less than this time.
*/
if (pr->power.states[ACPI_STATE_C2].valid) {
pr->power.states[ACPI_STATE_C1].promotion.threshold.count = 10;
pr->power.states[ACPI_STATE_C1].promotion.threshold.time =
(2 * pr->power.states[ACPI_STATE_C2].latency);
pr->power.states[ACPI_STATE_C1].promotion.state = ACPI_STATE_C2;
pr->power.states[ACPI_STATE_C2].demotion.threshold.count = 1;
pr->power.states[ACPI_STATE_C2].demotion.threshold.time =
(2 * pr->power.states[ACPI_STATE_C2].latency);
pr->power.states[ACPI_STATE_C2].demotion.state = ACPI_STATE_C1;
}
/*
* C2/C3
* -----
* Set default C2 promotion and C3 demotion policies, where we promote
* from C2 to C3 after 4 cycles (0x0F) of no bus mastering activity
* (while maintaining sleep time criteria). Demote immediately on a
* short or whenever bus mastering activity occurs.
*/
if ((pr->power.states[ACPI_STATE_C2].valid) &&
(pr->power.states[ACPI_STATE_C3].valid)) {
pr->power.states[ACPI_STATE_C2].promotion.threshold.count = 1;
pr->power.states[ACPI_STATE_C2].promotion.threshold.time =
(2 * pr->power.states[ACPI_STATE_C3].latency);
pr->power.states[ACPI_STATE_C2].promotion.threshold.bm = 0x0F;
pr->power.states[ACPI_STATE_C2].promotion.state = ACPI_STATE_C3;
pr->power.states[ACPI_STATE_C3].demotion.threshold.count = 1;
pr->power.states[ACPI_STATE_C3].demotion.threshold.time =
(2 * pr->power.states[ACPI_STATE_C3].latency);
pr->power.states[ACPI_STATE_C3].demotion.threshold.bm = 0x0F;
pr->power.states[ACPI_STATE_C3].demotion.state = ACPI_STATE_C2;
}
return_VALUE(0);
}
int
acpi_processor_get_power_info (
struct acpi_processor *pr)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_processor_get_power_info");
if (!pr)
return_VALUE(-EINVAL);
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"lvl2[0x%08x] lvl3[0x%08x]\n",
pr->power.states[ACPI_STATE_C2].address,
pr->power.states[ACPI_STATE_C3].address));
/* TBD: Support ACPI 2.0 objects */
/*
* C0
* --
* This state exists only as filler in our array.
*/
pr->power.states[ACPI_STATE_C0].valid = TRUE;
/*
* C1
* --
* ACPI requires C1 support for all processors.
*
* TBD: What about PROC_C1?
*/
pr->power.states[ACPI_STATE_C1].valid = TRUE;
/*
* C2
* --
* We're (currently) only supporting C2 on UP systems.
*
* TBD: Support for C2 on MP (P_LVL2_UP).
*/
if (pr->power.states[ACPI_STATE_C2].address) {
pr->power.states[ACPI_STATE_C2].latency = acpi_fadt.plvl2_lat;
if (acpi_fadt.plvl2_lat > ACPI_PROCESSOR_MAX_C2_LATENCY)
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"C2 latency too large [%d]\n",
acpi_fadt.plvl2_lat));
else if (!acpi_processor_smp)
pr->power.states[ACPI_STATE_C2].valid = TRUE;
}
/*
* C3
* --
* We're (currently) only supporting C3 on UP systems that include
* bus mastering arbitration control. Note that this method of
* maintaining cache coherency (disabling of bus mastering) cannot be
* used on SMP systems, and flushing caches (e.g. WBINVD) is simply
* too costly (at this time).
*/
if (pr->power.states[ACPI_STATE_C3].address) {
pr->power.states[ACPI_STATE_C3].latency = acpi_fadt.plvl3_lat;
if (acpi_fadt.plvl3_lat > ACPI_PROCESSOR_MAX_C3_LATENCY)
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"C3 latency too large [%d]\n",
acpi_fadt.plvl3_lat));
else if (!acpi_processor_smp && pr->flags.bm_control)
pr->power.states[ACPI_STATE_C3].valid = 1;
}
/*
* Set Default Policy
* ------------------
* Now that we know which state are supported, set the default
* policy. Note that this policy can be changed dynamically
* (e.g. encourage deeper sleeps to conserve battery life when
* not on AC).
*/
result = acpi_processor_set_power_policy(pr);
if (0 != result)
return_VALUE(result);
/*
* If this processor supports C2 or C3 we denote it as being 'power
* manageable'. Note that there's really no policy involved for
* when only C1 is supported.
*/
if (pr->power.states[ACPI_STATE_C2].valid
|| pr->power.states[ACPI_STATE_C3].valid)
pr->flags.power = 1;
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Performance Management
-------------------------------------------------------------------------- */
static int
acpi_processor_get_performance (
struct acpi_processor *pr)
{
ACPI_FUNCTION_TRACE("acpi_processor_get_performance_state");
if (!pr)
return_VALUE(-EINVAL);
if (!pr->flags.performance)
return_VALUE(0);
/* TBD */
return_VALUE(0);
}
static int
acpi_processor_set_performance (
struct acpi_processor *pr,
int state)
{
ACPI_FUNCTION_TRACE("acpi_processor_set_performance_state");
if (!pr)
return_VALUE(-EINVAL);
if (!pr->flags.performance)
return_VALUE(-ENODEV);
if (state >= pr->performance.state_count)
return_VALUE(-ENODEV);
if (state == pr->performance.state)
return_VALUE(0);
/* TBD */
return_VALUE(0);
}
static int
acpi_processor_get_performance_info (
struct acpi_processor *pr)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_processor_get_performance_info");
if (!pr)
return_VALUE(-EINVAL);
/* TBD: Support ACPI 2.0 objects */
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Throttling Control
-------------------------------------------------------------------------- */
static int
acpi_processor_get_throttling (
struct acpi_processor *pr)
{
int state = 0;
u32 value = 0;
u32 duty_mask = 0;
u32 duty_value = 0;
ACPI_FUNCTION_TRACE("acpi_processor_get_throttling");
if (!pr)
return_VALUE(-EINVAL);
if (!pr->flags.throttling)
return_VALUE(-ENODEV);
pr->throttling.state = 0;
__cli();
duty_mask = pr->throttling.state_count - 1;
duty_mask <<= pr->throttling.duty_offset;
value = inb(pr->throttling.address);
/*
* Compute the current throttling state when throttling is enabled
* (bit 4 is on). Note that the reverse_throttling flag indicates
* that the duty_value is opposite of that specified by ACPI.
*/
if (value & 0x10) {
duty_value = value & duty_mask;
duty_value >>= pr->throttling.duty_offset;
if (duty_value) {
if (pr->errata.piix4.reverse_throttle)
state = duty_value;
else
state = pr->throttling.state_count-duty_value;
}
}
pr->throttling.state = state;
__sti();
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Throttling state is T%d (%d%% throttling applied)\n",
state, pr->throttling.states[state].performance));
return_VALUE(0);
}
static int
acpi_processor_set_throttling (
struct acpi_processor *pr,
int state)
{
u32 value = 0;
u32 duty_mask = 0;
u32 duty_value = 0;
ACPI_FUNCTION_TRACE("acpi_processor_set_throttling");
if (!pr)
return_VALUE(-EINVAL);
if ((state < 0) || (state > (pr->throttling.state_count - 1)))
return_VALUE(-EINVAL);
if (!pr->flags.throttling || !pr->throttling.states[state].valid)
return_VALUE(-ENODEV);
if (state == pr->throttling.state)
return_VALUE(0);
__cli();
/*
* Calculate the duty_value and duty_mask. Note that the
* reverse_throttling flag indicates that the duty_value is
* opposite of that specified by ACPI.
*/
if (state) {
if (pr->errata.piix4.reverse_throttle)
duty_value = state;
else
duty_value = pr->throttling.state_count - state;
duty_value <<= pr->throttling.duty_offset;
/* Used to clear all duty_value bits */
duty_mask = pr->performance.state_count - 1;
duty_mask <<= acpi_fadt.duty_offset;
duty_mask = ~duty_mask;
}
/*
* Disable throttling by writing a 0 to bit 4. Note that we must
* turn it off before you can change the duty_value.
*/
value = inb(pr->throttling.address);
if (value & 0x10) {
value &= 0xFFFFFFEF;
outl(value, pr->throttling.address);
}
/*
* Write the new duty_value and then enable throttling. Note
* that a state value of 0 leaves throttling disabled.
*/
if (state) {
value &= duty_mask;
value |= duty_value;
outl(value, pr->throttling.address);
value |= 0x00000010;
outl(value, pr->throttling.address);
}
pr->throttling.state = state;
__sti();
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Throttling state set to T%d (%d%%)\n",
state, (pr->throttling.states[state].performance?pr->throttling.states[state].performance/10:0)));
return_VALUE(0);
}
static int
acpi_processor_get_throttling_info (
struct acpi_processor *pr)
{
int result = 0;
int step = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_processor_get_throttling_info");
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"pblk_address[0x%08x] duty_offset[%d] duty_width[%d]\n",
pr->throttling.address,
pr->throttling.duty_offset,
pr->throttling.duty_width));
if (!pr)
return_VALUE(-EINVAL);
/* TBD: Support ACPI 2.0 objects */
if (!pr->throttling.address) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No throttling register\n"));
return_VALUE(0);
}
else if (!pr->throttling.duty_width) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid duty_width\n"));
return_VALUE(-EFAULT);
}
/* TBD: Support duty_cycle values that span bit 4. */
else if ((pr->throttling.duty_offset
+ pr->throttling.duty_width) > 4) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "duty_cycle spans bit 4\n"));
return_VALUE(0);
}
pr->throttling.state_count = POWER_OF_2[acpi_fadt.duty_width];
/*
* Compute state values. Note that throttling displays a linear power/
* performance relationship (at 50% performance the CPU will consume
* 50% power). Values are in 1/10th of a percent to preserve accuracy.
*/
step = (1000 / pr->throttling.state_count);
for (i=0; i<pr->throttling.state_count; i++) {
pr->throttling.states[i].performance = step * i;
pr->throttling.states[i].power = step * i;
pr->throttling.states[i].valid = 1;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %d throttling states\n",
pr->throttling.state_count));
pr->flags.throttling = 1;
/*
* Disable throttling (if enabled). We'll let subsequent policy (e.g.
* thermal) decide to lower performance if it so chooses, but for now
* we'll crank up the speed.
*/
result = acpi_processor_get_throttling(pr);
if (0 != result)
goto end;
if (pr->throttling.state) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Disabling throttling (was T%d)\n",
pr->throttling.state));
result = acpi_processor_set_throttling(pr, 0);
if (0 != result)
goto end;
}
end:
if (0 != result)
pr->flags.throttling = 0;
return_VALUE(result);
}
/* --------------------------------------------------------------------------
Limit Interface
-------------------------------------------------------------------------- */
int
acpi_processor_set_limit (
acpi_handle handle,
int type,
int *state)
{
int result = 0;
struct acpi_processor *pr = NULL;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_processor_set_limit");
if (!state)
return_VALUE(-EINVAL);
result = acpi_bus_get_device(handle, &device);
if (0 != result)
return_VALUE(result);
pr = (struct acpi_processor *) acpi_driver_data(device);
if (!pr)
return_VALUE(-ENODEV);
if (!pr->flags.limit)
return_VALUE(-ENODEV);
switch (type) {
case ACPI_PROCESSOR_LIMIT_NONE:
*state = 0;
pr->limit.state = 0;
break;
case ACPI_PROCESSOR_LIMIT_INCREMENT:
if (*state == (pr->limit.state_count - 1)) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Already at maximum limit state\n"));
return_VALUE(1);
}
*state = ++pr->limit.state;
break;
case ACPI_PROCESSOR_LIMIT_DECREMENT:
if (*state == 0) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Already at minimum limit state\n"));
return_VALUE(1);
}
*state = --pr->limit.state;
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid limit type [%d]\n",
type));
*state = pr->limit.state;
return_VALUE(-EINVAL);
break;
}
if (pr->flags.performance) {
result = acpi_processor_set_performance(pr,
pr->limit.states[*state].px);
if (0 != result)
goto end;
}
if (pr->flags.throttling) {
result = acpi_processor_set_throttling(pr,
pr->limit.states[*state].tx);
if (0 != result)
goto end;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Processor [%d] limit now %d%% (P%d:T%d)\n",
pr->id,
pr->limit.states[*state].performance / 10,
pr->limit.states[*state].px,
pr->limit.states[*state].tx));
end:
if (0 != result)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unable to set limit\n"));
return_VALUE(result);
}
static int
acpi_processor_get_limit_info (
struct acpi_processor *pr)
{
int i = 0;
int px = 0;
int tx = 0;
int base_perf = 1000;
int throttle = 0;
ACPI_FUNCTION_TRACE("acpi_processor_get_limit_info");
if (!pr)
return_VALUE(-EINVAL);
/*
* Limit
* -----
* Our default policy is to only use throttling at the lowest
* performance state. This is enforced by adding throttling states
* after perormance states. We also only expose throttling states
* less than the maximum throttle value (e.g. 50%).
*/
if (pr->flags.performance) {
for (px=0; px<pr->performance.state_count; px++) {
if (!pr->performance.states[px].valid)
continue;
i = pr->limit.state_count++;
pr->limit.states[i].px = px;
pr->limit.states[i].performance = (pr->performance.states[px].core_frequency / pr->performance.states[0].core_frequency) * 1000;
pr->limit.states[i].valid = 1;
}
px--;
base_perf = pr->limit.states[i].performance;
}
if (pr->flags.throttling) {
for (tx=0; tx<pr->throttling.state_count; tx++) {
if (!pr->throttling.states[tx].valid)
continue;
if (pr->throttling.states[tx].performance > ACPI_PROCESSOR_MAX_THROTTLE)
continue;
i = pr->limit.state_count++;
pr->limit.states[i].px = px;
pr->limit.states[i].tx = tx;
throttle = (base_perf * pr->throttling.states[tx].performance) / 1000;
pr->limit.states[i].performance = base_perf - throttle;
pr->limit.states[i].valid = 1;
}
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %d limit states\n",
pr->limit.state_count));
if (pr->limit.state_count)
pr->flags.limit = 1;
return_VALUE(0);
}
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_processor_dir = NULL;
static int
acpi_processor_read_info (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_processor *pr = (struct acpi_processor *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_processor_read_info");
if (!pr || (off != 0))
goto end;
p += sprintf(p, "processor id: %d\n",
pr->id);
p += sprintf(p, "acpi id: %d\n",
pr->acpi_id);
p += sprintf(p, "bus mastering control: %s\n",
pr->flags.bm_control ? "yes" : "no");
p += sprintf(p, "power management: %s\n",
pr->flags.power ? "yes" : "no");
p += sprintf(p, "throttling control: %s\n",
pr->flags.throttling ? "yes" : "no");
p += sprintf(p, "performance management: %s\n",
pr->flags.performance ? "yes" : "no");
p += sprintf(p, "limit interface: %s\n",
pr->flags.limit ? "yes" : "no");
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_processor_read_power (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_processor *pr = (struct acpi_processor *) data;
char *p = page;
int len = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_processor_read_power");
if (!pr || (off != 0))
goto end;
p += sprintf(p, "active state: C%d\n",
pr->power.state);
p += sprintf(p, "default state: C%d\n",
pr->power.default_state);
p += sprintf(p, "bus master activity: %08x\n",
pr->power.bm_activity);
p += sprintf(p, "states:\n");
for (i=1; i<ACPI_C_STATE_COUNT; i++) {
p += sprintf(p, " %cC%d: ",
(i == pr->power.state?'*':' '), i);
if (!pr->power.states[i].valid) {
p += sprintf(p, "<not supported>\n");
continue;
}
if (pr->power.states[i].promotion.state)
p += sprintf(p, "promotion[C%d] ",
pr->power.states[i].promotion.state);
else
p += sprintf(p, "promotion[--] ");
if (pr->power.states[i].demotion.state)
p += sprintf(p, "demotion[C%d] ",
pr->power.states[i].demotion.state);
else
p += sprintf(p, "demotion[--] ");
p += sprintf(p, "latency[%03d] usage[%08d]\n",
pr->power.states[i].latency,
pr->power.states[i].usage);
}
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_processor_read_performance (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_processor *pr = (struct acpi_processor *) data;
char *p = page;
int len = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_processor_read_performance");
if (!pr || (off != 0))
goto end;
if (!pr->flags.performance) {
p += sprintf(p, "<not supported>\n");
goto end;
}
p += sprintf(p, "state count: %d\n",
pr->performance.state_count);
p += sprintf(p, "active state: P%d\n",
pr->performance.state);
p += sprintf(p, "states:\n");
for (i=0; i<pr->performance.state_count; i++)
p += sprintf(p, " %cP%d: %d Mhz, %d mW %s\n",
(i == pr->performance.state?'*':' '), i,
pr->performance.states[i].core_frequency,
pr->performance.states[i].power,
(pr->performance.states[i].valid?"":"(disabled)"));
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_processor_write_performance (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
struct acpi_processor *pr = (struct acpi_processor *) data;
char state_string[12] = {'\0'};
ACPI_FUNCTION_TRACE("acpi_processor_write_performance");
if (!pr || (count > sizeof(state_string) - 1))
return_VALUE(-EINVAL);
if (copy_from_user(state_string, buffer, count))
return_VALUE(-EFAULT);
state_string[count] = '\0';
result = acpi_processor_set_throttling(pr,
simple_strtoul(state_string, NULL, 0));
if (0 != result)
return_VALUE(result);
return_VALUE(count);
}
static int
acpi_processor_read_throttling (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_processor *pr = (struct acpi_processor *) data;
char *p = page;
int len = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_processor_read_throttling");
if (!pr || (off != 0))
goto end;
if (!(pr->throttling.state_count > 0)) {
p += sprintf(p, "<not supported>\n");
goto end;
}
p += sprintf(p, "state count: %d\n",
pr->throttling.state_count);
p += sprintf(p, "active state: T%d\n",
pr->throttling.state);
p += sprintf(p, "states:\n");
for (i=0; i<pr->throttling.state_count; i++)
p += sprintf(p, " %cT%d: %02d%% %s\n",
(i == pr->throttling.state?'*':' '), i,
(pr->throttling.states[i].performance?pr->throttling.states[i].performance/10:0),
(pr->throttling.states[i].valid?"":"(disabled)"));
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_processor_write_throttling (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
struct acpi_processor *pr = (struct acpi_processor *) data;
char state_string[12] = {'\0'};
ACPI_FUNCTION_TRACE("acpi_processor_write_throttling");
if (!pr || (count > sizeof(state_string) - 1))
return_VALUE(-EINVAL);
if (copy_from_user(state_string, buffer, count))
return_VALUE(-EFAULT);
state_string[count] = '\0';
result = acpi_processor_set_throttling(pr,
simple_strtoul(state_string, NULL, 0));
if (0 != result)
return_VALUE(result);
return_VALUE(count);
}
static int
acpi_processor_read_limit (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_processor *pr = (struct acpi_processor *) data;
char *p = page;
int len = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_processor_read_limit");
if (!pr || (off != 0))
goto end;
if (!pr->flags.limit) {
p += sprintf(p, "<not supported>\n");
goto end;
}
p += sprintf(p, "state count: %d\n",
pr->limit.state_count);
p += sprintf(p, "active state: L%d\n",
pr->limit.state);
p += sprintf(p, "states:\n");
for (i=0; i<pr->limit.state_count; i++)
p += sprintf(p, " %cL%d: %02d%% [P%d:T%d] %s\n",
(i == pr->limit.state?'*':' '),
i,
pr->limit.states[i].performance / 10,
pr->limit.states[i].px,
pr->limit.states[i].tx,
pr->limit.states[i].valid?"":"(disabled)");
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_processor_write_limit (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
struct acpi_processor *pr = (struct acpi_processor *) data;
char limit_string[12] = {'\0'};
int limit = 0;
int state = 0;
ACPI_FUNCTION_TRACE("acpi_processor_write_limit");
if (!pr || (count > sizeof(limit_string) - 1))
return_VALUE(-EINVAL);
if (copy_from_user(limit_string, buffer, count))
return_VALUE(-EFAULT);
limit_string[count] = '\0';
limit = simple_strtoul(limit_string, NULL, 0);
result = acpi_processor_set_limit(pr->handle, limit, &state);
if (0 != result)
return_VALUE(result);
return_VALUE(count);
}
static int
acpi_processor_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_processor_add_fs");
if (!acpi_processor_dir) {
acpi_processor_dir = proc_mkdir(ACPI_PROCESSOR_CLASS,
acpi_root_dir);
if (!acpi_processor_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_processor_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
/* 'info' [R] */
entry = create_proc_entry(ACPI_PROCESSOR_FILE_INFO,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_PROCESSOR_FILE_INFO));
else {
entry->read_proc = acpi_processor_read_info;
entry->data = acpi_driver_data(device);
}
/* 'power' [R] */
entry = create_proc_entry(ACPI_PROCESSOR_FILE_POWER,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_PROCESSOR_FILE_POWER));
else {
entry->read_proc = acpi_processor_read_power;
entry->data = acpi_driver_data(device);
}
/* 'performance' [R/W] */
entry = create_proc_entry(ACPI_PROCESSOR_FILE_PERFORMANCE,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_PROCESSOR_FILE_PERFORMANCE));
else {
entry->read_proc = acpi_processor_read_performance;
entry->write_proc = acpi_processor_write_performance;
entry->data = acpi_driver_data(device);
}
/* 'throttling' [R/W] */
entry = create_proc_entry(ACPI_PROCESSOR_FILE_THROTTLING,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_PROCESSOR_FILE_THROTTLING));
else {
entry->read_proc = acpi_processor_read_throttling;
entry->write_proc = acpi_processor_write_throttling;
entry->data = acpi_driver_data(device);
}
/* 'thermal_limit' [R/W] */
entry = create_proc_entry(ACPI_PROCESSOR_FILE_LIMIT,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_PROCESSOR_FILE_LIMIT));
else {
entry->read_proc = acpi_processor_read_limit;
entry->write_proc = acpi_processor_write_limit;
entry->data = acpi_driver_data(device);
}
return_VALUE(0);
}
static int
acpi_processor_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_processor_remove_fs");
if (!acpi_processor_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_processor_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
static int
acpi_processor_get_info (
struct acpi_processor *pr)
{
acpi_status status = 0;
acpi_object object = {0};
acpi_buffer buffer = {sizeof(acpi_object), &object};
static int cpu_count = 0;
ACPI_FUNCTION_TRACE("acpi_processor_get_info");
if (!pr)
return_VALUE(-EINVAL);
#ifdef CONFIG_SMP
if (smp_num_cpus > 1)
acpi_processor_smp = smp_num_cpus;
#endif
acpi_processor_get_errata(pr);
/*
* Check to see if we have bus mastering arbitration control. This
* is required for proper C3 usage (to maintain cache coherency).
*/
if (acpi_fadt.V1_pm2_cnt_blk && acpi_fadt.pm2_cnt_len) {
pr->flags.bm_control = 1;
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Bus mastering arbitration control present\n"));
}
else
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"No bus mastering arbitration control\n"));
/*
* Evalute the processor object. Note that it is common on SMP to
* have the first (boot) processor with a valid PBLK address while
* all others have a NULL address.
*/
status = acpi_evaluate_object(pr->handle, NULL, NULL, &buffer);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error evaluating processor object\n"));
return_VALUE(-ENODEV);
}
/*
* TBD: Synch processor ID (via LAPIC/LSAPIC structures) on SMP.
* >>> 'acpi_get_processor_id(acpi_id, &id)' in arch/xxx/acpi.c
*/
pr->id = cpu_count++;
pr->acpi_id = object.processor.proc_id;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Processor [%d:%d]\n", pr->id,
pr->acpi_id));
if (!object.processor.pblk_address)
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No PBLK (NULL address)\n"));
else if (object.processor.pblk_length < 6)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid PBLK length [%d]\n",
object.processor.pblk_length));
else {
pr->throttling.address = object.processor.pblk_address;
pr->throttling.duty_offset = acpi_fadt.duty_offset;
pr->throttling.duty_width = acpi_fadt.duty_width;
pr->power.states[ACPI_STATE_C2].address =
object.processor.pblk_address + 4;
pr->power.states[ACPI_STATE_C3].address =
object.processor.pblk_address + 5;
}
acpi_processor_get_power_info(pr);
acpi_processor_get_performance_info(pr);
acpi_processor_get_throttling_info(pr);
acpi_processor_get_limit_info(pr);
return_VALUE(0);
}
static void
acpi_processor_notify (
acpi_handle handle,
u32 event,
void *data)
{
struct acpi_processor *pr = (struct acpi_processor *) data;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_processor_notify");
if (!pr)
return_VOID;
if (0 != acpi_bus_get_device(pr->handle, &device))
return_VOID;
switch (event) {
case ACPI_PROCESSOR_NOTIFY_PERFORMANCE:
case ACPI_PROCESSOR_NOTIFY_POWER:
acpi_bus_generate_event(device, event, 0);
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Unsupported event [0x%x]\n", event));
break;
}
return_VOID;
}
static int
acpi_processor_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_processor *pr = NULL;
u32 i = 0;
ACPI_FUNCTION_TRACE("acpi_processor_add");
if (!device)
return_VALUE(-EINVAL);
pr = kmalloc(sizeof(struct acpi_processor), GFP_KERNEL);
if (!pr)
return_VALUE(-ENOMEM);
memset(pr, 0, sizeof(struct acpi_processor));
pr->handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_PROCESSOR_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_PROCESSOR_CLASS);
acpi_driver_data(device) = pr;
result = acpi_processor_get_info(pr);
if (0 != result)
goto end;
result = acpi_processor_add_fs(device);
if (0 != result)
goto end;
/*
* TBD: Fix notify handler installation for processors.
*
status = acpi_install_notify_handler(pr->handle, ACPI_DEVICE_NOTIFY,
acpi_processor_notify, pr);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error installing notify handler\n"));
result = -ENODEV;
goto end;
}
*/
acpi_processor_list[pr->id] = pr;
/*
* Set Idle Handler
* ----------------
* Install the idle handler if power management (states other than C1)
* is supported. Note that the default idle handler (default_idle)
* will be used on platforms that only support C1.
*/
if ((pr->id == 0) && (pr->flags.power)) {
pm_idle_save = pm_idle;
pm_idle = acpi_processor_idle;
}
printk(KERN_INFO PREFIX "%s [%s] (supports",
acpi_device_name(device), acpi_device_bid(device));
for (i=1; i<ACPI_C_STATE_COUNT; i++)
if (pr->power.states[i].valid)
printk(" C%d", i);
if (pr->flags.performance)
printk(", %d performance states", pr->performance.state_count);
if (pr->flags.throttling)
printk(", %d throttling states", pr->throttling.state_count);
if (pr->errata.piix4.bmisx)
printk(", PIIX4 errata");
printk(")\n");
end:
if (0 != result) {
acpi_processor_remove_fs(device);
kfree(pr);
}
return_VALUE(result);
}
static int
acpi_processor_remove (
struct acpi_device *device,
int type)
{
acpi_status status = AE_OK;
struct acpi_processor *pr = NULL;
ACPI_FUNCTION_TRACE("acpi_processor_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
pr = (struct acpi_processor *) acpi_driver_data(device);
/*
status = acpi_remove_notify_handler(pr->handle, ACPI_DEVICE_NOTIFY,
acpi_processor_notify);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error removing notify handler\n"));
return_VALUE(-ENODEV);
}
*/
/* Unregister the idle handler when processor #0 is removed. */
if (pr->id == 0)
pm_idle = pm_idle_save;
acpi_processor_remove_fs(device);
acpi_processor_list[pr->id] = NULL;
kfree(pr);
return_VALUE(0);
}
static int __init
acpi_processor_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_processor_init");
memset(&acpi_processor_list, 0, sizeof(acpi_processor_list));
result = acpi_bus_register_driver(&acpi_processor_driver);
if (0 > result)
return_VALUE(-ENODEV);
return_VALUE(0);
}
static void __exit
acpi_processor_exit (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_processor_exit");
result = acpi_bus_unregister_driver(&acpi_processor_driver);
if (0 == result)
remove_proc_entry(ACPI_PROCESSOR_CLASS, acpi_root_dir);
return_VOID;
}
module_init(acpi_processor_init);
module_exit(acpi_processor_exit);
/*
* acpi_system.c - ACPI System Driver ($Revision: 40 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/poll.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <asm/uaccess.h>
#include <asm/acpi.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#ifdef CONFIG_X86
#ifdef CONFIG_ACPI_SLEEP
#include <linux/mc146818rtc.h>
#endif
#endif
#define _COMPONENT ACPI_SYSTEM_COMPONENT
ACPI_MODULE_NAME ("acpi_system")
#define PREFIX "ACPI: "
extern FADT_DESCRIPTOR acpi_fadt;
static int acpi_system_add (struct acpi_device *device);
static int acpi_system_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_system_driver = {
name: ACPI_SYSTEM_DRIVER_NAME,
class: ACPI_SYSTEM_CLASS,
ids: ACPI_SYSTEM_HID,
ops: {
add: acpi_system_add,
remove: acpi_system_remove
},
};
struct acpi_system
{
acpi_handle handle;
u8 states[ACPI_S_STATE_COUNT];
};
/* Global vars for handling event proc entry */
static spinlock_t acpi_system_event_lock = SPIN_LOCK_UNLOCKED;
int event_is_open = 0;
extern struct list_head acpi_bus_event_list;
extern wait_queue_head_t acpi_bus_event_queue;
/* --------------------------------------------------------------------------
System Sleep
-------------------------------------------------------------------------- */
#ifdef CONFIG_PM
static void
acpi_power_off (void)
{
acpi_enter_sleep_state_prep(ACPI_STATE_S5);
acpi_disable_irqs();
acpi_enter_sleep_state(ACPI_STATE_S5);
acpi_disable_irqs();
}
#endif /*CONFIG_PM*/
#ifdef CONFIG_ACPI_SLEEP
/**
* acpi_system_restore_state - OS-specific restoration of state
* @state: sleep state we're exiting
*
* Note that if we're coming back from S4, the memory image should have already
* been loaded from the disk and is already in place. (Otherwise how else would we
* be here?).
*/
acpi_status
acpi_system_restore_state (
u32 state)
{
/* restore processor state
* We should only be here if we're coming back from STR or STD.
* And, in the case of the latter, the memory image should have already
* been loaded from disk.
*/
if (state > ACPI_STATE_S1)
acpi_restore_state_mem();
/* wait for power to come back */
mdelay(10);
#ifdef HAVE_NEW_DEVICE_MODEL
/* turn all the devices back on */
device_resume(RESUME_POWER_ON);
/* enable interrupts once again */
acpi_enable_irqs();
/* restore device context */
device_resume(RESUME_RESTORE_STATE);
#endif
return AE_OK;
}
/**
* acpi_system_save_state - save OS specific state and power down devices
* @state: sleep state we're entering.
*
* This handles saving all context to memory, and possibly disk.
* First, we call to the device driver layer to save device state.
* Once we have that, we save whatevery processor and kernel state we
* need to memory.
* If we're entering S4, we then write the memory image to disk.
*
* Only then is it safe for us to power down devices, since we may need
* the disks and upstream buses to write to.
*/
acpi_status
acpi_system_save_state(
u32 state)
{
int error = 0;
#ifdef HAVE_NEW_DEVICE_MODEL
/* Send notification to devices that they will be suspended.
* If any device or driver cannot make the transition, either up
* or down, we'll get an error back.
*/
error = device_suspend(state, SUSPEND_NOTIFY);
if (error)
return AE_ERROR;
#endif
if (state < ACPI_STATE_S5) {
#ifdef HAVE_NEW_DEVICE_MODEL
/* Tell devices to stop I/O and actually save their state.
* It is theoretically possible that something could fail,
* so handle that gracefully..
*/
error = device_suspend(state, SUSPEND_SAVE_STATE);
if (error) {
/* tell devices to restore state if they have
* it saved and to start taking I/O requests.
*/
device_resume(RESUME_RESTORE_STATE);
return error;
}
#endif
/* flush caches */
wbinvd();
/* Do arch specific saving of state. */
if (state > ACPI_STATE_S1) {
error = acpi_save_state_mem();
if (!error && (state == ACPI_STATE_S4))
error = acpi_save_state_disk();
#ifdef HAVE_NEW_DEVICE_MODEL
if (error) {
device_resume(RESUME_RESTORE_STATE);
return error;
}
#endif
}
}
#ifdef HAVE_NEW_DEVICE_MODEL
/* disable interrupts
* Note that acpi_suspend -- our caller -- will do this once we return.
* But, we want it done early, so we don't get any suprises during
* the device suspend sequence.
*/
acpi_disable_irqs();
/* Unconditionally turn off devices.
* Obvious if we enter a sleep state.
* If entering S5 (soft off), this should put devices in a
* quiescent state.
*/
error = device_suspend(state, SUSPEND_POWER_DOWN);
/* We're pretty screwed if we got an error from this.
* We try to recover by simply calling our own restore_state
* function; see above for definition.
*
* If it's S5 though, go through with it anyway..
*/
if (error && state != ACPI_STATE_S5)
acpi_system_restore_state(state);
#endif
return error ? AE_ERROR : AE_OK;
}
/****************************************************************************
*
* FUNCTION: acpi_system_suspend
*
* PARAMETERS: %state: Sleep state to enter.
*
* RETURN: acpi_status, whether or not we successfully entered and
* exited sleep.
*
* DESCRIPTION: Perform OS-specific action to enter sleep state.
* This is the final step in going to sleep, per spec. If we
* know we're coming back (i.e. not entering S5), we save the
* processor flags. [ We'll have to save and restore them anyway,
* so we use the arch-agnostic save_flags and restore_flags
* here.] We then set the place to return to in arch-specific
* globals using arch_set_return_point. Finally, we call the
* ACPI function to write the proper values to I/O ports.
*
****************************************************************************/
acpi_status
acpi_system_suspend(
u32 state)
{
acpi_status status = AE_ERROR;
unsigned long flags = 0;
save_flags(flags);
switch (state)
{
case ACPI_STATE_S1:
/* do nothing */
break;
case ACPI_STATE_S2:
case ACPI_STATE_S3:
acpi_save_register_state((unsigned long)&&acpi_sleep_done);
break;
}
barrier();
status = acpi_enter_sleep_state(state);
acpi_sleep_done:
acpi_restore_register_state();
restore_flags(flags);
return status;
}
/**
* acpi_suspend - OS-agnostic system suspend/resume support (S? states)
* @state: state we're entering
*
*/
acpi_status
acpi_suspend (
u32 state)
{
acpi_status status;
/* get out if state is invalid */
if (state < ACPI_STATE_S1 || state > ACPI_STATE_S5)
return AE_ERROR;
/* do we have a wakeup address for S2 and S3? */
if (state == ACPI_STATE_S2 || state == ACPI_STATE_S3) {
if (!acpi_wakeup_address)
return AE_ERROR;
acpi_set_firmware_waking_vector((ACPI_PHYSICAL_ADDRESS) acpi_wakeup_address);
}
acpi_enter_sleep_state_prep(state);
status = acpi_system_save_state(state);
if (!ACPI_SUCCESS(status))
return status;
/* disable interrupts and flush caches */
acpi_disable_irqs();
wbinvd();
/* perform OS-specific sleep actions */
status = acpi_system_suspend(state);
/* Even if we failed to go to sleep, all of the devices are in an suspended
* mode. So, we run these unconditionaly to make sure we have a usable system
* no matter what.
*/
acpi_system_restore_state(state);
acpi_leave_sleep_state(state);
/* make sure interrupts are enabled */
acpi_enable_irqs();
/* reset firmware waking vector */
acpi_set_firmware_waking_vector((ACPI_PHYSICAL_ADDRESS) 0);
return status;
}
#endif /* CONFIG_ACPI_SLEEP */
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
static int
acpi_system_read_info (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_system *system = (struct acpi_system *) data;
char *p = page;
int size = 0;
u32 i = 0;
ACPI_FUNCTION_TRACE("acpi_system_read_info");
if (!system || (off != 0))
goto end;
p += sprintf(p, "version: %x\n", ACPI_CA_VERSION);
p += sprintf(p, "states: ");
for (i=0; i<ACPI_S_STATE_COUNT; i++) {
if (system->states[i])
p += sprintf(p, "S%d ", i);
}
p += sprintf(p, "\n");
end:
size = (p - page);
if (size <= off+count) *eof = 1;
*start = page + off;
size -= off;
if (size>count) size = count;
if (size<0) size = 0;
return_VALUE(size);
}
static int acpi_system_open_event(struct inode *inode, struct file *file);
static ssize_t acpi_system_read_event (struct file*, char*, size_t, loff_t*);
static int acpi_system_close_event(struct inode *inode, struct file *file);
static unsigned int acpi_system_poll_event(struct file *file, poll_table *wait);
static struct file_operations acpi_system_event_ops = {
open: acpi_system_open_event,
read: acpi_system_read_event,
release: acpi_system_close_event,
poll: acpi_system_poll_event,
};
static int
acpi_system_open_event(struct inode *inode, struct file *file)
{
spin_lock_irq (&acpi_system_event_lock);
if(event_is_open)
goto out_busy;
event_is_open = 1;
spin_unlock_irq (&acpi_system_event_lock);
return 0;
out_busy:
spin_unlock_irq (&acpi_system_event_lock);
return -EBUSY;
}
static ssize_t
acpi_system_read_event (
struct file *file,
char *buffer,
size_t count,
loff_t *ppos)
{
int result = 0;
char outbuf[ACPI_MAX_STRING];
int size = 0;
struct acpi_bus_event event;
ACPI_FUNCTION_TRACE("acpi_system_read_event");
memset(&event, 0, sizeof(struct acpi_bus_event));
if (count < ACPI_MAX_STRING)
goto end;
if ((file->f_flags & O_NONBLOCK)
&& (list_empty(&acpi_bus_event_list)))
return_VALUE(-EAGAIN);
result = acpi_bus_receive_event(&event);
if (0 != result) {
size = sprintf(outbuf, "error\n");
goto end;
}
size = sprintf(outbuf, "%s %s %08x %08x\n",
event.device_class?event.device_class:"<unknown>",
event.bus_id?event.bus_id:"<unknown>",
event.type,
event.data);
end:
if (copy_to_user(buffer, outbuf, size))
return_VALUE(-EFAULT);
*ppos += size;
return_VALUE(size);
}
static int
acpi_system_close_event(struct inode *inode, struct file *file)
{
spin_lock_irq (&acpi_system_event_lock);
event_is_open = 0;
spin_unlock_irq (&acpi_system_event_lock);
return 0;
}
static unsigned int
acpi_system_poll_event(
struct file *file,
poll_table *wait)
{
poll_wait(file, &acpi_bus_event_queue, wait);
if (!list_empty(&acpi_bus_event_list))
return POLLIN | POLLRDNORM;
return 0;
}
static ssize_t acpi_system_read_dsdt (struct file*, char*, size_t, loff_t*);
static struct file_operations acpi_system_dsdt_ops = {
read: acpi_system_read_dsdt,
};
static ssize_t
acpi_system_read_dsdt (
struct file *file,
char *buffer,
size_t count,
loff_t *ppos)
{
acpi_status status = AE_OK;
acpi_buffer dsdt = {ACPI_ALLOCATE_BUFFER, NULL};
void *data = 0;
size_t size = 0;
ACPI_FUNCTION_TRACE("acpi_system_read_dsdt");
status = acpi_get_table(ACPI_TABLE_DSDT, 1, &dsdt);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
if (*ppos < dsdt.length) {
data = dsdt.pointer + file->f_pos;
size = dsdt.length - file->f_pos;
if (size > count)
size = count;
if (copy_to_user(buffer, data, size)) {
kfree(dsdt.pointer);
return_VALUE(-EFAULT);
}
}
kfree(dsdt.pointer);
*ppos += size;
return_VALUE(size);
}
static ssize_t acpi_system_read_fadt (struct file*, char*, size_t, loff_t*);
static struct file_operations acpi_system_fadt_ops = {
read: acpi_system_read_fadt,
};
static ssize_t
acpi_system_read_fadt (
struct file *file,
char *buffer,
size_t count,
loff_t *ppos)
{
acpi_status status = AE_OK;
acpi_buffer fadt = {ACPI_ALLOCATE_BUFFER, NULL};
void *data = 0;
size_t size = 0;
ACPI_FUNCTION_TRACE("acpi_system_read_fadt");
status = acpi_get_table(ACPI_TABLE_FADT, 1, &fadt);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
if (*ppos < fadt.length) {
data = fadt.pointer + file->f_pos;
size = fadt.length - file->f_pos;
if (size > count)
size = count;
if (copy_to_user(buffer, data, size)) {
kfree(fadt.pointer);
return_VALUE(-EFAULT);
}
}
kfree(fadt.pointer);
*ppos += size;
return_VALUE(size);
}
#ifdef ACPI_DEBUG
static int
acpi_system_read_debug (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
char *p = page;
int size = 0;
u32 var;
if (off != 0)
goto end;
switch ((unsigned long) data) {
case 0:
p += sprintf(p, "0x%08x\n", acpi_dbg_layer);
break;
case 1:
p += sprintf(p, "0x%08x\n", acpi_dbg_level);
break;
default:
p += sprintf(p, "Invalid debug option\n");
break;
}
end:
size = (p - page);
if (size <= off+count) *eof = 1;
*start = page + off;
size -= off;
if (size>count) size = count;
if (size<0) size = 0;
return size;
}
static int
acpi_system_write_debug (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
char debug_string[12] = {'\0'};
u32 *pvar;
ACPI_FUNCTION_TRACE("acpi_system_write_debug");
if (count > sizeof(debug_string) - 1)
return_VALUE(-EINVAL);
if (copy_from_user(debug_string, buffer, count))
return_VALUE(-EFAULT);
debug_string[count] = '\0';
switch ((unsigned long) data) {
case 0:
acpi_dbg_layer = simple_strtoul(debug_string, NULL, 0);
break;
case 1:
acpi_dbg_level = simple_strtoul(debug_string, NULL, 0);
break;
default:
return_VALUE(-EINVAL);
}
return_VALUE(count);
}
#endif /* ACPI_DEBUG */
#ifdef CONFIG_ACPI_SLEEP
static int
acpi_system_read_sleep (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_system *system = (struct acpi_system *) data;
char *p = page;
int size;
int i;
ACPI_FUNCTION_TRACE("acpi_system_read_sleep");
if (!system || (off != 0))
goto end;
for (i = 0; i <= ACPI_STATE_S5; i++) {
if (system->states[i])
p += sprintf(p,"S%d ", i);
}
p += sprintf(p, "\n");
end:
size = (p - page);
if (size <= off+count) *eof = 1;
*start = page + off;
size -= off;
if (size>count) size = count;
if (size<0) size = 0;
return_VALUE(size);
}
static int
acpi_system_write_sleep (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
acpi_status status = AE_OK;
struct acpi_system *system = (struct acpi_system *) data;
char state_string[12] = {'\0'};
u32 state = 0;
ACPI_FUNCTION_TRACE("acpi_system_write_sleep");
if (!system || (count > sizeof(state_string) - 1))
return_VALUE(-EINVAL);
if (copy_from_user(state_string, buffer, count))
return_VALUE(-EFAULT);
state_string[count] = '\0';
state = simple_strtoul(state_string, NULL, 0);
if (!system->states[state])
return_VALUE(-ENODEV);
status = acpi_suspend(state);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
return_VALUE(count);
}
static int
acpi_system_read_alarm (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *context)
{
char *p = page;
int size = 0;
u32 sec, min, hr;
u32 day, mo, yr;
ACPI_FUNCTION_TRACE("acpi_system_read_alarm");
if (off != 0)
goto end;
spin_lock(&rtc_lock);
sec = CMOS_READ(RTC_SECONDS_ALARM);
min = CMOS_READ(RTC_MINUTES_ALARM);
hr = CMOS_READ(RTC_HOURS_ALARM);
#if 0 /* If we ever get an FACP with proper values... */
if (acpi_gbl_FADT->day_alrm)
day = CMOS_READ(acpi_gbl_FADT->day_alrm);
else
day = CMOS_READ(RTC_DAY_OF_MONTH);
if (acpi_gbl_FADT->mon_alrm)
mo = CMOS_READ(acpi_gbl_FADT->mon_alrm);
else
mo = CMOS_READ(RTC_MONTH);;
if (acpi_gbl_FADT->century)
yr = CMOS_READ(acpi_gbl_FADT->century) * 100 + CMOS_READ(RTC_YEAR);
else
yr = CMOS_READ(RTC_YEAR);
#else
day = CMOS_READ(RTC_DAY_OF_MONTH);
mo = CMOS_READ(RTC_MONTH);
yr = CMOS_READ(RTC_YEAR);
#endif
spin_unlock(&rtc_lock);
BCD_TO_BIN(sec);
BCD_TO_BIN(min);
BCD_TO_BIN(hr);
BCD_TO_BIN(day);
BCD_TO_BIN(mo);
BCD_TO_BIN(yr);
p += sprintf(p,"%4.4u-", yr);
p += (mo > 12) ? sprintf(p, "**-") : sprintf(p, "%2.2u-", mo);
p += (day > 31) ? sprintf(p, "** ") : sprintf(p, "%2.2u ", day);
p += (hr > 23) ? sprintf(p, "**:") : sprintf(p, "%2.2u:", hr);
p += (min > 59) ? sprintf(p, "**:") : sprintf(p, "%2.2u:", min);
p += (sec > 59) ? sprintf(p, "**\n") : sprintf(p, "%2.2u\n", sec);
end:
size = p - page;
if (size < count) *eof = 1;
else if (size > count) size = count;
if (size < 0) size = 0;
*start = page;
return_VALUE(size);
}
static int
get_date_field (
char **p,
u32 *value)
{
char *next = NULL;
char *string_end = NULL;
int result = -EINVAL;
/*
* Try to find delimeter, only to insert null. The end of the
* string won't have one, but is still valid.
*/
next = strpbrk(*p, "- :");
if (next)
*next++ = '\0';
*value = simple_strtoul(*p, &string_end, 10);
/* Signal success if we got a good digit */
if (string_end != *p)
result = 0;
if (next)
*p = next;
return result;
}
static int
acpi_system_write_alarm (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
char alarm_string[30] = {'\0'};
char *p = alarm_string;
u32 sec, min, hr, day, mo, yr;
int adjust = 0;
unsigned char rtc_control = 0;
ACPI_FUNCTION_TRACE("acpi_system_write_alarm");
if (count > sizeof(alarm_string) - 1)
return_VALUE(-EINVAL);
if (copy_from_user(alarm_string, buffer, count))
return_VALUE(-EFAULT);
alarm_string[count] = '\0';
/* check for time adjustment */
if (alarm_string[0] == '+') {
p++;
adjust = 1;
}
if ((result = get_date_field(&p, &yr)))
goto end;
if ((result = get_date_field(&p, &mo)))
goto end;
if ((result = get_date_field(&p, &day)))
goto end;
if ((result = get_date_field(&p, &hr)))
goto end;
if ((result = get_date_field(&p, &min)))
goto end;
if ((result = get_date_field(&p, &sec)))
goto end;
if (sec > 59) {
min += 1;
sec -= 60;
}
if (min > 59) {
hr += 1;
min -= 60;
}
if (hr > 23) {
day += 1;
hr -= 24;
}
if (day > 31) {
mo += 1;
day -= 31;
}
if (mo > 12) {
yr += 1;
mo -= 12;
}
spin_lock_irq(&rtc_lock);
rtc_control = CMOS_READ(RTC_CONTROL);
if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
BIN_TO_BCD(yr);
BIN_TO_BCD(mo);
BIN_TO_BCD(day);
BIN_TO_BCD(hr);
BIN_TO_BCD(min);
BIN_TO_BCD(sec);
}
if (adjust) {
yr += CMOS_READ(RTC_YEAR);
mo += CMOS_READ(RTC_MONTH);
day += CMOS_READ(RTC_DAY_OF_MONTH);
hr += CMOS_READ(RTC_HOURS);
min += CMOS_READ(RTC_MINUTES);
sec += CMOS_READ(RTC_SECONDS);
}
spin_unlock_irq(&rtc_lock);
if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
BCD_TO_BIN(yr);
BCD_TO_BIN(mo);
BCD_TO_BIN(day);
BCD_TO_BIN(hr);
BCD_TO_BIN(min);
BCD_TO_BIN(sec);
}
if (sec > 59) {
min++;
sec -= 60;
}
if (min > 59) {
hr++;
min -= 60;
}
if (hr > 23) {
day++;
hr -= 24;
}
if (day > 31) {
mo++;
day -= 31;
}
if (mo > 12) {
yr++;
mo -= 12;
}
if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
BIN_TO_BCD(yr);
BIN_TO_BCD(mo);
BIN_TO_BCD(day);
BIN_TO_BCD(hr);
BIN_TO_BCD(min);
BIN_TO_BCD(sec);
}
spin_lock_irq(&rtc_lock);
/* write the fields the rtc knows about */
CMOS_WRITE(hr, RTC_HOURS_ALARM);
CMOS_WRITE(min, RTC_MINUTES_ALARM);
CMOS_WRITE(sec, RTC_SECONDS_ALARM);
/*
* If the system supports an enhanced alarm it will have non-zero
* offsets into the CMOS RAM here -- which for some reason are pointing
* to the RTC area of memory.
*/
#if 0
if (acpi_gbl_FADT->day_alrm)
CMOS_WRITE(day, acpi_gbl_FADT->day_alrm);
if (acpi_gbl_FADT->mon_alrm)
CMOS_WRITE(mo, acpi_gbl_FADT->mon_alrm);
if (acpi_gbl_FADT->century)
CMOS_WRITE(yr/100, acpi_gbl_FADT->century);
#endif
/* enable the rtc alarm interrupt */
if (!(rtc_control & RTC_AIE)) {
rtc_control |= RTC_AIE;
CMOS_WRITE(rtc_control,RTC_CONTROL);
CMOS_READ(RTC_INTR_FLAGS);
}
spin_unlock_irq(&rtc_lock);
acpi_hw_bit_register_write(ACPI_BITREG_RT_CLOCK_ENABLE, 1, ACPI_MTX_LOCK);
file->f_pos += count;
result = 0;
end:
return_VALUE(result ? result : count);
}
#endif /*CONFIG_ACPI_SLEEP*/
static int
acpi_system_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_system_add_fs");
if (!device)
return_VALUE(-EINVAL);
/* 'info' [R] */
entry = create_proc_entry(ACPI_SYSTEM_FILE_INFO,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_INFO));
else {
entry->read_proc = acpi_system_read_info;
entry->data = acpi_driver_data(device);
}
/* 'dsdt' [R] */
entry = create_proc_entry(ACPI_SYSTEM_FILE_DSDT,
S_IRUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_DSDT));
else
entry->proc_fops = &acpi_system_dsdt_ops;
/* 'fadt' [R] */
entry = create_proc_entry(ACPI_SYSTEM_FILE_FADT,
S_IRUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_FADT));
else
entry->proc_fops = &acpi_system_fadt_ops;
/* 'event' [R] */
entry = create_proc_entry(ACPI_SYSTEM_FILE_EVENT,
S_IRUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_EVENT));
else
entry->proc_fops = &acpi_system_event_ops;
#ifdef CONFIG_ACPI_SLEEP
/* 'sleep' [R/W]*/
entry = create_proc_entry(ACPI_SYSTEM_FILE_SLEEP,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_SLEEP));
else {
entry->read_proc = acpi_system_read_sleep;
entry->write_proc = acpi_system_write_sleep;
entry->data = acpi_driver_data(device);
}
/* 'alarm' [R/W] */
entry = create_proc_entry(ACPI_SYSTEM_FILE_ALARM,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_ALARM));
else {
entry->read_proc = acpi_system_read_alarm;
entry->write_proc = acpi_system_write_alarm;
entry->data = acpi_driver_data(device);
}
#endif /*CONFIG_ACPI_SLEEP*/
#ifdef ACPI_DEBUG
/* 'debug_layer' [R/W] */
entry = create_proc_entry(ACPI_SYSTEM_FILE_DEBUG_LAYER,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_DEBUG_LAYER));
else {
entry->read_proc = acpi_system_read_debug;
entry->write_proc = acpi_system_write_debug;
entry->data = (void *) 0;
}
/* 'debug_level' [R/W] */
entry = create_proc_entry(ACPI_SYSTEM_FILE_DEBUG_LEVEL,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_SYSTEM_FILE_DEBUG_LEVEL));
else {
entry->read_proc = acpi_system_read_debug;
entry->write_proc = acpi_system_write_debug;
entry->data = (void *) 1;
}
#endif /*ACPI_DEBUG*/
return_VALUE(0);
}
static int
acpi_system_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_system_remove_fs");
if (!device)
return_VALUE(-EINVAL);
remove_proc_entry(ACPI_SYSTEM_FILE_INFO, acpi_device_dir(device));
remove_proc_entry(ACPI_SYSTEM_FILE_DSDT, acpi_device_dir(device));
remove_proc_entry(ACPI_SYSTEM_FILE_EVENT, acpi_device_dir(device));
#ifdef CONFIG_ACPI_SLEEP
remove_proc_entry(ACPI_SYSTEM_FILE_SLEEP, acpi_device_dir(device));
remove_proc_entry(ACPI_SYSTEM_FILE_ALARM, acpi_device_dir(device));
#endif
#ifdef ACPI_DEBUG
remove_proc_entry(ACPI_SYSTEM_FILE_DEBUG_LAYER,
acpi_device_dir(device));
remove_proc_entry(ACPI_SYSTEM_FILE_DEBUG_LEVEL,
acpi_device_dir(device));
#endif
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
static int
acpi_system_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_system *system = NULL;
u8 i = 0;
ACPI_FUNCTION_TRACE("acpi_system_add");
if (!device)
return_VALUE(-EINVAL);
system = kmalloc(sizeof(struct acpi_system), GFP_KERNEL);
if (!system)
return_VALUE(-ENOMEM);
memset(system, 0, sizeof(struct acpi_system));
system->handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_SYSTEM_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_SYSTEM_CLASS);
acpi_driver_data(device) = system;
result = acpi_system_add_fs(device);
if (0 != result)
goto end;
printk(KERN_INFO PREFIX "%s [%s] (supports",
acpi_device_name(device), acpi_device_bid(device));
for (i=0; i<ACPI_S_STATE_COUNT; i++) {
u8 type_a, type_b;
status = acpi_hw_get_sleep_type_data(i, &type_a, &type_b);
if (ACPI_SUCCESS(status)) {
system->states[i] = 1;
printk(" S%d", i);
}
}
printk(")\n");
#ifdef CONFIG_PM
/* Install the soft-off (S5) handler. */
if (system->states[ACPI_STATE_S5])
pm_power_off = acpi_power_off;
#endif
end:
if (0 != result)
kfree(system);
return_VALUE(result);
}
static int
acpi_system_remove (
struct acpi_device *device,
int type)
{
struct acpi_system *system = NULL;
ACPI_FUNCTION_TRACE("acpi_system_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
system = (struct acpi_system *) acpi_driver_data(device);
#ifdef CONFIG_PM
/* Remove the soft-off (S5) handler. */
if (system->states[ACPI_STATE_S5])
pm_power_off = NULL;
#endif
acpi_system_remove_fs(device);
kfree(system);
return 0;
}
int __init
acpi_system_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_system_init");
result = acpi_bus_register_driver(&acpi_system_driver);
if (0 > result)
return_VALUE(-ENODEV);
return_VALUE(0);
}
void __exit
acpi_system_exit (void)
{
ACPI_FUNCTION_TRACE("acpi_system_exit");
acpi_bus_unregister_driver(&acpi_system_driver);
return_VOID;
}
/*
* acpi_tables.c - ACPI Boot-Time Table Parsing
*
* Copyright (C) 2001 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*/
#include <linux/config.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/irq.h>
#include <linux/acpi.h>
#define PREFIX "ACPI: "
#define ACPI_MAX_TABLES ACPI_TABLE_COUNT
static char *acpi_table_signatures[ACPI_TABLE_COUNT] = {
[ACPI_TABLE_UNKNOWN] = "????",
[ACPI_APIC] = "APIC",
[ACPI_BOOT] = "BOOT",
[ACPI_DBGP] = "DBGP",
[ACPI_DSDT] = "DSDT",
[ACPI_ECDT] = "ECDT",
[ACPI_ETDT] = "ETDT",
[ACPI_FACP] = "FACP",
[ACPI_FACS] = "FACS",
[ACPI_OEMX] = "OEM",
[ACPI_PSDT] = "PSDT",
[ACPI_SBST] = "SBST",
[ACPI_SLIT] = "SLIT",
[ACPI_SPCR] = "SPCR",
[ACPI_SRAT] = "SRAT",
[ACPI_SSDT] = "SSDT",
[ACPI_SPMI] = "SPMI"
};
/* System Description Table (RSDT/XSDT) */
struct acpi_table_sdt {
unsigned long pa; /* Physical Address */
unsigned long count; /* Table count */
struct {
unsigned long pa;
enum acpi_table_id id;
unsigned long size;
} entry[ACPI_MAX_TABLES];
} __attribute__ ((packed));
static struct acpi_table_sdt sdt;
acpi_madt_entry_handler madt_handlers[ACPI_MADT_ENTRY_COUNT];
struct acpi_boot_flags acpi_boot = {1, 0}; /* Enabled by default */
void
acpi_table_print (
struct acpi_table_header *header,
unsigned long phys_addr)
{
char *name = NULL;
if (!header)
return;
/* Some table signatures aren't good table names */
if (0 == strncmp((char *) &header->signature,
acpi_table_signatures[ACPI_APIC],
sizeof(header->signature))) {
name = "MADT";
}
else if (0 == strncmp((char *) &header->signature,
acpi_table_signatures[ACPI_FACP],
sizeof(header->signature))) {
name = "FADT";
}
else
name = header->signature;
printk(KERN_INFO PREFIX "%.4s (v%3.3d %6.6s %8.8s %5.5d.%5.5d) @ 0x%p\n",
name, header->revision, header->oem_id,
header->oem_table_id, header->oem_revision >> 16,
header->oem_revision & 0xffff, (void *) phys_addr);
}
void
acpi_table_print_madt_entry (
acpi_table_entry_header *header)
{
if (!header)
return;
switch (header->type) {
case ACPI_MADT_LAPIC:
{
struct acpi_table_lapic *p =
(struct acpi_table_lapic*) header;
printk(KERN_INFO PREFIX "LAPIC (acpi_id[0x%02x] lapic_id[0x%02x] %s)\n",
p->acpi_id, p->id, p->flags.enabled?"enabled":"disabled");
}
break;
case ACPI_MADT_IOAPIC:
{
struct acpi_table_ioapic *p =
(struct acpi_table_ioapic*) header;
printk(KERN_INFO PREFIX "IOAPIC (id[0x%02x] address[0x%08x] global_irq_base[0x%x])\n",
p->id, p->address, p->global_irq_base);
}
break;
case ACPI_MADT_INT_SRC_OVR:
{
struct acpi_table_int_src_ovr *p =
(struct acpi_table_int_src_ovr*) header;
printk(KERN_INFO PREFIX "INT_SRC_OVR (bus[%d] irq[0x%x] global_irq[0x%x] polarity[0x%x] trigger[0x%x])\n",
p->bus, p->bus_irq, p->global_irq, p->flags.polarity, p->flags.trigger);
}
break;
case ACPI_MADT_NMI_SRC:
{
struct acpi_table_nmi_src *p =
(struct acpi_table_nmi_src*) header;
printk(KERN_INFO PREFIX "NMI_SRC (polarity[0x%x] trigger[0x%x] global_irq[0x%x])\n",
p->flags.polarity, p->flags.trigger, p->global_irq);
}
break;
case ACPI_MADT_LAPIC_NMI:
{
struct acpi_table_lapic_nmi *p =
(struct acpi_table_lapic_nmi*) header;
printk(KERN_INFO PREFIX "LAPIC_NMI (acpi_id[0x%02x] polarity[0x%x] trigger[0x%x] lint[0x%x])\n",
p->acpi_id, p->flags.polarity, p->flags.trigger, p->lint);
}
break;
case ACPI_MADT_LAPIC_ADDR_OVR:
{
struct acpi_table_lapic_addr_ovr *p =
(struct acpi_table_lapic_addr_ovr*) header;
printk(KERN_INFO PREFIX "LAPIC_ADDR_OVR (address[0x%016Lx])\n",
p->address);
}
break;
case ACPI_MADT_IOSAPIC:
{
struct acpi_table_iosapic *p =
(struct acpi_table_iosapic*) header;
printk(KERN_INFO PREFIX "IOSAPIC (id[0x%x] global_irq_base[0x%x] address[0x%016Lx])\n",
p->id, p->global_irq_base, p->address);
}
break;
case ACPI_MADT_LSAPIC:
{
struct acpi_table_lsapic *p =
(struct acpi_table_lsapic*) header;
printk(KERN_INFO PREFIX "LSAPIC (acpi_id[0x%02x] lsapic_id[0x%02x] lsapic_eid[0x%02x] %s)\n",
p->acpi_id, p->id, p->eid, p->flags.enabled?"enabled":"disabled");
}
break;
case ACPI_MADT_PLAT_INT_SRC:
{
struct acpi_table_plat_int_src *p =
(struct acpi_table_plat_int_src*) header;
printk(KERN_INFO PREFIX "PLAT_INT_SRC (polarity[0x%x] trigger[0x%x] type[0x%x] id[0x%04x] eid[0x%x] iosapic_vector[0x%x] global_irq[0x%x]\n",
p->flags.polarity, p->flags.trigger, p->type, p->id, p->eid, p->iosapic_vector, p->global_irq);
}
break;
default:
printk(KERN_WARNING PREFIX "Found unsupported MADT entry (type = 0x%x)\n",
header->type);
break;
}
}
static int
acpi_table_compute_checksum (
void *table_pointer,
unsigned long length)
{
u8 *p = (u8 *) table_pointer;
unsigned long remains = length;
unsigned long sum = 0;
if (!p || !length)
return -EINVAL;
while (remains--)
sum += *p++;
return (sum & 0xFF);
}
int __init
acpi_table_parse_madt (
enum acpi_table_id id,
acpi_madt_entry_handler handler)
{
struct acpi_table_madt *madt = NULL;
acpi_table_entry_header *entry = NULL;
unsigned long count = 0;
unsigned long madt_end = 0;
int i = 0;
if (!handler)
return -EINVAL;
/* Locate the MADT (if exists). There should only be one. */
for (i = 0; i < sdt.count; i++) {
if (sdt.entry[i].id != ACPI_APIC)
continue;
madt = (struct acpi_table_madt *)
__acpi_map_table(sdt.entry[i].pa, sdt.entry[i].size);
if (!madt) {
printk(KERN_WARNING PREFIX "Unable to map MADT\n");
return -ENODEV;
}
break;
}
if (!madt) {
printk(KERN_WARNING PREFIX "MADT not present\n");
return -ENODEV;
}
madt_end = (unsigned long) madt + sdt.entry[i].size;
/* Parse all entries looking for a match. */
entry = (acpi_table_entry_header *)
((unsigned long) madt + sizeof(struct acpi_table_madt));
while (((unsigned long) entry) < madt_end) {
if (entry->type == id) {
count++;
handler(entry);
}
entry = (acpi_table_entry_header *)
((unsigned long) entry += entry->length);
}
return count;
}
int __init
acpi_table_parse (
enum acpi_table_id id,
acpi_table_handler handler)
{
int count = 0;
int i = 0;
if (!handler)
return -EINVAL;
for (i = 0; i < sdt.count; i++) {
if (sdt.entry[i].id != id)
continue;
handler(sdt.entry[i].pa, sdt.entry[i].size);
count++;
}
return count;
}
static int __init
acpi_table_get_sdt (
struct acpi_table_rsdp *rsdp)
{
struct acpi_table_header *header = NULL;
int i, id = 0;
if (!rsdp)
return -EINVAL;
/* First check XSDT (but only on ACPI 2.0-compatible systems) */
if ((rsdp->revision >= 2) &&
(((struct acpi20_table_rsdp*)rsdp)->xsdt_address)) {
struct acpi_table_xsdt *mapped_xsdt = NULL;
sdt.pa = ((struct acpi20_table_rsdp*)rsdp)->xsdt_address;
header = (struct acpi_table_header *)
__acpi_map_table(sdt.pa, sizeof(struct acpi_table_header));
if (!header) {
printk(KERN_WARNING PREFIX "Unable to map XSDT header\n");
return -ENODEV;
}
if (strncmp(header->signature, "XSDT", 4)) {
printk(KERN_WARNING PREFIX "XSDT signature incorrect\n");
return -ENODEV;
}
sdt.count = (header->length - sizeof(struct acpi_table_header)) >> 3;
if (sdt.count > ACPI_MAX_TABLES) {
printk(KERN_WARNING PREFIX "Truncated %lu XSDT entries\n",
(ACPI_MAX_TABLES - sdt.count));
sdt.count = ACPI_MAX_TABLES;
}
mapped_xsdt = (struct acpi_table_xsdt *)
__acpi_map_table(sdt.pa, header->length);
if (!mapped_xsdt) {
printk(KERN_WARNING PREFIX "Unable to map XSDT\n");
return -ENODEV;
}
header = &mapped_xsdt->header;
for (i = 0; i < sdt.count; i++)
sdt.entry[i].pa = (unsigned long) mapped_xsdt->entry[i];
}
/* Then check RSDT */
else if (rsdp->rsdt_address) {
struct acpi_table_rsdt *mapped_rsdt = NULL;
sdt.pa = rsdp->rsdt_address;
header = (struct acpi_table_header *)
__acpi_map_table(sdt.pa, sizeof(struct acpi_table_header));
if (!header) {
printk(KERN_WARNING PREFIX "Unable to map RSDT header\n");
return -ENODEV;
}
if (strncmp(header->signature, "RSDT", 4)) {
printk(KERN_WARNING PREFIX "RSDT signature incorrect\n");
return -ENODEV;
}
sdt.count = (header->length - sizeof(struct acpi_table_header)) >> 2;
if (sdt.count > ACPI_MAX_TABLES) {
printk(KERN_WARNING PREFIX "Truncated %lu RSDT entries\n",
(ACPI_TABLE_COUNT - sdt.count));
sdt.count = ACPI_MAX_TABLES;
}
mapped_rsdt = (struct acpi_table_rsdt *)
__acpi_map_table(sdt.pa, header->length);
if (!mapped_rsdt) {
printk(KERN_WARNING PREFIX "Unable to map RSDT\n");
return -ENODEV;
}
header = &mapped_rsdt->header;
for (i = 0; i < sdt.count; i++)
sdt.entry[i].pa = (unsigned long) mapped_rsdt->entry[i];
}
else {
printk(KERN_WARNING PREFIX "No System Description Table (RSDT/XSDT) specified in RSDP\n");
return -ENODEV;
}
acpi_table_print(header, sdt.pa);
for (i = 0; i < sdt.count; i++) {
header = (struct acpi_table_header *)
__acpi_map_table(sdt.entry[i].pa,
sizeof(struct acpi_table_header));
if (!header)
continue;
acpi_table_print(header, sdt.entry[i].pa);
if (0 != acpi_table_compute_checksum(header, header->length)) {
printk(KERN_WARNING " >>> ERROR: Invalid checksum\n");
continue;
}
sdt.entry[i].size = header->length;
for (id = 0; id < ACPI_TABLE_COUNT; id++) {
if (0 == strncmp((char *) &header->signature,
acpi_table_signatures[id],
sizeof(header->signature))) {
sdt.entry[i].id = id;
}
}
}
return 0;
}
static void __init
acpi_table_parse_cmdline (
char *cmdline)
{
char *p = NULL;
/* NOTE: We're called too early in the boot process to use __setup */
if (!cmdline || !(p = strstr(cmdline, "acpi_boot=")))
return;
p += 10;
while (*p && (*p != ' ')) {
if (0 == memcmp(p, "madt", 4)) {
printk(KERN_INFO PREFIX "MADT processing enabled\n");
acpi_boot.madt = 1;
p += 4;
}
else if (0 == memcmp(p, "on", 2)) {
printk(KERN_INFO PREFIX "Boot-time table processing enabled\n");
acpi_boot.madt = 1;
p += 2;
}
else if (0 == memcmp(p, "off", 2)) {
printk(KERN_INFO PREFIX "Boot-time table processing disabled\n");
acpi_boot.madt = 0;
p += 3;
}
else
p++;
if (*p == ',')
p ++;
}
}
int __init
acpi_table_init (
char *cmdline)
{
struct acpi_table_rsdp *rsdp = NULL;
unsigned long rsdp_phys = 0;
int result = 0;
memset(&sdt, 0, sizeof(struct acpi_table_sdt));
memset(&madt_handlers, 0, sizeof(madt_handlers));
acpi_table_parse_cmdline(cmdline);
/* Locate and map the Root System Description Table (RSDP) */
if ((0 != acpi_find_rsdp(&rsdp_phys)) || !rsdp_phys) {
printk(KERN_ERR PREFIX "Unable to locate RSDP\n");
return -ENODEV;
}
rsdp = (struct acpi_table_rsdp *) __va(rsdp_phys);
if (!rsdp) {
printk(KERN_WARNING PREFIX "Unable to map RSDP\n");
return -ENODEV;
}
printk(KERN_INFO PREFIX "RSDP (v%3.3d %6.6s ) @ 0x%p\n",
rsdp->revision, rsdp->oem_id, (void *) rsdp_phys);
if (rsdp->revision < 2)
result = acpi_table_compute_checksum(rsdp, sizeof(struct acpi_table_rsdp));
else
result = acpi_table_compute_checksum(rsdp, ((struct acpi20_table_rsdp *)rsdp)->length);
if (0 != result) {
printk(KERN_WARNING " >>> ERROR: Invalid checksum\n");
return -ENODEV;
}
/* Locate and map the System Description table (RSDT/XSDT) */
if (0 != acpi_table_get_sdt(rsdp))
return -ENODEV;
return 0;
}
/*
* acpi_thermal.c - ACPI Thermal Zone Driver ($Revision: 33 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This driver fully implements the ACPI thermal policy as described in the
* ACPI 2.0 Specification.
*
* TBD: 1. Implement passive cooling hysteresis.
* 2. Enhance passive cooling (CPU) states/limit interface to support
* concepts of 'multiple limiters', upper/lower limits, etc.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/kmod.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_THERMAL_COMPONENT
ACPI_MODULE_NAME ("acpi_thermal")
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION(ACPI_THERMAL_DRIVER_NAME);
MODULE_LICENSE("GPL");
static int tzp = 0;
MODULE_PARM(tzp, "i");
MODULE_PARM_DESC(tzp, "Thermal zone polling frequency, in 1/10 seconds.\n");
#define PREFIX "ACPI: "
#define ACPI_THERMAL_MAX_ACTIVE 10
#define KELVIN_TO_CELSIUS(t) ((t-2732+5)/10)
static int acpi_thermal_add (struct acpi_device *device);
static int acpi_thermal_remove (struct acpi_device *device, int type);
static struct acpi_driver acpi_thermal_driver = {
name: ACPI_THERMAL_DRIVER_NAME,
class: ACPI_THERMAL_CLASS,
ids: ACPI_THERMAL_HID,
ops: {
add: acpi_thermal_add,
remove: acpi_thermal_remove,
},
};
struct acpi_thermal_state {
u8 critical:1;
u8 hot:1;
u8 passive:1;
u8 active:1;
u8 reserved:4;
int passive_index; /* a.k.a. limit state */
int active_index;
};
struct acpi_thermal_state_flags {
u8 valid:1;
u8 enabled:1;
u8 reserved:6;
};
struct acpi_thermal_critical {
struct acpi_thermal_state_flags flags;
unsigned long temperature;
};
struct acpi_thermal_hot {
struct acpi_thermal_state_flags flags;
unsigned long temperature;
};
struct acpi_thermal_passive {
struct acpi_thermal_state_flags flags;
unsigned long temperature;
unsigned long tc1;
unsigned long tc2;
unsigned long tsp;
struct acpi_handle_list devices;
};
struct acpi_thermal_active {
struct acpi_thermal_state_flags flags;
unsigned long temperature;
struct acpi_handle_list devices;
};
struct acpi_thermal_trips {
struct acpi_thermal_critical critical;
struct acpi_thermal_hot hot;
struct acpi_thermal_passive passive;
struct acpi_thermal_active active[ACPI_THERMAL_MAX_ACTIVE];
};
struct acpi_thermal_flags {
u8 cooling_mode:1; /* _SCP */
u8 devices:1; /* _TZD */
u8 reserved:6;
};
struct acpi_thermal {
acpi_handle handle;
acpi_bus_id name;
unsigned long temperature;
unsigned long last_temperature;
unsigned long polling_frequency;
u8 cooling_mode;
struct acpi_thermal_flags flags;
struct acpi_thermal_state state;
struct acpi_thermal_trips trips;
struct acpi_handle_list devices;
struct timer_list timer;
};
/* --------------------------------------------------------------------------
Thermal Zone Management
-------------------------------------------------------------------------- */
static int
acpi_thermal_get_temperature (
struct acpi_thermal *tz)
{
acpi_status status = AE_OK;
ACPI_FUNCTION_TRACE("acpi_thermal_get_temperature");
if (!tz)
return_VALUE(-EINVAL);
tz->last_temperature = tz->temperature;
status = acpi_evaluate_integer(tz->handle, "_TMP", NULL, &tz->temperature);
if (ACPI_FAILURE(status))
return -ENODEV;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Temperature is %lu dK\n", tz->temperature));
return_VALUE(0);
}
static int
acpi_thermal_get_polling_frequency (
struct acpi_thermal *tz)
{
acpi_status status = AE_OK;
ACPI_FUNCTION_TRACE("acpi_thermal_get_polling_frequency");
if (!tz)
return_VALUE(-EINVAL);
status = acpi_evaluate_integer(tz->handle, "_TZP", NULL, &tz->polling_frequency);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Polling frequency is %lu dS\n", tz->polling_frequency));
return_VALUE(0);
}
static int
acpi_thermal_set_polling (
struct acpi_thermal *tz,
int seconds)
{
ACPI_FUNCTION_TRACE("acpi_thermal_set_polling");
if (!tz)
return_VALUE(-EINVAL);
tz->polling_frequency = seconds * 10; /* Convert value to deci-seconds */
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Polling frequency set to %lu seconds\n", tz->polling_frequency));
return_VALUE(0);
}
static int
acpi_thermal_set_cooling_mode (
struct acpi_thermal *tz,
int mode)
{
acpi_status status = AE_OK;
acpi_object arg0 = {ACPI_TYPE_INTEGER};
acpi_object_list arg_list= {1, &arg0};
acpi_handle handle = NULL;
ACPI_FUNCTION_TRACE("acpi_thermal_set_cooling_mode");
if (!tz)
return_VALUE(-EINVAL);
status = acpi_get_handle(tz->handle, "_SCP", &handle);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "_SCP not present\n"));
return_VALUE(-ENODEV);
}
arg0.integer.value = mode;
status = acpi_evaluate(handle, NULL, &arg_list, NULL);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
tz->cooling_mode = mode;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Cooling mode [%s]\n",
mode?"passive":"active"));
return_VALUE(0);
}
static int
acpi_thermal_get_trip_points (
struct acpi_thermal *tz)
{
acpi_status status = AE_OK;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_get_trip_points");
if (!tz)
return_VALUE(-EINVAL);
/* Critical Shutdown (required) */
status = acpi_evaluate_integer(tz->handle, "_CRT", NULL,
&tz->trips.critical.temperature);
if (ACPI_FAILURE(status)) {
tz->trips.critical.flags.valid = 0;
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "No critical threshold\n"));
return -ENODEV;
}
else {
tz->trips.critical.flags.valid = 1;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found critical threshold [%lu]\n", tz->trips.critical.temperature));
}
/* Critical Sleep (optional) */
status = acpi_evaluate_integer(tz->handle, "_HOT", NULL, &tz->trips.hot.temperature);
if (ACPI_FAILURE(status)) {
tz->trips.hot.flags.valid = 0;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No hot threshold\n"));
}
else {
tz->trips.hot.flags.valid = 1;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found hot threshold [%lu]\n", tz->trips.hot.temperature));
}
/* Passive: Processors (optional) */
status = acpi_evaluate_integer(tz->handle, "_PSV", NULL, &tz->trips.passive.temperature);
if (ACPI_FAILURE(status)) {
tz->trips.passive.flags.valid = 0;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No passive threshold\n"));
}
else {
tz->trips.passive.flags.valid = 1;
status = acpi_evaluate_integer(tz->handle, "_TC1", NULL, &tz->trips.passive.tc1);
if (ACPI_FAILURE(status))
tz->trips.passive.flags.valid = 0;
status = acpi_evaluate_integer(tz->handle, "_TC2", NULL, &tz->trips.passive.tc2);
if (ACPI_FAILURE(status))
tz->trips.passive.flags.valid = 0;
status = acpi_evaluate_integer(tz->handle, "_TSP", NULL, &tz->trips.passive.tsp);
if (ACPI_FAILURE(status))
tz->trips.passive.flags.valid = 0;
status = acpi_evaluate_reference(tz->handle, "_PSL", NULL, &tz->trips.passive.devices);
if (ACPI_FAILURE(status))
tz->trips.passive.flags.valid = 0;
if (!tz->trips.passive.flags.valid)
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid passive threshold\n"));
else
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found passive threshold [%lu]\n", tz->trips.passive.temperature));
}
/* Active: Fans, etc. (optional) */
for (i=0; i<ACPI_THERMAL_MAX_ACTIVE; i++) {
char name[5] = {'_','A','C',('0'+i),'\0'};
status = acpi_evaluate_integer(tz->handle, name, NULL, &tz->trips.active[i].temperature);
if (ACPI_FAILURE(status))
break;
name[2] = 'L';
status = acpi_evaluate_reference(tz->handle, name, NULL, &tz->trips.active[i].devices);
if (ACPI_SUCCESS(status)) {
tz->trips.active[i].flags.valid = 1;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found active threshold [%d]:[%lu]\n", i, tz->trips.active[i].temperature));
}
else
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid active threshold [%d]\n", i));
}
return_VALUE(0);
}
static int
acpi_thermal_get_devices (
struct acpi_thermal *tz)
{
acpi_status status = AE_OK;
ACPI_FUNCTION_TRACE("acpi_thermal_get_devices");
if (!tz)
return_VALUE(-EINVAL);
status = acpi_evaluate_reference(tz->handle, "_TZD", NULL, &tz->devices);
if (ACPI_FAILURE(status))
return_VALUE(-ENODEV);
return_VALUE(0);
}
static int
acpi_thermal_call_usermode (
char *path)
{
char *argv[2] = {NULL, NULL};
char *envp[3] = {NULL, NULL, NULL};
ACPI_FUNCTION_TRACE("acpi_thermal_call_usermode");
if (!path)
return_VALUE(-EINVAL);;
argv[0] = path;
/* minimal command environment */
envp[0] = "HOME=/";
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
call_usermodehelper(argv[0], argv, envp);
return_VALUE(0);
}
static int
acpi_thermal_critical (
struct acpi_thermal *tz)
{
int result = 0;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_thermal_critical");
if (!tz || !tz->trips.critical.flags.valid)
return_VALUE(-EINVAL);
if (tz->temperature >= tz->trips.critical.temperature) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Critical trip point\n"));
tz->trips.critical.flags.enabled = 1;
}
else if (tz->trips.critical.flags.enabled)
tz->trips.critical.flags.enabled = 0;
result = acpi_bus_get_device(tz->handle, &device);
if (0 != result)
return_VALUE(result);
acpi_bus_generate_event(device, ACPI_THERMAL_NOTIFY_CRITICAL, tz->trips.critical.flags.enabled);
acpi_thermal_call_usermode(ACPI_THERMAL_PATH_POWEROFF);
return_VALUE(0);
}
static int
acpi_thermal_hot (
struct acpi_thermal *tz)
{
int result = 0;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_thermal_hot");
if (!tz || !tz->trips.hot.flags.valid)
return_VALUE(-EINVAL);
if (tz->temperature >= tz->trips.hot.temperature) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Hot trip point\n"));
tz->trips.hot.flags.enabled = 1;
}
else if (tz->trips.hot.flags.enabled)
tz->trips.hot.flags.enabled = 0;
result = acpi_bus_get_device(tz->handle, &device);
if (0 != result)
return_VALUE(result);
acpi_bus_generate_event(device, ACPI_THERMAL_NOTIFY_HOT, tz->trips.hot.flags.enabled);
/* TBD: Call user-mode "sleep(S4)" function */
return_VALUE(0);
}
static int
acpi_thermal_passive (
struct acpi_thermal *tz)
{
int result = 0;
struct acpi_thermal_passive *passive = NULL;
int trend = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_passive");
if (!tz || !tz->trips.passive.flags.valid)
return_VALUE(-EINVAL);
passive = &(tz->trips.passive);
/*
* Above Trip?
* -----------
* Calculate the thermal trend (using the passive cooling equation)
* and modify the performance limit for all passive cooling devices
* accordingly. Note that we assume symmetry.
*/
if (tz->temperature >= passive->temperature) {
trend = (passive->tc1 * (tz->temperature - tz->last_temperature)) + (passive->tc2 * (tz->temperature - passive->temperature));
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "trend[%d]=(tc1[%lu]*(tmp[%lu]-last[%lu]))+(tc2[%lu]*(tmp[%lu]-psv[%lu]))\n", trend, passive->tc1, tz->temperature, tz->last_temperature, passive->tc2, tz->temperature, passive->temperature));
/* Heating up? */
if (trend > 0)
for (i=0; i<passive->devices.count; i++)
acpi_processor_set_limit(passive->devices.handles[i], ACPI_PROCESSOR_LIMIT_INCREMENT, &tz->state.passive_index);
/* Cooling off? */
else if (trend < 0)
for (i=0; i<passive->devices.count; i++)
acpi_processor_set_limit(passive->devices.handles[i], ACPI_PROCESSOR_LIMIT_DECREMENT, &tz->state.passive_index);
}
/*
* Below Trip?
* -----------
* Implement passive cooling hysteresis to slowly increase performance
* and avoid thrashing around the passive trip point. Note that we
* assume symmetry.
*/
else if (tz->trips.passive.flags.enabled) {
for (i=0; i<passive->devices.count; i++)
acpi_processor_set_limit(passive->devices.handles[i], ACPI_PROCESSOR_LIMIT_DECREMENT, &tz->state.passive_index);
if (0 == tz->state.passive_index) {
tz->trips.passive.flags.enabled = 0;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Disabling passive cooling (zone is cool)\n"));
}
}
return_VALUE(0);
}
static int
acpi_thermal_active (
struct acpi_thermal *tz)
{
int result = 0;
struct acpi_thermal_active *active = NULL;
int i = 0;
int j = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_active");
if (!tz)
return_VALUE(-EINVAL);
for (i=0; i<ACPI_THERMAL_MAX_ACTIVE; i++) {
active = &(tz->trips.active[i]);
if (!active || !active->flags.valid)
break;
/*
* Above Threshold?
* ----------------
* If not already enabled, turn ON all cooling devices
* associated with this active threshold.
*/
if (tz->temperature >= active->temperature) {
tz->state.active_index = i;
if (!active->flags.enabled) {
for (j = 0; j < active->devices.count; j++) {
result = acpi_bus_set_power(active->devices.handles[j], ACPI_STATE_D0);
if (0 != result) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Unable to turn cooling device [%p] 'on'\n", active->devices.handles[j]));
continue;
}
active->flags.enabled = 1;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Cooling device [%p] now 'on'\n", active->devices.handles[j]));
}
}
}
/*
* Below Threshold?
* ----------------
* Turn OFF all cooling devices associated with this
* threshold.
*/
else if (active->flags.enabled) {
for (j = 0; j < active->devices.count; j++) {
result = acpi_bus_set_power(active->devices.handles[j], ACPI_STATE_D3);
if (0 != result) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Unable to turn cooling device [%p] 'off'\n", active->devices.handles[j]));
continue;
}
active->flags.enabled = 0;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Cooling device [%p] now 'off'\n", active->devices.handles[j]));
}
}
}
return_VALUE(0);
}
static void acpi_thermal_check (void *context);
static void
acpi_thermal_run (
unsigned long data)
{
acpi_os_queue_for_execution(OSD_PRIORITY_GPE, acpi_thermal_check, (void *) data);
}
static void
acpi_thermal_check (
void *data)
{
int result = 0;
struct acpi_thermal *tz = (struct acpi_thermal *) data;
unsigned long sleep_time = 0;
int i = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_check");
if (!tz) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid (NULL) context.\n"));
return_VOID;
}
result = acpi_thermal_get_temperature(tz);
if (0 != result)
return_VOID;
memset(&tz->state, 0, sizeof(tz->state));
/*
* Check Trip Points
* -----------------
* Compare the current temperature to the trip point values to see
* if we've entered one of the thermal policy states. Note that
* this function determines when a state is entered, but the
* individual policy decides when it is exited (e.g. hysteresis).
*/
if ((tz->trips.critical.flags.valid) && (tz->temperature >= tz->trips.critical.temperature))
tz->trips.critical.flags.enabled = 1;
if ((tz->trips.hot.flags.valid) && (tz->temperature >= tz->trips.hot.temperature))
tz->trips.hot.flags.enabled = 1;
if ((tz->trips.passive.flags.valid) && (tz->temperature >= tz->trips.passive.temperature))
tz->trips.passive.flags.enabled = 1;
for (i=0; i<ACPI_THERMAL_MAX_ACTIVE; i++)
if ((tz->trips.active[i].flags.valid) && (tz->temperature >= tz->trips.active[i].temperature))
tz->trips.active[i].flags.enabled = 1;
/*
* Invoke Policy
* -------------
* Separated from the above check to allow individual policy to
* determine when to exit a given state.
*/
if (tz->trips.critical.flags.enabled)
acpi_thermal_critical(tz);
if (tz->trips.hot.flags.enabled)
acpi_thermal_hot(tz);
if (tz->trips.passive.flags.enabled)
acpi_thermal_passive(tz);
if (tz->trips.active[0].flags.enabled)
acpi_thermal_active(tz);
/*
* Calculate State
* ---------------
* Again, separated from the above two to allow independent policy
* decisions.
*/
if (tz->trips.critical.flags.enabled)
tz->state.critical = 1;
if (tz->trips.hot.flags.enabled)
tz->state.hot = 1;
if (tz->trips.passive.flags.enabled)
tz->state.passive = 1;
for (i=0; i<ACPI_THERMAL_MAX_ACTIVE; i++)
if (tz->trips.active[i].flags.enabled)
tz->state.active = 1;
/*
* Calculate Sleep Time
* --------------------
* If we're in the passive state, use _TSP's value. Otherwise
* use the default polling frequency (e.g. _TZP). If no polling
* frequency is specified then we'll wait forever (at least until
* a thermal event occurs). Note that _TSP and _TZD values are
* given in 1/10th seconds (we must covert to milliseconds).
*/
if (tz->state.passive)
sleep_time = tz->trips.passive.tsp * 100;
else if (tz->polling_frequency > 0)
sleep_time = tz->polling_frequency * 100;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s: temperature[%lu] sleep[%lu]\n",
tz->name, tz->temperature, sleep_time));
/*
* Schedule Next Poll
* ------------------
*/
if (!sleep_time) {
if (timer_pending(&(tz->timer)))
del_timer(&(tz->timer));
}
else {
if (timer_pending(&(tz->timer)))
mod_timer(&(tz->timer), (HZ * sleep_time) / 1000);
else {
tz->timer.data = (u32) tz;
tz->timer.function = acpi_thermal_run;
tz->timer.expires = jiffies + (HZ * sleep_time) / 1000;
add_timer(&(tz->timer));
}
}
return_VOID;
}
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
#include <linux/compatmac.h>
#include <linux/proc_fs.h>
struct proc_dir_entry *acpi_thermal_dir = NULL;
static int
acpi_thermal_read_state (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_thermal *tz = (struct acpi_thermal *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_read_state");
if (!tz || (off != 0))
goto end;
p += sprintf(p, "state: ");
if (!tz->state.critical && !tz->state.hot && !tz->state.passive && !tz->state.active)
p += sprintf(p, "ok\n");
else {
if (tz->state.critical)
p += sprintf(p, "critical ");
if (tz->state.hot)
p += sprintf(p, "hot ");
if (tz->state.passive)
p += sprintf(p, "passive[%d] ", tz->state.passive_index);
if (tz->state.active)
p += sprintf(p, "active[%d]", tz->state.active_index);
p += sprintf(p, "\n");
}
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_thermal_read_temperature (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
int result = 0;
struct acpi_thermal *tz = (struct acpi_thermal *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_read_temperature");
if (!tz || (off != 0))
goto end;
result = acpi_thermal_get_temperature(tz);
if (0 != result)
goto end;
p += sprintf(p, "temperature: %lu C\n",
KELVIN_TO_CELSIUS(tz->temperature));
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_thermal_read_trip_points (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_thermal *tz = (struct acpi_thermal *) data;
char *p = page;
int len = 0;
int i = 0;
int j = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_read_trip_points");
if (!tz || (off != 0))
goto end;
if (tz->trips.critical.flags.valid)
p += sprintf(p, "critical (S5): %lu C\n",
KELVIN_TO_CELSIUS(tz->trips.critical.temperature));
if (tz->trips.hot.flags.valid)
p += sprintf(p, "hot (S4): %lu C\n",
KELVIN_TO_CELSIUS(tz->trips.hot.temperature));
if (tz->trips.passive.flags.valid) {
p += sprintf(p, "passive: %lu C: tc1=%lu tc2=%lu tsp=%lu devices=",
KELVIN_TO_CELSIUS(tz->trips.passive.temperature),
tz->trips.passive.tc1,
tz->trips.passive.tc2,
tz->trips.passive.tsp);
for (j=0; j<tz->trips.passive.devices.count; j++)
p += sprintf(p, "0x%p ",
tz->trips.passive.devices.handles[j]);
p += sprintf(p, "\n");
}
for (i=0; i<ACPI_THERMAL_MAX_ACTIVE; i++) {
if (!(tz->trips.active[i].flags.valid))
break;
p += sprintf(p, "active[%d]: %lu C: devices=",
i, KELVIN_TO_CELSIUS(tz->trips.active[i].temperature));
for (j=0; j<tz->trips.active[i].devices.count; j++)
p += sprintf(p, "0x%p ",
tz->trips.active[i].devices.handles[j]);
p += sprintf(p, "\n");
}
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_thermal_read_cooling_mode (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_thermal *tz = (struct acpi_thermal *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_read_cooling_mode");
if (!tz || (off != 0))
goto end;
if (!tz->flags.cooling_mode) {
p += sprintf(p, "<not supported>\n");
goto end;
}
p += sprintf(p, "cooling mode: %s\n",
tz->cooling_mode?"passive":"active");
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_thermal_write_cooling_mode (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
struct acpi_thermal *tz = (struct acpi_thermal *) data;
char mode_string[12] = {'\0'};
ACPI_FUNCTION_TRACE("acpi_thermal_write_cooling_mode");
if (!tz || (count > sizeof(mode_string) - 1))
return_VALUE(-EINVAL);
if (!tz->flags.cooling_mode)
return_VALUE(-ENODEV);
if (copy_from_user(mode_string, buffer, count))
return_VALUE(-EFAULT);
mode_string[count] = '\0';
result = acpi_thermal_set_cooling_mode(tz,
simple_strtoul(mode_string, NULL, 0));
if (0 != result)
return_VALUE(result);
return_VALUE(count);
}
static int
acpi_thermal_read_polling (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
struct acpi_thermal *tz = (struct acpi_thermal *) data;
char *p = page;
int len = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_read_polling");
if (!tz || (off != 0))
goto end;
if (!tz->polling_frequency) {
p += sprintf(p, "<polling disabled>\n");
goto end;
}
p += sprintf(p, "polling frequency: %lu seconds\n",
(tz->polling_frequency / 10));
end:
len = (p - page);
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return_VALUE(len);
}
static int
acpi_thermal_write_polling (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
struct acpi_thermal *tz = (struct acpi_thermal *) data;
char polling_string[12] = {'\0'};
int seconds = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_write_polling");
if (!tz || (count > sizeof(polling_string) - 1))
return_VALUE(-EINVAL);
if (copy_from_user(polling_string, buffer, count))
return_VALUE(-EFAULT);
polling_string[count] = '\0';
seconds = simple_strtoul(polling_string, NULL, 0);
result = acpi_thermal_set_polling(tz, seconds);
if (0 != result)
return_VALUE(result);
acpi_thermal_check(tz);
return_VALUE(count);
}
static int
acpi_thermal_add_fs (
struct acpi_device *device)
{
struct proc_dir_entry *entry = NULL;
ACPI_FUNCTION_TRACE("acpi_thermal_add_fs");
if (!acpi_thermal_dir) {
acpi_thermal_dir = proc_mkdir(ACPI_THERMAL_CLASS,
acpi_root_dir);
if (!acpi_thermal_dir)
return_VALUE(-ENODEV);
}
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_thermal_dir);
if (!acpi_device_dir(device))
return_VALUE(-ENODEV);
}
/* 'state' [R] */
entry = create_proc_entry(ACPI_THERMAL_FILE_STATE,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_THERMAL_FILE_STATE));
else {
entry->read_proc = acpi_thermal_read_state;
entry->data = acpi_driver_data(device);
}
/* 'temperature' [R] */
entry = create_proc_entry(ACPI_THERMAL_FILE_TEMPERATURE,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_THERMAL_FILE_TEMPERATURE));
else {
entry->read_proc = acpi_thermal_read_temperature;
entry->data = acpi_driver_data(device);
}
/* 'trip_points' [R] */
entry = create_proc_entry(ACPI_THERMAL_FILE_TRIP_POINTS,
S_IRUGO, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_THERMAL_FILE_POLLING_FREQ));
else {
entry->read_proc = acpi_thermal_read_trip_points;
entry->data = acpi_driver_data(device);
}
/* 'cooling_mode' [R/W] */
entry = create_proc_entry(ACPI_THERMAL_FILE_COOLING_MODE,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_THERMAL_FILE_COOLING_MODE));
else {
entry->read_proc = acpi_thermal_read_cooling_mode;
entry->write_proc = acpi_thermal_write_cooling_mode;
entry->data = acpi_driver_data(device);
}
/* 'polling_frequency' [R/W] */
entry = create_proc_entry(ACPI_THERMAL_FILE_POLLING_FREQ,
S_IFREG|S_IRUGO|S_IWUSR, acpi_device_dir(device));
if (!entry)
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to create '%s' fs entry\n",
ACPI_THERMAL_FILE_POLLING_FREQ));
else {
entry->read_proc = acpi_thermal_read_polling;
entry->write_proc = acpi_thermal_write_polling;
entry->data = acpi_driver_data(device);
}
return_VALUE(0);
}
static int
acpi_thermal_remove_fs (
struct acpi_device *device)
{
ACPI_FUNCTION_TRACE("acpi_thermal_remove_fs");
if (!acpi_thermal_dir)
return_VALUE(-ENODEV);
if (acpi_device_dir(device))
remove_proc_entry(acpi_device_bid(device), acpi_thermal_dir);
return_VALUE(0);
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
static void
acpi_thermal_notify (
acpi_handle handle,
u32 event,
void *data)
{
struct acpi_thermal *tz = (struct acpi_thermal *) data;
struct acpi_device *device = NULL;
ACPI_FUNCTION_TRACE("acpi_thermal_notify");
if (!tz)
return_VOID;
if (0 != acpi_bus_get_device(tz->handle, &device))
return_VOID;
switch (event) {
case ACPI_THERMAL_NOTIFY_TEMPERATURE:
acpi_thermal_check(tz);
break;
case ACPI_THERMAL_NOTIFY_THRESHOLDS:
acpi_thermal_get_trip_points(tz);
acpi_thermal_check(tz);
acpi_bus_generate_event(device, event, 0);
break;
case ACPI_THERMAL_NOTIFY_DEVICES:
if (tz->flags.devices)
acpi_thermal_get_devices(tz);
acpi_bus_generate_event(device, event, 0);
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Unsupported event [0x%x]\n", event));
break;
}
return_VOID;
}
static int
acpi_thermal_get_info (
struct acpi_thermal *tz)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_get_info");
if (!tz)
return_VALUE(-EINVAL);
/* Get temperature [_TMP] (required) */
result = acpi_thermal_get_temperature(tz);
if (0 != result)
return_VALUE(result);
/* Set the cooling mode [_SCP] to active cooling (default) */
result = acpi_thermal_set_cooling_mode(tz, ACPI_THERMAL_MODE_ACTIVE);
if (0 == result)
tz->flags.cooling_mode = 1;
/* Get trip points [_CRT, _PSV, etc.] (required) */
result = acpi_thermal_get_trip_points(tz);
if (0 != result)
return_VALUE(result);
/* Get default polling frequency [_TZP] (optional) */
if (tzp)
tz->polling_frequency = tzp;
else
acpi_thermal_get_polling_frequency(tz);
/* Get devices in this thermal zone [_TZD] (optional) */
result = acpi_thermal_get_devices(tz);
if (0 == result)
tz->flags.devices = 1;
return_VALUE(0);
}
static int
acpi_thermal_add (
struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_thermal *tz = NULL;
ACPI_FUNCTION_TRACE("acpi_thermal_add");
if (!device)
return_VALUE(-EINVAL);
tz = kmalloc(sizeof(struct acpi_thermal), GFP_KERNEL);
if (!tz)
return_VALUE(-ENOMEM);
memset(tz, 0, sizeof(struct acpi_thermal));
tz->handle = device->handle;
sprintf(tz->name, "%s", device->pnp.bus_id);
sprintf(acpi_device_name(device), "%s", ACPI_THERMAL_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_THERMAL_CLASS);
acpi_driver_data(device) = tz;
result = acpi_thermal_get_info(tz);
if (0 != result)
goto end;
result = acpi_thermal_add_fs(device);
if (0 != result)
return_VALUE(result);
acpi_thermal_check(tz);
status = acpi_install_notify_handler(tz->handle,
ACPI_DEVICE_NOTIFY, acpi_thermal_notify, tz);
if (ACPI_FAILURE(status)) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error installing notify handler\n"));
result = -ENODEV;
goto end;
}
init_timer(&tz->timer);
printk(KERN_INFO PREFIX "%s [%s] (%lu C)\n",
acpi_device_name(device), acpi_device_bid(device),
KELVIN_TO_CELSIUS(tz->temperature));
end:
if (result) {
acpi_thermal_remove_fs(device);
kfree(tz);
}
return_VALUE(result);
}
static int
acpi_thermal_remove (
struct acpi_device *device,
int type)
{
acpi_status status = AE_OK;
struct acpi_thermal *tz = NULL;
ACPI_FUNCTION_TRACE("acpi_thermal_remove");
if (!device || !acpi_driver_data(device))
return_VALUE(-EINVAL);
tz = (struct acpi_thermal *) acpi_driver_data(device);
if (timer_pending(&(tz->timer)))
del_timer(&(tz->timer));
status = acpi_remove_notify_handler(tz->handle,
ACPI_DEVICE_NOTIFY, acpi_thermal_notify);
if (ACPI_FAILURE(status))
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Error removing notify handler\n"));
/* Terminate policy */
if (tz->trips.passive.flags.valid
&& tz->trips.passive.flags.enabled) {
tz->trips.passive.flags.enabled = 0;
acpi_thermal_passive(tz);
}
if (tz->trips.active[0].flags.valid
&& tz->trips.active[0].flags.enabled) {
tz->trips.active[0].flags.enabled = 0;
acpi_thermal_active(tz);
}
acpi_thermal_remove_fs(device);
return_VALUE(0);
}
static int __init
acpi_thermal_init (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_init");
result = acpi_bus_register_driver(&acpi_thermal_driver);
if (0 > result)
return_VALUE(-ENODEV);
return_VALUE(0);
}
static void __exit
acpi_thermal_exit (void)
{
int result = 0;
ACPI_FUNCTION_TRACE("acpi_thermal_exit");
result = acpi_bus_unregister_driver(&acpi_thermal_driver);
if (0 == result)
remove_proc_entry(ACPI_THERMAL_CLASS, acpi_root_dir);
return_VOID;
}
module_init(acpi_thermal_init);
module_exit(acpi_thermal_exit);
/*
* acpi_utils.c - ACPI Utility Functions ($Revision: 5 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include "acpi_bus.h"
#include "acpi_drivers.h"
#define _COMPONENT ACPI_BUS_COMPONENT
ACPI_MODULE_NAME ("acpi_utils")
/* --------------------------------------------------------------------------
Object Evaluation Helpers
-------------------------------------------------------------------------- */
#ifdef ACPI_DEBUG
#define acpi_util_eval_error(h,p,s) {\
char prefix[80] = {'\0'};\
acpi_buffer buffer = {sizeof(prefix), prefix};\
acpi_get_name(h, ACPI_FULL_PATHNAME, &buffer);\
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluate [%s.%s]: %s\n",\
(char *) prefix, p, acpi_format_exception(s))); }
#else
#define acpi_util_eval_error(h,p,s)
#endif
acpi_status
acpi_extract_package (
acpi_object *package,
acpi_buffer *format,
acpi_buffer *buffer)
{
u32 size_required = 0;
u32 tail_offset = 0;
char *format_string = NULL;
u32 format_count = 0;
u32 i = 0;
u8 *head = NULL;
u8 *tail = NULL;
ACPI_FUNCTION_TRACE("acpi_extract_package");
if (!package || (package->type != ACPI_TYPE_PACKAGE) || (package->package.count < 1)) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid 'package' argument\n"));
return_ACPI_STATUS(AE_BAD_PARAMETER);
}
if (!format || !format->pointer || (format->length < 1)) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid 'format' argument\n"));
return_ACPI_STATUS(AE_BAD_PARAMETER);
}
if (!buffer) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid 'buffer' argument\n"));
return_ACPI_STATUS(AE_BAD_PARAMETER);
}
format_count = (format->length/sizeof(char)) - 1;
if (format_count > package->package.count) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Format specifies more objects [%d] than exist in package [%d].", format_count, package->package.count));
return_ACPI_STATUS(AE_BAD_DATA);
}
format_string = (char*)format->pointer;
/*
* Calculate size_required.
*/
for (i=0; i<format_count; i++) {
acpi_object *element = &(package->package.elements[i]);
if (!element) {
return_ACPI_STATUS(AE_BAD_DATA);
}
switch (element->type) {
case ACPI_TYPE_INTEGER:
switch (format_string[i]) {
case 'N':
size_required += sizeof(acpi_integer);
tail_offset += sizeof(acpi_integer);
break;
case 'S':
size_required += sizeof(char*) + sizeof(acpi_integer) + sizeof(char);
tail_offset += sizeof(char*);
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid package element [%d]: got number, expecing [%c].\n", i, format_string[i]));
return_ACPI_STATUS(AE_BAD_DATA);
break;
}
break;
case ACPI_TYPE_STRING:
case ACPI_TYPE_BUFFER:
switch (format_string[i]) {
case 'S':
size_required += sizeof(char*) + (element->string.length * sizeof(char)) + sizeof(char);
tail_offset += sizeof(char*);
break;
case 'B':
size_required += sizeof(u8*) + (element->buffer.length * sizeof(u8));
tail_offset += sizeof(u8*);
break;
default:
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid package element [%d] got string/buffer, expecing [%c].\n", i, format_string[i]));
return_ACPI_STATUS(AE_BAD_DATA);
break;
}
break;
case ACPI_TYPE_PACKAGE:
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found unsupported element at index=%d\n", i));
/* TBD: handle nested packages... */
return_ACPI_STATUS(AE_SUPPORT);
break;
}
}
/*
* Validate output buffer.
*/
if (buffer->length < size_required) {
buffer->length = size_required;
return_ACPI_STATUS(AE_BUFFER_OVERFLOW);
}
else if (buffer->length != size_required || !buffer->pointer) {
return_ACPI_STATUS(AE_BAD_PARAMETER);
}
head = buffer->pointer;
tail = buffer->pointer + tail_offset;
/*
* Extract package data.
*/
for (i=0; i<format_count; i++) {
u8 **pointer = NULL;
acpi_object *element = &(package->package.elements[i]);
if (!element) {
return_ACPI_STATUS(AE_BAD_DATA);
}
switch (element->type) {
case ACPI_TYPE_INTEGER:
switch (format_string[i]) {
case 'N':
*((acpi_integer*)head) = element->integer.value;
head += sizeof(acpi_integer);
break;
case 'S':
pointer = (u8**)head;
*pointer = tail;
*((acpi_integer*)tail) = element->integer.value;
head += sizeof(acpi_integer*);
tail += sizeof(acpi_integer);
/* NULL terminate string */
*tail = (char)0;
tail += sizeof(char);
break;
default:
/* Should never get here */
break;
}
break;
case ACPI_TYPE_STRING:
case ACPI_TYPE_BUFFER:
switch (format_string[i]) {
case 'S':
pointer = (u8**)head;
*pointer = tail;
memcpy(tail, element->string.pointer, element->string.length);
head += sizeof(char*);
tail += element->string.length * sizeof(char);
/* NULL terminate string */
*tail = (char)0;
tail += sizeof(char);
break;
case 'B':
pointer = (u8**)head;
*pointer = tail;
memcpy(tail, element->buffer.pointer, element->buffer.length);
head += sizeof(u8*);
tail += element->buffer.length * sizeof(u8);
break;
default:
/* Should never get here */
break;
}
break;
case ACPI_TYPE_PACKAGE:
/* TBD: handle nested packages... */
default:
/* Should never get here */
break;
}
}
return_ACPI_STATUS(AE_OK);
}
acpi_status
acpi_evaluate (
acpi_handle handle,
acpi_string pathname,
acpi_object_list *arguments,
acpi_buffer *buffer)
{
acpi_status status = AE_OK;
ACPI_FUNCTION_TRACE("acpi_evaluate");
/* If caller provided a buffer it must be unallocated/zero'd. */
if (buffer && (buffer->length != 0 || buffer->pointer))
return_ACPI_STATUS(AE_BAD_PARAMETER);
/*
* Evalute object. The first attempt is just to get the size of the
* object data (that is unless there's no return data); the second
* gets the data.
*/
status = acpi_evaluate_object(handle, pathname, arguments, buffer);
if (ACPI_SUCCESS(status)) {
return_ACPI_STATUS(status);
}
else if (buffer && (status == AE_BUFFER_OVERFLOW)) {
/* Gotta allocate - CALLER MUST FREE! */
buffer->pointer = kmalloc(buffer->length, GFP_KERNEL);
if (!buffer->pointer) {
return_ACPI_STATUS(AE_NO_MEMORY);
}
memset(buffer->pointer, 0, buffer->length);
/* Re-evaluate - this time it should work. */
status = acpi_evaluate_object(handle, pathname, arguments,
buffer);
}
if (ACPI_FAILURE(status)) {
if (status != AE_NOT_FOUND)
acpi_util_eval_error(handle, pathname, status);
if (buffer && buffer->pointer) {
kfree(buffer->pointer);
buffer->length = 0;
}
}
return_ACPI_STATUS(status);
}
acpi_status
acpi_evaluate_integer (
acpi_handle handle,
acpi_string pathname,
acpi_object_list *arguments,
unsigned long *data)
{
acpi_status status = AE_OK;
acpi_object element;
acpi_buffer buffer = {sizeof(acpi_object), &element};
ACPI_FUNCTION_TRACE("acpi_evaluate_integer");
if (!data)
return_ACPI_STATUS(AE_BAD_PARAMETER);
status = acpi_evaluate_object(handle, pathname, arguments, &buffer);
if (ACPI_FAILURE(status)) {
acpi_util_eval_error(handle, pathname, status);
return_ACPI_STATUS(status);
}
if (element.type != ACPI_TYPE_INTEGER) {
acpi_util_eval_error(handle, pathname, AE_BAD_DATA);
return_ACPI_STATUS(AE_BAD_DATA);
}
*data = element.integer.value;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Return value [%lu]\n", *data));
return_ACPI_STATUS(AE_OK);
}
#if 0
acpi_status
acpi_evaluate_string (
acpi_handle handle,
acpi_string pathname,
acpi_object_list *arguments,
acpi_string *data)
{
acpi_status status = AE_OK;
acpi_object *element = NULL;
acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
ACPI_FUNCTION_TRACE("acpi_evaluate_string");
if (!data)
return_ACPI_STATUS(AE_BAD_PARAMETER);
status = acpi_evaluate_object(handle, pathname, arguments, &buffer);
if (ACPI_FAILURE(status)) {
acpi_util_eval_error(handle, pathname, status);
return_ACPI_STATUS(status);
}
element = (acpi_object *) buffer.pointer;
if ((element->type != ACPI_TYPE_STRING)
|| (element->type != ACPI_TYPE_BUFFER)
|| !element->string.length) {
acpi_util_eval_error(handle, pathname, AE_BAD_DATA);
return_ACPI_STATUS(AE_BAD_DATA);
}
*data = kmalloc(element->string.length + 1, GFP_KERNEL);
if (!data) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Memory allocation error\n"));
return_VALUE(-ENOMEM);
}
memset(*data, 0, element->string.length + 1);
memcpy(*data, element->string.pointer, element->string.length);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Return value [%s]\n", *data));
return_ACPI_STATUS(AE_OK);
}
#endif
acpi_status
acpi_evaluate_reference (
acpi_handle handle,
acpi_string pathname,
acpi_object_list *arguments,
struct acpi_handle_list *list)
{
acpi_status status = AE_OK;
acpi_object *package = NULL;
acpi_object *element = NULL;
acpi_buffer buffer = {0, NULL};
u32 i = 0;
ACPI_FUNCTION_TRACE("acpi_evaluate_reference");
if (!list) {
return_ACPI_STATUS(AE_BAD_PARAMETER);
}
/* Evaluate object. */
status = acpi_evaluate(handle, pathname, arguments, &buffer);
if (ACPI_FAILURE(status))
goto end;
package = (acpi_object *) buffer.pointer;
if (!package || (package->type != ACPI_TYPE_PACKAGE)
|| (package->package.count == 0)) {
status = AE_BAD_DATA;
acpi_util_eval_error(handle, pathname, status);
goto end;
}
/* Allocate list - CALLER MUST FREE! */
list->count = package->package.count;
if (list->count > 10) {
return AE_NO_MEMORY;
}
/* TBD: dynamically allocate */
/*
list->handles = kmalloc(sizeof(acpi_handle)*(list->count), GFP_KERNEL);
if (!list->handles) {
return_ACPI_STATUS(AE_NO_MEMORY);
}
memset(list->handles, 0, sizeof(acpi_handle)*(list->count));
*/
/* Parse package data. */
for (i = 0; i < list->count; i++) {
element = &(package->package.elements[i]);
if (!element || (element->type != ACPI_TYPE_STRING)) {
status = AE_BAD_DATA;
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid element in package (not a device reference)\n"));
acpi_util_eval_error(handle, pathname, status);
break;
}
/* Convert reference (e.g. "\_PR_.CPU_") to acpi_handle. */
status = acpi_get_handle(handle, element->string.pointer,
&(list->handles[i]));
if (ACPI_FAILURE(status)) {
status = AE_BAD_DATA;
ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Unable to resolve device reference [%s]\n", element->string.pointer));
acpi_util_eval_error(handle, pathname, status);
break;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resolved reference [%s]->[%p]\n", element->string.pointer, list->handles[i]));
}
end:
if (ACPI_FAILURE(status)) {
list->count = 0;
//kfree(list->handles);
}
kfree(buffer.pointer);
return_ACPI_STATUS(status);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment