[e16e8f2] | 1 | /* |
---|
| 2 | * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. |
---|
| 3 | * |
---|
| 4 | * This program is free software; you can redistribute it and/or |
---|
| 5 | * modify it under the terms of the GNU General Public License as |
---|
| 6 | * published by the Free Software Foundation; either version 2 of the |
---|
| 7 | * License, or any later version. |
---|
| 8 | * |
---|
| 9 | * This program is distributed in the hope that it will be useful, but |
---|
| 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
| 12 | * General Public License for more details. |
---|
| 13 | * |
---|
| 14 | * You should have received a copy of the GNU General Public License |
---|
| 15 | * along with this program; if not, write to the Free Software |
---|
| 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
---|
| 17 | */ |
---|
| 18 | |
---|
| 19 | FILE_LICENCE ( GPL2_OR_LATER ); |
---|
| 20 | |
---|
| 21 | #include <stddef.h> |
---|
| 22 | #include <gpxe/timer.h> |
---|
| 23 | #include <gpxe/list.h> |
---|
| 24 | #include <gpxe/process.h> |
---|
| 25 | #include <gpxe/init.h> |
---|
| 26 | #include <gpxe/retry.h> |
---|
| 27 | |
---|
| 28 | /** @file |
---|
| 29 | * |
---|
| 30 | * Retry timers |
---|
| 31 | * |
---|
| 32 | * A retry timer is a binary exponential backoff timer. It can be |
---|
| 33 | * used to build automatic retransmission into network protocols. |
---|
| 34 | * |
---|
| 35 | * This implementation of the timer is designed to satisfy RFC 2988 |
---|
| 36 | * and therefore be usable as a TCP retransmission timer. |
---|
| 37 | * |
---|
| 38 | * |
---|
| 39 | */ |
---|
| 40 | |
---|
| 41 | /* The theoretical minimum that the algorithm in stop_timer() can |
---|
| 42 | * adjust the timeout back down to is seven ticks, so set the minimum |
---|
| 43 | * timeout to at least that value for the sake of consistency. |
---|
| 44 | */ |
---|
| 45 | #define MIN_TIMEOUT 7 |
---|
| 46 | |
---|
| 47 | /** List of running timers */ |
---|
| 48 | static LIST_HEAD ( timers ); |
---|
| 49 | |
---|
| 50 | /** |
---|
| 51 | * Start timer |
---|
| 52 | * |
---|
| 53 | * @v timer Retry timer |
---|
| 54 | * |
---|
| 55 | * This starts the timer running with the current timeout value. If |
---|
| 56 | * stop_timer() is not called before the timer expires, the timer will |
---|
| 57 | * be stopped and the timer's callback function will be called. |
---|
| 58 | */ |
---|
| 59 | void start_timer ( struct retry_timer *timer ) { |
---|
| 60 | if ( ! timer->running ) |
---|
| 61 | list_add ( &timer->list, &timers ); |
---|
| 62 | timer->start = currticks(); |
---|
| 63 | timer->running = 1; |
---|
| 64 | |
---|
| 65 | /* 0 means "use default timeout" */ |
---|
| 66 | if ( timer->min_timeout == 0 ) |
---|
| 67 | timer->min_timeout = DEFAULT_MIN_TIMEOUT; |
---|
| 68 | /* We must never be less than MIN_TIMEOUT under any circumstances */ |
---|
| 69 | if ( timer->min_timeout < MIN_TIMEOUT ) |
---|
| 70 | timer->min_timeout = MIN_TIMEOUT; |
---|
| 71 | /* Honor user-specified minimum timeout */ |
---|
| 72 | if ( timer->timeout < timer->min_timeout ) |
---|
| 73 | timer->timeout = timer->min_timeout; |
---|
| 74 | |
---|
| 75 | DBG2 ( "Timer %p started at time %ld (expires at %ld)\n", |
---|
| 76 | timer, timer->start, ( timer->start + timer->timeout ) ); |
---|
| 77 | } |
---|
| 78 | |
---|
| 79 | /** |
---|
| 80 | * Start timer with a specified fixed timeout |
---|
| 81 | * |
---|
| 82 | * @v timer Retry timer |
---|
| 83 | * @v timeout Timeout, in ticks |
---|
| 84 | */ |
---|
| 85 | void start_timer_fixed ( struct retry_timer *timer, unsigned long timeout ) { |
---|
| 86 | start_timer ( timer ); |
---|
| 87 | timer->timeout = timeout; |
---|
| 88 | DBG2 ( "Timer %p expiry time changed to %ld\n", |
---|
| 89 | timer, ( timer->start + timer->timeout ) ); |
---|
| 90 | } |
---|
| 91 | |
---|
| 92 | /** |
---|
| 93 | * Stop timer |
---|
| 94 | * |
---|
| 95 | * @v timer Retry timer |
---|
| 96 | * |
---|
| 97 | * This stops the timer and updates the timer's timeout value. |
---|
| 98 | */ |
---|
| 99 | void stop_timer ( struct retry_timer *timer ) { |
---|
| 100 | unsigned long old_timeout = timer->timeout; |
---|
| 101 | unsigned long now = currticks(); |
---|
| 102 | unsigned long runtime; |
---|
| 103 | |
---|
| 104 | /* If timer was already stopped, do nothing */ |
---|
| 105 | if ( ! timer->running ) |
---|
| 106 | return; |
---|
| 107 | |
---|
| 108 | list_del ( &timer->list ); |
---|
| 109 | runtime = ( now - timer->start ); |
---|
| 110 | timer->running = 0; |
---|
| 111 | DBG2 ( "Timer %p stopped at time %ld (ran for %ld)\n", |
---|
| 112 | timer, now, runtime ); |
---|
| 113 | |
---|
| 114 | /* Update timer. Variables are: |
---|
| 115 | * |
---|
| 116 | * r = round-trip time estimate (i.e. runtime) |
---|
| 117 | * t = timeout value (i.e. timer->timeout) |
---|
| 118 | * s = smoothed round-trip time |
---|
| 119 | * |
---|
| 120 | * By choice, we set t = 4s, i.e. allow for four times the |
---|
| 121 | * normal round-trip time to pass before retransmitting. |
---|
| 122 | * |
---|
| 123 | * We want to smooth according to s := ( 7 s + r ) / 8 |
---|
| 124 | * |
---|
| 125 | * Since we don't actually store s, this reduces to |
---|
| 126 | * t := ( 7 t / 8 ) + ( r / 2 ) |
---|
| 127 | * |
---|
| 128 | */ |
---|
| 129 | if ( timer->count ) { |
---|
| 130 | timer->count--; |
---|
| 131 | } else { |
---|
| 132 | timer->timeout -= ( timer->timeout >> 3 ); |
---|
| 133 | timer->timeout += ( runtime >> 1 ); |
---|
| 134 | if ( timer->timeout != old_timeout ) { |
---|
| 135 | DBG ( "Timer %p timeout updated to %ld\n", |
---|
| 136 | timer, timer->timeout ); |
---|
| 137 | } |
---|
| 138 | } |
---|
| 139 | } |
---|
| 140 | |
---|
| 141 | /** |
---|
| 142 | * Handle expired timer |
---|
| 143 | * |
---|
| 144 | * @v timer Retry timer |
---|
| 145 | */ |
---|
| 146 | static void timer_expired ( struct retry_timer *timer ) { |
---|
| 147 | int fail; |
---|
| 148 | |
---|
| 149 | /* Stop timer without performing RTT calculations */ |
---|
| 150 | DBG2 ( "Timer %p stopped at time %ld on expiry\n", |
---|
| 151 | timer, currticks() ); |
---|
| 152 | assert ( timer->running ); |
---|
| 153 | list_del ( &timer->list ); |
---|
| 154 | timer->running = 0; |
---|
| 155 | timer->count++; |
---|
| 156 | |
---|
| 157 | /* Back off the timeout value */ |
---|
| 158 | timer->timeout <<= 1; |
---|
| 159 | if ( timer->max_timeout == 0 ) /* 0 means "use default timeout" */ |
---|
| 160 | timer->max_timeout = DEFAULT_MAX_TIMEOUT; |
---|
| 161 | if ( ( fail = ( timer->timeout > timer->max_timeout ) ) ) |
---|
| 162 | timer->timeout = timer->max_timeout; |
---|
| 163 | DBG ( "Timer %p timeout backed off to %ld\n", |
---|
| 164 | timer, timer->timeout ); |
---|
| 165 | |
---|
| 166 | /* Call expiry callback */ |
---|
| 167 | timer->expired ( timer, fail ); |
---|
| 168 | } |
---|
| 169 | |
---|
| 170 | /** |
---|
| 171 | * Single-step the retry timer list |
---|
| 172 | * |
---|
| 173 | * @v process Retry timer process |
---|
| 174 | */ |
---|
| 175 | static void retry_step ( struct process *process __unused ) { |
---|
| 176 | struct retry_timer *timer; |
---|
| 177 | struct retry_timer *tmp; |
---|
| 178 | unsigned long now = currticks(); |
---|
| 179 | unsigned long used; |
---|
| 180 | |
---|
| 181 | list_for_each_entry_safe ( timer, tmp, &timers, list ) { |
---|
| 182 | used = ( now - timer->start ); |
---|
| 183 | if ( used >= timer->timeout ) |
---|
| 184 | timer_expired ( timer ); |
---|
| 185 | } |
---|
| 186 | } |
---|
| 187 | |
---|
| 188 | /** Retry timer process */ |
---|
| 189 | struct process retry_process __permanent_process = { |
---|
| 190 | .list = LIST_HEAD_INIT ( retry_process.list ), |
---|
| 191 | .step = retry_step, |
---|
| 192 | }; |
---|