| /* |
| * acpi_system.c - ACPI System Driver ($Revision: 57 $) |
| * |
| * 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/interrupt.h> |
| #include <linux/sysrq.h> |
| #include <linux/compatmac.h> |
| #include <linux/proc_fs.h> |
| #include <linux/pm.h> |
| #include <asm/uaccess.h> |
| #include <asm/acpi.h> |
| #include <acpi/acpi_bus.h> |
| #include <acpi/acpi_drivers.h> |
| #include <linux/sched.h> |
| |
| #ifdef CONFIG_ACPI_SLEEP |
| #include <linux/mc146818rtc.h> |
| #include <linux/irq.h> |
| #include <asm/hw_irq.h> |
| |
| acpi_status acpi_system_save_state(u32); |
| #else |
| static inline acpi_status acpi_system_save_state(u32 state) |
| { |
| return AE_OK; |
| } |
| #endif /* !CONFIG_ACPI_SLEEP */ |
| |
| #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); |
| |
| acpi_status acpi_suspend (u32 state); |
| |
| 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) |
| { |
| if (unlikely(in_interrupt())) |
| BUG(); |
| /* Some SMP machines only can poweroff in boot CPU */ |
| set_cpus_allowed(current, 1UL << cpu_logical_map(0)); |
| acpi_enter_sleep_state_prep(ACPI_STATE_S5); |
| ACPI_DISABLE_IRQS(); |
| acpi_enter_sleep_state(ACPI_STATE_S5); |
| |
| printk(KERN_EMERG "ACPI: can not power off machine\n"); |
| } |
| |
| #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) |
| { |
| /* |
| * 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(); |
| |
| /* Do _early_ resume for irqs. Required by |
| * ACPI specs. |
| */ |
| /* TBD: call arch dependant reinitialization of the |
| * interrupts. |
| */ |
| #ifdef CONFIG_X86 |
| init_8259A(0); |
| #endif |
| /* wait for power to come back */ |
| mdelay(1000); |
| |
| } |
| |
| /* Be really sure that irqs are disabled. */ |
| ACPI_DISABLE_IRQS(); |
| |
| /* Wait a little again, just in case... */ |
| mdelay(1000); |
| |
| /* enable interrupts once again */ |
| ACPI_ENABLE_IRQS(); |
| |
| /* turn all the devices back on */ |
| if (state > ACPI_STATE_S1) |
| pm_send_all(PM_RESUME, (void *)0); |
| |
| 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 it is 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; |
| |
| /* 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. |
| */ |
| if (state > ACPI_STATE_S1) { |
| error = pm_send_all(PM_SAVE_STATE, (void *)3); |
| if (error) |
| return AE_ERROR; |
| } |
| |
| if (state <= ACPI_STATE_S5) { |
| /* Tell devices to stop I/O and actually save their state. |
| * It is theoretically possible that something could fail, |
| * so handle that gracefully.. |
| */ |
| if (state > ACPI_STATE_S1 && state != ACPI_STATE_S5) { |
| error = pm_send_all(PM_SUSPEND, (void *)3); |
| if (error) { |
| /* Tell devices to restore state if they have |
| * it saved and to start taking I/O requests. |
| */ |
| pm_send_all(PM_RESUME, (void *)0); |
| return error; |
| } |
| } |
| |
| /* flush caches */ |
| ACPI_FLUSH_CPU_CACHE(); |
| |
| /* Do arch specific saving of state. */ |
| if (state > ACPI_STATE_S1) { |
| error = acpi_save_state_mem(); |
| |
| /* TBD: if no s4bios, write codes for |
| * acpi_save_state_disk()... |
| */ |
| #if 0 |
| if (!error && (state == ACPI_STATE_S4)) |
| error = acpi_save_state_disk(); |
| #endif |
| if (error) { |
| pm_send_all(PM_RESUME, (void *)0); |
| return error; |
| } |
| } |
| } |
| /* 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. |
| */ |
| |
| if (state > ACPI_STATE_S1) { |
| error = pm_send_all(PM_SUSPEND, (void *)3); |
| |
| /* 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); |
| } |
| 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; |
| |
| local_irq_save(flags); |
| /* kernel_fpu_begin(); */ |
| |
| switch (state) { |
| case ACPI_STATE_S1: |
| case ACPI_STATE_S5: |
| barrier(); |
| status = acpi_enter_sleep_state(state); |
| break; |
| case ACPI_STATE_S4: |
| do_suspend_lowlevel_s4bios(0); |
| break; |
| } |
| |
| /* kernel_fpu_end(); */ |
| local_irq_restore(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; |
| |
| /* only support S1 and S5 on kernel 2.4 */ |
| if (state != ACPI_STATE_S1 && state != ACPI_STATE_S4 |
| && state != ACPI_STATE_S5) |
| return AE_ERROR; |
| |
| |
| if (ACPI_STATE_S4 == state) { |
| /* For s4bios, we need a wakeup address. */ |
| if (1 == acpi_gbl_FACS->S4bios_f && |
| 0 != acpi_gbl_FADT->smi_cmd) { |
| if (!acpi_wakeup_address) |
| return AE_ERROR; |
| acpi_set_firmware_waking_vector((acpi_physical_address) acpi_wakeup_address); |
| } else |
| /* We don't support S4 under 2.4. Give up */ |
| return AE_ERROR; |
| } |
| |
| status = acpi_system_save_state(state); |
| if (!ACPI_SUCCESS(status) && state != ACPI_STATE_S5) |
| return status; |
| |
| acpi_enter_sleep_state_prep(state); |
| |
| /* disable interrupts and flush caches */ |
| ACPI_DISABLE_IRQS(); |
| ACPI_FLUSH_CPU_CACHE(); |
| |
| /* 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_leave_sleep_state(state); |
| acpi_system_restore_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) |
| -------------------------------------------------------------------------- */ |
| |
| 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); |
| if (i == ACPI_STATE_S4 && |
| acpi_gbl_FACS->S4bios_f && |
| 0 != acpi_gbl_FADT->smi_cmd) |
| p += sprintf(p, "S4Bios "); |
| } |
| } |
| 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; |
| struct acpi_bus_event event; |
| static char str[ACPI_MAX_STRING]; |
| static int chars_remaining = 0; |
| static char *ptr; |
| |
| |
| ACPI_FUNCTION_TRACE("acpi_system_read_event"); |
| |
| if (!chars_remaining) { |
| memset(&event, 0, sizeof(struct acpi_bus_event)); |
| |
| if ((file->f_flags & O_NONBLOCK) |
| && (list_empty(&acpi_bus_event_list))) |
| return_VALUE(-EAGAIN); |
| |
| result = acpi_bus_receive_event(&event); |
| if (result) { |
| return_VALUE(-EIO); |
| } |
| |
| chars_remaining = sprintf(str, "%s %s %08x %08x\n", |
| event.device_class?event.device_class:"<unknown>", |
| event.bus_id?event.bus_id:"<unknown>", |
| event.type, event.data); |
| ptr = str; |
| } |
| |
| if (chars_remaining < count) { |
| count = chars_remaining; |
| } |
| |
| if (copy_to_user(buffer, ptr, count)) |
| return_VALUE(-EFAULT); |
| |
| *ppos += count; |
| chars_remaining -= count; |
| ptr += count; |
| |
| return_VALUE(count); |
| } |
| |
| 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; |
| struct 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)) { |
| acpi_os_free(dsdt.pointer); |
| return_VALUE(-EFAULT); |
| } |
| } |
| |
| acpi_os_free(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; |
| struct 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)) { |
| acpi_os_free(fadt.pointer); |
| return_VALUE(-EFAULT); |
| } |
| } |
| |
| acpi_os_free(fadt.pointer); |
| |
| *ppos += size; |
| |
| return_VALUE(size); |
| } |
| |
| |
| #ifdef ACPI_DEBUG_OUTPUT |
| |
| 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; |
| |
| 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'}; |
| |
| 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_OUTPUT */ |
| |
| |
| #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); |
| if (i == ACPI_STATE_S4 && acpi_gbl_FACS->S4bios_f && |
| acpi_gbl_FADT->smi_cmd != 0) |
| p += sprintf(p, "S4Bios "); |
| } |
| } |
| |
| 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 (state >= ACPI_S_STATE_COUNT || !system->states[state]) |
| return_VALUE(-ENODEV); |
| |
| /* |
| * If S4 is supported by the OS, then we should assume that |
| * echo 4b > /proc/acpi/sleep is for s4bios. |
| * Since we have only s4bios, we assume that acpi_suspend failed |
| * if no s4bios support. |
| */ |
| 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); |
| |
| #if 0 |
| /* we're trusting the FADT (see above)*/ |
| #else |
| /* 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; |
| #endif |
| |
| 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_set_register(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_OUTPUT |
| |
| /* '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_OUTPUT */ |
| |
| 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_OUTPUT |
| 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 |
| -------------------------------------------------------------------------- */ |
| |
| #if defined(CONFIG_MAGIC_SYSRQ) && defined(CONFIG_PM) |
| |
| static int po_cb_active; |
| |
| static void acpi_po_tramp(void *x) |
| { |
| acpi_power_off(); |
| } |
| |
| /* Simple wrapper calling power down function. */ |
| static void acpi_sysrq_power_off(int key, struct pt_regs *pt_regs, |
| struct kbd_struct *kbd, struct tty_struct *tty) |
| { |
| static struct tq_struct tq = { .routine = acpi_po_tramp }; |
| if (po_cb_active++) |
| return; |
| schedule_task(&tq); |
| } |
| |
| struct sysrq_key_op sysrq_acpi_poweroff_op = { |
| .handler = &acpi_sysrq_power_off, |
| .help_msg = "Off", |
| .action_msg = "Power Off\n" |
| }; |
| |
| #endif /* CONFIG_MAGIC_SYSRQ */ |
| |
| 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 (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_get_sleep_type_data(i, &type_a, &type_b); |
| switch (i) { |
| case ACPI_STATE_S4: |
| if (acpi_gbl_FACS->S4bios_f && |
| 0 != acpi_gbl_FADT->smi_cmd) { |
| printk(" S4bios"); |
| system->states[i] = 1; |
| } |
| /* no break */ |
| default: |
| 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; |
| register_sysrq_key('o', &sysrq_acpi_poweroff_op); |
| } |
| #endif |
| |
| end: |
| if (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]) { |
| unregister_sysrq_key('o', &sysrq_acpi_poweroff_op); |
| 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 (result < 0) |
| 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; |
| } |