Implemented a hard kill to avoid hanging in a terminal on a Ctrl-C.
[xcircuit.git] / ngspice.c
blob5efcf33b54fe50b08a342d96f52f7a068ac052f9
1 /*----------------------------------------------------------------------*/
2 /* ngspice.c --- Handling of background ngspice process. */
3 /* Copyright (c) 2004 Tim Edwards, MultiGiG, Inc. */
4 /* These routines work only if ngspice is on the system. */
5 /*----------------------------------------------------------------------*/
7 /* #undef SPICE_DEBUG */
8 #define SPICE_DEBUG
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #ifndef _MSC_VER
14 #include <unistd.h>
15 #endif
16 #include <time.h>
17 #ifndef _MSC_VER
18 #include <sys/wait.h> /* for waitpid() */
19 #include <sys/time.h>
20 #include <ctype.h>
21 #endif
22 #include <sys/types.h>
23 #include <signal.h>
24 #include <fcntl.h>
26 /* This code should be able to run without requiring Tcl-based XCircuit */
27 /* but this has not been worked out, so instead it is just disabled. */
29 #ifdef TCL_WRAPPER
31 /*----------------------------------------------------------------------*/
32 /* Local includes */
33 /*----------------------------------------------------------------------*/
35 #include <tk.h>
37 #ifndef _MSC_VER
38 #include <X11/Intrinsic.h>
39 #include <X11/StringDefs.h>
40 #endif
42 #include "colordefs.h"
43 #include "xcircuit.h"
45 /*----------------------------------------------------------------------*/
46 /* Function prototype declarations */
47 /*----------------------------------------------------------------------*/
48 #include "prototypes.h"
50 /*------------------------------------------------------------------------*/
51 /* External Variable definitions */
52 /*------------------------------------------------------------------------*/
54 extern char _STR2[250], _STR[150];
55 extern Globaldata xobjs;
56 extern XCWindowData *areawin;
57 extern Display *dpy;
58 extern int number_colors;
59 extern colorindex *colorlist;
60 extern Cursor appcursors[NUM_CURSORS];
62 #ifndef HAVE_VFORK
63 #define vfork fork
64 #endif
66 int spice_state; /* Track state of the simulator */
68 #define SPICE_INIT 0 /* Initial state; ngspice interpreter is idle. */
69 #define SPICE_BUSY 1 /* Simulation in progress; ngspice is busy. */
70 #define SPICE_READY 2 /* Simulation at break; ngspice is waiting for */
71 /* "resume". */
73 #define EXPECT_ANY 0 /* Read routine accepts all input until input */
74 /* buffer is empty, then returns. */
75 #define EXPECT_PROMPT 1 /* Read routine accepts all input up to the */
76 /* next ngspice prompt, removes the prompt, */
77 /* sets the Tcl return value to the history */
78 /* number, and returns. */
79 #define EXPECT_REF 2 /* Read routine accepts all input until the */
80 /* first "Reference value" line, truncates the */
81 /* output, and sets the Tcl return value to the */
82 /* reference value seen. */
84 #ifndef CONST84
85 #define CONST84
86 #endif
88 /*------------------------------------------------------*/
89 /* Global variable definitions */
90 /*------------------------------------------------------*/
92 #ifndef _MSC_VER
93 pid_t spiceproc = -1; /* ngspice process */
94 #else
95 HANDLE spiceproc = INVALID_HANDLE_VALUE;
96 #endif
98 int pipeRead, pipeWrite; /* I/O pipe pair for ngspice */
100 /*------------------------------------------------------*/
101 /* Start a ngspice process and arrange the I/O pipes */
102 /* (Commented lines cause ngspice to relay its stderr */
103 /* to xcircuit's stderr) */
104 /* */
105 /* Return codes: */
106 /* 0 success */
107 /* -1 failed---ngspice exited with error condition */
108 /* -2 exec failed---e.g., unable to find ngspice */
109 /* 1 ngspice is already running */
110 /*------------------------------------------------------*/
112 #ifdef _MSC_VER
114 int start_spice()
116 STARTUPINFO st_info;
117 PROCESS_INFORMATION pr_info;
118 SECURITY_ATTRIBUTES attr;
119 HANDLE child_stdin, child_stdout, tmp;
120 char cmd[4096];
122 attr.nLength = sizeof(SECURITY_ATTRIBUTES);
123 attr.lpSecurityDescriptor = NULL;
124 attr.bInheritHandle = FALSE;
126 if (!CreatePipe(&tmp, &child_stdout, &attr, 0))
127 goto spice_error;
129 SetHandleInformation(tmp, HANDLE_FLAG_INHERIT, 0);
130 pipeRead = _open_osfhandle(tmp, _O_RDONLY);
131 if (!CreatePipe(&child_stdin, &tmp, &attr, 0))
132 goto spice_error;
133 SetHandleInformation(tmp, HANDLE_FLAG_INHERIT, 0);
134 pipeWrite = _open_osfhandle(tmp, _O_WRONLY);
136 ZeroMemory(&pr_info, sizeof(PROCESS_INFORMATION));
137 ZeroMemory(&st_info, sizeof(STARTUPINFO));
138 st_info.cb = sizeof(STARTUPINFO);
139 st_info.dwFlags = STARTF_USESTDHANDLES;
140 st_info.hStdError = st_info.hStdOutput = child_stdout;
141 st_info.hStdInput = child_stdin;
143 snprintf(cmd, 4095, "%s -p", SPICE_EXEC);
144 if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL,
145 &st_info, &pr_info) == 0)
146 goto spice_error;
148 CloseHandle(pr_info.hThread);
149 spiceproc = pr_info.hProcess;
150 return 0;
152 spice_error:
153 Fprintf(stderr, "Exec of ngspice failed\n");
154 return -2;
157 #else /* ! MSC_VER */
159 int start_spice()
161 int std_in[2], std_out[2], ret;
163 ret = pipe(std_in);
164 ret = pipe(std_out);
166 /* Input goes from xcircuit to ngspice to provide commands to the */
167 /* ngspice interpreter front-end. Output goes from ngspice back to */
168 /* xcircuit to provide data for displaying. */
170 if (spiceproc < 0) { /* ngspice is not running yet */
171 spiceproc = vfork();
172 if (spiceproc == 0) { /* child process (ngspice) */
173 #ifdef SPICE_DEBUG
174 fprintf(stdout, "Calling %s\n", SPICE_EXEC);
175 #endif
176 close(std_in[0]);
177 close(std_out[1]);
179 dup2(std_in[1], fileno(stdout));
180 dup2(std_in[1], fileno(stderr));
181 dup2(std_out[0], fileno(stdin));
183 Flush(stderr);
185 /* Force interactive mode with "-p". Otherwise, ngspice will */
186 /* detect that the pipe is not a terminal and switch to batch- */
187 /* processing mode. */
189 execlp(SPICE_EXEC, "ngspice", "-p", (char *)NULL);
190 spiceproc = -1;
191 Fprintf(stderr, "Exec of ngspice failed\n");
192 return -2;
194 else if (spiceproc < 0) {
195 Wprintf("Error: ngspice not running");
196 close(std_in[0]);
197 close(std_in[1]);
198 close(std_out[0]);
199 close(std_out[1]);
200 return -1; /* error condition */
202 else { /* parent process */
203 close(std_in[1]);
204 close(std_out[0]);
205 pipeRead = std_in[0];
206 pipeWrite = std_out[1];
207 return 0; /* success */
210 return 1; /* spice is already running */
213 #endif /* ! MSC_VER */
215 /*------------------------------------------------------*/
216 /* Send text to the ngspice simulator */
217 /*------------------------------------------------------*/
219 void send_to_spice(char *text)
221 int tlen = strlen(text);
222 write(pipeWrite, text, tlen);
223 if (*(text + tlen - 1) != '\n')
224 write(pipeWrite, "\n", 1);
226 if (!strncmp(text, "run", 3) || !strncmp(text, "resume", 6))
227 spice_state = SPICE_BUSY;
228 else if (!strncmp(text, "quit", 4) || !strncmp(text, "exit", 4))
229 spice_state = SPICE_INIT;
231 #ifdef SPICE_DEBUG
232 /* fprintf(stdout, "writing: %s\n", text); */
233 #endif
236 /*------------------------------------------------------*/
237 /* Get text from the ngspice simulator */
238 /*------------------------------------------------------*/
240 #define RECV_BUFSIZE 1024
242 char *recv_from_spice(Tcl_Interp *interp, int expect)
244 int n, numc, totc, nfd;
245 fd_set readfds, writefds, exceptfds;
246 static char *buffer = NULL;
247 struct timeval timeout;
248 char *pptr, *bufptr;
249 float refval;
251 if (buffer == NULL)
252 buffer = (char *)malloc(RECV_BUFSIZE);
254 /* 2-second timeout on reads */
255 timeout.tv_sec = (expect == EXPECT_ANY) ? 0 : 2;
256 timeout.tv_usec = 0;
258 FD_ZERO(&readfds);
259 FD_ZERO(&exceptfds);
261 bufptr = buffer;
262 totc = 0;
263 numc = RECV_BUFSIZE - 1;
264 while (numc == RECV_BUFSIZE - 1) {
266 nfd = pipeRead + 1;
267 FD_ZERO(&writefds);
268 FD_SET(pipeRead, &readfds);
269 FD_SET(pipeRead, &exceptfds);
271 *bufptr = '\0';
272 n = select(nfd, &readfds, &writefds, &exceptfds, &timeout);
273 if (n == 0) {
274 if (expect != EXPECT_ANY)
275 Fprintf(stderr, "Timeout during select()\n");
276 return buffer; /* Timeout, or no data */
278 else if (n < 0) {
279 /* Interrupt here? */
280 Fprintf(stderr, "Exception received by select()\n");
281 return buffer;
283 numc = read(pipeRead, bufptr, RECV_BUFSIZE - 1);
284 *(bufptr + numc) = '\0'; /* Terminate the line just read */
285 totc += numc;
287 switch(expect) {
288 case EXPECT_PROMPT:
289 for (pptr = bufptr + numc - 1; pptr >= buffer; pptr--)
290 if (*pptr == '\n')
291 break;
292 if (!strncmp(pptr + 1, "ngspice", 7)) {
293 *(pptr) = '\0';
294 if (sscanf(pptr + 8, "%d", &numc) == 1) {
295 sprintf(_STR2, "%d", numc);
296 Tcl_SetResult(interp, _STR2, TCL_STATIC);
298 return buffer;
300 /* Force the process to continue reading until the */
301 /* prompt is received. */
302 numc = RECV_BUFSIZE - 1;
303 break;
305 case EXPECT_REF:
306 for (pptr = bufptr + numc - 1; pptr > buffer; pptr--) {
307 if (*pptr == '\r') {
308 while ((--pptr >= buffer) && !isspace(*pptr));
309 if (sscanf(pptr + 1, "%g", &refval)) {
310 sprintf(_STR2, "%g", refval);
311 Tcl_SetResult(interp, _STR2, TCL_STATIC);
313 return buffer;
316 /* Force the process to continue reading until a */
317 /* reference value is received. */
318 numc = RECV_BUFSIZE - 1;
320 /* Drop through to below to remove the control chars */
322 case EXPECT_ANY:
323 /* Remove control characters from the input */
324 pptr = bufptr;
325 while (*pptr != '\0') {
326 if (*pptr == '\r') *pptr = '\n';
327 else if (!isprint(*pptr)) *pptr = ' ';
328 pptr++;
330 break;
333 if (numc == RECV_BUFSIZE - 1) {
334 buffer = (char *)realloc(buffer, totc + RECV_BUFSIZE);
335 bufptr = buffer + totc;
339 #ifdef SPICE_DEBUG
340 /* fprintf(stdout, "read %d characters: %s", totc, buffer); */
341 #endif
342 return buffer;
345 /*------------------------------------------------------*/
346 /* Send break (ctrl-c) to the ngspice simulator */
347 /* Return 0 on success, -1 if ngspice didn't return a */
348 /* prompt within the timeout period. */
349 /*------------------------------------------------------*/
351 int break_spice(Tcl_Interp *interp)
353 char *msg;
355 #ifdef _MSC_VER
356 if (spiceproc == INVALID_HANDLE_VALUE) return 0; /* No process to interrupt */
357 #else
358 if (spiceproc == -1) return 0; /* No process to interrupt */
359 #endif
361 /* Sending SIGINT in any state other than "busy" will kill */
362 /* the ngspice process, not interrupt it! */
364 if (spice_state == SPICE_BUSY) {
365 #ifndef _MSC_VER
366 kill(spiceproc, SIGINT);
367 #else
368 TerminateProcess(spiceproc, -1);
369 #endif
370 msg = recv_from_spice(interp, EXPECT_PROMPT); /* Flush the output */
371 if (*msg == '\0')
372 return -1;
375 spice_state = SPICE_READY;
376 #ifdef SPICE_DEBUG
377 /* fprintf(stdout, "interrupt ngspice\n"); */
378 #endif
379 return 0;
382 /*------------------------------------------------------*/
383 /* Resume the ngspice simulator */
384 /*------------------------------------------------------*/
386 void resume_spice()
388 spice_state = SPICE_BUSY;
389 write(pipeWrite, "resume\n", 7);
390 #ifdef SPICE_DEBUG
391 /* fprintf(stdout, "resume ngspice\n"); */
392 #endif
395 /*------------------------------------------------------*/
396 /* Exit ngspice. . . not so gently */
397 /*------------------------------------------------------*/
399 int exit_spice()
401 #ifdef _MSC_VER
402 if (spiceproc == INVALID_HANDLE_VALUE) return -1; /* ngspice not running */
403 #else
404 if (spiceproc < 0) return -1; /* ngspice not running */
405 #endif
407 #ifdef SPICE_DEBUG
408 fprintf(stdout, "Waiting for ngspice to exit\n");
409 #endif
410 #ifndef _MSC_VER
411 kill(spiceproc, SIGKILL);
412 waitpid(spiceproc, NULL, 0);
413 #else
414 TerminateProcess(spiceproc, -1);
415 #endif
416 #ifdef SPICE_DEBUG
417 fprintf(stdout, "ngspice has exited\n");
418 #endif
420 #ifdef _MSC_VER
421 spiceproc = INVALID_HANDLE_VALUE;
422 #else
423 spiceproc = -1;
424 #endif
425 spice_state = SPICE_INIT;
427 return 0;
430 /*------------------------------------------------------*/
431 /* Spice command */
432 /* */
433 /* Run the ngspice simulator from the tcl command line. */
434 /* Currently, the Tcl command-line interface is the */
435 /* *only* way to access the ngspice simulator through */
436 /* XCircuit. ngspice runs as a separate process via a */
437 /* fork call, and sends and receives through the */
438 /* "spice" command in Tcl-XCircuit. */
439 /*------------------------------------------------------*/
441 int xctcl_spice(ClientData clientData, Tcl_Interp *interp,
442 int objc, Tcl_Obj *CONST objv[])
444 int result, idx;
445 char *msg, *msgin, *eptr;
447 static char *subCmds[] = {
448 "start", "send", "get", "break", "resume", "status",
449 "flush", "exit", "run", "print", NULL
451 enum SubIdx {
452 StartIdx, SendIdx, GetIdx, BreakIdx, ResumeIdx, StatusIdx,
453 FlushIdx, ExitIdx, RunIdx, PrintIdx
456 if (objc == 1 || objc > 3) {
457 Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
458 return TCL_ERROR;
460 else if ((result = Tcl_GetIndexFromObj(interp, objv[1],
461 (CONST84 char **)subCmds,
462 "option", 0, &idx)) != TCL_OK) {
463 return result;
466 switch(idx) {
467 case StartIdx:
468 if (spice_state != SPICE_INIT) {
469 Tcl_SetResult(interp, "ngspice process already running", NULL);
470 return TCL_ERROR;
472 result = start_spice();
473 if (result != 0) {
474 Tcl_SetResult(interp, "unable to run ngspice", NULL);
475 return TCL_ERROR;
477 msg = recv_from_spice(interp, EXPECT_PROMPT);
478 if (*msg == '\0')
479 return TCL_ERROR;
480 Fprintf(stdout, "%s", msg);
481 Flush(stdout);
483 /* Force the output not to use "more" */
484 send_to_spice("set nomoremode true");
485 msg = recv_from_spice(interp, EXPECT_PROMPT);
487 /* This needs to be considerably more clever. Should */
488 /* also generate the spice file if it does not exist. */
489 /* Finally, needs to add models and some statement */
490 /* like "trans" to the end to start the simulation. */
492 sprintf(_STR2, "source %s.spc", topobject->name);
493 send_to_spice(_STR2);
494 msg = recv_from_spice(interp, EXPECT_PROMPT);
495 if (*msg == '\0')
496 return TCL_ERROR;
497 else {
498 Fprintf(stdout, "%s", msg);
499 Flush(stdout);
500 spice_state = SPICE_READY;
502 break;
504 case RunIdx:
505 if (spice_state != SPICE_READY) {
506 Tcl_SetResult(interp, "Spice process busy or nonexistant", NULL);
507 return TCL_ERROR;
510 send_to_spice("run");
511 msg = recv_from_spice(interp, EXPECT_REF);
512 if (*msg == '\0')
513 return TCL_ERROR;
514 else {
515 spice_state = SPICE_BUSY;
516 Fprintf(stdout, "%s", msg);
517 Flush(stdout);
519 break;
521 case SendIdx:
522 /* Allow commands in INIT mode, because they may come from */
523 /* parameterized labels. No output will be generated. */
524 if (spice_state == SPICE_INIT)
525 break;
526 else if (spice_state == SPICE_BUSY)
527 if (break_spice(interp) < 0)
528 return TCL_ERROR;
530 if (objc == 2) break;
532 /* Prevent execution of "run" and "resume" using this */
533 /* method, so we keep control over the output and the */
534 /* state of the simulator. */
536 msgin = Tcl_GetString(objv[2]);
537 if (!strncmp(msgin, "run", 3) || !strncmp(msgin, "resume", 6)) {
538 Tcl_SetResult(interp, "Do not use \"send\" with "
539 "\"run\" or \"resume\"\n", NULL);
540 return TCL_ERROR;
542 else
543 send_to_spice(msgin);
545 msg = recv_from_spice(interp, EXPECT_PROMPT);
546 if (*msg == '\0')
547 return TCL_ERROR;
548 else {
549 char *mp = msg;
550 /* ngspice echos the terminal, so walk past input */
551 while (*mp++ == *msgin++);
552 Tcl_SetResult(interp, mp, TCL_STATIC);
554 break;
556 case FlushIdx:
557 if (spice_state == SPICE_INIT)
558 break;
559 msg = recv_from_spice(interp, EXPECT_ANY); /* Grab input and discard */
560 break;
562 case GetIdx:
563 if (spice_state == SPICE_INIT)
564 break;
565 msg = recv_from_spice(interp, EXPECT_ANY); /* Grab input and return */
566 if (msg)
567 Tcl_SetResult(interp, msg, TCL_STATIC);
568 break;
570 case BreakIdx:
571 if (spice_state == SPICE_INIT)
572 break;
573 else if (spice_state == SPICE_BUSY)
574 if (break_spice(interp) < 0)
575 return TCL_ERROR;
577 /* Return the value of the stepsize from the function. */
578 send_to_spice("print length(TIME)");
579 goto process_result;
581 case PrintIdx:
582 /* Allow command in INIT mode, because it may come from */
583 /* parameterized labels. No output will be generated. */
584 if (spice_state == SPICE_INIT)
585 break;
587 /* Do *not* allow this command to be sent while spice */
588 /* is running. */
589 else if (spice_state == SPICE_BUSY)
590 if (break_spice(interp) < 0)
591 return TCL_ERROR;
593 if (objc == 2) break;
595 /* Similar to "spice send {print ...}", but processes */
596 /* the output like "spice break" does to return just */
597 /* the result. However, if no index is provided for */
598 /* the variable, we determine the timestep and set the */
599 /* index accordingly. */
601 msg = Tcl_GetString(objv[2]);
602 if (strchr(msg, '[') != NULL)
603 sprintf(_STR2, "print %s", msg);
604 else {
605 float refval;
606 char *stepstr;
608 send_to_spice("print length(TIME)");
609 stepstr = recv_from_spice(interp, EXPECT_PROMPT);
610 if (stepstr && ((eptr = strrchr(stepstr, '=')) != NULL)) {
611 eptr++;
612 while (isspace(*eptr)) eptr++;
613 if (sscanf(eptr, "%g", &refval) == 1)
614 sprintf(_STR2, "print %s[%d]", msg, (int)(refval - 1));
615 else
616 sprintf(_STR2, "print %s", msg);
618 else
619 sprintf(_STR2, "print %s", msg);
621 send_to_spice(_STR2);
623 process_result:
624 msg = recv_from_spice(interp, EXPECT_PROMPT); /* Grab the output */
625 if (msg && ((eptr = strrchr(msg, '=')) != NULL)) {
626 eptr++;
627 while (isspace(*eptr)) eptr++;
628 Tcl_SetResult(interp, eptr, TCL_STATIC);
630 else if (*msg == '\0')
631 return TCL_ERROR;
632 break;
634 case ResumeIdx:
635 if (spice_state != SPICE_READY) {
636 Tcl_SetResult(interp, "Spice process busy or nonexistant", NULL);
637 return TCL_ERROR;
639 resume_spice();
640 break;
642 case StatusIdx:
643 switch(spice_state) {
644 case SPICE_INIT:
645 Tcl_SetResult(interp, "init", NULL);
646 break;
647 case SPICE_BUSY:
648 Tcl_SetResult(interp, "busy", NULL);
649 break;
650 case SPICE_READY:
651 Tcl_SetResult(interp, "ready", NULL);
652 break;
654 break;
656 case ExitIdx:
657 exit_spice();
658 break;
660 return XcTagCallback(interp, objc, objv);
663 #endif /* TCL_WRAPPER */
664 /*----------------------------------------------------------------------*/