]> git.kernelconcepts.de Git - karo-tx-redboot.git/blob - packages/devs/watchdog/synth/v2_0/host/watchdog.tcl
Initial revision
[karo-tx-redboot.git] / packages / devs / watchdog / synth / v2_0 / host / watchdog.tcl
1 # {{{  Banner                                                   
2
3 # ============================================================================
4
5 #      watchdog.tcl
6
7 #      Watchdog support for the eCos synthetic target I/O auxiliary
8
9 # ============================================================================
10 # ####COPYRIGHTBEGIN####
11 #                                                                           
12 #  ----------------------------------------------------------------------------
13 #  Copyright (C) 2002 Bart Veer
14
15 #  This file is part of the eCos host tools.
16
17 #  This program is free software; you can redistribute it and/or modify it 
18 #  under the terms of the GNU General Public License as published by the Free 
19 #  Software Foundation; either version 2 of the License, or (at your option) 
20 #  any later version.
21 #  
22 #  This program is distributed in the hope that it will be useful, but WITHOUT 
23 #  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
24 #  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
25 #  more details.
26 #  
27 #  You should have received a copy of the GNU General Public License along with
28 #  this program; if not, write to the Free Software Foundation, Inc., 
29 #  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
30
31 #  ----------------------------------------------------------------------------
32 #                                                                           
33 # ####COPYRIGHTEND####
34 # ============================================================================
35 # #####DESCRIPTIONBEGIN####
36
37 #  Author(s):   bartv
38 #  Contact(s):  bartv
39 #  Date:        2002/09/04
40 #  Version:     0.01
41 #  Description:
42 #      Implementation of the watchdog device. This script should only ever
43 #      be run from inside the ecosynth auxiliary.
44
45 # ####DESCRIPTIONEND####
46 # ============================================================================
47
48 # }}}
49
50 namespace eval watchdog {
51
52     # Was initialization successful?
53     variable init_ok 1
54
55     # Has the alarm triggered?
56     variable alarm_triggered 0
57     
58     # The eCos application process id. This is needed to send a SIGPWR signal
59     # if the watchdog triggers, and to access /proc/<pid>/stat to obtain
60     # timing information. Strictly speaking _ppid is not exported by
61     # the I/O auxiliary.
62     if { ! [info exists synth::_ppid] } {
63         synth::report_error "Watchdog initialization failed, _ppid variable required"
64         return ""
65     }
66     variable ecos_pid $synth::_ppid
67
68     # Resolution, i.e. how long to go between checks. Currently this is hard-wired
69     # to one second, or 1000 ms. This may become configurable, either on the
70     # target-side via CDL or on the host-side via the target definition file.
71     # Note that currently the watchdog device and the GUI get updated via the
72     # same timer. If the resolution is changed to e.g. 10 seconds then it might
73     # be a good idea to update the GUI more frequently, although there are
74     # arguments for keeping the animation in step with the real work.
75     variable resolution 1000
76     
77     # Options from the target definition file
78     variable use_wallclock 0
79     variable window_pack   "-in .main.n -side right"
80     variable sound_file    ""
81     variable sound_player  "play"
82
83     if { [synth::tdf_has_device "watchdog"] } {
84         if { [synth::tdf_has_option "watchdog" "use"] } {
85             set _use [synth::tdf_get_option "watchdog" "use"]
86             if { "wallclock_time" == $_use } {
87                 set watchdog::use_wallclock 1
88             } elseif { "consumed_cpu_time" == $_use } {
89                 set watchdog::use_wallclock 0
90             } else {
91                 synth::report_error "Invalid entry in target definition file $synth::target_definition\n\
92                                     \    Device watchdog, option \"use\" should be \"wallclock_time\" or \"consumed_cpu_time\"\n"
93             }
94             unset _use
95         }
96         if { [synth::tdf_has_option "watchdog" "watchdog_pack"] } {
97             set watchdog::window_pack [synth::tdf_get_option "watchdog" "watchdog_pack"]
98             # Too complicated to validate here, instead leave it to a catch statement
99             # when the window actually gets packed
100         }
101         if { [synth::tdf_has_option "watchdog" "sound"] } {
102             set _sound_file [synth::tdf_get_option "watchdog" "sound"]
103             # Look for this sound file in the install tree first, then absolute or relative
104             if { [file exists [file join $synth::device_install_dir $_sound_file] ] } {
105                 set _sound_file [file join $synth::device_install_dir $_sound_file]
106             }
107             if { ![file exists $_sound_file] } {
108                 synth::report_error "Invalid entry in target definition file $synth::target_definition\n\
109                                     \    Device watchdog, option \"sound\", failed to find $_sound_file\n"
110             } elseif { ! [file readable $_sound_file] } {
111                 synth::report_error "Invalid entry in target definition file $synth::target_definition\n\
112                                     \    Device watchdog, option \"sound\", no read access to file $_sound_file\n"
113             } else {
114                 set watchdog::sound_file $_sound_file
115             }
116             unset _sound_file
117         }
118         if { [synth::tdf_has_option "watchdog" "sound_player"] } {
119             set watchdog::sound_player [synth::tdf_get_option "watchdog" "sound_player"]
120         }
121     }
122
123     # There is no point in creating the watchdog window if any of the image files are missing
124     if { $synth::flag_gui } {
125         foreach _image [list "doghouse.gif" "alarm.gif" "eye.gif" "asleep.gif"] {
126             variable image_[file rootname $_image]
127             if { ! [synth::load_image "watchdog::image_[file rootname $_image]" [file join $synth::device_install_dir $_image]] } {
128                 synth::report_error "Watchdog device, unable to load image $_image\n\
129                                     \    This file should have been installed in $synth::device_install_dir\n"
130                 set watchdog::init_ok 0
131             }
132         }
133     }
134     if { $synth::flag_gui && $watchdog::init_ok } {
135         canvas .watchdog -width [image width $image_doghouse] -height [image height $image_doghouse] \
136             -borderwidth 0
137         variable background [.watchdog create image 0 0 -anchor nw -image $image_doghouse]
138     
139         # Eye positions inside the doghouse. The eye is an 8x10 gif,
140         # mostly white but transparent around the corners
141         variable left_eye_x         48
142         variable left_eye_y         70
143         variable right_eye_x        58
144         variable right_eye_y        70
145         
146         # Pupil positions relative to the eye. The pupils are 3x3 rectangles.
147         # The dog can look in one of nine different directions, with both eyes
148         # looking in the same direction (if visible)
149         variable pupil_positions { { 1 6 } { 1 5 } { 1 3 } { 3 1 } { 3 4 } { 3 6 } { 4 3 } { 4 5 } { 4 6 } }
150     
151         # Which eyes are currently visible: none, left, right or both
152         variable eyes "none"
153         # What is the current pupil position?
154         variable pupils 4
155         
156         variable left_eye  [.watchdog create image $left_eye_x $left_eye_y -anchor nw -image $image_eye]
157         variable right_eye [.watchdog create image $right_eye_x $right_eye_y -anchor nw -image $image_eye]
158     
159         variable left_pupil \
160             [.watchdog create rectangle \
161                  [expr $left_eye_x + [lindex [lindex $pupil_positions $pupils] 0]]     \
162                  [expr $left_eye_y + [lindex [lindex $pupil_positions $pupils] 1]]     \
163                  [expr $left_eye_x + [lindex [lindex $pupil_positions $pupils] 0] + 2] \
164                  [expr $left_eye_y + [lindex [lindex $pupil_positions $pupils] 1] + 2] \
165                  -fill black]
166         variable right_pupil \
167             [.watchdog create rectangle \
168                  [expr $right_eye_x + [lindex [lindex $pupil_positions $pupils] 0]]     \
169                  [expr $right_eye_y + [lindex [lindex $pupil_positions $pupils] 1]]     \
170                  [expr $right_eye_x + [lindex [lindex $pupil_positions $pupils] 0] + 2] \
171                  [expr $right_eye_y + [lindex [lindex $pupil_positions $pupils] 1] + 2] \
172                  -fill black]
173
174     
175         # The dog is asleep until the eCos application activates the watchdog device
176         .watchdog lower $left_eye $background
177         .watchdog lower $right_eye $background
178         .watchdog lower $left_pupil $background
179         .watchdog lower $right_pupil $background
180
181         # Prepare for an alarm, but obviously the alarm picture should be hidden for now.
182         variable alarm [.watchdog create image 30 56 -anchor nw -image $image_alarm]
183         .watchdog lower $alarm $background
184
185         # Start asleep
186         variable asleep [.watchdog create image 48 70 -anchor nw -image $image_asleep]
187         
188         # Now try to pack the watchdog window using the option provided by the
189         # user. If that fails, report the error and pack in a default window.
190         if { [catch { eval pack .watchdog $watchdog::window_pack } message] } {
191             synth::report_error "Watchdog device, failed to pack window in $watchdog::window_pack\n    $message"
192             pack .watchdog -in .main.n -side right
193         }
194                    
195         # Updating the display. This happens once a second.
196         # If neither eye is visible, choose randomly between
197         # left-only, right-only or both. Otherwise there is
198         # a one in eight chance of blinking, probably switching
199         # to one of the other eye modes
200         #
201         # Also, the visible pupil(s) will move every second, to one
202         # of nine positions
203         proc gui_update { } {
204         
205             if { "none" == $watchdog::eyes} {
206                 set rand [expr int(3 * rand())]
207                 if { 0 == $rand } {
208                     set watchdog::eyes "left"
209                     .watchdog raise $watchdog::left_eye   $watchdog::background
210                     .watchdog raise $watchdog::left_pupil $watchdog::left_eye
211                 } elseif { 1 == $rand } {
212                     set watchdog::eyes "right"
213                     .watchdog raise $watchdog::right_eye   $watchdog::background
214                     .watchdog raise $watchdog::right_pupil $watchdog::right_eye
215                 } else {
216                     set watchdog::eyes "both"
217                     .watchdog raise $watchdog::left_eye    $watchdog::background
218                     .watchdog raise $watchdog::left_pupil  $watchdog::left_eye
219                     .watchdog raise $watchdog::right_eye   $watchdog::background
220                     .watchdog raise $watchdog::right_pupil $watchdog::right_eye
221                 }
222             } else {
223                 if { 0 == [expr int(8 * rand())] } {
224                     set watchdog::eyes "none"
225                     .watchdog lower $watchdog::left_eye    $watchdog::background
226                     .watchdog lower $watchdog::right_eye   $watchdog::background
227                     .watchdog lower $watchdog::left_pupil  $watchdog::background
228                     .watchdog lower $watchdog::right_pupil $watchdog::background
229
230                     # There is no point in moving the pupils if both eyes are shut
231                     return
232                 }
233             }
234
235             set watchdog::pupils [expr int(9 * rand())]
236             set new_pupil_x [lindex [lindex $watchdog::pupil_positions $watchdog::pupils] 0]
237             set new_pupil_y [lindex [lindex $watchdog::pupil_positions $watchdog::pupils] 1]
238
239             if { ("left" == $watchdog::eyes) || ("both" == $watchdog::eyes) } {
240                 .watchdog coords $watchdog::left_pupil              \
241                     [expr $watchdog::left_eye_x + $new_pupil_x]     \
242                     [expr $watchdog::left_eye_y + $new_pupil_y]     \
243                     [expr $watchdog::left_eye_x + $new_pupil_x + 2] \
244                     [expr $watchdog::left_eye_y + $new_pupil_y + 2]
245             }
246             if { ("right" == $watchdog::eyes) || ("both" == $watchdog::eyes) } {
247                 .watchdog coords $watchdog::right_pupil              \
248                     [expr $watchdog::right_eye_x + $new_pupil_x]     \
249                     [expr $watchdog::right_eye_y + $new_pupil_y]     \
250                     [expr $watchdog::right_eye_x + $new_pupil_x + 2] \
251                     [expr $watchdog::right_eye_y + $new_pupil_y + 2]
252             }
253         }
254
255         # Cancel the gui display when the eCos application has exited.
256         # The watchdog is allowed to go back to sleep. If the application
257         # exited because of the watchdog then of course the alarm picture
258         # should remain visible, otherwise it would be just a flash.
259         proc gui_cancel { } {
260             .watchdog lower $watchdog::left_eye    $watchdog::background
261             .watchdog lower $watchdog::right_eye   $watchdog::background
262             .watchdog lower $watchdog::left_pupil  $watchdog::background
263             .watchdog lower $watchdog::right_pupil $watchdog::background
264             if { ! $watchdog::alarm_triggered } {
265                 .watchdog raise $watchdog::asleep      $watchdog::background
266             }
267         }
268         
269         # Raise the alarm. This involves hiding the eyes and raising
270         # the alarm picture. If sound is enabled, the sound player
271         # should be invoked
272         proc gui_alarm { } {
273             .watchdog lower $watchdog::asleep      $watchdog::background
274             .watchdog lower $watchdog::left_eye    $watchdog::background
275             .watchdog lower $watchdog::right_eye   $watchdog::background
276             .watchdog lower $watchdog::left_pupil  $watchdog::background
277             .watchdog lower $watchdog::right_pupil $watchdog::background
278             .watchdog raise $watchdog::alarm       $watchdog::background
279
280             if { "" != $watchdog::sound_file } {
281                 # Catch errors on the actual exec, e.g. if the sound player is
282                 # invalid, but play the sound in the background. If there are
283                 # problems actually playing the sound then the user should
284                 # still see a message on stderr. Blocking the entire auxiliary
285                 # for a few seconds is not acceptable.
286                 if { [catch { eval exec -- $watchdog::sound_player $watchdog::sound_file & } message] } {
287                     synth::report_warning "Watchdog device, failed to play alarm sound file\n    $message\n"
288                 }
289             }
290         }
291
292         set _watchdog_help [file join $synth::device_src_dir "doc" "devs-watchdog-synth.html"]
293         if { ![file readable $_watchdog_help] } {
294             synth::report_warning "Failed to locate synthetic watchdog documentation $_watchdog_help\n\
295                                   \    Help->Watchdog target menu option disabled.\n"
296             set _watchdog_help ""
297         }
298         if { "" == $_watchdog_help } {
299             .menubar.help add command -label "Watchdog" -state disabled
300         } else {
301             .menubar.help add command -label "Watchdog" -command [list synth::handle_help "file://$_watchdog_help"]
302         }
303     }
304
305     # Now for the real work. By default the watchdog is asleep. The eCos
306     # application can activate it with a start message, which results
307     # in an "after" timer. That runs once a second to check whether or not
308     # the watchdog should trigger, and also updates the GUI.
309     #
310     # The target-side code should perform a watchdog reset at least once
311     # a second, which involves another message to this script and the
312     # setting of the reset_received flag.
313     #
314     # The update handler gets information about the eCos application using
315     # /proc/<pid>/stat (see man 5 proc). The "state" field is important:
316     # a state of T indicates that the application is stopped, probably
317     # inside gdb, so cannot reset the watchdog. The other important field
318     # is utime, the total number of jiffies (0.01 seconds) executed in
319     # user space. The code maintains an open file handle to the /proc file.
320
321     variable reset_received     0
322     variable after_id           ""
323     variable proc_stat          ""
324     variable last_jiffies       0
325     
326     set _filename "/proc/[set watchdog::ecos_pid]/stat"
327     if { [catch { open $_filename "r" } proc_stat ] } {
328         synth::report_error "Watchdog device, failed to open $_filename\n    $proc_stat\n"
329         set watchdog::init_ok 0
330     }
331     unset _filename
332
333     proc update { } {
334         set watchdog::after_id [after $watchdog::resolution watchdog::update]
335         if { $synth::flag_gui } {
336             watchdog::gui_update
337         }
338         seek $watchdog::proc_stat 0 "start"
339         set line [gets $watchdog::proc_stat]
340         scan $line "%*d %*s %s %*d %*d %*d %*d %*d %*lu %*lu %*lu %*lu %*lu %lu" state jiffies
341
342         # In theory it is possible to examine the state field (the third argument).
343         # If set to T then that indicates the eCos application is traced or
344         # stopped, probably inside gdb, and it would make sense to act as if
345         # the application had sent a reset. Unfortunately the state also appears
346         # to be set to T if the application is blocked in a system call while
347         # being debugged - including the idle select(), making the test useless.
348         # FIXME: figure out how to distinguish between being blocked inside gdb
349         # and being in a system call.
350         #if { "T" == $state } {
351         #    set watchdog::reset_received 1
352         #    return
353         #}
354         
355         # If there has been a recent reset the eCos application can continue to run for a bit longer.
356         if { $watchdog::reset_received } {
357             set watchdog::last_jiffies $jiffies
358             set watchdog::reset_received 0
359             return
360         }
361
362         # We have not received a reset. If the watchdog is using wallclock time then
363         # that is serious. If the watchdog is using elapsed cpu time then the eCos
364         # application may not actually have consumed a whole second of cpu time yet.
365         if { $watchdog::use_wallclock || (($jiffies - $watchdog::last_jiffies) > ($watchdog::resolution / 10)) } {
366             set watchdog::alarm_triggered 1
367             # Report the situation via the main text window
368             synth::report "Watchdog device: the eCos application has not sent a recent reset\n    Raising SIGPWR signal.\n"
369             # Then kill off the eCos application
370             exec kill -PWR $watchdog::ecos_pid
371             # There is no point in another run of the timer
372             after cancel $watchdog::after_id
373             # And if the GUI is running, raise the alarm visually
374             if { $synth::flag_gui } {
375                 watchdog::gui_alarm
376             }
377         }
378     }
379
380     # When the eCos application has exited, cancel the timer and
381     # clean-up the GUI. Also get rid of the open file since the
382     # /proc/<pid>/stat file is no longer meaningful
383     proc exit_hook { arg_list } {
384         if { "" != $watchdog::after_id } {
385             after cancel $watchdog::after_id
386         }
387         if { $synth::flag_gui } {
388             watchdog::gui_cancel
389         }
390         close $watchdog::proc_stat
391     }
392     synth::hook_add "ecos_exit" watchdog::exit_hook
393     
394     proc handle_request { id reqcode arg1 arg2 reqdata reqlen reply_len } {
395         if { 0x01 == $reqcode } {
396             # A "start" request. If the watchdog has already started,
397             # this request is a no-op. Otherwise a timer is enabled.
398             # This is made to run almost immediately, so that the
399             # GUI gets a quick update. Setting the reset_received flag
400             # ensures that the watchdog will not trigger immediately
401             set watchdog::reset_received 1
402             if { "" == $watchdog::after_id } {
403                 set watchdog::after_id [after 1 watchdog::update]
404             }
405             if { $synth::flag_gui } {
406                 .watchdog lower $watchdog::asleep $watchdog::background
407             }
408         } elseif { 0x02 == $reqcode } {
409             # A "reset" request. Just set a flag, the update handler
410             # will detect this next time it runs.
411             set watchdog::reset_received 1
412         }
413     }
414     
415     proc instantiate { id name data } {
416         return watchdog::handle_request
417     }
418 }
419
420 if { $watchdog::init_ok } {
421     return watchdog::instantiate
422 } else {
423     synth::report "Watchdog cannot be instantiated, initialization failed.\n"
424     return ""
425 }