/* * driver.c - ACPI driver * * Copyright (C) 2000 Andrew Henroid * * 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 */ /* * Changes * David Woodhouse 2000-12-6 * - Fix interruptible_sleep_on() races */ #include #include #include #include #include #include #include #include #include #include #include #include "acpi.h" #include "driver.h" #ifdef CONFIG_ACPI_KERNEL_CONFIG #include #define ACPI_CAN_USE_EFI_STRUCT #endif #define _COMPONENT OS_DEPENDENT MODULE_NAME ("driver") struct acpi_run_entry { void (*callback)(void*); void *context; struct tq_struct task; }; static spinlock_t acpi_event_lock = SPIN_LOCK_UNLOCKED; static volatile u32 acpi_event_status = 0; static volatile acpi_sstate_t acpi_event_state = ACPI_STATE_S0; static DECLARE_WAIT_QUEUE_HEAD(acpi_event_wait); static volatile int acpi_thread_pid = -1; static int acpi_start = 1; /************************************************/ /* DECLARE_TASK_QUEUE is defined in */ /* /usr/src/linux/include/linux/tqueue.h */ /* So, acpi_thread_run is a pointer to a */ /* tq_struct structure,defined in the same file.*/ /************************************************/ static DECLARE_TASK_QUEUE(acpi_thread_run); static DECLARE_WAIT_QUEUE_HEAD(acpi_thread_wait); static struct ctl_table_header *acpi_sysctl = NULL; /* * Examine/modify value */ static int acpi_do_ulong(ctl_table * ctl, int write, struct file *file, void *buffer, size_t * len) { char str[2 * sizeof(unsigned long) + 4], *strend; unsigned long val; int size; if (!write) { if (file->f_pos) { *len = 0; return 0; } val = *(unsigned long *) ctl->data; size = sprintf(str, "0x%08lx\n", val); if (*len >= size) { copy_to_user(buffer, str, size); *len = size; } else *len = 0; } else { size = sizeof(str) - 1; if (size > *len) size = *len; copy_from_user(str, buffer, size); str[size] = '\0'; val = simple_strtoul(str, &strend, 0); if (strend == str) return -EINVAL; *(unsigned long *) ctl->data = val; } file->f_pos += *len; return 0; } static int acpi_do_pm_timer(ctl_table * ctl, int write, struct file *file, void *buffer, size_t * len) { int size; u32 val = 0; char str[12]; if (file->f_pos) { *len = 0; return 0; } val = acpi_read_pm_timer(); size = sprintf(str, "0x%08x\n", val); if (*len >= size) { copy_to_user(buffer, str, size); *len = size; } else *len = 0; file->f_pos += *len; return 0; } /* * Handle ACPI event */ static u32 acpi_event(void *context) { unsigned long flags; int event = (int)(long)context; int mask = 0; switch (event) { case ACPI_EVENT_POWER_BUTTON: mask = ACPI_PWRBTN; break; case ACPI_EVENT_SLEEP_BUTTON: mask = ACPI_SLPBTN; break; default: return AE_ERROR; } if (mask) { // notify process waiting on /dev/acpi spin_lock_irqsave(&acpi_event_lock, flags); acpi_event_status |= mask; spin_unlock_irqrestore(&acpi_event_lock, flags); acpi_event_state = acpi_sleep_state; wake_up_interruptible(&acpi_event_wait); } return AE_OK; } /* * Wait for next event */ static int acpi_do_event(ctl_table * ctl, int write, struct file *file, void *buffer, size_t * len) { u32 event_status = 0; acpi_sstate_t event_state = 0; char str[27]; int size; if (write) return -EPERM; if (*len < sizeof(str)) { *len = 0; return 0; } while (!event_status) { unsigned long flags; DECLARE_WAITQUEUE(wait, current); set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&acpi_event_wait, &wait); // we need an atomic exchange here spin_lock_irqsave(&acpi_event_lock, flags); event_status = acpi_event_status; acpi_event_status = 0; spin_unlock_irqrestore(&acpi_event_lock, flags); event_state = acpi_event_state; if (!event_status) schedule(); remove_wait_queue(&acpi_event_wait, &wait); set_current_state(TASK_RUNNING); if (signal_pending(current)) return -ERESTARTSYS; } size = sprintf(str, "0x%08x 0x%08x 0x%01x\n", event_status, 0, event_state); copy_to_user(buffer, str, size); *len = size; file->f_pos += size; return 0; } /* * Enter system sleep state */ /*static int acpi_do_sleep(ctl_table * ctl, int write, struct file *file, void *buffer, size_t * len) { if (!write) { if (file->f_pos) { *len = 0; return 0; } } else { int status = acpi_enter_sx(ACPI_STATE_S1); if (status) return status; } file->f_pos += *len; return 0; }*/ /* * Output important ACPI tables to proc */ static int acpi_do_table(ctl_table * ctl, int write, struct file *file, void *buffer, size_t * len) { u32 table_type; size_t size; ACPI_BUFFER buf; u8* data; table_type = (u32) ctl->data; size = 0; buf.length = 0; buf.pointer = NULL; /* determine what buffer size we will need */ if (acpi_get_table(table_type, 1, &buf) != AE_BUFFER_OVERFLOW) { *len = 0; return 0; } buf.pointer = kmalloc(buf.length, GFP_KERNEL); if (!buf.pointer) { return -ENOMEM; } /* get the table for real */ if (!ACPI_SUCCESS(acpi_get_table(table_type, 1, &buf))) { kfree(buf.pointer); *len = 0; return 0; } if (file->f_pos < buf.length) { data = buf.pointer + file->f_pos; size = buf.length - file->f_pos; if (size > *len) size = *len; if (copy_to_user(buffer, data, size)) return -EFAULT; } kfree(buf.pointer); *len = size; file->f_pos += size; return 0; } /********************************************************************/ /* R U N Q U E U E D C A L L B A C K */ /* */ /* The "callback" function address that was tramped through via */ /* "acpi_run" below is finally called and executed. If we trace all */ /* this down, the function is acpi_ev_asynch_execute_gpe_method, in */ /* evevent.c The only other function that is ever queued is */ /* acpi_ev_global_lock_thread in evmisc.c. */ /********************************************************************/ static void acpi_run_exec(void *context) { struct acpi_run_entry *entry = (struct acpi_run_entry*) context; (*entry->callback)(entry->context); kfree(entry); } /* * Queue for execution by the ACPI thread */ int acpi_run(void (*callback)(void*), void *context) { struct acpi_run_entry *entry; entry = kmalloc(sizeof(*entry), GFP_ATOMIC); if (!entry) return -1; memset(entry, 0, sizeof(entry)); entry->callback = callback; entry->context = context; entry->task.routine = acpi_run_exec; entry->task.data = entry; queue_task(&entry->task, &acpi_thread_run); if (waitqueue_active(&acpi_thread_wait)) wake_up(&acpi_thread_wait); return 0; } static struct ctl_table acpi_table[] = { {ACPI_P_LVL2_LAT, "c2_exit_latency", &acpi_c2_exit_latency, sizeof(acpi_c2_exit_latency), 0644, NULL, &acpi_do_ulong}, {ACPI_ENTER_LVL2_LAT, "c2_enter_latency", &acpi_c2_enter_latency, sizeof(acpi_c2_enter_latency), 0644, NULL, &acpi_do_ulong}, {ACPI_P_LVL3_LAT, "c3_exit_latency", &acpi_c3_exit_latency, sizeof(acpi_c3_exit_latency), 0644, NULL, &acpi_do_ulong}, {ACPI_ENTER_LVL3_LAT, "c3_enter_latency", &acpi_c3_enter_latency, sizeof(acpi_c3_enter_latency), 0644, NULL, &acpi_do_ulong}, {ACPI_C1_COUNT, "c1_count", &acpi_c1_count, sizeof(acpi_c1_count), 0644, NULL, &acpi_do_ulong}, {ACPI_C2_COUNT, "c2_count", &acpi_c2_count, sizeof(acpi_c2_count), 0644, NULL, &acpi_do_ulong}, {ACPI_C3_COUNT, "c3_count", &acpi_c3_count, sizeof(acpi_c3_count), 0644, NULL, &acpi_do_ulong}, /* until it actually works */ /* {ACPI_SLEEP, "sleep", NULL, 0, 0600, NULL, &acpi_do_sleep},*/ {ACPI_EVENT, "event", NULL, 0, 0400, NULL, &acpi_do_event}, {ACPI_FADT, "fadt", (void *) ACPI_TABLE_FADT, sizeof(int), 0444, NULL, &acpi_do_table}, {ACPI_DSDT, "dsdt", (void *) ACPI_TABLE_DSDT, sizeof(int), 0444, NULL, &acpi_do_table}, {ACPI_FACS, "facs", (void *) ACPI_TABLE_FACS, sizeof(int), 0444, NULL, &acpi_do_table}, {ACPI_XSDT, "xsdt", (void *) ACPI_TABLE_XSDT, sizeof(int), 0444, NULL, &acpi_do_table}, {ACPI_PMTIMER, "pm_timer", NULL, 0, 0444, NULL, &acpi_do_pm_timer}, {0} }; static struct ctl_table acpi_dir_table[] = { {CTL_ACPI, "acpi", NULL, 0, 0555, acpi_table}, {0} }; /* * Initialize and run interpreter within a kernel thread */ static int acpi_thread(void *context) { ACPI_PHYSICAL_ADDRESS rsdp_phys; ACPI_BUFFER buffer; ACPI_SYSTEM_INFO sys_info; /* * initialize */ daemonize(); strcpy(current->comm, "kacpid"); if (!ACPI_SUCCESS(acpi_initialize_subsystem())) { printk(KERN_ERR "ACPI: Driver initialization failed\n"); return -ENODEV; } #ifndef ACPI_CAN_USE_EFI_STRUCT if (!ACPI_SUCCESS(acpi_find_root_pointer(&rsdp_phys))) { printk(KERN_ERR "ACPI: System description tables not found\n"); return -ENODEV; } #else rsdp_phys = efi.acpi; #endif if (!ACPI_SUCCESS(acpi_find_and_load_tables(rsdp_phys))) return -ENODEV; if (PM_IS_ACTIVE()) { printk(KERN_NOTICE "ACPI: APM is already active, exiting\n"); acpi_terminate(); return -ENODEV; } buffer.length = sizeof(sys_info); buffer.pointer = &sys_info; if (!ACPI_SUCCESS (acpi_get_system_info(&buffer))) { printk(KERN_ERR "ACPI: Could not get system info\n"); acpi_terminate(); return -ENODEV; } printk(KERN_INFO "ACPI: Core Subsystem version [%x]\n", sys_info.acpi_ca_version); if (!ACPI_SUCCESS(acpi_enable_subsystem(ACPI_FULL_INITIALIZATION))) { printk(KERN_ERR "ACPI: Subsystem enable failed\n"); acpi_terminate(); return -ENODEV; } printk(KERN_ERR "ACPI: Subsystem enabled\n"); pm_active = 1; acpi_cpu_init(); acpi_sys_init(); acpi_ec_init(); acpi_power_init(); /* * Non-intuitive: 0 means pwr and sleep are implemented using the fixed * feature model, so we install handlers. 1 means a control method * implementation, or none at all, so do nothing. See ACPI spec. */ if (acpi_fadt.pwr_button == 0) { if (!ACPI_SUCCESS(acpi_install_fixed_event_handler( ACPI_EVENT_POWER_BUTTON, acpi_event, (void *) ACPI_EVENT_POWER_BUTTON))) { printk(KERN_ERR "ACPI: power button enable failed\n"); } } if (acpi_fadt.sleep_button == 0) { if (!ACPI_SUCCESS(acpi_install_fixed_event_handler( ACPI_EVENT_SLEEP_BUTTON, acpi_event, (void *) ACPI_EVENT_SLEEP_BUTTON))) { printk(KERN_ERR "ACPI: sleep button enable failed\n"); } } acpi_sysctl = register_sysctl_table(acpi_dir_table, 1); /* * run */ for (;;) { DECLARE_WAITQUEUE(wait, current); set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&acpi_thread_wait, &wait); if (list_empty(&acpi_thread_run)) schedule(); remove_wait_queue(&acpi_thread_wait, &wait); set_current_state(TASK_RUNNING); if (signal_pending(current)) break; run_task_queue(&acpi_thread_run); } /* * terminate */ unregister_sysctl_table(acpi_sysctl); /* do not terminate, because we need acpi in order to shut down */ /*acpi_terminate();*/ acpi_thread_pid = -1; return 0; } /* * Start the interpreter */ int __init acpi_init(void) { if (acpi_start) { acpi_thread_pid = kernel_thread(acpi_thread, NULL, (CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD)); } else { printk(KERN_INFO "ACPI: Disabled\n"); } return ((acpi_thread_pid >= 0) ? 0:-ENODEV); } /* * Terminate the interpreter */ void __exit acpi_exit(void) { int count; if (!kill_proc(acpi_thread_pid, SIGTERM, 1)) { // wait until thread terminates (at most 5 seconds) count = 5 * HZ; while (acpi_thread_pid >= 0 && --count) { current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); } } pm_idle = NULL; pm_power_off = NULL; pm_active = 0; } module_init(acpi_init); module_exit(acpi_exit); #ifndef MODULE static int __init acpi_setup(char *str) { while ((str != NULL) && (*str != '\0')) { if (strncmp(str, "no-idle", 7) == 0) acpi_use_idle = 0; if (strncmp(str, "off", 3) == 0) acpi_start = 0; str = strchr(str, ','); if (str != NULL) str += strspn(str, ", \t"); } return 1; } __setup("acpi=", acpi_setup); #endif