/* * Copyright (c) 2016 Cisco and/or its affiliates. * Licensed 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. */ /* * Copyright (c) 2016 Intel Corporation. * Licensed 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. */ #include #include #include #include #include #define TW_SLOTS_PER_RING 512 #define TW_RING_SHIFT 9 #define TW_RING_MASK (TW_SLOTS_PER_RING - 1) #define MAX_TIMER_BURST 0x20 enum { TW_RING_FAST, TW_RING_SLOW, TW_N_RINGS, }; struct tle_timer_list; struct tle_timer_elmt { void *obj; /** object for which timer is created */ struct tle_timer_list *list; /* current list object belongs to */ /** Slow ring only, saved when timer added to ring */ uint16_t fast_index; LIST_ENTRY(tle_timer_elmt) link; }; struct tle_timer_list { uint32_t num; LIST_HEAD(, tle_timer_elmt) head; }; struct tle_timer_wheel { uint64_t next_run_time; /** Next time the wheel should run */ uint64_t last_run_time; /** Last time the wheel ran */ uint32_t current_tick; /** current tick */ uint32_t current_index[TW_N_RINGS]; /** current wheel indices */ struct tle_timer_list free; /** free timers to be used */ struct tle_timer_list expired; /** expired timers to be pulled */ struct tle_timer_wheel_args prm; /** timer wheel configuration params */ /** wheel arrays */ struct tle_timer_list w[TW_N_RINGS][TW_SLOTS_PER_RING]; }; /** helper functions to manipulate the linked lists */ static inline uint32_t get_timers(struct tle_timer_list *list, struct tle_timer_elmt *re[], uint32_t num) { struct tle_timer_elmt *e; uint32_t i, n; n = RTE_MIN(list->num, num); for (i = 0; i != n; i++) { e = LIST_FIRST(&list->head); LIST_REMOVE(e, link); e->list = NULL; re[i] = e; } list->num -= n; return n; } static inline struct tle_timer_elmt * get_timer(struct tle_timer_list *list) { struct tle_timer_elmt *e; e = LIST_FIRST(&list->head); LIST_REMOVE(e, link); e->list = NULL; list->num--; return e; } static inline void put_timers(struct tle_timer_list *list, struct tle_timer_elmt *te[], uint32_t num) { uint32_t i; for (i = 0; i != num; i++) { te[i]->list = list; LIST_INSERT_HEAD(&list->head, te[i], link); } list->num += num; } static inline void put_timer(struct tle_timer_list *list, struct tle_timer_elmt *e) { e->list = list; LIST_INSERT_HEAD(&list->head, e, link); list->num++; } static inline void rem_timer(struct tle_timer_list *list, struct tle_timer_elmt *e) { LIST_REMOVE(e, link); e->list = NULL; list->num--; } /** create the tle timer wheel */ struct tle_timer_wheel * tle_timer_create(struct tle_timer_wheel_args *prm, uint64_t now) { uint32_t i, j; size_t sz; struct tle_timer_wheel *tw; struct tle_timer_elmt *e; struct tle_timer_elmt *timers; if (prm == NULL) { rte_errno = -EINVAL; return NULL; } /* at least one timer has to be created */ if (prm->max_timer == 0) { rte_errno = -EINVAL; return NULL; } /* do not allow tick size smaller than 1ms */ if (prm->tick_size == 0) { rte_errno = -EINVAL; return NULL; } sz = sizeof(*tw) + prm->max_timer * sizeof(struct tle_timer_elmt); /* allocate memory */ tw = rte_zmalloc_socket(NULL, sz, RTE_CACHE_LINE_SIZE, prm->socket_id); if (tw == NULL) { rte_errno = -ENOMEM; return NULL; } tw->last_run_time = now; tw->prm = *prm; timers = (struct tle_timer_elmt *)(tw + 1); /* initialize the lists */ LIST_INIT(&tw->free.head); LIST_INIT(&tw->expired.head); for (i = 0; i < prm->max_timer; i++) { e = timers + i; put_timer(&tw->free, e); } for (i = 0; i < TW_N_RINGS; i++) for (j = 0; j < TW_SLOTS_PER_RING; j++) LIST_INIT(&tw->w[i][j].head); return tw; } /** free the tle timer wheel */ void tle_timer_free(struct tle_timer_wheel *tw) { rte_free(tw); } /** start a timer */ void * tle_timer_start(struct tle_timer_wheel *tw, void *obj, uint64_t interval) { uint16_t slow_ring_index, fast_ring_index; struct tle_timer_list *ts; struct tle_timer_elmt *e; uint32_t carry; uint32_t nb_tick; rte_errno = 0; if (!interval) { rte_errno = EINVAL; return NULL; } if (tw->free.num == 0) { rte_errno = ENOMEM; return NULL; } nb_tick = interval / tw->prm.tick_size; fast_ring_index = nb_tick & TW_RING_MASK; fast_ring_index += tw->current_index[TW_RING_FAST]; carry = fast_ring_index >= TW_SLOTS_PER_RING ? 1 : 0; fast_ring_index %= TW_SLOTS_PER_RING; slow_ring_index = (nb_tick >> TW_RING_SHIFT) + carry; /* Timer duration exceeds ~7 hrs? Oops */ if (slow_ring_index >= TW_SLOTS_PER_RING) { rte_errno = ERANGE; return NULL; } /* Timer expires more than 51.2 seconds from now? */ if (slow_ring_index) { slow_ring_index += tw->current_index[TW_RING_SLOW]; slow_ring_index %= TW_SLOTS_PER_RING; ts = &tw->w[TW_RING_SLOW][slow_ring_index]; e = get_timer(&tw->free); e->obj = obj; e->fast_index = fast_ring_index; put_timer(ts, e); /* Return the user timer-cancellation handle */ return (void *)e; } /* Timer expires less than 51.2 seconds from now */ ts = &tw->w[TW_RING_FAST][fast_ring_index]; e = get_timer(&tw->free); e->obj = obj; put_timer(ts, e); /* Give the user a handle to cancel the timer */ return (void *)e; } /** stop a timer */ void tle_timer_stop(struct tle_timer_wheel *tw, void *timer) { struct tle_timer_elmt *e; struct tle_timer_list *ts; /* Cancel the timer */ e = (struct tle_timer_elmt *)timer; ts = e->list; rem_timer(ts, e); put_timer(&tw->free, e); } /** run the timer wheel. Call in every tick_size cycles * (e.g. equivalent of 100ms). */ void tle_timer_expire(struct tle_timer_wheel *tw, uint64_t now) { uint32_t nb_tick, i, n; uint32_t fast_wheel_index, slow_wheel_index, demoted_index; struct tle_timer_list *ts, *ts2; struct tle_timer_elmt *re[MAX_TIMER_BURST], *e; /* Shouldn't happen */ if (unlikely(now < tw->next_run_time)) return; /* Number of tick_size cycles which have occurred */ nb_tick = (now - tw->last_run_time) / tw->prm.tick_size; if (nb_tick == 0) return; /* Remember when we ran, compute next runtime */ tw->next_run_time = (now + tw->prm.tick_size); tw->last_run_time = now; for (i = 0; i < nb_tick; i++) { fast_wheel_index = tw->current_index[TW_RING_FAST]; /* If we've been around the fast ring once, * process one slot in the slow ring before we handle * the fast ring. */ if (unlikely(fast_wheel_index == TW_SLOTS_PER_RING)) { fast_wheel_index = tw->current_index[TW_RING_FAST] = 0; tw->current_index[TW_RING_SLOW]++; tw->current_index[TW_RING_SLOW] %= TW_SLOTS_PER_RING; slow_wheel_index = tw->current_index[TW_RING_SLOW]; ts = &tw->w[TW_RING_SLOW][slow_wheel_index]; /* Deal slow-ring elements into the fast ring. */ while (ts->num != 0) { e = get_timer(ts); demoted_index = e->fast_index; ts2 = &tw->w[TW_RING_FAST][demoted_index]; put_timer(ts2, e); }; LIST_INIT(&ts->head); } /* Handle the fast ring */ ts = &tw->w[TW_RING_FAST][fast_wheel_index]; /* Clear the fast-ring slot and move timers in expired list*/ n = get_timers(ts, re, RTE_DIM(re)); while (n != 0) { put_timers(&tw->expired, re, n); n = get_timers(ts, re, RTE_DIM(re)); }; LIST_INIT(&ts->head); tw->current_index[TW_RING_FAST]++; tw->current_tick++; } } /** bulk retrieve of expired timers */ int tle_timer_get_expired_bulk(struct tle_timer_wheel *tw, void *rt[], uint32_t num) { uint32_t i, n; struct tle_timer_elmt *e[MAX_TIMER_BURST]; n = get_timers(&tw->expired, e, num); for (i = 0; i != n; i++) rt[i] = e[i]->obj; put_timers(&tw->free, e, n); return n; }