diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 554d1292b..433f5499a 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -16,6 +16,17 @@ should also be read to understand what has changed since earlier releases. +### Fix embedded implementations of `epicsEvent` + +[GH:202](https://github.com/epics-base/epics-base/issues/202) and +[GH:206](https://github.com/epics-base/epics-base/pull/206) + +Heinz Junkes provided a new implementation of the `epicsEvent` API suitable for +RTEMS Posix targets (RTEMS 5.1 and later). In review a few issues related to +overflow of timeout values surfaced in this and other embedded implementations, +and these were also been fixed in this Pull Request. The API documentation for +this and some other routines has also been updated. + ### Breakpoint Table Names diff --git a/modules/ca/src/client/cadef.h b/modules/ca/src/client/cadef.h index fc94dfb6c..12861c384 100644 --- a/modules/ca/src/client/cadef.h +++ b/modules/ca/src/client/cadef.h @@ -559,19 +559,19 @@ LIBCA_API chid epicsStdCall ca_evid_to_chid ( evid id ); /* * ca_pend_event() * - * timeOut R wait for this delay in seconds + * timeout R wait for this delay in seconds */ -LIBCA_API int epicsStdCall ca_pend_event (ca_real timeOut); +LIBCA_API int epicsStdCall ca_pend_event (ca_real timeout); #define ca_poll() ca_pend_event(1e-12) /* * ca_pend_io() * - * timeOut R wait for this delay in seconds but return early + * timeout R wait for this delay in seconds but return early * if all get requests (or search requests with null * connection handler pointer have completed) */ -LIBCA_API int epicsStdCall ca_pend_io (ca_real timeOut); +LIBCA_API int epicsStdCall ca_pend_io (ca_real timeout); /* calls ca_pend_io() if early is true otherwise ca_pend_event() is called */ LIBCA_API int epicsStdCall ca_pend (ca_real timeout, int early); diff --git a/modules/libcom/src/osi/epicsEvent.cpp b/modules/libcom/src/osi/epicsEvent.cpp index d8b2533eb..1797366f4 100644 --- a/modules/libcom/src/osi/epicsEvent.cpp +++ b/modules/libcom/src/osi/epicsEvent.cpp @@ -70,9 +70,9 @@ void epicsEvent::wait () } } -bool epicsEvent::wait (double timeOut) +bool epicsEvent::wait (double timeout) { - epicsEventStatus status = epicsEventWaitWithTimeout (this->id, timeOut); + epicsEventStatus status = epicsEventWaitWithTimeout (this->id, timeout); if (status == epicsEventOK) { return true; diff --git a/modules/libcom/src/osi/epicsEvent.h b/modules/libcom/src/osi/epicsEvent.h index 8cc16fe61..4fd2d338a 100644 --- a/modules/libcom/src/osi/epicsEvent.h +++ b/modules/libcom/src/osi/epicsEvent.h @@ -99,10 +99,12 @@ public: **/ void wait (); /**\brief Wait for the event or until the specified timeout. - * \param timeOut The timeout delay in seconds. + * \param timeout The timeout delay in seconds. A timeout of zero is + * equivalent to calling tryWait(); NaN or any value too large to be + * represented to the target OS is equivalent to no timeout. * \return True if the event was triggered, False if it timed out. **/ - bool wait ( double timeOut ); + bool wait ( double timeout ); /**\brief Similar to wait() except that if the event is currently empty the * call will return immediately. * \return True if the event was full (triggered), False if empty. @@ -190,11 +192,13 @@ LIBCOM_API void epicsEventMustWait(epicsEventId id); /**\brief Wait an the event or until the specified timeout period is over. * \note Blocks until full or timeout. * \param id The event identifier. - * \param timeOut The timeout delay in seconds. + * \param timeout The timeout delay in seconds. A timeout of zero is + * equivalent to calling epicsEventTryWait(); NaN or any value too large + * to be represented to the target OS is equivalent to no timeout. * \return Status indicator. **/ LIBCOM_API epicsEventStatus epicsEventWaitWithTimeout( - epicsEventId id, double timeOut); + epicsEventId id, double timeout); /**\brief Similar to wait() except that if the event is currently empty the * call will return immediately with status \c epicsEventWaitTimeout. diff --git a/modules/libcom/src/osi/epicsMessageQueue.h b/modules/libcom/src/osi/epicsMessageQueue.h index 8240c7a5c..946195067 100644 --- a/modules/libcom/src/osi/epicsMessageQueue.h +++ b/modules/libcom/src/osi/epicsMessageQueue.h @@ -84,6 +84,9 @@ public: /** * \brief Send a message or timeout. + * \param timeout The timeout delay in seconds. A timeout of zero is + * equivalent to calling trySend(); NaN or any value too large to be + * represented to the target OS is equivalent to no timeout. * \returns 0 if the message was sent to a receiver or queued for * future delivery. * \returns -1 if the timeout was reached before the @@ -124,12 +127,16 @@ public: int receive ( void *message, unsigned int size ); /** - * \brief Wait for a message to be queued. - * Wait up to \p timeout seconds for a message to be sent if the queue - * is empty, then move the first message to the specified location. + * \brief Wait for and fetch the next message. + * \param timeout The timeout delay in seconds. A timeout of zero is + * equivalent to calling tryReceive(); NaN or any value too large to + * be represented to the target OS is equivalent to no timeout. * - * If the received message is larger than the specified - * messageBufferSize it may either return -1, or truncate the + * Waits up to \p timeout seconds for a message to arrive if the queue + * is empty, then moves the first message to the specified location. + * + * If the received message is larger than the specified message size + * the implementation may either return -1, or truncate the * message. It is most efficient if the messageBufferSize is equal * to the maximumMessageSize with which the message queue was * created. diff --git a/modules/libcom/src/osi/os/RTEMS-posix/osdEvent.c b/modules/libcom/src/osi/os/RTEMS-posix/osdEvent.c new file mode 100644 index 000000000..8c2c81e3e --- /dev/null +++ b/modules/libcom/src/osi/os/RTEMS-posix/osdEvent.c @@ -0,0 +1,116 @@ +/*************************************************************************\ +* Copyright (c) 2021 Fritz Haber Institute, Berlin +* SPDX-License-Identifier: EPICS +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * RTEMS osdEvent.c + * Author: H. Junkes + * junkes@fhi.mpg.de + */ + +#include +#include +#include +#include +#include + +#include + +#include "libComAPI.h" +#include "epicsEvent.h" + +typedef struct epicsEventOSD { + rtems_binary_semaphore rbs; +} epicsEventOSD; + +/* + * Create a simple binary semaphore + */ +LIBCOM_API epicsEventId +epicsEventCreate(epicsEventInitialState initialState) +{ + epicsEventOSD *pSem = malloc (sizeof(*pSem)); + + if (pSem) { + rtems_binary_semaphore_init(&pSem->rbs, NULL); + if (initialState) + rtems_binary_semaphore_post(&pSem->rbs); + } + return pSem; +} + +LIBCOM_API void +epicsEventDestroy(epicsEventId pSem) +{ + rtems_binary_semaphore_destroy(&pSem->rbs); +} + +LIBCOM_API epicsEventStatus +epicsEventTrigger(epicsEventId pSem) +{ + rtems_binary_semaphore_post(&pSem->rbs); + return epicsEventOK; +} + +LIBCOM_API epicsEventStatus +epicsEventWait(epicsEventId pSem) +{ + rtems_binary_semaphore_wait(&pSem->rbs); + return epicsEventOK; +} + +LIBCOM_API epicsEventStatus +epicsEventWaitWithTimeout(epicsEventId pSem, double timeout) +{ + int sc; + rtems_interval delay; + rtems_interval rate = rtems_clock_get_ticks_per_second(); + + if (!rate) + return epicsEventError; + + if (timeout <= 0.0) { + sc = rtems_binary_semaphore_try_wait(&pSem->rbs); + if (!sc) + return epicsEventOK; + else + return epicsEventWaitTimeout; + } + else if (timeout < (double) UINT32_MAX / rate) { + delay = timeout * rate; + if (delay == 0) { + /* 0 < timeout < 1/rate; round up */ + delay = 1; + } + } + else { + /* timeout is NaN or too big to represent; wait forever */ + delay = RTEMS_NO_TIMEOUT; + } + + sc = rtems_binary_semaphore_wait_timed_ticks(&pSem->rbs, delay); + if (!sc) + return epicsEventOK; + else if (sc == ETIMEDOUT) + return epicsEventWaitTimeout; + else + return epicsEventError; +} + +LIBCOM_API epicsEventStatus +epicsEventTryWait(epicsEventId pSem) +{ + int sc = rtems_binary_semaphore_try_wait(&pSem->rbs); + + if (!sc) + return epicsEventOK; + else + return epicsEventWaitTimeout; +} + +LIBCOM_API void +epicsEventShow(epicsEventId pSem, unsigned int level) +{ +} diff --git a/modules/libcom/src/osi/os/RTEMS-posix/osdEvent.h b/modules/libcom/src/osi/os/RTEMS-posix/osdEvent.h new file mode 100644 index 000000000..6778c4662 --- /dev/null +++ b/modules/libcom/src/osi/os/RTEMS-posix/osdEvent.h @@ -0,0 +1,8 @@ +/*************************************************************************\ +* Copyright (c) 2021 Fritz Haber Institute, Berlin +* SPDX-License-Identifier: EPICS +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* No definitions are needed for this implementation */ diff --git a/modules/libcom/src/osi/os/RTEMS-score/osdEvent.c b/modules/libcom/src/osi/os/RTEMS-score/osdEvent.c index 7cad7b9b8..277e7882f 100644 --- a/modules/libcom/src/osi/os/RTEMS-score/osdEvent.c +++ b/modules/libcom/src/osi/os/RTEMS-score/osdEvent.c @@ -20,6 +20,7 @@ #define __RTEMS_VIOLATE_KERNEL_VISIBILITY__ 1 #include +#include #include #include @@ -122,19 +123,27 @@ epicsEventWait(epicsEventId id) } epicsEventStatus -epicsEventWaitWithTimeout(epicsEventId id, double timeOut) +epicsEventWaitWithTimeout(epicsEventId id, double timeout) { rtems_id sid = (rtems_id)id; rtems_status_code sc; rtems_interval delay; extern double rtemsTicksPerSecond_double; - if (timeOut <= 0.0) + if (timeout <= 0.0) return epicsEventTryWait(id); SEMSTAT(1) - delay = timeOut * rtemsTicksPerSecond_double; - if (delay == 0) - delay++; + if (timeout < (double) UINT32_MAX / rtemsTicksPerSecond_double) { + delay = timeout * rtemsTicksPerSecond_double; + if (delay == 0) { + /* 0 < timeout < 1/rtemsTicksPerSecond, round up */ + delay++; + } + } + else { + /* timeout is NaN or too big to represent in ticks */ + delay = RTEMS_NO_TIMEOUT; + } sc = rtems_semaphore_obtain (sid, RTEMS_WAIT, delay); if (sc == RTEMS_SUCCESSFUL) return epicsEventOK; diff --git a/modules/libcom/src/osi/os/WIN32/osdEvent.c b/modules/libcom/src/osi/os/WIN32/osdEvent.c index 62117cb16..6eb64d4ca 100644 --- a/modules/libcom/src/osi/os/WIN32/osdEvent.c +++ b/modules/libcom/src/osi/os/WIN32/osdEvent.c @@ -90,7 +90,7 @@ LIBCOM_API epicsEventStatus epicsEventWait ( epicsEventId pSem ) * epicsEventWaitWithTimeout () */ LIBCOM_API epicsEventStatus epicsEventWaitWithTimeout ( - epicsEventId pSem, double timeOut ) + epicsEventId pSem, double timeout ) { /* waitable timers use 100 nanosecond intervals, like FILETIME */ static const unsigned ivalPerSec = 10000000u; /* number of 100ns intervals per second */ @@ -101,17 +101,17 @@ LIBCOM_API epicsEventStatus epicsEventWaitWithTimeout ( HANDLE timer; LONGLONG nIvals; /* number of intervals */ - if ( timeOut <= 0.0 ) { + if ( timeout <= 0.0 ) { tmo.QuadPart = 0u; } - else if ( timeOut >= INFINITE / mSecPerSec ) { + else if ( timeout >= INFINITE / mSecPerSec ) { /* we need to apply a maximum wait time to stop an overflow. We choose (INFINITE - 1) milliseconds, to be compatible with previous WaitForSingleObject() implementation */ nIvals = (LONGLONG)(INFINITE - 1) * (ivalPerSec / mSecPerSec); tmo.QuadPart = -nIvals; /* negative value means a relative time offset for timer */ } else { - nIvals = (LONGLONG)(timeOut * ivalPerSec + 0.999999); + nIvals = (LONGLONG)(timeout * ivalPerSec + 0.999999); tmo.QuadPart = -nIvals; } diff --git a/modules/libcom/src/osi/os/vxWorks/osdEvent.c b/modules/libcom/src/osi/os/vxWorks/osdEvent.c index 27b271e51..38c4d1806 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdEvent.c +++ b/modules/libcom/src/osi/os/vxWorks/osdEvent.c @@ -34,20 +34,25 @@ void epicsEventDestroy(epicsEventId id) semDelete((SEM_ID)id); } -epicsEventStatus epicsEventWaitWithTimeout(epicsEventId id, double timeOut) +epicsEventStatus epicsEventWaitWithTimeout(epicsEventId id, double timeout) { int rate = sysClkRateGet(); int status; int ticks; - if (timeOut <= 0.0) { + if (timeout <= 0.0) { ticks = 0; - } else if (timeOut >= (double) INT_MAX / rate) { - ticks = WAIT_FOREVER; - } else { - ticks = timeOut * rate; - if (ticks <= 0) + } + else if (timeout < (double) INT_MAX / rate) { + ticks = timeout * rate; + if (ticks == 0) { + /* 0 < timeout < 1/rate; round up */ ticks = 1; + } + } + else { + /* timeout is NaN or too big to represent in ticks */ + ticks = WAIT_FOREVER; } status = semTake((SEM_ID)id, ticks); if (status == OK) diff --git a/modules/libcom/src/osi/os/vxWorks/osdMessageQueue.cpp b/modules/libcom/src/osi/os/vxWorks/osdMessageQueue.cpp index bf27daf1b..fe3ef1563 100644 --- a/modules/libcom/src/osi/os/vxWorks/osdMessageQueue.cpp +++ b/modules/libcom/src/osi/os/vxWorks/osdMessageQueue.cpp @@ -21,18 +21,28 @@ extern "C" int sysClkRateGet(void); LIBCOM_API int epicsStdCall epicsMessageQueueSendWithTimeout( epicsMessageQueueId id, void *message, - unsigned int messageSize, + unsigned int size, double timeout) { int ticks; + int rate = sysClkRateGet(); - if (timeout<=0.0) { + if (timeout <= 0.0) { ticks = 0; - } else { - ticks = (int)(timeout*sysClkRateGet()); - if(ticks<=0) ticks = 1; } - return msgQSend((MSG_Q_ID)id, (char *)message, messageSize, ticks, MSG_PRI_NORMAL); + else if (timeout < (double) INT_MAX / rate) { + ticks = timeout * rate; + if (ticks == 0) { + /* 0 < timeout < 1/rate; round up */ + ticks = 1; + } + } + else { + /* timeout is NaN or too big to represent in ticks */ + ticks = WAIT_FOREVER; + } + + return msgQSend((MSG_Q_ID)id, (char *)message, size, ticks, MSG_PRI_NORMAL); } LIBCOM_API int epicsStdCall epicsMessageQueueReceiveWithTimeout( @@ -42,12 +52,22 @@ LIBCOM_API int epicsStdCall epicsMessageQueueReceiveWithTimeout( double timeout) { int ticks; + int rate = sysClkRateGet(); - if (timeout<=0.0) { + if (timeout <= 0.0) { ticks = 0; - } else { - ticks = (int)(timeout*sysClkRateGet()); - if(ticks<=0) ticks = 1; } + else if (timeout < (double) INT_MAX / rate) { + ticks = timeout * rate; + if (ticks == 0) { + /* 0 < timeout < 1/rate, round up */ + ticks = 1; + } + } + else { + /* timeout is NaN or too big to represent in ticks */ + ticks = WAIT_FOREVER; + } + return msgQReceive((MSG_Q_ID)id, (char *)message, size, ticks); }