|
@@ -78,6 +78,92 @@ static struct attribute_group rtc_attr_group = {
|
|
|
.attrs = rtc_attrs,
|
|
|
};
|
|
|
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+rtc_sysfs_show_wakealarm(struct class_device *dev, char *buf)
|
|
|
+{
|
|
|
+ ssize_t retval;
|
|
|
+ unsigned long alarm;
|
|
|
+ struct rtc_wkalrm alm;
|
|
|
+
|
|
|
+ /* Don't show disabled alarms; but the RTC could leave the
|
|
|
+ * alarm enabled after it's already triggered. Alarms are
|
|
|
+ * conceptually one-shot, even though some common hardware
|
|
|
+ * (PCs) doesn't actually work that way.
|
|
|
+ *
|
|
|
+ * REVISIT maybe we should require RTC implementations to
|
|
|
+ * disable the RTC alarm after it triggers, for uniformity.
|
|
|
+ */
|
|
|
+ retval = rtc_read_alarm(dev, &alm);
|
|
|
+ if (retval == 0 && alm.enabled) {
|
|
|
+ rtc_tm_to_time(&alm.time, &alarm);
|
|
|
+ retval = sprintf(buf, "%lu\n", alarm);
|
|
|
+ }
|
|
|
+
|
|
|
+ return retval;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+rtc_sysfs_set_wakealarm(struct class_device *dev, const char *buf, size_t n)
|
|
|
+{
|
|
|
+ ssize_t retval;
|
|
|
+ unsigned long now, alarm;
|
|
|
+ struct rtc_wkalrm alm;
|
|
|
+
|
|
|
+ /* Only request alarms that trigger in the future. Disable them
|
|
|
+ * by writing another time, e.g. 0 meaning Jan 1 1970 UTC.
|
|
|
+ */
|
|
|
+ retval = rtc_read_time(dev, &alm.time);
|
|
|
+ if (retval < 0)
|
|
|
+ return retval;
|
|
|
+ rtc_tm_to_time(&alm.time, &now);
|
|
|
+
|
|
|
+ alarm = simple_strtoul(buf, NULL, 0);
|
|
|
+ if (alarm > now) {
|
|
|
+ /* Avoid accidentally clobbering active alarms; we can't
|
|
|
+ * entirely prevent that here, without even the minimal
|
|
|
+ * locking from the /dev/rtcN api.
|
|
|
+ */
|
|
|
+ retval = rtc_read_alarm(dev, &alm);
|
|
|
+ if (retval < 0)
|
|
|
+ return retval;
|
|
|
+ if (alm.enabled)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ alm.enabled = 1;
|
|
|
+ } else {
|
|
|
+ alm.enabled = 0;
|
|
|
+
|
|
|
+ /* Provide a valid future alarm time. Linux isn't EFI,
|
|
|
+ * this time won't be ignored when disabling the alarm.
|
|
|
+ */
|
|
|
+ alarm = now + 300;
|
|
|
+ }
|
|
|
+ rtc_time_to_tm(alarm, &alm.time);
|
|
|
+
|
|
|
+ retval = rtc_set_alarm(dev, &alm);
|
|
|
+ return (retval < 0) ? retval : n;
|
|
|
+}
|
|
|
+static const CLASS_DEVICE_ATTR(wakealarm, S_IRUGO | S_IWUSR,
|
|
|
+ rtc_sysfs_show_wakealarm, rtc_sysfs_set_wakealarm);
|
|
|
+
|
|
|
+
|
|
|
+/* The reason to trigger an alarm with no process watching it (via sysfs)
|
|
|
+ * is its side effect: waking from a system state like suspend-to-RAM or
|
|
|
+ * suspend-to-disk. So: no attribute unless that side effect is possible.
|
|
|
+ * (Userspace may disable that mechanism later.)
|
|
|
+ */
|
|
|
+static inline int rtc_does_wakealarm(struct class_device *class_dev)
|
|
|
+{
|
|
|
+ struct rtc_device *rtc;
|
|
|
+
|
|
|
+ if (!device_can_wakeup(class_dev->dev))
|
|
|
+ return 0;
|
|
|
+ rtc = to_rtc_device(class_dev);
|
|
|
+ return rtc->ops->set_alarm != NULL;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
static int rtc_sysfs_add_device(struct class_device *class_dev,
|
|
|
struct class_interface *class_intf)
|
|
|
{
|
|
@@ -87,8 +173,18 @@ static int rtc_sysfs_add_device(struct class_device *class_dev,
|
|
|
|
|
|
err = sysfs_create_group(&class_dev->kobj, &rtc_attr_group);
|
|
|
if (err)
|
|
|
- dev_err(class_dev->dev,
|
|
|
- "failed to create sysfs attributes\n");
|
|
|
+ dev_err(class_dev->dev, "failed to create %s\n",
|
|
|
+ "sysfs attributes");
|
|
|
+ else if (rtc_does_wakealarm(class_dev)) {
|
|
|
+ /* not all RTCs support both alarms and wakeup */
|
|
|
+ err = class_device_create_file(class_dev,
|
|
|
+ &class_device_attr_wakealarm);
|
|
|
+ if (err) {
|
|
|
+ dev_err(class_dev->dev, "failed to create %s\n",
|
|
|
+ "alarm attribute");
|
|
|
+ sysfs_remove_group(&class_dev->kobj, &rtc_attr_group);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
return err;
|
|
|
}
|
|
@@ -96,6 +192,9 @@ static int rtc_sysfs_add_device(struct class_device *class_dev,
|
|
|
static void rtc_sysfs_remove_device(struct class_device *class_dev,
|
|
|
struct class_interface *class_intf)
|
|
|
{
|
|
|
+ if (rtc_does_wakealarm(class_dev))
|
|
|
+ class_device_remove_file(class_dev,
|
|
|
+ &class_device_attr_wakealarm);
|
|
|
sysfs_remove_group(&class_dev->kobj, &rtc_attr_group);
|
|
|
}
|
|
|
|