PageRenderTime 44ms CodeModel.GetById 8ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/arch/arm/kernel/apm.c

https://bitbucket.org/evzijst/gittest
C | 610 lines | 350 code | 100 blank | 160 comment | 67 complexity | f76265b935f3482d6fb5da752fce70de MD5 | raw file
  1/*
  2 * bios-less APM driver for ARM Linux 
  3 *  Jamey Hicks <jamey@crl.dec.com>
  4 *  adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
  5 *
  6 * APM 1.2 Reference:
  7 *   Intel Corporation, Microsoft Corporation. Advanced Power Management
  8 *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
  9 *
 10 * [This document is available from Microsoft at:
 11 *    http://www.microsoft.com/hwdev/busbios/amp_12.htm]
 12 */
 13#include <linux/config.h>
 14#include <linux/module.h>
 15#include <linux/poll.h>
 16#include <linux/timer.h>
 17#include <linux/slab.h>
 18#include <linux/proc_fs.h>
 19#include <linux/miscdevice.h>
 20#include <linux/apm_bios.h>
 21#include <linux/sched.h>
 22#include <linux/pm.h>
 23#include <linux/device.h>
 24#include <linux/kernel.h>
 25#include <linux/list.h>
 26#include <linux/init.h>
 27#include <linux/completion.h>
 28
 29#include <asm/apm.h> /* apm_power_info */
 30#include <asm/system.h>
 31
 32/*
 33 * The apm_bios device is one of the misc char devices.
 34 * This is its minor number.
 35 */
 36#define APM_MINOR_DEV	134
 37
 38/*
 39 * See Documentation/Config.help for the configuration options.
 40 *
 41 * Various options can be changed at boot time as follows:
 42 * (We allow underscores for compatibility with the modules code)
 43 *	apm=on/off			enable/disable APM
 44 */
 45
 46/*
 47 * Maximum number of events stored
 48 */
 49#define APM_MAX_EVENTS		16
 50
 51struct apm_queue {
 52	unsigned int		event_head;
 53	unsigned int		event_tail;
 54	apm_event_t		events[APM_MAX_EVENTS];
 55};
 56
 57/*
 58 * The per-file APM data
 59 */
 60struct apm_user {
 61	struct list_head	list;
 62
 63	unsigned int		suser: 1;
 64	unsigned int		writer: 1;
 65	unsigned int		reader: 1;
 66
 67	int			suspend_result;
 68	unsigned int		suspend_state;
 69#define SUSPEND_NONE	0		/* no suspend pending */
 70#define SUSPEND_PENDING	1		/* suspend pending read */
 71#define SUSPEND_READ	2		/* suspend read, pending ack */
 72#define SUSPEND_ACKED	3		/* suspend acked */
 73#define SUSPEND_DONE	4		/* suspend completed */
 74
 75	struct apm_queue	queue;
 76};
 77
 78/*
 79 * Local variables
 80 */
 81static int suspends_pending;
 82static int apm_disabled;
 83
 84static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
 85static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
 86
 87/*
 88 * This is a list of everyone who has opened /dev/apm_bios
 89 */
 90static DECLARE_RWSEM(user_list_lock);
 91static LIST_HEAD(apm_user_list);
 92
 93/*
 94 * kapmd info.  kapmd provides us a process context to handle
 95 * "APM" events within - specifically necessary if we're going
 96 * to be suspending the system.
 97 */
 98static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
 99static DECLARE_COMPLETION(kapmd_exit);
100static DEFINE_SPINLOCK(kapmd_queue_lock);
101static struct apm_queue kapmd_queue;
102
103
104static const char driver_version[] = "1.13";	/* no spaces */
105
106
107
108/*
109 * Compatibility cruft until the IPAQ people move over to the new
110 * interface.
111 */
112static void __apm_get_power_status(struct apm_power_info *info)
113{
114}
115
116/*
117 * This allows machines to provide their own "apm get power status" function.
118 */
119void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
120EXPORT_SYMBOL(apm_get_power_status);
121
122
123/*
124 * APM event queue management.
125 */
126static inline int queue_empty(struct apm_queue *q)
127{
128	return q->event_head == q->event_tail;
129}
130
131static inline apm_event_t queue_get_event(struct apm_queue *q)
132{
133	q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
134	return q->events[q->event_tail];
135}
136
137static void queue_add_event(struct apm_queue *q, apm_event_t event)
138{
139	q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
140	if (q->event_head == q->event_tail) {
141		static int notified;
142
143		if (notified++ == 0)
144		    printk(KERN_ERR "apm: an event queue overflowed\n");
145		q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
146	}
147	q->events[q->event_head] = event;
148}
149
150static void queue_event_one_user(struct apm_user *as, apm_event_t event)
151{
152	if (as->suser && as->writer) {
153		switch (event) {
154		case APM_SYS_SUSPEND:
155		case APM_USER_SUSPEND:
156			/*
157			 * If this user already has a suspend pending,
158			 * don't queue another one.
159			 */
160			if (as->suspend_state != SUSPEND_NONE)
161				return;
162
163			as->suspend_state = SUSPEND_PENDING;
164			suspends_pending++;
165			break;
166		}
167	}
168	queue_add_event(&as->queue, event);
169}
170
171static void queue_event(apm_event_t event, struct apm_user *sender)
172{
173	struct apm_user *as;
174
175	down_read(&user_list_lock);
176	list_for_each_entry(as, &apm_user_list, list) {
177		if (as != sender && as->reader)
178			queue_event_one_user(as, event);
179	}
180	up_read(&user_list_lock);
181	wake_up_interruptible(&apm_waitqueue);
182}
183
184static void apm_suspend(void)
185{
186	struct apm_user *as;
187	int err = pm_suspend(PM_SUSPEND_MEM);
188
189	/*
190	 * Anyone on the APM queues will think we're still suspended.
191	 * Send a message so everyone knows we're now awake again.
192	 */
193	queue_event(APM_NORMAL_RESUME, NULL);
194
195	/*
196	 * Finally, wake up anyone who is sleeping on the suspend.
197	 */
198	down_read(&user_list_lock);
199	list_for_each_entry(as, &apm_user_list, list) {
200		as->suspend_result = err;
201		as->suspend_state = SUSPEND_DONE;
202	}
203	up_read(&user_list_lock);
204
205	wake_up(&apm_suspend_waitqueue);
206}
207
208static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
209{
210	struct apm_user *as = fp->private_data;
211	apm_event_t event;
212	int i = count, ret = 0;
213
214	if (count < sizeof(apm_event_t))
215		return -EINVAL;
216
217	if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
218		return -EAGAIN;
219
220	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
221
222	while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
223		event = queue_get_event(&as->queue);
224
225		ret = -EFAULT;
226		if (copy_to_user(buf, &event, sizeof(event)))
227			break;
228
229		if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)
230			as->suspend_state = SUSPEND_READ;
231
232		buf += sizeof(event);
233		i -= sizeof(event);
234	}
235
236	if (i < count)
237		ret = count - i;
238
239	return ret;
240}
241
242static unsigned int apm_poll(struct file *fp, poll_table * wait)
243{
244	struct apm_user *as = fp->private_data;
245
246	poll_wait(fp, &apm_waitqueue, wait);
247	return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
248}
249
250/*
251 * apm_ioctl - handle APM ioctl
252 *
253 * APM_IOC_SUSPEND
254 *   This IOCTL is overloaded, and performs two functions.  It is used to:
255 *     - initiate a suspend
256 *     - acknowledge a suspend read from /dev/apm_bios.
257 *   Only when everyone who has opened /dev/apm_bios with write permission
258 *   has acknowledge does the actual suspend happen.
259 */
260static int
261apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
262{
263	struct apm_user *as = filp->private_data;
264	unsigned long flags;
265	int err = -EINVAL;
266
267	if (!as->suser || !as->writer)
268		return -EPERM;
269
270	switch (cmd) {
271	case APM_IOC_SUSPEND:
272		as->suspend_result = -EINTR;
273
274		if (as->suspend_state == SUSPEND_READ) {
275			/*
276			 * If we read a suspend command from /dev/apm_bios,
277			 * then the corresponding APM_IOC_SUSPEND ioctl is
278			 * interpreted as an acknowledge.
279			 */
280			as->suspend_state = SUSPEND_ACKED;
281			suspends_pending--;
282		} else {
283			/*
284			 * Otherwise it is a request to suspend the system.
285			 * Queue an event for all readers, and expect an
286			 * acknowledge from all writers who haven't already
287			 * acknowledged.
288			 */
289			queue_event(APM_USER_SUSPEND, as);
290		}
291
292		/*
293		 * If there are no further acknowledges required, suspend
294		 * the system.
295		 */
296		if (suspends_pending == 0)
297			apm_suspend();
298
299		/*
300		 * Wait for the suspend/resume to complete.  If there are
301		 * pending acknowledges, we wait here for them.
302		 *
303		 * Note that we need to ensure that the PM subsystem does
304		 * not kick us out of the wait when it suspends the threads.
305		 */
306		flags = current->flags;
307		current->flags |= PF_NOFREEZE;
308
309		/*
310		 * Note: do not allow a thread which is acking the suspend
311		 * to escape until the resume is complete.
312		 */
313		if (as->suspend_state == SUSPEND_ACKED)
314			wait_event(apm_suspend_waitqueue,
315					 as->suspend_state == SUSPEND_DONE);
316		else
317			wait_event_interruptible(apm_suspend_waitqueue,
318					 as->suspend_state == SUSPEND_DONE);
319
320		current->flags = flags;
321		err = as->suspend_result;
322		as->suspend_state = SUSPEND_NONE;
323		break;
324	}
325
326	return err;
327}
328
329static int apm_release(struct inode * inode, struct file * filp)
330{
331	struct apm_user *as = filp->private_data;
332	filp->private_data = NULL;
333
334	down_write(&user_list_lock);
335	list_del(&as->list);
336	up_write(&user_list_lock);
337
338	/*
339	 * We are now unhooked from the chain.  As far as new
340	 * events are concerned, we no longer exist.  However, we
341	 * need to balance suspends_pending, which means the
342	 * possibility of sleeping.
343	 */
344	if (as->suspend_state != SUSPEND_NONE) {
345		suspends_pending -= 1;
346		if (suspends_pending == 0)
347			apm_suspend();
348	}
349
350	kfree(as);
351	return 0;
352}
353
354static int apm_open(struct inode * inode, struct file * filp)
355{
356	struct apm_user *as;
357
358	as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
359	if (as) {
360		memset(as, 0, sizeof(*as));
361
362		/*
363		 * XXX - this is a tiny bit broken, when we consider BSD
364		 * process accounting. If the device is opened by root, we
365		 * instantly flag that we used superuser privs. Who knows,
366		 * we might close the device immediately without doing a
367		 * privileged operation -- cevans
368		 */
369		as->suser = capable(CAP_SYS_ADMIN);
370		as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
371		as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
372
373		down_write(&user_list_lock);
374		list_add(&as->list, &apm_user_list);
375		up_write(&user_list_lock);
376
377		filp->private_data = as;
378	}
379
380	return as ? 0 : -ENOMEM;
381}
382
383static struct file_operations apm_bios_fops = {
384	.owner		= THIS_MODULE,
385	.read		= apm_read,
386	.poll		= apm_poll,
387	.ioctl		= apm_ioctl,
388	.open		= apm_open,
389	.release	= apm_release,
390};
391
392static struct miscdevice apm_device = {
393	.minor		= APM_MINOR_DEV,
394	.name		= "apm_bios",
395	.fops		= &apm_bios_fops
396};
397
398
399#ifdef CONFIG_PROC_FS
400/*
401 * Arguments, with symbols from linux/apm_bios.h.
402 *
403 *   0) Linux driver version (this will change if format changes)
404 *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
405 *   2) APM flags from APM Installation Check (0x00):
406 *	bit 0: APM_16_BIT_SUPPORT
407 *	bit 1: APM_32_BIT_SUPPORT
408 *	bit 2: APM_IDLE_SLOWS_CLOCK
409 *	bit 3: APM_BIOS_DISABLED
410 *	bit 4: APM_BIOS_DISENGAGED
411 *   3) AC line status
412 *	0x00: Off-line
413 *	0x01: On-line
414 *	0x02: On backup power (BIOS >= 1.1 only)
415 *	0xff: Unknown
416 *   4) Battery status
417 *	0x00: High
418 *	0x01: Low
419 *	0x02: Critical
420 *	0x03: Charging
421 *	0x04: Selected battery not present (BIOS >= 1.2 only)
422 *	0xff: Unknown
423 *   5) Battery flag
424 *	bit 0: High
425 *	bit 1: Low
426 *	bit 2: Critical
427 *	bit 3: Charging
428 *	bit 7: No system battery
429 *	0xff: Unknown
430 *   6) Remaining battery life (percentage of charge):
431 *	0-100: valid
432 *	-1: Unknown
433 *   7) Remaining battery life (time units):
434 *	Number of remaining minutes or seconds
435 *	-1: Unknown
436 *   8) min = minutes; sec = seconds
437 */
438static int apm_get_info(char *buf, char **start, off_t fpos, int length)
439{
440	struct apm_power_info info;
441	char *units;
442	int ret;
443
444	info.ac_line_status = 0xff;
445	info.battery_status = 0xff;
446	info.battery_flag   = 0xff;
447	info.battery_life   = -1;
448	info.time	    = -1;
449	info.units	    = -1;
450
451	if (apm_get_power_status)
452		apm_get_power_status(&info);
453
454	switch (info.units) {
455	default:	units = "?";	break;
456	case 0: 	units = "min";	break;
457	case 1: 	units = "sec";	break;
458	}
459
460	ret = sprintf(buf, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
461		     driver_version, APM_32_BIT_SUPPORT,
462		     info.ac_line_status, info.battery_status,
463		     info.battery_flag, info.battery_life,
464		     info.time, units);
465
466 	return ret;
467}
468#endif
469
470static int kapmd(void *arg)
471{
472	daemonize("kapmd");
473	current->flags |= PF_NOFREEZE;
474
475	do {
476		apm_event_t event;
477
478		wait_event_interruptible(kapmd_wait,
479				!queue_empty(&kapmd_queue) || !pm_active);
480
481		if (!pm_active)
482			break;
483
484		spin_lock_irq(&kapmd_queue_lock);
485		event = 0;
486		if (!queue_empty(&kapmd_queue))
487			event = queue_get_event(&kapmd_queue);
488		spin_unlock_irq(&kapmd_queue_lock);
489
490		switch (event) {
491		case 0:
492			break;
493
494		case APM_LOW_BATTERY:
495		case APM_POWER_STATUS_CHANGE:
496			queue_event(event, NULL);
497			break;
498
499		case APM_USER_SUSPEND:
500		case APM_SYS_SUSPEND:
501			queue_event(event, NULL);
502			if (suspends_pending == 0)
503				apm_suspend();
504			break;
505
506		case APM_CRITICAL_SUSPEND:
507			apm_suspend();
508			break;
509		}
510	} while (1);
511
512	complete_and_exit(&kapmd_exit, 0);
513}
514
515static int __init apm_init(void)
516{
517	int ret;
518
519	if (apm_disabled) {
520		printk(KERN_NOTICE "apm: disabled on user request.\n");
521		return -ENODEV;
522	}
523
524	if (PM_IS_ACTIVE()) {
525		printk(KERN_NOTICE "apm: overridden by ACPI.\n");
526		return -EINVAL;
527	}
528
529	pm_active = 1;
530
531	ret = kernel_thread(kapmd, NULL, CLONE_KERNEL);
532	if (ret < 0) {
533		pm_active = 0;
534		return ret;
535	}
536
537#ifdef CONFIG_PROC_FS
538	create_proc_info_entry("apm", 0, NULL, apm_get_info);
539#endif
540
541	ret = misc_register(&apm_device);
542	if (ret != 0) {
543		remove_proc_entry("apm", NULL);
544
545		pm_active = 0;
546		wake_up(&kapmd_wait);
547		wait_for_completion(&kapmd_exit);
548	}
549
550	return ret;
551}
552
553static void __exit apm_exit(void)
554{
555	misc_deregister(&apm_device);
556	remove_proc_entry("apm", NULL);
557
558	pm_active = 0;
559	wake_up(&kapmd_wait);
560	wait_for_completion(&kapmd_exit);
561}
562
563module_init(apm_init);
564module_exit(apm_exit);
565
566MODULE_AUTHOR("Stephen Rothwell");
567MODULE_DESCRIPTION("Advanced Power Management");
568MODULE_LICENSE("GPL");
569
570#ifndef MODULE
571static int __init apm_setup(char *str)
572{
573	while ((str != NULL) && (*str != '\0')) {
574		if (strncmp(str, "off", 3) == 0)
575			apm_disabled = 1;
576		if (strncmp(str, "on", 2) == 0)
577			apm_disabled = 0;
578		str = strchr(str, ',');
579		if (str != NULL)
580			str += strspn(str, ", \t");
581	}
582	return 1;
583}
584
585__setup("apm=", apm_setup);
586#endif
587
588/**
589 * apm_queue_event - queue an APM event for kapmd
590 * @event: APM event
591 *
592 * Queue an APM event for kapmd to process and ultimately take the
593 * appropriate action.  Only a subset of events are handled:
594 *   %APM_LOW_BATTERY
595 *   %APM_POWER_STATUS_CHANGE
596 *   %APM_USER_SUSPEND
597 *   %APM_SYS_SUSPEND
598 *   %APM_CRITICAL_SUSPEND
599 */
600void apm_queue_event(apm_event_t event)
601{
602	unsigned long flags;
603
604	spin_lock_irqsave(&kapmd_queue_lock, flags);
605	queue_add_event(&kapmd_queue, event);
606	spin_unlock_irqrestore(&kapmd_queue_lock, flags);
607
608	wake_up_interruptible(&kapmd_wait);
609}
610EXPORT_SYMBOL(apm_queue_event);