summaryrefslogtreecommitdiff
path: root/ecos/packages/devs/watchdog/synth/current/host/watchdog.tcl
blob: 9e1a02177fa6795b7c01b2c229bc89712ab85966 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# {{{  Banner                                                   

# ============================================================================
# 
#      watchdog.tcl
# 
#      Watchdog support for the eCos synthetic target I/O auxiliary
# 
# ============================================================================
# ####ECOSHOSTGPLCOPYRIGHTBEGIN####                                         
# -------------------------------------------                               
# This file is part of the eCos host tools.                                 
# Copyright (C) 2002 Free Software Foundation, Inc.                         
#
# This program is free software; you can redistribute it and/or modify      
# it under the terms of the GNU General Public License as published by      
# the Free Software Foundation; either version 2 or (at your option) any    
# later version.                                                            
#
# This program is distributed in the hope that it will be useful, but       
# WITHOUT ANY WARRANTY; without even the implied warranty of                
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         
# General Public License for more details.                                  
#
# You should have received a copy of the GNU General Public License         
# along with this program; if not, write to the                             
# Free Software Foundation, Inc., 51 Franklin Street,                       
# Fifth Floor, Boston, MA  02110-1301, USA.                                 
# -------------------------------------------                               
# ####ECOSHOSTGPLCOPYRIGHTEND####                                           
# ============================================================================
# #####DESCRIPTIONBEGIN####
# 
#  Author(s):   bartv
#  Contact(s):  bartv
#  Date:        2002/09/04
#  Version:     0.01
#  Description:
#      Implementation of the watchdog device. This script should only ever
#      be run from inside the ecosynth auxiliary.
# 
# ####DESCRIPTIONEND####
# ============================================================================

# }}}

namespace eval watchdog {

    # Was initialization successful?
    variable init_ok 1

    # Has the alarm triggered?
    variable alarm_triggered 0
    
    # The eCos application process id. This is needed to send a SIGPWR signal
    # if the watchdog triggers, and to access /proc/<pid>/stat to obtain
    # timing information. Strictly speaking _ppid is not exported by
    # the I/O auxiliary.
    if { ! [info exists synth::_ppid] } {
        synth::report_error "Watchdog initialization failed, _ppid variable required"
        return ""
    }
    variable ecos_pid $synth::_ppid

    # Resolution, i.e. how long to go between checks. Currently this is hard-wired
    # to one second, or 1000 ms. This may become configurable, either on the
    # target-side via CDL or on the host-side via the target definition file.
    # Note that currently the watchdog device and the GUI get updated via the
    # same timer. If the resolution is changed to e.g. 10 seconds then it might
    # be a good idea to update the GUI more frequently, although there are
    # arguments for keeping the animation in step with the real work.
    variable resolution 1000
    
    # Options from the target definition file
    variable use_wallclock 0
    variable window_pack   "-in .main.n -side right"
    variable sound_file    ""
    variable sound_player  "play"

    if { [synth::tdf_has_device "watchdog"] } {
        if { [synth::tdf_has_option "watchdog" "use"] } {
            set _use [synth::tdf_get_option "watchdog" "use"]
            if { "wallclock_time" == $_use } {
                set watchdog::use_wallclock 1
            } elseif { "consumed_cpu_time" == $_use } {
                set watchdog::use_wallclock 0
            } else {
                synth::report_error "Invalid entry in target definition file $synth::target_definition\n\
                                    \    Device watchdog, option \"use\" should be \"wallclock_time\" or \"consumed_cpu_time\"\n"
            }
            unset _use
        }
        if { [synth::tdf_has_option "watchdog" "watchdog_pack"] } {
            set watchdog::window_pack [synth::tdf_get_option "watchdog" "watchdog_pack"]
            # Too complicated to validate here, instead leave it to a catch statement
            # when the window actually gets packed
        }
        if { [synth::tdf_has_option "watchdog" "sound"] } {
            set _sound_file [synth::tdf_get_option "watchdog" "sound"]
            # Look for this sound file in the install tree first, then absolute or relative
            if { [file exists [file join $synth::device_install_dir $_sound_file] ] } {
                set _sound_file [file join $synth::device_install_dir $_sound_file]
            }
            if { ![file exists $_sound_file] } {
                synth::report_error "Invalid entry in target definition file $synth::target_definition\n\
                                    \    Device watchdog, option \"sound\", failed to find $_sound_file\n"
            } elseif { ! [file readable $_sound_file] } {
                synth::report_error "Invalid entry in target definition file $synth::target_definition\n\
                                    \    Device watchdog, option \"sound\", no read access to file $_sound_file\n"
            } else {
                set watchdog::sound_file $_sound_file
            }
            unset _sound_file
        }
        if { [synth::tdf_has_option "watchdog" "sound_player"] } {
            set watchdog::sound_player [synth::tdf_get_option "watchdog" "sound_player"]
        }
    }

    # There is no point in creating the watchdog window if any of the image files are missing
    if { $synth::flag_gui } {
        foreach _image [list "doghouse.gif" "alarm.gif" "eye.gif" "asleep.gif"] {
            variable image_[file rootname $_image]
            if { ! [synth::load_image "watchdog::image_[file rootname $_image]" [file join $synth::device_install_dir $_image]] } {
                synth::report_error "Watchdog device, unable to load image $_image\n\
                                    \    This file should have been installed in $synth::device_install_dir\n"
                set watchdog::init_ok 0
            }
        }
    }
    if { $synth::flag_gui && $watchdog::init_ok } {
        canvas .watchdog -width [image width $image_doghouse] -height [image height $image_doghouse] \
            -borderwidth 0
        variable background [.watchdog create image 0 0 -anchor nw -image $image_doghouse]
    
        # Eye positions inside the doghouse. The eye is an 8x10 gif,
        # mostly white but transparent around the corners
        variable left_eye_x         48
        variable left_eye_y         70
        variable right_eye_x        58
        variable right_eye_y        70
        
        # Pupil positions relative to the eye. The pupils are 3x3 rectangles.
        # The dog can look in one of nine different directions, with both eyes
        # looking in the same direction (if visible)
        variable pupil_positions { { 1 6 } { 1 5 } { 1 3 } { 3 1 } { 3 4 } { 3 6 } { 4 3 } { 4 5 } { 4 6 } }
    
        # Which eyes are currently visible: none, left, right or both
        variable eyes "none"
        # What is the current pupil position?
        variable pupils 4
        
        variable left_eye  [.watchdog create image $left_eye_x $left_eye_y -anchor nw -image $image_eye]
        variable right_eye [.watchdog create image $right_eye_x $right_eye_y -anchor nw -image $image_eye]
    
        variable left_pupil \
            [.watchdog create rectangle \
                 [expr $left_eye_x + [lindex [lindex $pupil_positions $pupils] 0]]     \
                 [expr $left_eye_y + [lindex [lindex $pupil_positions $pupils] 1]]     \
                 [expr $left_eye_x + [lindex [lindex $pupil_positions $pupils] 0] + 2] \
                 [expr $left_eye_y + [lindex [lindex $pupil_positions $pupils] 1] + 2] \
                 -fill black]
        variable right_pupil \
            [.watchdog create rectangle \
                 [expr $right_eye_x + [lindex [lindex $pupil_positions $pupils] 0]]     \
                 [expr $right_eye_y + [lindex [lindex $pupil_positions $pupils] 1]]     \
                 [expr $right_eye_x + [lindex [lindex $pupil_positions $pupils] 0] + 2] \
                 [expr $right_eye_y + [lindex [lindex $pupil_positions $pupils] 1] + 2] \
                 -fill black]

    
        # The dog is asleep until the eCos application activates the watchdog device
        .watchdog lower $left_eye $background
        .watchdog lower $right_eye $background
        .watchdog lower $left_pupil $background
        .watchdog lower $right_pupil $background

        # Prepare for an alarm, but obviously the alarm picture should be hidden for now.
        variable alarm [.watchdog create image 30 56 -anchor nw -image $image_alarm]
        .watchdog lower $alarm $background

        # Start asleep
        variable asleep [.watchdog create image 48 70 -anchor nw -image $image_asleep]
        
        # Now try to pack the watchdog window using the option provided by the
        # user. If that fails, report the error and pack in a default window.
        if { [catch { eval pack .watchdog $watchdog::window_pack } message] } {
            synth::report_error "Watchdog device, failed to pack window in $watchdog::window_pack\n    $message"
            pack .watchdog -in .main.n -side right
        }
                   
        # Updating the display. This happens once a second.
        # If neither eye is visible, choose randomly between
        # left-only, right-only or both. Otherwise there is
        # a one in eight chance of blinking, probably switching
        # to one of the other eye modes
        #
        # Also, the visible pupil(s) will move every second, to one
        # of nine positions
        proc gui_update { } {
        
            if { "none" == $watchdog::eyes} {
                set rand [expr int(3 * rand())]
                if { 0 == $rand } {
                    set watchdog::eyes "left"
                    .watchdog raise $watchdog::left_eye   $watchdog::background
                    .watchdog raise $watchdog::left_pupil $watchdog::left_eye
                } elseif { 1 == $rand } {
                    set watchdog::eyes "right"
                    .watchdog raise $watchdog::right_eye   $watchdog::background
                    .watchdog raise $watchdog::right_pupil $watchdog::right_eye
                } else {
                    set watchdog::eyes "both"
                    .watchdog raise $watchdog::left_eye    $watchdog::background
                    .watchdog raise $watchdog::left_pupil  $watchdog::left_eye
                    .watchdog raise $watchdog::right_eye   $watchdog::background
                    .watchdog raise $watchdog::right_pupil $watchdog::right_eye
                }
            } else {
                if { 0 == [expr int(8 * rand())] } {
                    set watchdog::eyes "none"
                    .watchdog lower $watchdog::left_eye    $watchdog::background
                    .watchdog lower $watchdog::right_eye   $watchdog::background
                    .watchdog lower $watchdog::left_pupil  $watchdog::background
                    .watchdog lower $watchdog::right_pupil $watchdog::background

                    # There is no point in moving the pupils if both eyes are shut
                    return
                }
            }

            set watchdog::pupils [expr int(9 * rand())]
            set new_pupil_x [lindex [lindex $watchdog::pupil_positions $watchdog::pupils] 0]
            set new_pupil_y [lindex [lindex $watchdog::pupil_positions $watchdog::pupils] 1]

            if { ("left" == $watchdog::eyes) || ("both" == $watchdog::eyes) } {
                .watchdog coords $watchdog::left_pupil              \
                    [expr $watchdog::left_eye_x + $new_pupil_x]     \
                    [expr $watchdog::left_eye_y + $new_pupil_y]     \
                    [expr $watchdog::left_eye_x + $new_pupil_x + 2] \
                    [expr $watchdog::left_eye_y + $new_pupil_y + 2]
            }
            if { ("right" == $watchdog::eyes) || ("both" == $watchdog::eyes) } {
                .watchdog coords $watchdog::right_pupil              \
                    [expr $watchdog::right_eye_x + $new_pupil_x]     \
                    [expr $watchdog::right_eye_y + $new_pupil_y]     \
                    [expr $watchdog::right_eye_x + $new_pupil_x + 2] \
                    [expr $watchdog::right_eye_y + $new_pupil_y + 2]
            }
        }

        # Cancel the gui display when the eCos application has exited.
        # The watchdog is allowed to go back to sleep. If the application
        # exited because of the watchdog then of course the alarm picture
        # should remain visible, otherwise it would be just a flash.
        proc gui_cancel { } {
            .watchdog lower $watchdog::left_eye    $watchdog::background
            .watchdog lower $watchdog::right_eye   $watchdog::background
            .watchdog lower $watchdog::left_pupil  $watchdog::background
            .watchdog lower $watchdog::right_pupil $watchdog::background
            if { ! $watchdog::alarm_triggered } {
                .watchdog raise $watchdog::asleep      $watchdog::background
            }
        }
        
        # Raise the alarm. This involves hiding the eyes and raising
        # the alarm picture. If sound is enabled, the sound player
        # should be invoked
        proc gui_alarm { } {
            .watchdog lower $watchdog::asleep      $watchdog::background
            .watchdog lower $watchdog::left_eye    $watchdog::background
            .watchdog lower $watchdog::right_eye   $watchdog::background
            .watchdog lower $watchdog::left_pupil  $watchdog::background
            .watchdog lower $watchdog::right_pupil $watchdog::background
            .watchdog raise $watchdog::alarm       $watchdog::background

            if { "" != $watchdog::sound_file } {
                # Catch errors on the actual exec, e.g. if the sound player is
                # invalid, but play the sound in the background. If there are
                # problems actually playing the sound then the user should
                # still see a message on stderr. Blocking the entire auxiliary
                # for a few seconds is not acceptable.
                if { [catch { eval exec -- $watchdog::sound_player $watchdog::sound_file & } message] } {
                    synth::report_warning "Watchdog device, failed to play alarm sound file\n    $message\n"
                }
            }
        }

	set _watchdog_help [file join $synth::device_src_dir "doc" "devs-watchdog-synth.html"]
	if { ![file readable $_watchdog_help] } {
	    synth::report_warning "Failed to locate synthetic watchdog documentation $_watchdog_help\n\
			          \    Help->Watchdog target menu option disabled.\n"
	    set _watchdog_help ""
	}
	if { "" == $_watchdog_help } {
	    .menubar.help add command -label "Watchdog" -state disabled
	} else {
	    .menubar.help add command -label "Watchdog" -command [list synth::handle_help "file://$_watchdog_help"]
	}
    }

    # Now for the real work. By default the watchdog is asleep. The eCos
    # application can activate it with a start message, which results
    # in an "after" timer. That runs once a second to check whether or not
    # the watchdog should trigger, and also updates the GUI.
    #
    # The target-side code should perform a watchdog reset at least once
    # a second, which involves another message to this script and the
    # setting of the reset_received flag.
    #
    # The update handler gets information about the eCos application using
    # /proc/<pid>/stat (see man 5 proc). The "state" field is important:
    # a state of T indicates that the application is stopped, probably
    # inside gdb, so cannot reset the watchdog. The other important field
    # is utime, the total number of jiffies (0.01 seconds) executed in
    # user space. The code maintains an open file handle to the /proc file.

    variable reset_received     0
    variable after_id           ""
    variable proc_stat          ""
    variable last_jiffies       0
    
    set _filename "/proc/[set watchdog::ecos_pid]/stat"
    if { [catch { open $_filename "r" } proc_stat ] } {
        synth::report_error "Watchdog device, failed to open $_filename\n    $proc_stat\n"
        set watchdog::init_ok 0
    }
    unset _filename

    proc update { } {
        set watchdog::after_id [after $watchdog::resolution watchdog::update]
        if { $synth::flag_gui } {
            watchdog::gui_update
        }
        seek $watchdog::proc_stat 0 "start"
        set line [gets $watchdog::proc_stat]
        scan $line "%*d %*s %s %*d %*d %*d %*d %*d %*lu %*lu %*lu %*lu %*lu %lu" state jiffies

	# In theory it is possible to examine the state field (the third argument).
	# If set to T then that indicates the eCos application is traced or
	# stopped, probably inside gdb, and it would make sense to act as if
	# the application had sent a reset. Unfortunately the state also appears
	# to be set to T if the application is blocked in a system call while
	# being debugged - including the idle select(), making the test useless.
	# FIXME: figure out how to distinguish between being blocked inside gdb
	# and being in a system call.
	#if { "T" == $state } {
	#    set watchdog::reset_received 1
	#    return
	#}
	
        # If there has been a recent reset the eCos application can continue to run for a bit longer.
        if { $watchdog::reset_received } {
            set watchdog::last_jiffies $jiffies
            set watchdog::reset_received 0
            return
        }

        # We have not received a reset. If the watchdog is using wallclock time then
        # that is serious. If the watchdog is using elapsed cpu time then the eCos
        # application may not actually have consumed a whole second of cpu time yet.
        if { $watchdog::use_wallclock || (($jiffies - $watchdog::last_jiffies) > ($watchdog::resolution / 10)) } {
            set watchdog::alarm_triggered 1
            # Report the situation via the main text window
            synth::report "Watchdog device: the eCos application has not sent a recent reset\n    Raising SIGPWR signal.\n"
            # Then kill off the eCos application
            exec kill -PWR $watchdog::ecos_pid
            # There is no point in another run of the timer
            after cancel $watchdog::after_id
            # And if the GUI is running, raise the alarm visually
            if { $synth::flag_gui } {
                watchdog::gui_alarm
            }
        }
    }

    # When the eCos application has exited, cancel the timer and
    # clean-up the GUI. Also get rid of the open file since the
    # /proc/<pid>/stat file is no longer meaningful
    proc exit_hook { arg_list } {
        if { "" != $watchdog::after_id } {
            after cancel $watchdog::after_id
        }
        if { $synth::flag_gui } {
            watchdog::gui_cancel
        }
        close $watchdog::proc_stat
    }
    synth::hook_add "ecos_exit" watchdog::exit_hook
    
    proc handle_request { id reqcode arg1 arg2 reqdata reqlen reply_len } {
        if { 0x01 == $reqcode } {
            # A "start" request. If the watchdog has already started,
            # this request is a no-op. Otherwise a timer is enabled.
            # This is made to run almost immediately, so that the
            # GUI gets a quick update. Setting the reset_received flag
            # ensures that the watchdog will not trigger immediately
            set watchdog::reset_received 1
            if { "" == $watchdog::after_id } {
                set watchdog::after_id [after 1 watchdog::update]
            }
            if { $synth::flag_gui } {
                .watchdog lower $watchdog::asleep $watchdog::background
            }
        } elseif { 0x02 == $reqcode } {
            # A "reset" request. Just set a flag, the update handler
            # will detect this next time it runs.
            set watchdog::reset_received 1
        }
    }
    
    proc instantiate { id name data } {
        return watchdog::handle_request
    }
}

if { $watchdog::init_ok } {
    return watchdog::instantiate
} else {
    synth::report "Watchdog cannot be instantiated, initialization failed.\n"
    return ""
}