# {{{ 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//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//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//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 "" }