proc.c 10.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#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
17 18 19 20 21 22 23

/*
 * this file provides support for:
 * /proc/acpi/alarm
 * /proc/acpi/wakeup
 */

L
Len Brown 已提交
24
ACPI_MODULE_NAME("sleep")
L
Linus Torvalds 已提交
25

L
Len Brown 已提交
26
#if defined(CONFIG_RTC_DRV_CMOS) || defined(CONFIG_RTC_DRV_CMOS_MODULE) || !defined(CONFIG_X86)
D
David Brownell 已提交
27 28 29 30 31 32 33
/* use /sys/class/rtc/rtcX/wakealarm instead; it's not ACPI-specific */
#else
#define	HAVE_ACPI_LEGACY_ALARM
#endif

#ifdef	HAVE_ACPI_LEGACY_ALARM

L
Len Brown 已提交
34 35
static u32 cmos_bcd_read(int offset, int rtc_control);

L
Linus Torvalds 已提交
36 37
static int acpi_system_alarm_seq_show(struct seq_file *seq, void *offset)
{
L
Len Brown 已提交
38
	u32 sec, min, hr;
39
	u32 day, mo, yr, cent = 0;
40
	u32 today = 0;
L
Len Brown 已提交
41 42
	unsigned char rtc_control = 0;
	unsigned long flags;
L
Linus Torvalds 已提交
43 44 45 46

	spin_lock_irqsave(&rtc_lock, flags);

	rtc_control = CMOS_READ(RTC_CONTROL);
47 48 49
	sec = cmos_bcd_read(RTC_SECONDS_ALARM, rtc_control);
	min = cmos_bcd_read(RTC_MINUTES_ALARM, rtc_control);
	hr = cmos_bcd_read(RTC_HOURS_ALARM, rtc_control);
L
Linus Torvalds 已提交
50 51

	/* If we ever get an FACP with proper values... */
52
	if (acpi_gbl_FADT.day_alarm) {
L
Linus Torvalds 已提交
53
		/* ACPI spec: only low 6 its should be cared */
54
		day = CMOS_READ(acpi_gbl_FADT.day_alarm) & 0x3F;
55 56 57 58
		if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
			day = bcd2bin(day);
	} else
		day = cmos_bcd_read(RTC_DAY_OF_MONTH, rtc_control);
59
	if (acpi_gbl_FADT.month_alarm)
60 61 62 63 64
		mo = cmos_bcd_read(acpi_gbl_FADT.month_alarm, rtc_control);
	else {
		mo = cmos_bcd_read(RTC_MONTH, rtc_control);
		today = cmos_bcd_read(RTC_DAY_OF_MONTH, rtc_control);
	}
65
	if (acpi_gbl_FADT.century)
66
		cent = cmos_bcd_read(acpi_gbl_FADT.century, rtc_control);
67

68
	yr = cmos_bcd_read(RTC_YEAR, rtc_control);
L
Linus Torvalds 已提交
69 70 71

	spin_unlock_irqrestore(&rtc_lock, flags);

L
Len Brown 已提交
72
	/* we're trusting the FADT (see above) */
73
	if (!acpi_gbl_FADT.century)
L
Len Brown 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
		/* 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
		 *
		 */
L
Linus Torvalds 已提交
92
		yr += 2000;
93 94
	else
		yr += cent * 100;
L
Linus Torvalds 已提交
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109
	/*
	 * Show correct dates for alarms up to a month into the future.
	 * This solves issues for nearly all situations with the common
	 * 30-day alarm clocks in PC hardware.
	 */
	if (day < today) {
		if (mo < 12) {
			mo += 1;
		} else {
			mo = 1;
			yr += 1;
		}
	}

L
Len Brown 已提交
110 111 112 113 114
	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);
L
Linus Torvalds 已提交
115 116 117 118 119 120 121 122 123 124
	(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);
}

L
Len Brown 已提交
125
static int get_date_field(char **p, u32 * value)
L
Linus Torvalds 已提交
126
{
L
Len Brown 已提交
127 128 129
	char *next = NULL;
	char *string_end = NULL;
	int result = -EINVAL;
L
Linus Torvalds 已提交
130 131 132 133 134

	/*
	 * Try to find delimeter, only to insert null.  The end of the
	 * string won't have one, but is still valid.
	 */
135 136 137
	if (*p == NULL)
		return result;

L
Linus Torvalds 已提交
138 139 140 141 142 143 144 145 146 147 148 149
	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;
150 151
	else
		*p = NULL;
L
Linus Torvalds 已提交
152 153 154 155

	return result;
}

156 157 158 159 160
/* Read a possibly BCD register, always return binary */
static u32 cmos_bcd_read(int offset, int rtc_control)
{
	u32 val = CMOS_READ(offset);
	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
A
Adrian Bunk 已提交
161
		val = bcd2bin(val);
162 163 164 165 166 167 168
	return val;
}

/* Write binary value into possibly BCD register */
static void cmos_bcd_write(u32 val, int offset, int rtc_control)
{
	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
A
Adrian Bunk 已提交
169
		val = bin2bcd(val);
170 171 172
	CMOS_WRITE(val, offset);
}

L
Linus Torvalds 已提交
173
static ssize_t
L
Len Brown 已提交
174 175
acpi_system_write_alarm(struct file *file,
			const char __user * buffer, size_t count, loff_t * ppos)
L
Linus Torvalds 已提交
176
{
L
Len Brown 已提交
177 178 179 180 181 182
	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;
L
Linus Torvalds 已提交
183 184

	if (count > sizeof(alarm_string) - 1)
185
		return -EINVAL;
L
Len Brown 已提交
186

L
Linus Torvalds 已提交
187
	if (copy_from_user(alarm_string, buffer, count))
188
		return -EFAULT;
L
Linus Torvalds 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215

	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;

	spin_lock_irq(&rtc_lock);

	rtc_control = CMOS_READ(RTC_CONTROL);

	if (adjust) {
216 217 218 219 220 221
		yr += cmos_bcd_read(RTC_YEAR, rtc_control);
		mo += cmos_bcd_read(RTC_MONTH, rtc_control);
		day += cmos_bcd_read(RTC_DAY_OF_MONTH, rtc_control);
		hr += cmos_bcd_read(RTC_HOURS, rtc_control);
		min += cmos_bcd_read(RTC_MINUTES, rtc_control);
		sec += cmos_bcd_read(RTC_SECONDS, rtc_control);
L
Linus Torvalds 已提交
222 223 224 225 226
	}

	spin_unlock_irq(&rtc_lock);

	if (sec > 59) {
227 228
		min += sec/60;
		sec = sec%60;
L
Linus Torvalds 已提交
229 230
	}
	if (min > 59) {
231 232
		hr += min/60;
		min = min%60;
L
Linus Torvalds 已提交
233 234
	}
	if (hr > 23) {
235 236
		day += hr/24;
		hr = hr%24;
L
Linus Torvalds 已提交
237 238
	}
	if (day > 31) {
239 240
		mo += day/32;
		day = day%32;
L
Linus Torvalds 已提交
241 242
	}
	if (mo > 12) {
243 244
		yr += mo/13;
		mo = mo%13;
L
Linus Torvalds 已提交
245 246 247 248 249 250 251 252 253 254 255 256
	}

	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 */
257 258 259
	cmos_bcd_write(hr, RTC_HOURS_ALARM, rtc_control);
	cmos_bcd_write(min, RTC_MINUTES_ALARM, rtc_control);
	cmos_bcd_write(sec, RTC_SECONDS_ALARM, rtc_control);
L
Linus Torvalds 已提交
260 261 262 263 264 265

	/*
	 * 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.
	 */
266
	if (acpi_gbl_FADT.day_alarm)
267
		cmos_bcd_write(day, acpi_gbl_FADT.day_alarm, rtc_control);
268
	if (acpi_gbl_FADT.month_alarm)
269
		cmos_bcd_write(mo, acpi_gbl_FADT.month_alarm, rtc_control);
270 271 272
	if (acpi_gbl_FADT.century) {
		if (adjust)
			yr += cmos_bcd_read(acpi_gbl_FADT.century, rtc_control) * 100;
273
		cmos_bcd_write(yr / 100, acpi_gbl_FADT.century, rtc_control);
274
	}
L
Linus Torvalds 已提交
275 276 277 278 279 280 281 282 283 284 285 286 287
	/* 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;
L
Len Brown 已提交
288
      end:
289
	return result ? result : count;
L
Linus Torvalds 已提交
290
}
L
Len Brown 已提交
291
#endif				/* HAVE_ACPI_LEGACY_ALARM */
L
Linus Torvalds 已提交
292 293 294 295

static int
acpi_system_wakeup_device_seq_show(struct seq_file *seq, void *offset)
{
L
Len Brown 已提交
296
	struct list_head *node, *next;
L
Linus Torvalds 已提交
297

298
	seq_printf(seq, "Device\tS-state\t  Status   Sysfs node\n");
L
Linus Torvalds 已提交
299

300
	mutex_lock(&acpi_device_lock);
L
Linus Torvalds 已提交
301
	list_for_each_safe(node, next, &acpi_wakeup_device_list) {
L
Len Brown 已提交
302 303
		struct acpi_device *dev =
		    container_of(node, struct acpi_device, wakeup_list);
304
		struct device *ldev;
L
Linus Torvalds 已提交
305 306 307

		if (!dev->wakeup.flags.valid)
			continue;
308 309 310

		ldev = acpi_get_physical_device(dev->handle);
		seq_printf(seq, "%s\t  S%d\t%c%-8s  ",
L
Len Brown 已提交
311 312
			   dev->pnp.bus_id,
			   (u32) dev->wakeup.sleep_state,
313
			   dev->wakeup.flags.run_wake ? '*' : ' ',
314 315 316
			   (device_may_wakeup(&dev->dev)
			     || (ldev && device_may_wakeup(ldev))) ?
			       "enabled" : "disabled");
317 318
		if (ldev)
			seq_printf(seq, "%s:%s",
319
				   ldev->bus ? ldev->bus->name : "no-bus",
320
				   dev_name(ldev));
321 322 323
		seq_printf(seq, "\n");
		put_device(ldev);

L
Linus Torvalds 已提交
324
	}
325
	mutex_unlock(&acpi_device_lock);
L
Linus Torvalds 已提交
326 327 328
	return 0;
}

329 330 331 332
static void physical_device_enable_wakeup(struct acpi_device *adev)
{
	struct device *dev = acpi_get_physical_device(adev->handle);

333 334 335 336
	if (dev && device_can_wakeup(dev)) {
		bool enable = !device_may_wakeup(dev);
		device_set_wakeup_enable(dev, enable);
	}
337 338
}

L
Linus Torvalds 已提交
339
static ssize_t
L
Len Brown 已提交
340 341 342
acpi_system_write_wakeup_device(struct file *file,
				const char __user * buffer,
				size_t count, loff_t * ppos)
L
Linus Torvalds 已提交
343
{
L
Len Brown 已提交
344 345 346
	struct list_head *node, *next;
	char strbuf[5];
	char str[5] = "";
347
	unsigned int len = count;
L
Linus Torvalds 已提交
348

L
Len Brown 已提交
349 350
	if (len > 4)
		len = 4;
351 352
	if (len < 0)
		return -EFAULT;
L
Linus Torvalds 已提交
353 354 355 356 357 358

	if (copy_from_user(strbuf, buffer, len))
		return -EFAULT;
	strbuf[len] = '\0';
	sscanf(strbuf, "%s", str);

359
	mutex_lock(&acpi_device_lock);
L
Linus Torvalds 已提交
360
	list_for_each_safe(node, next, &acpi_wakeup_device_list) {
L
Len Brown 已提交
361 362
		struct acpi_device *dev =
		    container_of(node, struct acpi_device, wakeup_list);
L
Linus Torvalds 已提交
363 364 365 366
		if (!dev->wakeup.flags.valid)
			continue;

		if (!strncmp(dev->pnp.bus_id, str, 4)) {
367 368 369 370 371 372
			if (device_can_wakeup(&dev->dev)) {
				bool enable = !device_may_wakeup(&dev->dev);
				device_set_wakeup_enable(&dev->dev, enable);
			} else {
				physical_device_enable_wakeup(dev);
			}
L
Linus Torvalds 已提交
373 374 375
			break;
		}
	}
376
	mutex_unlock(&acpi_device_lock);
L
Linus Torvalds 已提交
377 378 379 380 381 382
	return count;
}

static int
acpi_system_wakeup_device_open_fs(struct inode *inode, struct file *file)
{
L
Len Brown 已提交
383 384
	return single_open(file, acpi_system_wakeup_device_seq_show,
			   PDE(inode)->data);
L
Linus Torvalds 已提交
385 386
}

387
static const struct file_operations acpi_system_wakeup_device_fops = {
388
	.owner = THIS_MODULE,
L
Len Brown 已提交
389 390 391 392 393
	.open = acpi_system_wakeup_device_open_fs,
	.read = seq_read,
	.write = acpi_system_write_wakeup_device,
	.llseek = seq_lseek,
	.release = single_release,
L
Linus Torvalds 已提交
394 395
};

D
David Brownell 已提交
396
#ifdef	HAVE_ACPI_LEGACY_ALARM
397
static const struct file_operations acpi_system_alarm_fops = {
398
	.owner = THIS_MODULE,
L
Len Brown 已提交
399 400 401 402 403
	.open = acpi_system_alarm_open_fs,
	.read = seq_read,
	.write = acpi_system_write_alarm,
	.llseek = seq_lseek,
	.release = single_release,
L
Linus Torvalds 已提交
404 405
};

L
Len Brown 已提交
406
static u32 rtc_handler(void *context)
L
Linus Torvalds 已提交
407 408 409 410 411 412
{
	acpi_clear_event(ACPI_EVENT_RTC);
	acpi_disable_event(ACPI_EVENT_RTC, 0);

	return ACPI_INTERRUPT_HANDLED;
}
L
Len Brown 已提交
413
#endif				/* HAVE_ACPI_LEGACY_ALARM */
L
Linus Torvalds 已提交
414

415
int __init acpi_sleep_proc_init(void)
L
Linus Torvalds 已提交
416
{
D
David Brownell 已提交
417
#ifdef	HAVE_ACPI_LEGACY_ALARM
L
Linus Torvalds 已提交
418
	/* 'alarm' [R/W] */
419 420
	proc_create("alarm", S_IFREG | S_IRUGO | S_IWUSR,
		    acpi_root_dir, &acpi_system_alarm_fops);
L
Linus Torvalds 已提交
421

D
David Brownell 已提交
422
	acpi_install_fixed_event_handler(ACPI_EVENT_RTC, rtc_handler, NULL);
423 424 425 426 427 428
	/*
	 * Disable the RTC event after installing RTC handler.
	 * Only when RTC alarm is set will it be enabled.
	 */
	acpi_clear_event(ACPI_EVENT_RTC);
	acpi_disable_event(ACPI_EVENT_RTC, 0);
L
Len Brown 已提交
429
#endif				/* HAVE_ACPI_LEGACY_ALARM */
D
David Brownell 已提交
430

P
Pavel Machek 已提交
431
	/* 'wakeup device' [R/W] */
432 433
	proc_create("wakeup", S_IFREG | S_IRUGO | S_IWUSR,
		    acpi_root_dir, &acpi_system_wakeup_device_fops);
L
Linus Torvalds 已提交
434 435 436

	return 0;
}