/**************************************************************************** * drivers/timers/rtc.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #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 */ mutex_t lock; /* 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 g_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, /* mmap */ NULL, /* truncate */ 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 */ nxmutex_destroy(&upper->lock); 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); } } #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. */ inode = filep->f_inode; DEBUGASSERT(inode->i_private); upper = inode->i_private; /* Get exclusive access to the device structures */ ret = nxmutex_lock(&upper->lock); if (ret < 0) { return ret; } /* Increment the count of open references on the RTC driver */ upper->crefs++; DEBUGASSERT(upper->crefs > 0); nxmutex_unlock(&upper->lock); 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. */ inode = filep->f_inode; DEBUGASSERT(inode->i_private); upper = inode->i_private; /* Get exclusive access to the device structures */ ret = nxmutex_lock(&upper->lock); if (ret < 0) { return ret; } /* Decrement the count of open references on the RTC driver */ DEBUGASSERT(upper->crefs > 0); upper->crefs--; nxmutex_unlock(&upper->lock); /* 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; /* Get the reference to our internal state structure from the inode * structure. */ inode = filep->f_inode; DEBUGASSERT(inode->i_private); upper = inode->i_private; DEBUGASSERT(upper->lower && upper->lower->ops); /* Get exclusive access to the device structures */ ret = nxmutex_lock(&upper->lock); 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. */ ret = -ENOSYS; 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); if (ret >= 0) { /* If the RTC time was set successfully, then update the * current system time to match. */ clock_synchronize(NULL); } } } 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) { 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 = nxsched_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) { 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 = nxsched_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) { 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 = nxsched_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: { #ifdef CONFIG_RTC_IOCTL if (ops->ioctl) { ret = ops->ioctl(upper->lower, cmd, arg); } #endif } break; } nxmutex_unlock(&upper->lock); 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->i_private); upper = inode->i_private; /* Get exclusive access to the device structures */ ret = nxmutex_lock(&upper->lock); if (ret < 0) { return ret; } /* Indicate that the driver has been unlinked */ upper->unlinked = true; nxmutex_unlock(&upper->lock); /* 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[20]; 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 */ nxmutex_init(&upper->lock); #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 10 * characters */ snprintf(devpath, sizeof(devpath), "/dev/rtc%d", minor); /* And, finally, register the new RTC driver */ ret = register_driver(devpath, &g_rtc_fops, 0666, upper); if (ret < 0) { nxmutex_destroy(&upper->lock); kmm_free(upper); return ret; } return OK; }