2ca698ba691968561e4cacbc1c24d61f9ce07623
[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                 timeout.tv_sec=2;
140                 timeout.tv_usec=0;/*500*1000;*/
141
142                 res = select(PtyRDFD+1,&readfds,NULL/*writefds*/,NULL/*exceptfds*/,&timeout);
143
144                 switch (res) {
145                         case 0: /* Timeout */
146                                 N6110_SendStatusRequest();
147                                 break;
148
149                         case -1:
150                                 if (errno==EINTR)
151                                         continue;
152                                 perror("VM_ThreadLoop - select");
153                                 return (false);
154
155                         default:
156                           if (FD_ISSET(PtyRDFD,&readfds)) {
157                             VM_CharHandler();
158                           } else usleep(500); /* Probably the file has been closed */
159                           break;
160                 }
161           }
162         }
163         
164 }
165
166         /* Application should call VM_Terminate to shut down
167            the virtual modem thread */
168 void            VM_Terminate(void)
169 {
170         if (!UseSTDIO) {
171                 close (PtyRDFD);
172                 close (PtyWRFD);
173         }
174 }
175
176 static int      VM_GetMasterPty(char **name);
177
178         /* Open pseudo tty interface and (in due course create a symlink
179            to be /dev/gnokii etc. ) */
180
181 static int              VM_PtySetup(char *bindir)
182 {
183         int                     err;
184 #ifndef UCLINUX
185         char            mgnokiidev[200];
186 #endif /* UCLINUX */
187         char            *slave_name;
188 #ifndef UCLINUX
189         char            cmdline[200];
190 #endif /* UCLINUX */
191
192 #ifndef UCLINUX
193         if (bindir) {
194                 strncpy(mgnokiidev, bindir, 200);
195                 strcat(mgnokiidev, "/");
196         }
197         strncat(mgnokiidev, "mgnokiidev", 200 - strlen(bindir));
198 #endif /* UCLINUX */
199
200         if (UseSTDIO) {
201                 PtyRDFD = STDIN_FILENO;
202                 PtyWRFD = STDOUT_FILENO;
203                 return (0);
204         }
205         
206         PtyRDFD = VM_GetMasterPty(&slave_name);
207         if (PtyRDFD < 0) {
208                 fprintf (stderr, _("Couldn't open pty!\n"));
209                 return(-1);
210         }
211         PtyWRFD = PtyRDFD;
212
213 #ifndef UCLINUX
214                 /* Check we haven't been installed setuid root for some reason
215                    if so, don't create /dev/gnokii */
216         if (getuid() != geteuid()) {
217                 fprintf(stderr, _("gnokiid should not be installed setuid root!\n"));
218                 return (0);
219         }
220 #endif
221
222 #ifdef DEBUG
223         fprintf (stderr, _("Slave pty is %s, calling %s to create \"%s\".\n"), slave_name,
224 #ifndef UCLINUX
225                         mgnokiidev
226 #else /* UCLINUX */
227                         "<uClinux>"
228 #endif /* UCLINUX */
229                         ,GNOKII_DEV);
230 #endif /* DEBUG */
231
232 #ifndef UCLINUX
233                 /* Create command line, something line ./mkgnokiidev ttyp0 */
234         sprintf(cmdline, "%s %s", mgnokiidev, slave_name);
235
236                 /* And use system to call it. */        
237         err = system (cmdline);
238 #else /* UCLINUX */
239
240         /* Remove symlink in case it already exists. Don't care if it fails.  */
241         unlink (GNOKII_DEV);
242
243         /* Create symlink */
244         err = symlink(slave_name, GNOKII_DEV);
245
246 #endif /* UCLINUX */
247         
248         return (err);
249
250 }
251
252     /* Handler called when characters received from serial port.
253        calls state machine code to process it. */
254 static void    VM_CharHandler(void)
255 {
256     unsigned char   buffer[255];
257     int             res;
258
259
260     /* If we are in command mode, get the character, otherwise leave it */
261
262     if (CommandMode && ATEM_Initialised) {
263       
264       res = read(PtyRDFD, buffer, 255);
265       
266       /* A returned value of -1 means something serious has gone wrong - so quit!! */
267       /* Note that file closure etc. should have been dealt with in ThreadLoop */
268       
269       if (res < 0) {    
270 //          TerminateThread=true;
271             return;
272       }
273         
274       ATEM_HandleIncomingData(buffer, res);
275     }   
276 }     
277
278         /* Initialise GSM interface, returning GSM_Error as appropriate  */
279 static GSM_Error        VM_GSMInitialise(char *model, char *port, char *initlength, GSM_ConnectionType connection, char *synchronizetime)
280 {
281         int             count=0;
282         GSM_Error       error;
283
284                 /* Initialise the code for the GSM interface. */     
285
286         error = GSM_Initialise(model,port, initlength, connection, RLP_DisplayF96Frame, synchronizetime);
287
288         if (error != GE_NONE) {
289                 fprintf(stderr, _("GSM/FBUS init failed! (Unknown model ?). Quitting.\n"));
290         return (error);
291         }
292
293                 /* First (and important!) wait for GSM link to be active. We allow 10
294                    seconds... */
295
296         while (count++ < 200 && *GSM_LinkOK == false) {
297         usleep(50000);
298         }
299
300         if (*GSM_LinkOK == false) {
301                 fprintf (stderr, _("Hmmm... GSM_LinkOK never went true. Quitting. \n"));
302                 return (GE_NOLINK); 
303         }
304
305         return (GE_NONE);
306 }
307
308 /* VM_GetMasterPty()
309    Takes a double-indirect character pointer in which to put a slave
310    name, and returns an integer file descriptor.  If it returns < 0, an
311    error has occurred.  Otherwise, it has returned the master pty
312    file descriptor, and fills in *name with the name of the
313    corresponding slave pty.  Once the slave pty has been opened,
314    you are responsible to free *name.  Code is from Developling Linux
315    Applications by Troan and Johnson */
316
317
318 static int      VM_GetMasterPty(char **name) { 
319
320 #ifdef USE_UNIX98PTYS
321         int master, err;
322
323         master = open("/dev/ptmx", O_RDWR | O_NOCTTY | O_NONBLOCK);
324         if (master >= 0) {
325                 err = grantpt(master);
326                 err = err || unlockpt(master);
327                 if (!err) {
328                         *name = ptsname(master);
329                 } else {
330                         return(-1);
331                 }
332         }
333 #else /* USE_UNIX98PTYS */
334    int i = 0 , j = 0;
335    /* default to returning error */
336    int master = -1;
337
338    /* create a dummy name to fill in */
339    *name = strdup("/dev/ptyXX");
340
341    /* search for an unused pty */
342    for (i=0; i<16 && master <= 0; i++) {
343       for (j=0; j<16 && master <= 0; j++) {
344          (*name)[8] = "pqrstuvwxyzPQRST"[i];
345          (*name)[9] = "0123456789abcdef"[j];
346          /* open the master pty */
347          if ((master = open(*name, O_RDWR | O_NOCTTY | O_NONBLOCK )) < 0) {
348             if (errno == ENOENT) {
349                /* we are out of pty devices */
350                free (*name);
351                return (master);
352             }
353          }
354       }
355    }
356    if ((master < 0) && (i == 16) && (j == 16)) {
357       /* must have tried every pty unsuccessfully */
358       free (*name);
359       return (master);
360    }
361
362    /* By substituting a letter, we change the master pty
363     * name into the slave pty name.
364     */
365    (*name)[5] = 't';
366
367 #endif /* USE_UNIX98PTYS */
368
369    return (master);
370 }
371