8aa55591bf
This updates /proc/acpi/wakeup to be more informative, primarily by showing the sysfs node associated with each wakeup-enabled device. Example: Device S-state Status Sysfs node PCI0 S4 disabled no-bus:pci0000:00 PS2M S4 disabled pnp:00:05 PS2K S4 disabled pnp:00:06 UAR1 S4 disabled pnp:00:08 USB1 S3 disabled pci:0000:00:03.0 USB2 S3 disabled pci:0000:00:03.1 USB3 S3 disabled USB4 S3 disabled pci:0000:00:03.3 S139 S4 disabled LAN S4 disabled pci:0000:00:04.0 MDM S4 disabled AUD S4 disabled pci:0000:00:02.7 SLPB S4 *enabled Eventually this file should be removed, but until then it's almost the only way we have to tell how the relevant ACPI tables are broken (and cope). In that example, two devices don't actually exist (USB3, S139), one can't issue wakeup events (PCI0), and two seem harmlessly (?) confused (MDM and AUD are the same PCI device, but it's the _modem_ that does wake-on-ring). In particular, we need to be sure driver model nodes are properly hooked up before we can get rid of this ACPI-only interface for wakeup events. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Len Brown <len.brown@intel.com>
517 lines
12 KiB
C
517 lines
12 KiB
C
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/bcd.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <acpi/acpi_bus.h>
|
|
#include <acpi/acpi_drivers.h>
|
|
|
|
#ifdef CONFIG_X86
|
|
#include <linux/mc146818rtc.h>
|
|
#endif
|
|
|
|
#include "sleep.h"
|
|
|
|
#define _COMPONENT ACPI_SYSTEM_COMPONENT
|
|
ACPI_MODULE_NAME("sleep")
|
|
#ifdef CONFIG_ACPI_SLEEP_PROC_SLEEP
|
|
static int acpi_system_sleep_seq_show(struct seq_file *seq, void *offset)
|
|
{
|
|
int i;
|
|
|
|
ACPI_FUNCTION_TRACE("acpi_system_sleep_seq_show");
|
|
|
|
for (i = 0; i <= ACPI_STATE_S5; i++) {
|
|
if (sleep_states[i]) {
|
|
seq_printf(seq, "S%d ", i);
|
|
}
|
|
}
|
|
|
|
seq_puts(seq, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int acpi_system_sleep_open_fs(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, acpi_system_sleep_seq_show, PDE(inode)->data);
|
|
}
|
|
|
|
static ssize_t
|
|
acpi_system_write_sleep(struct file *file,
|
|
const char __user * buffer, size_t count, loff_t * ppos)
|
|
{
|
|
char str[12];
|
|
u32 state = 0;
|
|
int error = 0;
|
|
|
|
if (count > sizeof(str) - 1)
|
|
goto Done;
|
|
memset(str, 0, sizeof(str));
|
|
if (copy_from_user(str, buffer, count))
|
|
return -EFAULT;
|
|
|
|
/* Check for S4 bios request */
|
|
if (!strcmp(str, "4b")) {
|
|
error = acpi_suspend(4);
|
|
goto Done;
|
|
}
|
|
state = simple_strtoul(str, NULL, 0);
|
|
#ifdef CONFIG_SOFTWARE_SUSPEND
|
|
if (state == 4) {
|
|
error = software_suspend();
|
|
goto Done;
|
|
}
|
|
#endif
|
|
error = acpi_suspend(state);
|
|
Done:
|
|
return error ? error : count;
|
|
}
|
|
#endif /* CONFIG_ACPI_SLEEP_PROC_SLEEP */
|
|
|
|
static int acpi_system_alarm_seq_show(struct seq_file *seq, void *offset)
|
|
{
|
|
u32 sec, min, hr;
|
|
u32 day, mo, yr, cent = 0;
|
|
unsigned char rtc_control = 0;
|
|
unsigned long flags;
|
|
|
|
ACPI_FUNCTION_TRACE("acpi_system_alarm_seq_show");
|
|
|
|
spin_lock_irqsave(&rtc_lock, flags);
|
|
|
|
sec = CMOS_READ(RTC_SECONDS_ALARM);
|
|
min = CMOS_READ(RTC_MINUTES_ALARM);
|
|
hr = CMOS_READ(RTC_HOURS_ALARM);
|
|
rtc_control = CMOS_READ(RTC_CONTROL);
|
|
|
|
/* If we ever get an FACP with proper values... */
|
|
if (acpi_gbl_FADT.day_alarm)
|
|
/* ACPI spec: only low 6 its should be cared */
|
|
day = CMOS_READ(acpi_gbl_FADT.day_alarm) & 0x3F;
|
|
else
|
|
day = CMOS_READ(RTC_DAY_OF_MONTH);
|
|
if (acpi_gbl_FADT.month_alarm)
|
|
mo = CMOS_READ(acpi_gbl_FADT.month_alarm);
|
|
else
|
|
mo = CMOS_READ(RTC_MONTH);
|
|
if (acpi_gbl_FADT.century)
|
|
cent = CMOS_READ(acpi_gbl_FADT.century);
|
|
|
|
yr = CMOS_READ(RTC_YEAR);
|
|
|
|
spin_unlock_irqrestore(&rtc_lock, flags);
|
|
|
|
if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
|
|
BCD_TO_BIN(sec);
|
|
BCD_TO_BIN(min);
|
|
BCD_TO_BIN(hr);
|
|
BCD_TO_BIN(day);
|
|
BCD_TO_BIN(mo);
|
|
BCD_TO_BIN(yr);
|
|
BCD_TO_BIN(cent);
|
|
}
|
|
|
|
/* we're trusting the FADT (see above) */
|
|
if (!acpi_gbl_FADT.century)
|
|
/* If we're not trusting the FADT, we should at least make it
|
|
* right for _this_ century... ehm, what is _this_ century?
|
|
*
|
|
* TBD:
|
|
* ASAP: find piece of code in the kernel, e.g. star tracker driver,
|
|
* which we can trust to determine the century correctly. Atom
|
|
* watch driver would be nice, too...
|
|
*
|
|
* if that has not happened, change for first release in 2050:
|
|
* if (yr<50)
|
|
* yr += 2100;
|
|
* else
|
|
* yr += 2000; // current line of code
|
|
*
|
|
* if that has not happened either, please do on 2099/12/31:23:59:59
|
|
* s/2000/2100
|
|
*
|
|
*/
|
|
yr += 2000;
|
|
else
|
|
yr += cent * 100;
|
|
|
|
seq_printf(seq, "%4.4u-", yr);
|
|
(mo > 12) ? seq_puts(seq, "**-") : seq_printf(seq, "%2.2u-", mo);
|
|
(day > 31) ? seq_puts(seq, "** ") : seq_printf(seq, "%2.2u ", day);
|
|
(hr > 23) ? seq_puts(seq, "**:") : seq_printf(seq, "%2.2u:", hr);
|
|
(min > 59) ? seq_puts(seq, "**:") : seq_printf(seq, "%2.2u:", min);
|
|
(sec > 59) ? seq_puts(seq, "**\n") : seq_printf(seq, "%2.2u\n", sec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int acpi_system_alarm_open_fs(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, acpi_system_alarm_seq_show, PDE(inode)->data);
|
|
}
|
|
|
|
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 ssize_t
|
|
acpi_system_write_alarm(struct file *file,
|
|
const char __user * buffer, size_t count, loff_t * ppos)
|
|
{
|
|
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);
|
|
/*
|
|
* Disable alarm interrupt before setting alarm timer or else
|
|
* when ACPI_EVENT_RTC is enabled, a spurious ACPI interrupt occurs
|
|
*/
|
|
rtc_control &= ~RTC_AIE;
|
|
CMOS_WRITE(rtc_control, RTC_CONTROL);
|
|
CMOS_READ(RTC_INTR_FLAGS);
|
|
|
|
/* 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 (acpi_gbl_FADT.day_alarm)
|
|
CMOS_WRITE(day, acpi_gbl_FADT.day_alarm);
|
|
if (acpi_gbl_FADT.month_alarm)
|
|
CMOS_WRITE(mo, acpi_gbl_FADT.month_alarm);
|
|
if (acpi_gbl_FADT.century)
|
|
CMOS_WRITE(yr / 100, acpi_gbl_FADT.century);
|
|
/* enable the rtc alarm interrupt */
|
|
rtc_control |= RTC_AIE;
|
|
CMOS_WRITE(rtc_control, RTC_CONTROL);
|
|
CMOS_READ(RTC_INTR_FLAGS);
|
|
|
|
spin_unlock_irq(&rtc_lock);
|
|
|
|
acpi_clear_event(ACPI_EVENT_RTC);
|
|
acpi_enable_event(ACPI_EVENT_RTC, 0);
|
|
|
|
*ppos += count;
|
|
|
|
result = 0;
|
|
end:
|
|
return_VALUE(result ? result : count);
|
|
}
|
|
|
|
extern struct list_head acpi_wakeup_device_list;
|
|
extern spinlock_t acpi_device_lock;
|
|
|
|
static int
|
|
acpi_system_wakeup_device_seq_show(struct seq_file *seq, void *offset)
|
|
{
|
|
struct list_head *node, *next;
|
|
|
|
seq_printf(seq, "Device\tS-state\t Status Sysfs node\n");
|
|
|
|
spin_lock(&acpi_device_lock);
|
|
list_for_each_safe(node, next, &acpi_wakeup_device_list) {
|
|
struct acpi_device *dev =
|
|
container_of(node, struct acpi_device, wakeup_list);
|
|
struct device *ldev;
|
|
|
|
if (!dev->wakeup.flags.valid)
|
|
continue;
|
|
spin_unlock(&acpi_device_lock);
|
|
|
|
ldev = acpi_get_physical_device(dev->handle);
|
|
seq_printf(seq, "%s\t S%d\t%c%-8s ",
|
|
dev->pnp.bus_id,
|
|
(u32) dev->wakeup.sleep_state,
|
|
dev->wakeup.flags.run_wake ? '*' : ' ',
|
|
dev->wakeup.state.enabled ? "enabled" : "disabled");
|
|
if (ldev)
|
|
seq_printf(seq, "%s:%s",
|
|
ldev->bus ? ldev->bus->name : "no-bus",
|
|
ldev->bus_id);
|
|
seq_printf(seq, "\n");
|
|
put_device(ldev);
|
|
|
|
spin_lock(&acpi_device_lock);
|
|
}
|
|
spin_unlock(&acpi_device_lock);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
acpi_system_write_wakeup_device(struct file *file,
|
|
const char __user * buffer,
|
|
size_t count, loff_t * ppos)
|
|
{
|
|
struct list_head *node, *next;
|
|
char strbuf[5];
|
|
char str[5] = "";
|
|
int len = count;
|
|
struct acpi_device *found_dev = NULL;
|
|
|
|
if (len > 4)
|
|
len = 4;
|
|
|
|
if (copy_from_user(strbuf, buffer, len))
|
|
return -EFAULT;
|
|
strbuf[len] = '\0';
|
|
sscanf(strbuf, "%s", str);
|
|
|
|
spin_lock(&acpi_device_lock);
|
|
list_for_each_safe(node, next, &acpi_wakeup_device_list) {
|
|
struct acpi_device *dev =
|
|
container_of(node, struct acpi_device, wakeup_list);
|
|
if (!dev->wakeup.flags.valid)
|
|
continue;
|
|
|
|
if (!strncmp(dev->pnp.bus_id, str, 4)) {
|
|
dev->wakeup.state.enabled =
|
|
dev->wakeup.state.enabled ? 0 : 1;
|
|
found_dev = dev;
|
|
break;
|
|
}
|
|
}
|
|
if (found_dev) {
|
|
list_for_each_safe(node, next, &acpi_wakeup_device_list) {
|
|
struct acpi_device *dev = container_of(node,
|
|
struct
|
|
acpi_device,
|
|
wakeup_list);
|
|
|
|
if ((dev != found_dev) &&
|
|
(dev->wakeup.gpe_number ==
|
|
found_dev->wakeup.gpe_number)
|
|
&& (dev->wakeup.gpe_device ==
|
|
found_dev->wakeup.gpe_device)) {
|
|
printk(KERN_WARNING
|
|
"ACPI: '%s' and '%s' have the same GPE, "
|
|
"can't disable/enable one seperately\n",
|
|
dev->pnp.bus_id, found_dev->pnp.bus_id);
|
|
dev->wakeup.state.enabled =
|
|
found_dev->wakeup.state.enabled;
|
|
}
|
|
}
|
|
}
|
|
spin_unlock(&acpi_device_lock);
|
|
return count;
|
|
}
|
|
|
|
static int
|
|
acpi_system_wakeup_device_open_fs(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, acpi_system_wakeup_device_seq_show,
|
|
PDE(inode)->data);
|
|
}
|
|
|
|
static const struct file_operations acpi_system_wakeup_device_fops = {
|
|
.open = acpi_system_wakeup_device_open_fs,
|
|
.read = seq_read,
|
|
.write = acpi_system_write_wakeup_device,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
#ifdef CONFIG_ACPI_SLEEP_PROC_SLEEP
|
|
static const struct file_operations acpi_system_sleep_fops = {
|
|
.open = acpi_system_sleep_open_fs,
|
|
.read = seq_read,
|
|
.write = acpi_system_write_sleep,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
#endif /* CONFIG_ACPI_SLEEP_PROC_SLEEP */
|
|
|
|
static const struct file_operations acpi_system_alarm_fops = {
|
|
.open = acpi_system_alarm_open_fs,
|
|
.read = seq_read,
|
|
.write = acpi_system_write_alarm,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static u32 rtc_handler(void *context)
|
|
{
|
|
acpi_clear_event(ACPI_EVENT_RTC);
|
|
acpi_disable_event(ACPI_EVENT_RTC, 0);
|
|
|
|
return ACPI_INTERRUPT_HANDLED;
|
|
}
|
|
|
|
static int acpi_sleep_proc_init(void)
|
|
{
|
|
struct proc_dir_entry *entry = NULL;
|
|
|
|
if (acpi_disabled)
|
|
return 0;
|
|
|
|
#ifdef CONFIG_ACPI_SLEEP_PROC_SLEEP
|
|
/* 'sleep' [R/W] */
|
|
entry =
|
|
create_proc_entry("sleep", S_IFREG | S_IRUGO | S_IWUSR,
|
|
acpi_root_dir);
|
|
if (entry)
|
|
entry->proc_fops = &acpi_system_sleep_fops;
|
|
#endif
|
|
|
|
/* 'alarm' [R/W] */
|
|
entry =
|
|
create_proc_entry("alarm", S_IFREG | S_IRUGO | S_IWUSR,
|
|
acpi_root_dir);
|
|
if (entry)
|
|
entry->proc_fops = &acpi_system_alarm_fops;
|
|
|
|
/* 'wakeup device' [R/W] */
|
|
entry =
|
|
create_proc_entry("wakeup", S_IFREG | S_IRUGO | S_IWUSR,
|
|
acpi_root_dir);
|
|
if (entry)
|
|
entry->proc_fops = &acpi_system_wakeup_device_fops;
|
|
|
|
acpi_install_fixed_event_handler(ACPI_EVENT_RTC, rtc_handler, NULL);
|
|
return 0;
|
|
}
|
|
|
|
late_initcall(acpi_sleep_proc_init);
|