X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=arch%2Farm%2Fkernel%2Fapm.c;h=2c37b70b17ab45087ff7c43d30aea6006d3f4e81;hb=97bf2856c6014879bd04983a3e9dfcdac1e7fe85;hp=b0bbd1e62ebb70e107268ce7b176de0ffcd13c8e;hpb=6a77f38946aaee1cd85eeec6cf4229b204c15071;p=linux-2.6.git diff --git a/arch/arm/kernel/apm.c b/arch/arm/kernel/apm.c index b0bbd1e62..2c37b70b1 100644 --- a/arch/arm/kernel/apm.c +++ b/arch/arm/kernel/apm.c @@ -10,14 +10,13 @@ * [This document is available from Microsoft at: * http://www.microsoft.com/hwdev/busbios/amp_12.htm] */ -#include #include #include -#include #include #include #include #include +#include #include #include #include @@ -25,6 +24,8 @@ #include #include #include +#include +#include #include /* apm_power_info */ #include @@ -70,7 +71,8 @@ struct apm_user { #define SUSPEND_PENDING 1 /* suspend pending read */ #define SUSPEND_READ 2 /* suspend read, pending ack */ #define SUSPEND_ACKED 3 /* suspend acked */ -#define SUSPEND_DONE 4 /* suspend completed */ +#define SUSPEND_WAIT 4 /* waiting for suspend */ +#define SUSPEND_DONE 5 /* suspend completed */ struct apm_queue queue; }; @@ -80,6 +82,7 @@ struct apm_user { */ static int suspends_pending; static int apm_disabled; +static struct task_struct *kapmd_tsk; static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue); static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue); @@ -96,10 +99,10 @@ static LIST_HEAD(apm_user_list); * to be suspending the system. */ static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait); -static DECLARE_COMPLETION(kapmd_exit); static DEFINE_SPINLOCK(kapmd_queue_lock); static struct apm_queue kapmd_queue; +static DEFINE_MUTEX(state_lock); static const char driver_version[] = "1.13"; /* no spaces */ @@ -147,38 +150,60 @@ static void queue_add_event(struct apm_queue *q, apm_event_t event) q->events[q->event_head] = event; } -static void queue_event_one_user(struct apm_user *as, apm_event_t event) +static void queue_event(apm_event_t event) { - if (as->suser && as->writer) { - switch (event) { - case APM_SYS_SUSPEND: - case APM_USER_SUSPEND: - /* - * If this user already has a suspend pending, - * don't queue another one. - */ - if (as->suspend_state != SUSPEND_NONE) - return; + struct apm_user *as; - as->suspend_state = SUSPEND_PENDING; - suspends_pending++; - break; - } + down_read(&user_list_lock); + list_for_each_entry(as, &apm_user_list, list) { + if (as->reader) + queue_add_event(&as->queue, event); } - queue_add_event(&as->queue, event); + up_read(&user_list_lock); + wake_up_interruptible(&apm_waitqueue); } -static void queue_event(apm_event_t event, struct apm_user *sender) +/* + * queue_suspend_event - queue an APM suspend event. + * + * Check that we're in a state where we can suspend. If not, + * return -EBUSY. Otherwise, queue an event to all "writer" + * users. If there are no "writer" users, return '1' to + * indicate that we can immediately suspend. + */ +static int queue_suspend_event(apm_event_t event, struct apm_user *sender) { struct apm_user *as; + int ret = 1; + mutex_lock(&state_lock); down_read(&user_list_lock); + + /* + * If a thread is still processing, we can't suspend, so reject + * the request. + */ list_for_each_entry(as, &apm_user_list, list) { - if (as != sender && as->reader) - queue_event_one_user(as, event); + if (as != sender && as->reader && as->writer && as->suser && + as->suspend_state != SUSPEND_NONE) { + ret = -EBUSY; + goto out; + } } + + list_for_each_entry(as, &apm_user_list, list) { + if (as != sender && as->reader && as->writer && as->suser) { + as->suspend_state = SUSPEND_PENDING; + suspends_pending++; + queue_add_event(&as->queue, event); + ret = 0; + } + } + out: up_read(&user_list_lock); + mutex_unlock(&state_lock); wake_up_interruptible(&apm_waitqueue); + return ret; } static void apm_suspend(void) @@ -190,17 +215,22 @@ static void apm_suspend(void) * Anyone on the APM queues will think we're still suspended. * Send a message so everyone knows we're now awake again. */ - queue_event(APM_NORMAL_RESUME, NULL); + queue_event(APM_NORMAL_RESUME); /* * Finally, wake up anyone who is sleeping on the suspend. */ + mutex_lock(&state_lock); down_read(&user_list_lock); list_for_each_entry(as, &apm_user_list, list) { - as->suspend_result = err; - as->suspend_state = SUSPEND_DONE; + if (as->suspend_state == SUSPEND_WAIT || + as->suspend_state == SUSPEND_ACKED) { + as->suspend_result = err; + as->suspend_state = SUSPEND_DONE; + } } up_read(&user_list_lock); + mutex_unlock(&state_lock); wake_up(&apm_suspend_waitqueue); } @@ -226,8 +256,11 @@ static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t if (copy_to_user(buf, &event, sizeof(event))) break; - if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND) + mutex_lock(&state_lock); + if (as->suspend_state == SUSPEND_PENDING && + (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)) as->suspend_state = SUSPEND_READ; + mutex_unlock(&state_lock); buf += sizeof(event); i -= sizeof(event); @@ -269,9 +302,13 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) switch (cmd) { case APM_IOC_SUSPEND: + mutex_lock(&state_lock); + as->suspend_result = -EINTR; if (as->suspend_state == SUSPEND_READ) { + int pending; + /* * If we read a suspend command from /dev/apm_bios, * then the corresponding APM_IOC_SUSPEND ioctl is @@ -279,47 +316,73 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) */ as->suspend_state = SUSPEND_ACKED; suspends_pending--; + pending = suspends_pending == 0; + mutex_unlock(&state_lock); + + /* + * If there are no further acknowledges required, + * suspend the system. + */ + if (pending) + apm_suspend(); + + /* + * Wait for the suspend/resume to complete. If there + * are pending acknowledges, we wait here for them. + * + * Note: we need to ensure that the PM subsystem does + * not kick us out of the wait when it suspends the + * threads. + */ + flags = current->flags; + current->flags |= PF_NOFREEZE; + + wait_event(apm_suspend_waitqueue, + as->suspend_state == SUSPEND_DONE); } else { + as->suspend_state = SUSPEND_WAIT; + mutex_unlock(&state_lock); + /* * Otherwise it is a request to suspend the system. * Queue an event for all readers, and expect an * acknowledge from all writers who haven't already * acknowledged. */ - queue_event(APM_USER_SUSPEND, as); - } - - /* - * If there are no further acknowledges required, suspend - * the system. - */ - if (suspends_pending == 0) - apm_suspend(); + err = queue_suspend_event(APM_USER_SUSPEND, as); + if (err < 0) { + /* + * Avoid taking the lock here - this + * should be fine. + */ + as->suspend_state = SUSPEND_NONE; + break; + } + + if (err > 0) + apm_suspend(); - /* - * Wait for the suspend/resume to complete. If there are - * pending acknowledges, we wait here for them. - * - * Note that we need to ensure that the PM subsystem does - * not kick us out of the wait when it suspends the threads. - */ - flags = current->flags; - current->flags |= PF_NOFREEZE; + /* + * Wait for the suspend/resume to complete. If there + * are pending acknowledges, we wait here for them. + * + * Note: we need to ensure that the PM subsystem does + * not kick us out of the wait when it suspends the + * threads. + */ + flags = current->flags; + current->flags |= PF_NOFREEZE; - /* - * Note: do not allow a thread which is acking the suspend - * to escape until the resume is complete. - */ - if (as->suspend_state == SUSPEND_ACKED) - wait_event(apm_suspend_waitqueue, - as->suspend_state == SUSPEND_DONE); - else wait_event_interruptible(apm_suspend_waitqueue, as->suspend_state == SUSPEND_DONE); + } current->flags = flags; + + mutex_lock(&state_lock); err = as->suspend_result; as->suspend_state = SUSPEND_NONE; + mutex_unlock(&state_lock); break; } @@ -329,6 +392,8 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) static int apm_release(struct inode * inode, struct file * filp) { struct apm_user *as = filp->private_data; + int pending = 0; + filp->private_data = NULL; down_write(&user_list_lock); @@ -341,11 +406,14 @@ static int apm_release(struct inode * inode, struct file * filp) * need to balance suspends_pending, which means the * possibility of sleeping. */ + mutex_lock(&state_lock); if (as->suspend_state != SUSPEND_NONE) { suspends_pending -= 1; - if (suspends_pending == 0) - apm_suspend(); + pending = suspends_pending == 0; } + mutex_unlock(&state_lock); + if (pending) + apm_suspend(); kfree(as); return 0; @@ -355,10 +423,8 @@ static int apm_open(struct inode * inode, struct file * filp) { struct apm_user *as; - as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL); + as = kzalloc(sizeof(*as), GFP_KERNEL); if (as) { - memset(as, 0, sizeof(*as)); - /* * XXX - this is a tiny bit broken, when we consider BSD * process accounting. If the device is opened by root, we @@ -469,16 +535,14 @@ static int apm_get_info(char *buf, char **start, off_t fpos, int length) static int kapmd(void *arg) { - daemonize("kapmd"); - current->flags |= PF_NOFREEZE; - do { apm_event_t event; + int ret; wait_event_interruptible(kapmd_wait, - !queue_empty(&kapmd_queue) || !pm_active); + !queue_empty(&kapmd_queue) || kthread_should_stop()); - if (!pm_active) + if (kthread_should_stop()) break; spin_lock_irq(&kapmd_queue_lock); @@ -493,13 +557,20 @@ static int kapmd(void *arg) case APM_LOW_BATTERY: case APM_POWER_STATUS_CHANGE: - queue_event(event, NULL); + queue_event(event); break; case APM_USER_SUSPEND: case APM_SYS_SUSPEND: - queue_event(event, NULL); - if (suspends_pending == 0) + ret = queue_suspend_event(event, NULL); + if (ret < 0) { + /* + * We were busy. Try again in 50ms. + */ + queue_add_event(&kapmd_queue, event); + msleep(50); + } + if (ret > 0) apm_suspend(); break; @@ -509,7 +580,7 @@ static int kapmd(void *arg) } } while (1); - complete_and_exit(&kapmd_exit, 0); + return 0; } static int __init apm_init(void) @@ -521,18 +592,14 @@ static int __init apm_init(void) return -ENODEV; } - if (PM_IS_ACTIVE()) { - printk(KERN_NOTICE "apm: overridden by ACPI.\n"); - return -EINVAL; - } - - pm_active = 1; - - ret = kernel_thread(kapmd, NULL, CLONE_KERNEL); - if (ret < 0) { - pm_active = 0; + kapmd_tsk = kthread_create(kapmd, NULL, "kapmd"); + if (IS_ERR(kapmd_tsk)) { + ret = PTR_ERR(kapmd_tsk); + kapmd_tsk = NULL; return ret; } + kapmd_tsk->flags |= PF_NOFREEZE; + wake_up_process(kapmd_tsk); #ifdef CONFIG_PROC_FS create_proc_info_entry("apm", 0, NULL, apm_get_info); @@ -541,10 +608,7 @@ static int __init apm_init(void) ret = misc_register(&apm_device); if (ret != 0) { remove_proc_entry("apm", NULL); - - pm_active = 0; - wake_up(&kapmd_wait); - wait_for_completion(&kapmd_exit); + kthread_stop(kapmd_tsk); } return ret; @@ -555,9 +619,7 @@ static void __exit apm_exit(void) misc_deregister(&apm_device); remove_proc_entry("apm", NULL); - pm_active = 0; - wake_up(&kapmd_wait); - wait_for_completion(&kapmd_exit); + kthread_stop(kapmd_tsk); } module_init(apm_init);