/**************************************************************************** * drivers/timers/rtc.c * * Copyright (C) 2015, 2017 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Private Types ****************************************************************************/ #if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_PERIODIC) struct rtc_alarminfo_s { bool active; /* True: alarm is active */ pid_t pid; /* Identifies task to be notified */ struct sigevent event; /* Describe the way a task is to be notified */ struct sigwork_s work; /* Signal work */ }; #endif struct rtc_upperhalf_s { FAR struct rtc_lowerhalf_s *lower; /* Contained lower half driver */ sem_t exclsem; /* Supports mutual exclusion */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS uint8_t crefs; /* Number of open references */ bool unlinked; /* True if the driver has been unlinked */ #endif #ifdef CONFIG_RTC_ALARM /* This is an array, indexed by the alarm ID, that provides information * needed to map an alarm expiration to a signal event. */ struct rtc_alarminfo_s alarminfo[CONFIG_RTC_NALARMS]; #endif #ifdef CONFIG_RTC_PERIODIC /* Currently only one periodic wakeup is supported. */ struct rtc_alarminfo_s periodicinfo; #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Internal logic */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static void rtc_destroy(FAR struct rtc_upperhalf_s *upper); #endif #ifdef CONFIG_RTC_ALARM static void rtc_alarm_callback(FAR void *priv, int id); #endif #ifdef CONFIG_RTC_PERIODIC static void rtc_periodic_callback(FAR void *priv, int id); #endif /* Character driver methods */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int rtc_open(FAR struct file *filep); static int rtc_close(FAR struct file *filep); #endif static ssize_t rtc_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t rtc_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int rtc_ioctl(FAR struct file *filep, int cmd, unsigned long arg); #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int rtc_unlink(FAR struct inode *inode); #endif /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations rtc_fops = { #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS rtc_open, /* open */ rtc_close, /* close */ #else NULL, /* open */ NULL, /* close */ #endif rtc_read, /* read */ rtc_write, /* write */ NULL, /* seek */ rtc_ioctl, /* ioctl */ NULL, /* poll */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS rtc_unlink /* unlink */ #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: rtc_destroy ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static void rtc_destroy(FAR struct rtc_upperhalf_s *upper) { /* If the lower half driver provided a destroy method, then call that * method now in order to clean up resources used by the lower-half driver. */ DEBUGASSERT(upper->lower && upper->lower->ops); if (upper->lower->ops->destroy) { upper->lower->ops->destroy(upper->lower); } /* And free our container */ nxsem_destroy(&upper->exclsem); kmm_free(upper); } #endif /**************************************************************************** * Name: rtc_alarm_callback ****************************************************************************/ #ifdef CONFIG_RTC_ALARM static void rtc_alarm_callback(FAR void *priv, int alarmid) { FAR struct rtc_upperhalf_s *upper = (FAR struct rtc_upperhalf_s *)priv; FAR struct rtc_alarminfo_s *alarminfo; DEBUGASSERT(upper != NULL && alarmid >= 0 && alarmid < CONFIG_RTC_NALARMS); alarminfo = &upper->alarminfo[alarmid]; /* Do we think that the alarm is active? It might be due to some * race condition between a cancellation event and the alarm * expiration. */ if (alarminfo->active) { /* Yes.. signal the alarm expiration */ nxsig_notification(alarminfo->pid, &alarminfo->event, SI_QUEUE, &alarminfo->work); } /* The alarm is no longer active */ alarminfo->active = false; } #endif /**************************************************************************** * Name: rtc_periodic_callback ****************************************************************************/ #ifdef CONFIG_RTC_PERIODIC static void rtc_periodic_callback(FAR void *priv, int alarmid) { FAR struct rtc_upperhalf_s *upper = (FAR struct rtc_upperhalf_s *)priv; FAR struct rtc_alarminfo_s *alarminfo; DEBUGASSERT(upper != NULL && alarmid >= 0); alarminfo = &upper->periodicinfo; /* Do we think that the alarm is active? It might be due to some * race condition between a cancellation event and the alarm * expiration. */ if (alarminfo->active) { /* Yes.. signal the alarm expiration */ nxsig_notification(alarminfo->pid, &alarminfo->event, SI_QUEUE, &alarminfo->work); } /* The alarm is no longer active */ alarminfo->active = false; } #endif /**************************************************************************** * Name: rtc_open ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int rtc_open(FAR struct file *filep) { FAR struct inode *inode; FAR struct rtc_upperhalf_s *upper; int ret; /* Get the reference to our internal state structure from the inode * structure. */ DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); upper = inode->i_private; /* Get exclusive access to the device structures */ ret = nxsem_wait(&upper->exclsem); if (ret < 0) { return ret; } /* Increment the count of open references on the RTC driver */ upper->crefs++; DEBUGASSERT(upper->crefs > 0); nxsem_post(&upper->exclsem); return OK; } #endif /**************************************************************************** * Name: rtc_close ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int rtc_close(FAR struct file *filep) { FAR struct inode *inode; FAR struct rtc_upperhalf_s *upper; int ret; /* Get the reference to our internal state structure from the inode * structure. */ DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); upper = inode->i_private; /* Get exclusive access to the device structures */ ret = nxsem_wait(&upper->exclsem); if (ret < 0) { return ret; } /* Decrement the count of open references on the RTC driver */ DEBUGASSERT(upper->crefs > 0); upper->crefs--; nxsem_post(&upper->exclsem); /* If the count has decremented to zero and the driver has been unlinked, * then commit Hara-Kiri now. */ if (upper->crefs == 0 && upper->unlinked) { rtc_destroy(upper); } return OK; } #endif /**************************************************************************** * Name: rtc_read ****************************************************************************/ static ssize_t rtc_read(FAR struct file *filep, FAR char *buffer, size_t len) { return 0; /* Return EOF */ } /**************************************************************************** * Name: rtc_write ****************************************************************************/ static ssize_t rtc_write(FAR struct file *filep, FAR const char *buffer, size_t len) { return len; /* Say that everything was written */ } /**************************************************************************** * Name: rtc_ioctl ****************************************************************************/ static int rtc_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode; FAR struct rtc_upperhalf_s *upper; FAR const struct rtc_ops_s *ops; int ret = -ENOSYS; /* Get the reference to our internal state structure from the inode * structure. */ DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); upper = inode->i_private; DEBUGASSERT(upper->lower && upper->lower->ops); /* Get exclusive access to the device structures */ ret = nxsem_wait(&upper->exclsem); if (ret < 0) { return ret; } /* We simply forward all ioctl() commands to the lower half. The upper * half is nothing more than a thin driver shell over the lower level * RTC implementation. */ ops = upper->lower->ops; switch (cmd) { /* RTC_RD_TIME returns the current RTC time. * * Argument: A writeable reference to a struct rtc_time to receive the * RTC's time. */ case RTC_RD_TIME: { FAR struct rtc_time *rtctime = (FAR struct rtc_time *)((uintptr_t)arg); if (ops->rdtime) { ret = ops->rdtime(upper->lower, rtctime); } } break; /* RTC_SET_TIME sets the RTC's time * * Argument: A read-only reference to a struct rtc_time containing * the new time to be set. */ case RTC_SET_TIME: { FAR const struct rtc_time *rtctime = (FAR const struct rtc_time *)((uintptr_t)arg); if (ops->settime) { ret = ops->settime(upper->lower, rtctime); } } break; /* RTC_HAVE_SET_TIME checks if RTC's time had been set * * Argument: A writable reference to a bool to receive true/false return * value of the check. */ case RTC_HAVE_SET_TIME: { FAR bool *have_set_time = (FAR bool *)((uintptr_t)arg); if (ops->havesettime) { *have_set_time = ops->havesettime(upper->lower); ret = OK; } } break; #ifdef CONFIG_RTC_ALARM /* RTC_SET_ALARM sets the alarm time. * * Argument: A read-only reference to a struct rtc_time containing the * new alarm time to be set. */ case RTC_SET_ALARM: { FAR const struct rtc_setalarm_s *alarminfo = (FAR const struct rtc_setalarm_s *)((uintptr_t)arg); FAR struct rtc_alarminfo_s *upperinfo; struct lower_setalarm_s lowerinfo; int alarmid; DEBUGASSERT(alarminfo != NULL); alarmid = alarminfo->id; DEBUGASSERT(alarmid >= 0 && alarmid < CONFIG_RTC_NALARMS); /* Is the alarm active? */ upperinfo = &upper->alarminfo[alarmid]; if (upperinfo->active) { /* Yes, cancel the alarm */ if (ops->cancelalarm) { (void)ops->cancelalarm(upper->lower, alarmid); } upperinfo->active = false; } if (ops->setalarm) { pid_t pid; /* A PID of zero means to signal the calling task */ pid = alarminfo->pid; if (pid == 0) { pid = getpid(); } /* Save the signal info to be used to notify the caller when the * alarm expires. */ upperinfo->active = true; upperinfo->pid = pid; upperinfo->event = alarminfo->event; /* Format the alarm info needed by the lower half driver */ lowerinfo.id = alarmid; lowerinfo.cb = rtc_alarm_callback; lowerinfo.priv = (FAR void *)upper; lowerinfo.time = alarminfo->time; /* Then set the alarm */ ret = ops->setalarm(upper->lower, &lowerinfo); if (ret < 0) { upperinfo->active = false; } } } break; /* RTC_SET_RELATIVE sets the alarm time relative to the current time. * * Argument: A read-only reference to a struct rtc_setrelative_s * containing the new relative alarm time to be set. */ case RTC_SET_RELATIVE: { FAR const struct rtc_setrelative_s *alarminfo = (FAR const struct rtc_setrelative_s *)((uintptr_t)arg); FAR struct rtc_alarminfo_s *upperinfo; struct lower_setrelative_s lowerinfo; int alarmid; DEBUGASSERT(alarminfo != NULL); alarmid = alarminfo->id; DEBUGASSERT(alarmid >= 0 && alarmid < CONFIG_RTC_NALARMS); /* Is the alarm active? */ upperinfo = &upper->alarminfo[alarmid]; if (upperinfo->active) { /* Yes, cancel the alarm */ if (ops->cancelalarm) { (void)ops->cancelalarm(upper->lower, alarmid); } upperinfo->active = false; } if (ops->setrelative) { pid_t pid; /* A PID of zero means to signal the calling task */ pid = alarminfo->pid; if (pid == 0) { pid = getpid(); } /* Save the signal info to be used to notify the caller when the * alarm expires. */ upperinfo->active = true; upperinfo->pid = pid; upperinfo->event = alarminfo->event; /* Format the alarm info needed by the lower half driver */ lowerinfo.id = alarmid; lowerinfo.cb = rtc_alarm_callback; lowerinfo.priv = (FAR void *)upper; lowerinfo.reltime = alarminfo->reltime; /* Then set the alarm */ ret = ops->setrelative(upper->lower, &lowerinfo); if (ret < 0) { upperinfo->active = false; } } } break; /* RTC_CANCEL_ALARM cancel the alarm. * * Argument: An ALARM ID value that indicates which alarm should be * canceled. */ case RTC_CANCEL_ALARM: { FAR struct rtc_alarminfo_s *upperinfo; int alarmid = (int)arg; DEBUGASSERT(alarmid >= 0 && alarmid < CONFIG_RTC_NALARMS); upperinfo = &upper->alarminfo[alarmid]; if (ops->cancelalarm) { ret = ops->cancelalarm(upper->lower, alarmid); if (ret == OK) { upperinfo->active = false; nxsig_cancel_notification(&upperinfo->work); } } } break; /* RTC_RD_ALARM query the alarm. * * Argument: A writable reference to the queried alarm. * */ case RTC_RD_ALARM: { FAR struct rtc_rdalarm_s *alarmquery = (FAR struct rtc_rdalarm_s *)((uintptr_t)arg); FAR struct rtc_alarminfo_s *upperinfo; struct lower_rdalarm_s lowerinfo; int alarmid; DEBUGASSERT(alarmquery != NULL); alarmid = alarmquery->id; DEBUGASSERT(alarmid >= 0 && alarmid < CONFIG_RTC_NALARMS); /* Is the alarm active? */ upperinfo = &upper->alarminfo[alarmid]; alarmquery->active = upperinfo->active; lowerinfo.id = alarmid; lowerinfo.priv = (FAR void *)upper; lowerinfo.time = (FAR struct rtc_time *)&alarmquery->time; if (ops->rdalarm) { ret = ops->rdalarm(upper->lower, &lowerinfo); } } break; #endif /* CONFIG_RTC_ALARM */ #ifdef CONFIG_RTC_PERIODIC /* RTC_SET_PERIODIC set a periodic wakeup. * * Argument: A read-only reference to a struct rtc_setperiodic_s * containing the new wakeup period to be set. */ case RTC_SET_PERIODIC: { FAR const struct rtc_setperiodic_s *alarminfo = (FAR const struct rtc_setperiodic_s *)((uintptr_t)arg); FAR struct rtc_alarminfo_s *upperinfo; struct lower_setperiodic_s lowerinfo; int id; DEBUGASSERT(alarminfo != NULL); id = alarminfo->id; DEBUGASSERT(id >= 0); /* Is the alarm active? */ upperinfo = &upper->periodicinfo; if (upperinfo->active) { /* Yes, cancel it */ if (ops->cancelperiodic) { (void)ops->cancelperiodic(upper->lower, id); } upperinfo->active = false; } if (ops->setperiodic) { pid_t pid; /* A PID of zero means to signal the calling task */ pid = alarminfo->pid; if (pid == 0) { pid = getpid(); } /* Save the signal info to be used to notify the caller when the * alarm expires. */ upperinfo->active = true; upperinfo->pid = pid; upperinfo->event = alarminfo->event; /* Format the alarm info needed by the lower half driver. */ lowerinfo.id = id; lowerinfo.cb = rtc_periodic_callback; lowerinfo.priv = (FAR void *)upper; lowerinfo.period = alarminfo->period; /* Then set the periodic wakeup. */ ret = ops->setperiodic(upper->lower, &lowerinfo); if (ret < 0) { upperinfo->active = false; } } } break; /* RTC_CANCEL_PERIODIC cancel the periodic wakeup. * * Argument: An ID value that indicates which wakeup should be canceled. */ case RTC_CANCEL_PERIODIC: { FAR struct rtc_alarminfo_s *upperinfo; int id = (int)arg; DEBUGASSERT(id >= 0); upperinfo = &upper->periodicinfo; if (ops->cancelperiodic) { ret = ops->cancelperiodic(upper->lower, id); if (ret == OK) { upperinfo->active = false; nxsig_cancel_notification(&upperinfo->work); } } } break; #endif /* CONFIG_RTC_PERIODIC */ /* Forward any unrecognized IOCTLs to the lower half driver... they * may represent some architecture-specific command. */ default: { ret = -ENOTTY; #ifdef CONFIG_RTC_IOCTL if (ops->ioctl) { ret = ops->ioctl(upper->lower, cmd, arg); } #endif } break; } nxsem_post(&upper->exclsem); return ret; } /**************************************************************************** * Name: rtc_unlink ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int rtc_unlink(FAR struct inode *inode) { FAR struct rtc_upperhalf_s *upper; int ret; /* Get the reference to our internal state structure from the inode * structure. */ DEBUGASSERT(inode && inode->i_private); upper = inode->i_private; /* Get exclusive access to the device structures */ ret = nxsem_wait(&upper->exclsem); if (ret < 0) { return ret; } /* Indicate that the driver has been unlinked */ upper->unlinked = true; nxsem_post(&upper->exclsem); /* If there are no further open references to the driver, then commit * Hara-Kiri now. */ if (upper->crefs == 0) { rtc_destroy(upper); } return OK; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: rtc_initialize * * Description: * Create an RTC driver by binding to the lower half RTC driver instance * provided to this function. The resulting RTC driver will be registered * at /dev/rtcN where N is the minor number provided to this function. * * Input Parameters: * minor - The minor number of the RTC device. The N in /dev/rtcN * lower - The lower half driver instance. * * Returned Value: * Zero (OK) on success; A negated errno value on failure. * ****************************************************************************/ int rtc_initialize(int minor, FAR struct rtc_lowerhalf_s *lower) { FAR struct rtc_upperhalf_s *upper; char devpath[16]; int ret; DEBUGASSERT(lower && lower->ops && minor >= 0 && minor < 1000); /* Allocate an upper half container structure */ upper = (FAR struct rtc_upperhalf_s *) kmm_zalloc(sizeof(struct rtc_upperhalf_s)); if (!upper) { return -ENOMEM; } /* Initialize the upper half container */ upper->lower = lower; /* Contain lower half driver */ nxsem_init(&upper->exclsem, 0, 1); #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS upper->crefs = 0; /* No open references */ upper->unlinked = false; /* Driver is not unlinked */ #endif /* Create the driver name. There is space for the a minor number up to 6 * characters */ snprintf(devpath, 16, "/dev/rtc%d", minor); /* And, finally, register the new RTC driver */ ret = register_driver(devpath, &rtc_fops, 0666, upper); if (ret < 0) { nxsem_destroy(&upper->exclsem); kmm_free(upper); return ret; } return OK; }