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

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

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

L
Len Brown 已提交
27
#if defined(CONFIG_RTC_DRV_CMOS) || defined(CONFIG_RTC_DRV_CMOS_MODULE) || !defined(CONFIG_X86)
D
David Brownell 已提交
28 29 30 31 32 33 34
/* 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 已提交
35 36
static u32 cmos_bcd_read(int offset, int rtc_control);

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

	spin_lock_irqsave(&rtc_lock, flags);

	rtc_control = CMOS_READ(RTC_CONTROL);
48 49 50
	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 已提交
51 52

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

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

	spin_unlock_irqrestore(&rtc_lock, flags);

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

97 98 99 100 101 102 103 104 105 106 107 108 109 110
	/*
	 * 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 已提交
111 112 113 114 115
	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 已提交
116 117 118 119 120 121 122 123 124 125
	(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 已提交
126
static int get_date_field(char **p, u32 * value)
L
Linus Torvalds 已提交
127
{
L
Len Brown 已提交
128 129 130
	char *next = NULL;
	char *string_end = NULL;
	int result = -EINVAL;
L
Linus Torvalds 已提交
131 132 133 134 135

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

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

	return result;
}

157 158 159 160 161
/* 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 已提交
162
		val = bcd2bin(val);
163 164 165 166 167 168 169
	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 已提交
170
		val = bin2bcd(val);
171 172 173
	CMOS_WRITE(val, offset);
}

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

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

L
Linus Torvalds 已提交
188
	if (copy_from_user(alarm_string, buffer, count))
189
		return -EFAULT;
L
Linus Torvalds 已提交
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 216

	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) {
217 218 219 220 221 222
		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 已提交
223 224 225 226 227
	}

	spin_unlock_irq(&rtc_lock);

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

	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 */
258 259 260
	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 已提交
261 262 263 264 265 266

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

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

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

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

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

310
		seq_printf(seq, "%s\t  S%d\t",
L
Len Brown 已提交
311
			   dev->pnp.bus_id,
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
			   (u32) dev->wakeup.sleep_state);

		if (!dev->physical_node_count)
			seq_printf(seq, "%c%-8s\n",
				dev->wakeup.flags.run_wake ?
				'*' : ' ', "disabled");
		else {
			struct device *ldev;
			list_for_each_entry(entry, &dev->physical_node_list,
					node) {
				ldev = get_device(entry->dev);
				if (!ldev)
					continue;

				if (&entry->node !=
						dev->physical_node_list.next)
					seq_printf(seq, "\t\t");

				seq_printf(seq, "%c%-8s  %s:%s\n",
					dev->wakeup.flags.run_wake ? '*' : ' ',
					(device_may_wakeup(&dev->dev) ||
					(ldev && device_may_wakeup(ldev))) ?
					"enabled" : "disabled",
					ldev->bus ? ldev->bus->name :
					"no-bus", dev_name(ldev));
				put_device(ldev);
			}
		}
L
Linus Torvalds 已提交
340
	}
341
	mutex_unlock(&acpi_device_lock);
L
Linus Torvalds 已提交
342 343 344
	return 0;
}

345 346
static void physical_device_enable_wakeup(struct acpi_device *adev)
{
347
	struct acpi_device_physical_node *entry;
348

349 350 351 352 353 354
	list_for_each_entry(entry,
		&adev->physical_node_list, node)
		if (entry->dev && device_can_wakeup(entry->dev)) {
			bool enable = !device_may_wakeup(entry->dev);
			device_set_wakeup_enable(entry->dev, enable);
		}
355 356
}

L
Linus Torvalds 已提交
357
static ssize_t
L
Len Brown 已提交
358 359 360
acpi_system_write_wakeup_device(struct file *file,
				const char __user * buffer,
				size_t count, loff_t * ppos)
L
Linus Torvalds 已提交
361
{
L
Len Brown 已提交
362 363 364
	struct list_head *node, *next;
	char strbuf[5];
	char str[5] = "";
365
	unsigned int len = count;
L
Linus Torvalds 已提交
366

L
Len Brown 已提交
367 368
	if (len > 4)
		len = 4;
369 370
	if (len < 0)
		return -EFAULT;
L
Linus Torvalds 已提交
371 372 373 374 375 376

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

377
	mutex_lock(&acpi_device_lock);
L
Linus Torvalds 已提交
378
	list_for_each_safe(node, next, &acpi_wakeup_device_list) {
L
Len Brown 已提交
379 380
		struct acpi_device *dev =
		    container_of(node, struct acpi_device, wakeup_list);
L
Linus Torvalds 已提交
381 382 383 384
		if (!dev->wakeup.flags.valid)
			continue;

		if (!strncmp(dev->pnp.bus_id, str, 4)) {
385 386 387 388 389 390
			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 已提交
391 392 393
			break;
		}
	}
394
	mutex_unlock(&acpi_device_lock);
L
Linus Torvalds 已提交
395 396 397 398 399 400
	return count;
}

static int
acpi_system_wakeup_device_open_fs(struct inode *inode, struct file *file)
{
L
Len Brown 已提交
401 402
	return single_open(file, acpi_system_wakeup_device_seq_show,
			   PDE(inode)->data);
L
Linus Torvalds 已提交
403 404
}

405
static const struct file_operations acpi_system_wakeup_device_fops = {
406
	.owner = THIS_MODULE,
L
Len Brown 已提交
407 408 409 410 411
	.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 已提交
412 413
};

D
David Brownell 已提交
414
#ifdef	HAVE_ACPI_LEGACY_ALARM
415
static const struct file_operations acpi_system_alarm_fops = {
416
	.owner = THIS_MODULE,
L
Len Brown 已提交
417 418 419 420 421
	.open = acpi_system_alarm_open_fs,
	.read = seq_read,
	.write = acpi_system_write_alarm,
	.llseek = seq_lseek,
	.release = single_release,
L
Linus Torvalds 已提交
422 423
};

L
Len Brown 已提交
424
static u32 rtc_handler(void *context)
L
Linus Torvalds 已提交
425 426 427 428 429 430
{
	acpi_clear_event(ACPI_EVENT_RTC);
	acpi_disable_event(ACPI_EVENT_RTC, 0);

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

433
int __init acpi_sleep_proc_init(void)
L
Linus Torvalds 已提交
434
{
D
David Brownell 已提交
435
#ifdef	HAVE_ACPI_LEGACY_ALARM
L
Linus Torvalds 已提交
436
	/* 'alarm' [R/W] */
437 438
	proc_create("alarm", S_IFREG | S_IRUGO | S_IWUSR,
		    acpi_root_dir, &acpi_system_alarm_fops);
L
Linus Torvalds 已提交
439

D
David Brownell 已提交
440
	acpi_install_fixed_event_handler(ACPI_EVENT_RTC, rtc_handler, NULL);
441 442 443 444 445 446
	/*
	 * 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 已提交
447
#endif				/* HAVE_ACPI_LEGACY_ALARM */
D
David Brownell 已提交
448

P
Pavel Machek 已提交
449
	/* 'wakeup device' [R/W] */
450 451
	proc_create("wakeup", S_IFREG | S_IRUGO | S_IWUSR,
		    acpi_root_dir, &acpi_system_wakeup_device_fops);
L
Linus Torvalds 已提交
452 453 454

	return 0;
}