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