3 # ============================================================================
7 # Watchdog support for the eCos synthetic target I/O auxiliary
9 # ============================================================================
10 # ####COPYRIGHTBEGIN####
12 # ----------------------------------------------------------------------------
13 # Copyright (C) 2002 Bart Veer
15 # This file is part of the eCos host tools.
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)
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
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.
31 # ----------------------------------------------------------------------------
33 # ####COPYRIGHTEND####
34 # ============================================================================
35 # #####DESCRIPTIONBEGIN####
42 # Implementation of the watchdog device. This script should only ever
43 # be run from inside the ecosynth auxiliary.
45 # ####DESCRIPTIONEND####
46 # ============================================================================
50 namespace eval watchdog {
52 # Was initialization successful?
55 # Has the alarm triggered?
56 variable alarm_triggered 0
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
62 if { ! [info exists synth::_ppid] } {
63 synth::report_error "Watchdog initialization failed, _ppid variable required"
66 variable ecos_pid $synth::_ppid
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
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"
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
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"
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
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]
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"
114 set watchdog::sound_file $_sound_file
118 if { [synth::tdf_has_option "watchdog" "sound_player"] } {
119 set watchdog::sound_player [synth::tdf_get_option "watchdog" "sound_player"]
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
134 if { $synth::flag_gui && $watchdog::init_ok } {
135 canvas .watchdog -width [image width $image_doghouse] -height [image height $image_doghouse] \
137 variable background [.watchdog create image 0 0 -anchor nw -image $image_doghouse]
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
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 } }
151 # Which eyes are currently visible: none, left, right or both
153 # What is the current pupil position?
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]
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] \
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] \
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
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
186 variable asleep [.watchdog create image 48 70 -anchor nw -image $image_asleep]
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
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
201 # Also, the visible pupil(s) will move every second, to one
203 proc gui_update { } {
205 if { "none" == $watchdog::eyes} {
206 set rand [expr int(3 * 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
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
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
230 # There is no point in moving the pupils if both eyes are shut
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]
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]
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]
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
269 # Raise the alarm. This involves hiding the eyes and raising
270 # the alarm picture. If sound is enabled, the sound player
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
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"
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 ""
298 if { "" == $_watchdog_help } {
299 .menubar.help add command -label "Watchdog" -state disabled
301 .menubar.help add command -label "Watchdog" -command [list synth::handle_help "file://$_watchdog_help"]
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.
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.
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.
321 variable reset_received 0
323 variable proc_stat ""
324 variable last_jiffies 0
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
334 set watchdog::after_id [after $watchdog::resolution watchdog::update]
335 if { $synth::flag_gui } {
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
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
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
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 } {
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
387 if { $synth::flag_gui } {
390 close $watchdog::proc_stat
392 synth::hook_add "ecos_exit" watchdog::exit_hook
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]
405 if { $synth::flag_gui } {
406 .watchdog lower $watchdog::asleep $watchdog::background
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
415 proc instantiate { id name data } {
416 return watchdog::handle_request
420 if { $watchdog::init_ok } {
421 return watchdog::instantiate
423 synth::report "Watchdog cannot be instantiated, initialization failed.\n"