mb_time_util.h
author Edouard Tisserant <edouard.tisserant@gmail.com>
Mon, 07 Jun 2021 11:21:26 +0200
changeset 17 e319814f1c17
parent 0 ae252e0fd9b8
permissions -rw-r--r--
merge
/*
 * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
 *
 * This file is part of the Modbus library for Beremiz and matiec.
 *
 * This Modbus library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this Modbus library.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This code is made available on the understanding that it will not be
 * used in safety-critical situations without a full and competent review.
 */



 /* Time handling functions used by the modbus protocols... */


#ifndef __MODBUS_TIME_UTIL_H
#define __MODBUS_TIME_UTIL_H


/************************************/
/**                                **/
/**     Time format conversion     **/
/**                                **/
/************************************/

/* Function to load a struct timeval correctly from a double. */
static inline struct timeval d_to_timeval(double time) {
  struct timeval tmp;

  tmp.tv_sec  = time;
  tmp.tv_usec = 1e6*(time - tmp.tv_sec);
  return tmp;
}


/* Function to load a struct timespec correctly from a double. */
static inline struct timespec d_to_timespec(double time) {
  struct timespec tmp;

  tmp.tv_sec  = time;
  tmp.tv_nsec = 1e9*(time - tmp.tv_sec);
  return tmp;
}



/* Function to ... */
static inline struct timespec timespec_dif(struct timespec ts1, struct timespec ts2) {
  struct timespec ts;

  ts.tv_sec  = ts1.tv_sec  - ts2.tv_sec;
  if(ts1.tv_nsec > ts2.tv_nsec) {
    ts.tv_nsec = ts1.tv_nsec - ts2.tv_nsec;
  } else {
    ts.tv_nsec = 1000000000 + ts1.tv_nsec - ts2.tv_nsec;
    ts.tv_sec--;
  }

  if (ts.tv_sec < 0)
    ts.tv_sec = ts.tv_nsec = 0;

  return ts;
}

/* Function to ... */
static inline struct timespec timespec_add(struct timespec ts1, struct timespec ts2) {
  struct timespec ts;

  ts.tv_sec  = ts1.tv_sec  + ts2.tv_sec;
  ts.tv_nsec = ts1.tv_nsec + ts2.tv_nsec;
  ts.tv_sec += ts.tv_nsec / 1000000000;
  ts.tv_nsec = ts.tv_nsec % 1000000000;
  return ts;
}

/* Function to convert a struct timespec to a struct timeval. */
static inline struct timeval timespec_to_timeval(struct timespec ts) {
  struct timeval tv;

  tv.tv_sec  = ts.tv_sec;
  tv.tv_usec = ts.tv_nsec/1000;
  return tv;
}


/*
 * NOTE: clock_gettime() is rather expensive, between 7000 and 7500 clock
 *       cycles (measured with rdtsc on an Intel Pentium)
 *       gettimeofday() is half as expensive (3000 to 3500 clock cycles),
 *       but is not POSIX compliant... :-(
 *       Nevertheless this is peanuts (20 us on a 350 MHz cpu) compared to
 *       the timescales required to read a modbus frame over a serial bus
 *       (aprox. 10 ms for a 10 byte frame on a 9600 baud bus!)
 */
static inline struct timespec timespec_add_curtime(struct timespec ts) {
  struct timespec  res     = {.tv_sec = 0, .tv_nsec = 0};
  
  /* is ts = 0 also return 0 !! */
  if ((ts.tv_sec != 0) || (ts.tv_nsec != 0))
    if (clock_gettime(CLOCK_MONOTONIC, &res) >= 0)
      res = timespec_add(res, ts);
  return res;
}
  
/************************************/
/**                                **/
/** select() with absolute timeout **/
/**                                **/
/************************************/




/* My private version of select using an absolute timeout, instead of the
 * usual relative timeout.
 *
 * NOTE: Ususal select semantics for (a: end_time == NULL) and
 *       (b: *end_time == 0) also apply.
 *
 *       (a) Indefinite timeout
 *       (b) Try once, and and quit if no data available.
 */
/* Returns: -1 on error
 *           0 on timeout
 *          >0 on success
 */
static int my_select(int fd, fd_set *rfds, fd_set *wfds, const struct timespec *end_time) {

  int res;
  struct timespec cur_time;
  struct timeval timeout, *tv_ptr;
  fd_set tmp_rfds, *tmp_rfds_ptr, tmp_wfds, *tmp_wfds_ptr;
  
  tmp_rfds_ptr = NULL;
  tmp_wfds_ptr = NULL;
  if (rfds != NULL) tmp_rfds_ptr = &tmp_rfds;
  if (wfds != NULL) tmp_wfds_ptr = &tmp_wfds;

  /*============================*
   * wait for data availability *
   *============================*/
  do {
    if (rfds != NULL) tmp_rfds = *rfds;
    if (wfds != NULL) tmp_wfds = *wfds;
      /* NOTE: To do the timeout correctly we would have to revert to timers
       *       and asociated signals. That is not very thread friendly, and is
       *       probably too much of a hassle trying to figure out which signal
       *       to use. What if we don't have any free signals?
       *
       *       The following solution is not correct, as it includes a race
       *       condition. The following five lines of code should really
       *       be atomic!
       *
       * NOTE: see also the timeout related comment in the
       *       modbus_tcp_read() function!
       */
    if (end_time == NULL) {
      tv_ptr = NULL;
    } else {
      tv_ptr = &timeout;
      if ((end_time->tv_sec == 0) && (end_time->tv_nsec == 0)) {
        timeout.tv_sec = timeout.tv_usec = 0;
      } else {
        /* ATOMIC - start */
        if (clock_gettime(CLOCK_MONOTONIC, &cur_time) < 0)
          return -1;
        timeout = timespec_to_timeval(timespec_dif(*end_time, cur_time));
      }
    }

    res = select(fd, tmp_rfds_ptr, tmp_wfds_ptr, NULL, tv_ptr);
  /* ATOMIC - end */

#ifdef DEBUG
  {int i;
   if (tmp_rfds_ptr != NULL)
     for (i = 0; i < fd; i++)
       if (FD_ISSET(i, tmp_rfds_ptr))
         fprintf(stderr,"fd=%d is ready for reading\n", i);
   if (tmp_wfds_ptr != NULL)
     for (i = 0; i < fd; i++)
       if (FD_ISSET(i, tmp_wfds_ptr))
         fprintf(stderr,"fd=%d is ready for writing\n", i);
  }
#endif
    if (res == 0) {
#ifdef DEBUG
      printf("Comms time out\n");
#endif
      return 0;
    }
    if ((res < 0) && (errno != EINTR)) {
      return -1;
    }
  } while (res <= 0);

  if (rfds != NULL) *rfds = tmp_rfds;
  if (wfds != NULL) *wfds = tmp_wfds;
  return res;
}






#endif  /* __MODBUS_TIME_UTIL_H */