VM_ThreadLoop select() silently restarted when EINTR
[gnokii.git] / common / data / virtmodem.c
1 /*
2   $Id$
3
4   G N O K I I
5
6   A Linux/Unix toolset and driver for Nokia mobile phones.
7
8   Released under the terms of the GNU GPL, see file COPYING for more details.
9
10   This file provides a virtual modem interface to the GSM phone by calling
11   code in gsm-api.c, at-emulator.c and datapump.c. The code here provides
12   the overall framework and coordinates switching between command mode
13   (AT-emulator) and "online" mode where the data pump code translates data
14   from/to the GSM handset and the modem data/fax stream.
15
16 */
17
18 #define         __virtmodem_c
19
20 #include <config.h>
21
22 /* This is the right way to include stdlib with __USE_XOPEN defined */
23 #ifdef USE_UNIX98PTYS
24 # define _XOPEN_SOURCE 500
25 # include <features.h>
26 #endif
27
28 #include <stdio.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <signal.h>
32 #include <termios.h>
33 #include <grp.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #ifndef UCLINUX
39 #include <sys/poll.h>
40 #include <pthread.h>
41 #endif /* UCLINUX */
42 #include <unistd.h>
43
44 #include "misc.h"
45 #include "gsm-api.h"
46 #include "gsm-common.h"
47 #include "data/at-emulator.h"
48 #include "data/datapump.h"
49 #include "data/virtmodem.h"
50 #include "data/rlp-common.h"
51
52         /* Global variables */
53
54 //extern bool TerminateThread;
55
56         /* Local variables */
57
58 int             PtyRDFD;        /* File descriptor for reading and writing to/from */
59 int             PtyWRFD;        /* pty interface - only different in debug mode. */ 
60
61 static bool     UseSTDIO;       /* Use STDIO for debugging purposes instead of pty */
62 bool    CommandMode;
63
64 static bool     VM_ThreadLoop(void);
65 static GSM_Error        VM_GSMInitialise(char *model, char *port, char *initlength, GSM_ConnectionType connection, char *synchronizetime);
66 static int              VM_PtySetup(char *bindir);
67
68         /* If initialised in debug mode, stdin/out is used instead
69            of ptys for interface. */
70 bool VM_Initialise(char *model,char *port, char *initlength, GSM_ConnectionType connection, char *bindir, bool debug_mode, bool GSMInit,char *synchronizetime)
71 {
72         CommandMode = true;
73
74         if (debug_mode == true) {
75                 UseSTDIO = true;
76         }
77         else {
78                 UseSTDIO = false;
79         }
80
81         if (GSMInit) {
82 #ifdef DEBUG
83           fprintf (stderr , "Initialising GSM\n");
84 #endif /* DEBUG */
85           if ((VM_GSMInitialise(model, port, initlength, connection, synchronizetime) != GE_NONE)) {
86                 fprintf (stderr, _("VM_Initialise - VM_GSMInitialise failed!\n"));
87                 return (false);
88                 
89           }
90         }
91         GSMInit=false;
92
93         if (VM_PtySetup(bindir) < 0) {
94                 fprintf (stderr, _("VM_Initialise - VM_PtySetup failed!\n"));
95                 return (false);
96         }
97     
98         if (ATEM_Initialise(PtyRDFD, PtyWRFD, model, port) != true) {
99                 fprintf (stderr, _("VM_Initialise - ATEM_Initialise failed!\n"));
100                 return (false);
101         }
102
103         if (DP_Initialise(PtyRDFD, PtyWRFD) != true) {
104                 fprintf (stderr, _("VM_Initialise - DP_Initialise failed!\n"));
105                 return (false);
106         }
107
108         /* Create and start thread, */
109         return VM_ThreadLoop();
110 }
111
112 static void    VM_CharHandler(void);
113
114 static bool VM_ThreadLoop(void)
115 {
116         int res;
117         fd_set readfds;
118         struct timeval timeout;
119
120                 /* Note we can't use signals here as they are already used
121                    in the FBUS code.  This may warrant changing the FBUS
122                    code around sometime to use select instead to free up
123                    the SIGIO handler for mainline code. */
124
125         for (;;) {
126           if (!CommandMode) {
127             sleep(1);
128           } else {  /* If we are in data mode, leave it to datapump to get the data */
129
130                 FD_ZERO(&readfds);
131                 FD_SET(PtyRDFD,&readfds);
132                 timeout.tv_sec=0;
133                 timeout.tv_usec=500*1000;
134
135                 res = select(PtyRDFD+1,&readfds,NULL/*writefds*/,NULL/*exceptfds*/,&timeout);
136
137                 switch (res) {
138                         case 0: /* Timeout */
139                                 break;
140
141                         case -1:
142                                 if (errno==EINTR)
143                                         continue;
144                                 perror("VM_ThreadLoop - select");
145                                 return (false);
146
147                         default:
148                           if (FD_ISSET(PtyRDFD,&readfds)) {
149                             VM_CharHandler();
150                           } else usleep(500); /* Probably the file has been closed */
151                           break;
152                 }
153           }
154         }
155         
156 }
157
158         /* Application should call VM_Terminate to shut down
159            the virtual modem thread */
160 void            VM_Terminate(void)
161 {
162         if (!UseSTDIO) {
163                 close (PtyRDFD);
164                 close (PtyWRFD);
165         }
166 }
167
168 static int      VM_GetMasterPty(char **name);
169
170         /* Open pseudo tty interface and (in due course create a symlink
171            to be /dev/gnokii etc. ) */
172
173 static int              VM_PtySetup(char *bindir)
174 {
175         int                     err;
176 #ifndef UCLINUX
177         char            mgnokiidev[200];
178 #endif /* UCLINUX */
179         char            *slave_name;
180 #ifndef UCLINUX
181         char            cmdline[200];
182 #endif /* UCLINUX */
183
184 #ifndef UCLINUX
185         if (bindir) {
186                 strncpy(mgnokiidev, bindir, 200);
187                 strcat(mgnokiidev, "/");
188         }
189         strncat(mgnokiidev, "mgnokiidev", 200 - strlen(bindir));
190 #endif /* UCLINUX */
191
192         if (UseSTDIO) {
193                 PtyRDFD = STDIN_FILENO;
194                 PtyWRFD = STDOUT_FILENO;
195                 return (0);
196         }
197         
198         PtyRDFD = VM_GetMasterPty(&slave_name);
199         if (PtyRDFD < 0) {
200                 fprintf (stderr, _("Couldn't open pty!\n"));
201                 return(-1);
202         }
203         PtyWRFD = PtyRDFD;
204
205 #ifndef UCLINUX
206                 /* Check we haven't been installed setuid root for some reason
207                    if so, don't create /dev/gnokii */
208         if (getuid() != geteuid()) {
209                 fprintf(stderr, _("gnokiid should not be installed setuid root!\n"));
210                 return (0);
211         }
212 #endif
213
214 #ifdef DEBUG
215         fprintf (stderr, _("Slave pty is %s, calling %s to create /dev/gnokii.\n"), slave_name,
216 #ifndef UCLINUX
217                         mgnokiidev
218 #else /* UCLINUX */
219                         "<uClinux>"
220 #endif /* UCLINUX */
221                         );
222 #endif /* DEBUG */
223
224 #ifndef UCLINUX
225                 /* Create command line, something line ./mkgnokiidev ttyp0 */
226         sprintf(cmdline, "%s %s", mgnokiidev, slave_name);
227
228                 /* And use system to call it. */        
229         err = system (cmdline);
230 #else /* UCLINUX */
231
232         /* Remove symlink in case it already exists. Don't care if it fails.  */
233         unlink ("/dev/gnokii");
234
235         /* Create symlink */
236         err = symlink(slave_name, "/dev/gnokii");
237
238 #endif /* UCLINUX */
239         
240         return (err);
241
242 }
243
244     /* Handler called when characters received from serial port.
245        calls state machine code to process it. */
246 static void    VM_CharHandler(void)
247 {
248     unsigned char   buffer[255];
249     int             res;
250
251
252     /* If we are in command mode, get the character, otherwise leave it */
253
254     if (CommandMode && ATEM_Initialised) {
255       
256       res = read(PtyRDFD, buffer, 255);
257       
258       /* A returned value of -1 means something serious has gone wrong - so quit!! */
259       /* Note that file closure etc. should have been dealt with in ThreadLoop */
260       
261       if (res < 0) {    
262 //          TerminateThread=true;
263             return;
264       }
265         
266       ATEM_HandleIncomingData(buffer, res);
267     }   
268 }     
269
270         /* Initialise GSM interface, returning GSM_Error as appropriate  */
271 static GSM_Error        VM_GSMInitialise(char *model, char *port, char *initlength, GSM_ConnectionType connection, char *synchronizetime)
272 {
273         int             count=0;
274         GSM_Error       error;
275
276                 /* Initialise the code for the GSM interface. */     
277
278         error = GSM_Initialise(model,port, initlength, connection, RLP_DisplayF96Frame, synchronizetime);
279
280         if (error != GE_NONE) {
281                 fprintf(stderr, _("GSM/FBUS init failed! (Unknown model ?). Quitting.\n"));
282         return (error);
283         }
284
285                 /* First (and important!) wait for GSM link to be active. We allow 10
286                    seconds... */
287
288         while (count++ < 200 && *GSM_LinkOK == false) {
289         usleep(50000);
290         }
291
292         if (*GSM_LinkOK == false) {
293                 fprintf (stderr, _("Hmmm... GSM_LinkOK never went true. Quitting. \n"));
294                 return (GE_NOLINK); 
295         }
296
297         return (GE_NONE);
298 }
299
300 /* VM_GetMasterPty()
301    Takes a double-indirect character pointer in which to put a slave
302    name, and returns an integer file descriptor.  If it returns < 0, an
303    error has occurred.  Otherwise, it has returned the master pty
304    file descriptor, and fills in *name with the name of the
305    corresponding slave pty.  Once the slave pty has been opened,
306    you are responsible to free *name.  Code is from Developling Linux
307    Applications by Troan and Johnson */
308
309
310 static int      VM_GetMasterPty(char **name) { 
311
312 #ifdef USE_UNIX98PTYS
313         int master, err;
314
315         master = open("/dev/ptmx", O_RDWR | O_NOCTTY | O_NONBLOCK);
316         if (master >= 0) {
317                 err = grantpt(master);
318                 err = err || unlockpt(master);
319                 if (!err) {
320                         *name = ptsname(master);
321                 } else {
322                         return(-1);
323                 }
324         }
325 #else /* USE_UNIX98PTYS */
326    int i = 0 , j = 0;
327    /* default to returning error */
328    int master = -1;
329
330    /* create a dummy name to fill in */
331    *name = strdup("/dev/ptyXX");
332
333    /* search for an unused pty */
334    for (i=0; i<16 && master <= 0; i++) {
335       for (j=0; j<16 && master <= 0; j++) {
336          (*name)[8] = "pqrstuvwxyzPQRST"[i];
337          (*name)[9] = "0123456789abcdef"[j];
338          /* open the master pty */
339          if ((master = open(*name, O_RDWR | O_NOCTTY | O_NONBLOCK )) < 0) {
340             if (errno == ENOENT) {
341                /* we are out of pty devices */
342                free (*name);
343                return (master);
344             }
345          }
346       }
347    }
348    if ((master < 0) && (i == 16) && (j == 16)) {
349       /* must have tried every pty unsuccessfully */
350       free (*name);
351       return (master);
352    }
353
354    /* By substituting a letter, we change the master pty
355     * name into the slave pty name.
356     */
357    (*name)[5] = 't';
358
359 #endif /* USE_UNIX98PTYS */
360
361    return (master);
362 }
363