]> git.karo-electronics.de Git - karo-tx-redboot.git/blob - packages/hal/synth/arch/v2_0/host/ecosynth.c
Initial revision
[karo-tx-redboot.git] / packages / hal / synth / arch / v2_0 / host / ecosynth.c
1 //============================================================================
2 //
3 //     ecosynth.c
4 //
5 //     The eCos synthetic target I/O auxiliary
6 //
7 //============================================================================
8 //####COPYRIGHTBEGIN####
9 //                                                                          
10 // ----------------------------------------------------------------------------
11 // Copyright (C) 2002 Bart Veer
12 //
13 // This file is part of the eCos host tools.
14 //
15 // This program is free software; you can redistribute it and/or modify it 
16 // under the terms of the GNU General Public License as published by the Free 
17 // Software Foundation; either version 2 of the License, or (at your option) 
18 // any later version.
19 // 
20 // This program is distributed in the hope that it will be useful, but WITHOUT 
21 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
22 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
23 // more details.
24 // 
25 // You should have received a copy of the GNU General Public License along with
26 // this program; if not, write to the Free Software Foundation, Inc., 
27 // 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
28 //
29 // ----------------------------------------------------------------------------
30 //                                                                          
31 //####COPYRIGHTEND####
32 //============================================================================
33 //#####DESCRIPTIONBEGIN####
34 //
35 // Author(s):   bartv
36 // Contact(s):  bartv
37 // Date:        2002-08-05
38 // Version:     0.01
39 // Description:
40 //
41 // The main module for the eCos synthetic target auxiliary. This
42 // program is fork'ed and execve'd during the initialization phase of
43 // any synthetic target application, and is primarily responsible for
44 // I/O operations. This program should never be run directly by a
45 // user.
46 //
47 //####DESCRIPTIONEND####
48 //============================================================================
49
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <signal.h>
54 #include <limits.h>
55 #include <unistd.h>
56 #include <assert.h>
57 #include <fcntl.h>
58 #include <sys/param.h>
59 #include <sys/types.h>
60 // Avoid compatibility problems with Tcl 8.4 vs. earlier
61 #define USE_NON_CONST
62 #include <tcl.h>
63 #include <tk.h>
64
65 // The protocol between host and target is defined by a private
66 // target-side header.
67 #include "../src/synth_protocol.h"
68
69 // ----------------------------------------------------------------------------
70 // Statics etc.
71 #define PROGNAME    "Synthetic target auxiliary"
72
73 static int  no_windows = 0; // -nw arg
74
75 static pid_t    parent_pid;
76
77 // ----------------------------------------------------------------------------
78 // Overview.
79 //
80 // When a synthetic target application is executed, whether directly
81 // or from inside gdb, it can fork() and execve() the synthetic
82 // target auxiliary: this program, ecosynth. For now this is disabled
83 // by default but can be enabled using a --io option on the command line.
84 // In future it may be enabled by default, but can be suppressed using
85 // --nio.
86 //
87 // stdin, stdout, and stderr will be inherited from the eCos application, so
88 // that attempts to use printf() or fprintf(stderr, ) from inside the
89 // auxiliary or its helper applications work as expected. In addition file
90 // descriptor 3 will be a pipe from the eCos application, and file descriptor
91 // 4 will be a pipe to the application. The protocol defined in
92 // ../src/synth_protocol.h runs over these pip
93 //
94 // argv[] and environ are also as per the application, with the exception
95 // of argv[0] which is the full path to this application.
96 //
97 // The bulk of the hard work is done inside Tcl. The C++ code is
98 // responsible only for initialization and for implementing some
99 // additional commands, for example to send a SIGIO signal to the
100 // parent when there is a new pending interrupt. The primary script is
101 // ecosynth.tcl which should be installed alongside the executable.
102 //
103 // This code makes use of the standard Tcl initialization facilities:
104 //
105 // 1) main() calls Tcl_Main() with the command-line arguments and an
106 //    application-specific initialization routine ecosynth_appinit().
107 //
108 // 2) Tcl_Main() goes through the initialization sequence in generic/tclMain.c.
109 //    There is one slight complication: Tcl_main() interprets arguments in
110 //    a way that makes sense for tclsh, but not for ecosynth. Specially if
111 //    argv[1] exists and does not begin with a - then it will be interpreted
112 //    as a script to be executed. Hence argv[] is re-allocated and the name
113 //    of a suitable script is inserted.
114 //
115 // 3) ecosynth_appinit() initializes the Tcl interpreter, and optionally
116 //    the Tk interpreter as well. This is controlled by the presence of
117 //    a -nw argument on the command line. This initialization routine
118 //    also takes care of adding some commands to the Tcl interpreter.
119 //
120 // 4) Tcl_Main() will now proceed to execute the startup script, which
121 //    is eccentric.tcl installed in the libexec directory.
122
123 static int  ecosynth_appinit(Tcl_Interp*);
124
125 int
126 main(int argc, char** argv)
127 {
128     char    ecosynth_tcl_path[_POSIX_PATH_MAX];
129     char**  new_argv;
130     int     i;
131
132     parent_pid = getppid();
133
134     // The various core Tcl scripts are installed in the same
135     // directory as ecosynth. The Tcl script itself will check whether
136     // there is a newer version of itself in the source tree and
137     // switch to that instead.
138     assert((strlen(LIBEXECDIR) + strlen(PACKAGE_INSTALL) + 20) < _POSIX_PATH_MAX);
139     strcpy(ecosynth_tcl_path, LIBEXECDIR);
140     strcat(ecosynth_tcl_path, "/ecos/");
141     strcat(ecosynth_tcl_path, PACKAGE_INSTALL);
142     strcat(ecosynth_tcl_path, "/ecosynth.tcl");
143
144     // Installation sanity checks.
145     if (0 != access(ecosynth_tcl_path, F_OK)) {
146         fprintf(stderr, PROGNAME ": error, a required Tcl script has not been installed.\n");
147         fprintf(stderr, "    The script is \"%s\"\n", ecosynth_tcl_path);
148         exit(EXIT_FAILURE);
149     }
150     if (0 != access(ecosynth_tcl_path, R_OK)) {
151         fprintf(stderr, PROGNAME ": error, no read access to a required Tcl script.\n");
152         fprintf(stderr, "    The script is \"%s\"\n", ecosynth_tcl_path);
153         exit(EXIT_FAILURE);
154     }
155
156     // Look for options -nw and -w. This information is needed by the appinit() routine.
157     no_windows = 0;
158     for (i = 1; i < argc; i++) {
159         if ((0 == strcmp("-nw", argv[i])) || (0 == strcmp("--nw", argv[i])) ||
160             (0 == strcmp("-no-windows", argv[i])) || (0 == strcmp("--no-windows", argv[i]))) {
161             no_windows = 1;
162         } else if ((0 == strcmp("-w", argv[i])) || (0 == strcmp("--w", argv[i])) ||
163                    (0 == strcmp("-windows", argv[i])) || (0 == strcmp("--windows", argv[i]))) {
164             no_windows = 0;
165         }
166     }
167     
168     new_argv = malloc((argc+2) * sizeof(char*));
169     if (NULL == new_argv) {
170         fprintf(stderr, PROGNAME ": internal error, out of memory.\n");
171         exit(EXIT_FAILURE);
172     }
173     new_argv[0] = argv[0];
174     new_argv[1] = ecosynth_tcl_path;
175     for (i = 1; i < argc; i++) {
176         new_argv[i+1] = argv[i];
177     }
178     new_argv[i+1] = NULL;
179
180     // Ignore SIGINT requests. Those can happen if e.g. the application is
181     // ctrl-C'd or if a gdb session is interrupted because this process is
182     // a child of the eCos application. Instead the normal code for handling
183     // application termination needs to run.
184     signal(SIGINT, SIG_IGN);
185
186     // Similarly ignore SIGTSTP if running in graphical mode, it would
187     // be inappropriate for the GUI to freeze if the eCos application is
188     // suspended. If running in text mode then it is better for both
189     // the application and the I/O auxiliary to freeze, halting any further
190     // output.
191     if (!no_windows) {
192         signal(SIGTSTP, SIG_IGN);
193     }
194
195     Tcl_Main(argc+1, new_argv, &ecosynth_appinit);
196     return EXIT_SUCCESS;
197 }
198
199
200 // ----------------------------------------------------------------------------
201 // Commands for the Tcl interpreter. These are few and far between because
202 // as much work as possible is done in Tcl.
203
204 // Send a SIGIO signal to the parent process. This is done whenever a new
205 // interrupt is pending. There is a possibility of strange behaviour if
206 // the synthetic target application is exiting at just the wrong moment
207 // and this process has become a zombie. An alternative approach would
208 // involve loading the Extended Tcl extension.
209 static int
210 ecosynth_send_SIGIO(ClientData  clientData __attribute__ ((unused)),
211                     Tcl_Interp* interp,
212                     int         argc,
213                     char**      argv __attribute__ ((unused)))
214 {
215     if (1 != argc) {
216         Tcl_SetResult(interp, "wrong # args: should be \"usbtest::_send_SIGIO\"" , TCL_STATIC);
217         return TCL_ERROR;
218     }
219     
220     (void) kill(parent_pid, SIGIO);
221     return TCL_OK;
222 }
223
224 // Similarly send a SIGKILL (-9) to the parent process. This allows the GUI
225 // code to kill of the eCos application
226 static int
227 ecosynth_send_SIGKILL(ClientData clientData __attribute__ ((unused)),
228                       Tcl_Interp* interp,
229                       int         argc,
230                       char**      argv __attribute__ ((unused)))
231 {
232     if (1 != argc) {
233         Tcl_SetResult(interp, "wrong # args: should be \"usbtest::_send_SIGIO\"" , TCL_STATIC);
234         return TCL_ERROR;
235     }
236     
237     (void) kill(parent_pid, SIGKILL);
238     return TCL_OK;
239 }
240
241
242 // ----------------------------------------------------------------------------
243 // Application-specific initialization.
244
245 static int
246 ecosynth_appinit(Tcl_Interp* interp)
247 {
248     Tcl_Channel from_app;
249     Tcl_Channel to_app;
250
251     // Tcl library initialization. This has the effect of executing init.tcl,
252     // thus setting up package load paths etc. Not all of that initialization
253     // is necessarily appropriate for ecosynth, but some devices may well want
254     // to load in additional packages or whatever.
255     if (Tcl_Init(interp) == TCL_ERROR) {
256         return TCL_ERROR;
257     }
258     // Optionally initialize Tk as well. This can be suppressed by an
259     // argument -nw. Possibly this could be done by the Tcl script
260     // instead using dynamic loading.
261     //
262     // There is a problem with the way that Tk does its argument processing.
263     // By default it will accept abbreviations for the standard wish arguments,
264     // so if the user specifies e.g. -v then the Tk code will interpret this
265     // as an abbreviation for -visual, and will probably complain because
266     // -visual takes an argument whereas ecosynth's -v is just a flag.
267     //
268     // The Tk argument processing can be suppressed simply by temporarily
269     // getting rid of the argv variable. The disadvantage is that some
270     // standard arguments now have to be processed explicitly:
271     //
272     //     -colormap map      ignored, of little interest these days
273     //     -display  display  currently ignored. This would have to be
274     //                        implemented at the C level, not the Tcl level,
275     //                        since Tk_Init() will start interacting with the
276     //                        X server. Its implementation would involve a
277     //                        setenv() call.
278     //     -geometry geom     implemented in the Tcl code using "wm geometry"
279     //     -name     name     ignored for now.
280     //     -sync              ignored, of little interest to typical users
281     //     -use      id       ignored, probably of little interest for now
282     //     -visual   visual   ignored, of little interest these days
283     //
284     // so actually the only extra work that is required is for the Tcl code
285     // to process -geometry.
286     if (!no_windows) {
287         Tcl_Obj* argv = Tcl_GetVar2Ex(interp, "argv", NULL, TCL_GLOBAL_ONLY);
288         Tcl_IncrRefCount(argv);
289         Tcl_UnsetVar(interp, "argv", TCL_GLOBAL_ONLY);
290         if (Tk_Init(interp) == TCL_ERROR) {
291             return TCL_ERROR;
292         }
293         Tcl_SetVar2Ex(interp, "argv", NULL, argv, TCL_GLOBAL_ONLY);
294         Tcl_DecrRefCount(argv);
295     }
296     
297     // Create the synth:: namespace. Currently this does not seem to be possible from
298     // inside C.
299     if (TCL_OK != Tcl_Eval(interp, 
300                            "namespace eval synth {\n"
301                            "    variable channel_from_app 0\n"
302                            "    variable channel_to_app 0\n"
303                            "}\n")) {
304         fprintf(stderr, PROGNAME ": internal error, failed to create Tcl synth:: namespace\n");
305         fprintf(stderr, "    Please check Tcl version (8.3 or later required).\n");
306         exit(EXIT_FAILURE);
307     }
308
309     // The pipe to/from the application is exposed to Tcl, allowing
310     // the main communication to be handled at the Tcl level and
311     // specifically via the event loop. This requires two additional
312     // Tcl channels to be created for file descriptors 3 and 4.
313     from_app = Tcl_MakeFileChannel((ClientData) 3, TCL_READABLE);
314     if (NULL == from_app) {
315         fprintf(stderr, PROGNAME ": internal error, failed to create Tcl channel for pipe from application.\n");
316         exit(EXIT_FAILURE);
317     }
318     Tcl_RegisterChannel(interp, from_app);
319
320     // The channel name will be something like file0. Add a variable to the
321     // interpreter to store this name.
322     if (NULL == Tcl_SetVar(interp, "synth::_channel_from_app",  Tcl_GetChannelName(from_app), TCL_GLOBAL_ONLY)) {
323         fprintf(stderr, PROGNAME ": internal error, failed to create Tcl variable from application channel 0\n");
324         exit(EXIT_FAILURE);
325     }
326
327     // Repeat for the channel to the application.
328     to_app = Tcl_MakeFileChannel((ClientData) 4, TCL_WRITABLE);
329     if (NULL == to_app) {
330         fprintf(stderr, PROGNAME ": internal error, failed to create Tcl channel for pipe to application.\n");
331         exit(EXIT_FAILURE);
332     }
333     Tcl_RegisterChannel(interp, to_app);
334     if (NULL == Tcl_SetVar(interp, "synth::_channel_to_app",  Tcl_GetChannelName(to_app), TCL_GLOBAL_ONLY)) {
335         fprintf(stderr, PROGNAME ": internal error, failed to create Tcl variable from application channel 1\n");
336         exit(EXIT_FAILURE);
337     }
338
339     // The auxiliary may spawn additional programs, via
340     // device-specific scripts. Those programs should not have
341     // direct access to the pipe - that would just lead to
342     // confusion.
343     fcntl(3, F_SETFD, FD_CLOEXEC);
344     fcntl(4, F_SETFD, FD_CLOEXEC);
345     
346     // Add synthetic target-specific commands to the Tcl interpreter.
347     Tcl_CreateCommand(interp, "synth::_send_SIGIO", &ecosynth_send_SIGIO, (ClientData)NULL, (Tcl_CmdDeleteProc*) NULL);
348     Tcl_CreateCommand(interp, "synth::_send_SIGKILL", &ecosynth_send_SIGKILL, (ClientData)NULL, (Tcl_CmdDeleteProc*) NULL);
349
350     // Define additional variables.
351
352     // The version, from the AM_INIT_AUTOMAKE() macro in configure.in
353     Tcl_SetVar(interp, "synth::_ecosynth_version", ECOSYNTH_VERSION, TCL_GLOBAL_ONLY);
354
355     // The parent process id, i.e. that for the eCos application itself.
356     Tcl_SetVar2Ex(interp, "synth::_ppid", NULL, Tcl_NewIntObj(getppid()), TCL_GLOBAL_ONLY);
357                
358     // The various directories
359     Tcl_SetVar(interp, "synth::_ecosynth_repository", ECOS_REPOSITORY, TCL_GLOBAL_ONLY);
360     Tcl_SetVar(interp, "synth::_ecosynth_libexecdir", LIBEXECDIR, TCL_GLOBAL_ONLY);
361     Tcl_SetVar(interp, "synth::_ecosynth_package_dir", PACKAGE_DIR, TCL_GLOBAL_ONLY);
362     Tcl_SetVar(interp, "synth::_ecosynth_package_version", PACKAGE_VERSION, TCL_GLOBAL_ONLY);
363     Tcl_SetVar(interp, "synth::_ecosynth_package_install", PACKAGE_INSTALL, TCL_GLOBAL_ONLY);
364     
365     return TCL_OK;
366 }