1 /*
2 (C) Copyright IBM Corp. 2008
3 
4 All rights reserved.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
8 
9 * Redistributions of source code must retain the above copyright notice,
10 this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14 * Neither the name of IBM nor the names of its contributors may be
15 used to endorse or promote products derived from this software without
16 specific prior written permission.
17 
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 POSSIBILITY OF SUCH DAMAGE.
29 */
30 
31 /* Second Level Interrupt handler and related services for SPU timers.  */
32 #include <picolibc.h>
33 
34 #include "spu_timer_internal.h"
35 /* Resets decrementer to the specified value. Also updates software timebase
36    to account for the time between the last decrementer reset and now. There
37    are two cases:
38     * Called by application to start a new timer.
39     * Called by spu_clock to active the next timer.
40    In both cases, the amount of time is the current interval timeout minus the
41    current decrementer value.  */
42 void
__reset_spu_decr(int val)43 __reset_spu_decr (int val)
44 {
45 
46   /* The interrupt occurs when the msb goes from 0 to 1 or when the decrementer
47      goes from 0 to -1.  To be precisely accurate we should set the timer to
48      the intverval -1, unless the interval passed in is 0 in which case it
49      should be left at 0.  */
50   int enable_val = (__likely (val)) ? val - 1 : 0;
51 
52   /* Decrementer must be stopped before writing it - minimize the time
53      stopped.  */
54   unsigned mask = __disable_spu_decr ();
55 
56   /* Perform tb correction before resettting the decrementer. the corrected
57      value is the current timeout value minus the current decrementer value.
58      Occasionally the read returns 0 - a second read will clear this
59      condition.  */
60   spu_readch (SPU_RdDec);
61   int decval = spu_readch (SPU_RdDec);
62   /* Restart decrementer with next timeout val.  */
63   __enable_spu_decr (enable_val, mask);
64 
65   /* Update the timebase values before enabling for interrupts.  */
66   __spu_tb_val += __spu_tb_timeout - decval;
67   __spu_tb_timeout = enable_val;
68 }
69 
70 /* Update software timebase and timeout value for the 'next to expire' timer.
71    Called when starting a new timer so the timer list will have timeouts
72    relative to the current time.  */
73 static inline void
__update_spu_tb_val(void)74 __update_spu_tb_val (void)
75 {
76   int elapsed = __spu_tb_timeout - spu_readch (SPU_RdDec);
77 #ifdef SPU_TIMER_DEBUG
78   if (elapsed < 0)
79     ABORT ();
80 #endif
81   __spu_tb_val += elapsed;
82 
83   /* Adjust the timeout for the timer next to expire. Note this could cause
84      the timeout to go negative, if it was just about to expire when we called
85      spu_timer_start.  This is OK, since this can happen any time interrupts
86      are disabled. We just schedule an immediate timeout in this case.  */
87   if (__spu_timers_active)
88     {
89       __spu_timers_active->tmout -= elapsed;
90       if (__spu_timers_active->tmout < 0)
91 	__spu_timers_active->tmout = 0;
92     }
93 }
94 
95 /* Add an allocated timer to the active list. The active list is sorted by
96    timeout value. The timer at the head of the list is the timer that will
97    expire next.  The rest of the timers have a timeout value that is relative
98    to the timer ahead of it on the list.  This relative value is determined
99    here, when the timer is added to the active list. When its position in the
100    list is found, the timer's timeout value is set to its interval minus the
101    sum of all the timeout values ahead of it.  The timeout value for the timer
102    following the newly added timer is then adjusted to a new relative value. If
103    the newly added timer is at the head of the list, the decrementer is reset.
104    This function is called by SLIH to restart multiple timers (reset == 0) or
105    by spu_timer_start() to start a single timer (reset == 1).  */
106 void
__spu_timer_start(int id,int reset)107 __spu_timer_start (int id, int reset)
108 {
109   spu_timer_t *t;
110   spu_timer_t **pn;
111   spu_timer_t *start = &__spu_timers[id];
112   unsigned tmout_time = 0;
113   unsigned my_intvl = start->intvl;
114   unsigned was_enabled = spu_readch (SPU_RdMachStat) & 0x1;
115 
116   spu_idisable ();
117 
118   t = __spu_timers_active;
119   pn = &__spu_timers_active;
120 
121   /* If the active list is empty, just add the timer with the timeout set to
122      the interval. Otherwise find the place in the list for the timer, setting
123      its timeout to its interval minus the sum of timeouts ahead of it.  */
124   start->state = SPU_TIMER_ACTIVE;
125   if (__likely (!t))
126     {
127       __spu_timers_active = start;
128       start->next = NULL;
129       start->tmout = my_intvl;
130     }
131   else
132     {
133 
134       /* Update swtb and timeout val of the next timer, so all times are
135          relative to now.  */
136       if (reset)
137 	__update_spu_tb_val ();
138 
139       while (t && (my_intvl >= (tmout_time + t->tmout)))
140 	{
141 	  tmout_time += t->tmout;
142 	  pn = &t->next;;
143 	  t = t->next;
144 	}
145       start->next = t;
146       start->tmout = my_intvl - tmout_time;
147       *pn = start;
148 
149       /* Adjust timeout for timer after us.  */
150       if (t)
151 	t->tmout -= start->tmout;
152     }
153 
154   if (reset && (__spu_timers_active == start))
155     __reset_spu_decr (__spu_timers_active->tmout);
156 
157   if (__unlikely (was_enabled))
158     spu_ienable ();
159 }
160 
161 /* SLIH for decrementer.  Manages software timebase and timers.
162    Called by SPU FLIH. Assumes decrementer is still running
163    (event not yet acknowledeged).  */
164 unsigned int
spu_clock_slih(unsigned status)165 spu_clock_slih (unsigned status)
166 {
167   int decr_reset_val;
168   spu_timer_t *active, *handled;
169   unsigned was_enabled = spu_readch (SPU_RdMachStat) & 0x1;
170 
171   status &= ~MFC_DECREMENTER_EVENT;
172 
173   spu_idisable ();
174 
175   /* The decrementer has now expired.  The decrementer event was acknowledged
176      in the FLIH but not disabled. The decrementer will continue to run while
177      we're running the clock/timer handler. The software clock keeps running,
178      and accounts for all the time spent running handlers. Add the current
179      timeout to the software timebase and set the timeout to DECR_MAX. This
180      allows the "clock read" code to continue to work while we're in here, and
181      gives us the most possible time to finish before another underflow.  */
182   __spu_tb_val += __spu_tb_timeout;
183   __spu_tb_timeout = DECR_MAX;
184 
185   /* For all timers that have the current timeout value, move them from the
186      active list to the handled list and call their handlers. Note that the
187      handled/stopped lists may be manipulated by the handlers if they wish to
188      stop/free the timers. Note that only the first expired timer will reflect
189      the real timeout value; the rest of the timers that had the same timeout
190      value will have a relative value of zero.  */
191   if (__spu_timers_active)
192     {
193       __spu_timers_active->tmout = 0;
194       while ((active = __spu_timers_active)
195 	     && (active->tmout <= TIMER_INTERVAL_WINDOW))
196 	{
197 	  __spu_timers_active = active->next;
198 	  active->next = __spu_timers_handled;
199 	  __spu_timers_handled = active;
200 	  active->state = SPU_TIMER_HANDLED;
201 	  (*active->func) (active->id);
202 	}
203     }
204 
205   /* put the handled timers back on the list and restart decrementer.  */
206   while ((handled = __spu_timers_handled) != NULL)
207     {
208       __spu_timers_handled = handled->next;
209       __spu_timer_start (handled->id, 0);
210     }
211 
212   /* Reset the decrementer before returning. If we have any active timers, we
213      set it to the timeout value for the timer at the head of the list, else
214      the default clock value.  */
215   decr_reset_val = __spu_timers_active ? __spu_timers_active->tmout : CLOCK_START_VALUE;
216 
217   __reset_spu_decr (decr_reset_val);
218 
219   if (__likely (was_enabled))
220     spu_ienable ();
221 
222   return status;
223 }
224