LCOV - code coverage report
Current view: top level - lib/eal/linux - eal_alarm.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 75 96 78.1 %
Date: 2025-01-02 22:41:34 Functions: 5 5 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 47 82 57.3 %

           Branch data     Line data    Source code
       1                 :            : /* SPDX-License-Identifier: BSD-3-Clause
       2                 :            :  * Copyright(c) 2010-2014 Intel Corporation
       3                 :            :  */
       4                 :            : #include <stdio.h>
       5                 :            : #include <stdint.h>
       6                 :            : #include <stdlib.h>
       7                 :            : #include <errno.h>
       8                 :            : #include <pthread.h>
       9                 :            : #include <sys/queue.h>
      10                 :            : #include <sys/time.h>
      11                 :            : #include <sys/timerfd.h>
      12                 :            : 
      13                 :            : #include <eal_trace_internal.h>
      14                 :            : #include <rte_interrupts.h>
      15                 :            : #include <rte_alarm.h>
      16                 :            : #include <rte_common.h>
      17                 :            : #include <rte_errno.h>
      18                 :            : #include <rte_spinlock.h>
      19                 :            : 
      20                 :            : #include <eal_private.h>
      21                 :            : 
      22                 :            : #ifndef TFD_NONBLOCK
      23                 :            : #include <fcntl.h>
      24                 :            : #define TFD_NONBLOCK    O_NONBLOCK
      25                 :            : #endif
      26                 :            : 
      27                 :            : #define NS_PER_US 1000
      28                 :            : #define US_PER_MS 1000
      29                 :            : #define MS_PER_S 1000
      30                 :            : #ifndef US_PER_S
      31                 :            : #define US_PER_S (US_PER_MS * MS_PER_S)
      32                 :            : #endif
      33                 :            : 
      34                 :            : #ifdef CLOCK_MONOTONIC_RAW /* Defined in glibc bits/time.h */
      35                 :            : #define CLOCK_TYPE_ID CLOCK_MONOTONIC_RAW
      36                 :            : #else
      37                 :            : #define CLOCK_TYPE_ID CLOCK_MONOTONIC
      38                 :            : #endif
      39                 :            : 
      40                 :            : struct alarm_entry {
      41                 :            :         LIST_ENTRY(alarm_entry) next;
      42                 :            :         struct timeval time;
      43                 :            :         rte_eal_alarm_callback cb_fn;
      44                 :            :         void *cb_arg;
      45                 :            :         volatile uint8_t executing;
      46                 :            :         volatile pthread_t executing_id;
      47                 :            : };
      48                 :            : 
      49                 :            : static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
      50                 :            : static rte_spinlock_t alarm_list_lk = RTE_SPINLOCK_INITIALIZER;
      51                 :            : 
      52                 :            : static struct rte_intr_handle *intr_handle;
      53                 :            : static int handler_registered = 0;
      54                 :            : static void eal_alarm_callback(void *arg);
      55                 :            : 
      56                 :            : void
      57                 :        251 : rte_eal_alarm_cleanup(void)
      58                 :            : {
      59                 :        251 :         rte_intr_instance_free(intr_handle);
      60                 :        251 : }
      61                 :            : 
      62                 :            : int
      63                 :        184 : rte_eal_alarm_init(void)
      64                 :            : {
      65                 :            : 
      66                 :        184 :         intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE);
      67         [ -  + ]:        184 :         if (intr_handle == NULL) {
      68                 :          0 :                 EAL_LOG(ERR, "Fail to allocate intr_handle");
      69                 :          0 :                 goto error;
      70                 :            :         }
      71                 :            : 
      72         [ -  + ]:        184 :         if (rte_intr_type_set(intr_handle, RTE_INTR_HANDLE_ALARM))
      73                 :          0 :                 goto error;
      74                 :            : 
      75                 :            :         /* create a timerfd file descriptor */
      76         [ -  + ]:        184 :         if (rte_intr_fd_set(intr_handle,
      77                 :            :                         timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)))
      78                 :          0 :                 goto error;
      79                 :            : 
      80         [ -  + ]:        184 :         if (rte_intr_fd_get(intr_handle) == -1)
      81                 :          0 :                 goto error;
      82                 :            :         return 0;
      83                 :            : 
      84                 :          0 : error:
      85                 :          0 :         rte_intr_instance_free(intr_handle);
      86                 :          0 :         rte_errno = errno;
      87                 :          0 :         return -1;
      88                 :            : }
      89                 :            : 
      90                 :            : static void
      91                 :          1 : eal_alarm_callback(void *arg __rte_unused)
      92                 :            : {
      93                 :            :         struct timespec now;
      94                 :            :         struct alarm_entry *ap;
      95                 :            : 
      96                 :            :         rte_spinlock_lock(&alarm_list_lk);
      97         [ +  - ]:          2 :         while ((ap = LIST_FIRST(&alarm_list)) !=NULL &&
      98         [ +  - ]:          2 :                         clock_gettime(CLOCK_TYPE_ID, &now) == 0 &&
      99   [ -  +  +  + ]:          2 :                         (ap->time.tv_sec < now.tv_sec || (ap->time.tv_sec == now.tv_sec &&
     100         [ +  - ]:          1 :                                                 (ap->time.tv_usec * NS_PER_US) <= now.tv_nsec))) {
     101                 :          1 :                 ap->executing = 1;
     102                 :          1 :                 ap->executing_id = pthread_self();
     103                 :            :                 rte_spinlock_unlock(&alarm_list_lk);
     104                 :            : 
     105                 :          1 :                 ap->cb_fn(ap->cb_arg);
     106                 :            : 
     107                 :            :                 rte_spinlock_lock(&alarm_list_lk);
     108                 :            : 
     109         [ +  - ]:          1 :                 LIST_REMOVE(ap, next);
     110                 :          1 :                 free(ap);
     111                 :            :         }
     112                 :            : 
     113         [ +  - ]:          1 :         if (!LIST_EMPTY(&alarm_list)) {
     114                 :          1 :                 struct itimerspec atime = { .it_interval = { 0, 0 } };
     115                 :            : 
     116                 :            :                 ap = LIST_FIRST(&alarm_list);
     117                 :          1 :                 atime.it_value.tv_sec = ap->time.tv_sec;
     118                 :          1 :                 atime.it_value.tv_nsec = ap->time.tv_usec * NS_PER_US;
     119                 :            :                 /* perform borrow for subtraction if necessary */
     120         [ +  - ]:          1 :                 if (now.tv_nsec > (ap->time.tv_usec * NS_PER_US))
     121                 :          1 :                         atime.it_value.tv_sec--, atime.it_value.tv_nsec += US_PER_S * NS_PER_US;
     122                 :            : 
     123                 :          1 :                 atime.it_value.tv_sec -= now.tv_sec;
     124                 :          1 :                 atime.it_value.tv_nsec -= now.tv_nsec;
     125                 :          1 :                 timerfd_settime(rte_intr_fd_get(intr_handle), 0, &atime, NULL);
     126                 :            :         }
     127                 :            :         rte_spinlock_unlock(&alarm_list_lk);
     128                 :          1 : }
     129                 :            : 
     130                 :            : int
     131                 :          6 : rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
     132                 :            : {
     133                 :            :         struct timespec now;
     134                 :            :         int ret = 0;
     135                 :            :         struct alarm_entry *ap, *new_alarm;
     136                 :            : 
     137                 :            :         /* Check parameters, including that us won't cause a uint64_t overflow */
     138   [ +  +  +  + ]:          6 :         if (us < 1 || us > (UINT64_MAX - US_PER_S) || cb_fn == NULL)
     139                 :            :                 return -EINVAL;
     140                 :            : 
     141                 :          3 :         new_alarm = calloc(1, sizeof(*new_alarm));
     142         [ +  - ]:          3 :         if (new_alarm == NULL)
     143                 :            :                 return -ENOMEM;
     144                 :            : 
     145                 :            :         /* use current time to calculate absolute time of alarm */
     146                 :          3 :         clock_gettime(CLOCK_TYPE_ID, &now);
     147                 :            : 
     148                 :          3 :         new_alarm->cb_fn = cb_fn;
     149                 :          3 :         new_alarm->cb_arg = cb_arg;
     150                 :          3 :         new_alarm->time.tv_usec = ((now.tv_nsec / NS_PER_US) + us) % US_PER_S;
     151                 :          3 :         new_alarm->time.tv_sec = now.tv_sec + (((now.tv_nsec / NS_PER_US) + us) / US_PER_S);
     152                 :            : 
     153                 :            :         rte_spinlock_lock(&alarm_list_lk);
     154         [ +  + ]:          3 :         if (!handler_registered) {
     155                 :            :                 /* registration can fail, callback can be registered later */
     156         [ +  - ]:          2 :                 if (rte_intr_callback_register(intr_handle,
     157                 :            :                                 eal_alarm_callback, NULL) == 0)
     158                 :          2 :                         handler_registered = 1;
     159                 :            :         }
     160                 :            : 
     161         [ +  + ]:          3 :         if (LIST_EMPTY(&alarm_list))
     162                 :          2 :                 LIST_INSERT_HEAD(&alarm_list, new_alarm, next);
     163                 :            :         else {
     164                 :            :                 LIST_FOREACH(ap, &alarm_list, next) {
     165   [ -  +  +  - ]:          1 :                         if (ap->time.tv_sec > new_alarm->time.tv_sec ||
     166                 :          0 :                                         (ap->time.tv_sec == new_alarm->time.tv_sec &&
     167         [ #  # ]:          0 :                                                         ap->time.tv_usec > new_alarm->time.tv_usec)){
     168                 :          0 :                                 LIST_INSERT_BEFORE(ap, new_alarm, next);
     169                 :          0 :                                 break;
     170                 :            :                         }
     171         [ +  - ]:          1 :                         if (LIST_NEXT(ap, next) == NULL) {
     172                 :          1 :                                 LIST_INSERT_AFTER(ap, new_alarm, next);
     173                 :          1 :                                 break;
     174                 :            :                         }
     175                 :            :                 }
     176                 :            :         }
     177                 :            : 
     178         [ +  + ]:          3 :         if (LIST_FIRST(&alarm_list) == new_alarm) {
     179                 :          2 :                 struct itimerspec alarm_time = {
     180                 :            :                         .it_interval = {0, 0},
     181                 :            :                         .it_value = {
     182                 :          2 :                                 .tv_sec = us / US_PER_S,
     183                 :          2 :                                 .tv_nsec = (us % US_PER_S) * NS_PER_US,
     184                 :            :                         },
     185                 :            :                 };
     186                 :          2 :                 ret |= timerfd_settime(rte_intr_fd_get(intr_handle), 0, &alarm_time, NULL);
     187                 :            :         }
     188                 :            :         rte_spinlock_unlock(&alarm_list_lk);
     189                 :            : 
     190                 :          3 :         rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
     191                 :          3 :         return ret;
     192                 :            : }
     193                 :            : 
     194                 :            : int
     195                 :          4 : rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
     196                 :            : {
     197                 :            :         struct alarm_entry *ap, *ap_prev;
     198                 :            :         int count = 0;
     199                 :            :         int err = 0;
     200                 :            :         int executing;
     201                 :            : 
     202         [ +  + ]:          4 :         if (!cb_fn) {
     203                 :          1 :                 rte_errno = EINVAL;
     204                 :          1 :                 return -1;
     205                 :            :         }
     206                 :            : 
     207                 :            :         do {
     208                 :            :                 executing = 0;
     209                 :            :                 rte_spinlock_lock(&alarm_list_lk);
     210                 :            :                 /* remove any matches at the start of the list */
     211                 :          8 :                 while ((ap = LIST_FIRST(&alarm_list)) != NULL &&
     212   [ +  +  +  -  :          5 :                                 cb_fn == ap->cb_fn &&
                   -  + ]
     213         [ +  + ]:          3 :                                 (cb_arg == (void *)-1 || cb_arg == ap->cb_arg)) {
     214                 :            : 
     215         [ +  - ]:          2 :                         if (ap->executing == 0) {
     216         [ -  + ]:          2 :                                 LIST_REMOVE(ap, next);
     217                 :          2 :                                 free(ap);
     218                 :          2 :                                 count++;
     219                 :            :                         } else {
     220                 :            :                                 /* If calling from other context, mark that alarm is executing
     221                 :            :                                  * so loop can spin till it finish. Otherwise we are trying to
     222                 :            :                                  * cancel our self - mark it by EINPROGRESS */
     223         [ #  # ]:          0 :                                 if (pthread_equal(ap->executing_id, pthread_self()) == 0)
     224                 :            :                                         executing++;
     225                 :            :                                 else
     226                 :            :                                         err = EINPROGRESS;
     227                 :            : 
     228                 :            :                                 break;
     229                 :            :                         }
     230                 :            :                 }
     231                 :            :                 ap_prev = ap;
     232                 :            : 
     233                 :            :                 /* now go through list, removing entries not at start */
     234         [ +  + ]:          4 :                 LIST_FOREACH(ap, &alarm_list, next) {
     235                 :            :                         /* this won't be true first time through */
     236   [ +  -  +  - ]:          1 :                         if (cb_fn == ap->cb_fn &&
     237         [ -  + ]:          1 :                                         (cb_arg == (void *)-1 || cb_arg == ap->cb_arg)) {
     238                 :            : 
     239         [ #  # ]:          0 :                                 if (ap->executing == 0) {
     240         [ #  # ]:          0 :                                         LIST_REMOVE(ap, next);
     241                 :          0 :                                         free(ap);
     242                 :          0 :                                         count++;
     243                 :            :                                         ap = ap_prev;
     244         [ #  # ]:          0 :                                 } else if (pthread_equal(ap->executing_id, pthread_self()) == 0)
     245                 :          0 :                                         executing++;
     246                 :            :                                 else
     247                 :            :                                         err = EINPROGRESS;
     248                 :            :                         }
     249                 :            :                         ap_prev = ap;
     250                 :            :                 }
     251                 :            : 
     252                 :            :                 rte_spinlock_unlock(&alarm_list_lk);
     253                 :            : 
     254                 :            :                 /* Yield control to a second thread executing eal_alarm_callback to avoid
     255                 :            :                  * its starvation, as it is waiting for the lock we have just released.
     256                 :            :                  */
     257                 :          3 :                 sched_yield();
     258         [ -  + ]:          3 :         } while (executing != 0);
     259                 :            : 
     260         [ +  + ]:          3 :         if (count == 0 && err == 0)
     261                 :          1 :                 rte_errno = ENOENT;
     262         [ -  + ]:          2 :         else if (err)
     263                 :          0 :                 rte_errno = err;
     264                 :            : 
     265                 :          3 :         rte_eal_trace_alarm_cancel(cb_fn, cb_arg, count);
     266                 :          3 :         return count;
     267                 :            : }

Generated by: LCOV version 1.14