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