Implemented raw terminal I/O
[webos-internals:precomd.git] / src / novacom.c
1 /* 
2   To compile:
3     $ gcc -o precomd precomd.c -lusb
4 */
5
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <string.h>
9 #include <usb.h>
10 #include <termios.h>
11
12 #include "novacom.h"
13 #include "novacom_private.h"
14
15 struct usb_dev_handle* novacom_find_endpoints( uint32 *ep_in, uint32 *ep_out ) {
16     int c, i, a, ep ;
17     usb_dev_handle *retval = 0 ;
18     int ret = -1 ;
19     struct usb_device *dev;
20     struct usb_bus *bus;
21
22     usb_init();
23     usb_find_busses();
24     usb_find_devices();
25
26     /* Get all the USB busses to iterate through ... */
27     for (bus = usb_get_busses(); bus; bus = bus->next) {
28
29         /* For each device .... */
30         for (dev = bus->devices; dev; dev = dev->next) {
31
32             /* ... that's a Palm device! */
33             if( dev->descriptor.idVendor != USB_VENDOR_PALM ) continue ;
34
35             /* Loop through all of the configurations */
36             for (c = 0; c < dev->descriptor.bNumConfigurations; c++) {
37                 /* Loop through all of the interfaces */
38                 for (i = 0; i < dev->config[c].bNumInterfaces; i++) {
39                     /* Loop through all of the alternate settings */
40                     for (a = 0; a < dev->config[c].interface[i].num_altsetting; a++) {
41                         /* Check if this interface is novacom on the phone */
42                         if (is_interface_novacom(&(dev->config[c].interface[i].altsetting[a]))) {
43                             /* Open the device, set the alternate setting, claim the interface and do your processing */
44                             // printf( "Novacom found!\n") ;
45                             retval = usb_open( dev ) ;
46
47                             if( (ret=usb_claim_interface(retval, i)) < 0 ) {
48                                 printf( "Error claiming interface %i: %i\n", i, ret ) ;
49                                 exit( 1 ) ;
50                             }
51
52                             if( (ret=usb_set_altinterface( retval, a)) < 0 ) {
53                                 printf( "Error setting altinterface %i: %i\n", a, ret ) ;
54                                 exit( 1 ) ;
55                             }
56
57                             for( ep = 0 ; ep < dev->config[c].interface[i].altsetting[a].bNumEndpoints ; ep++ ) {
58                                 if( dev->config[c].interface[i].altsetting[a].endpoint[ep].bmAttributes == USB_ENDPOINT_TYPE_BULK ) {
59                                     if( dev->config[c].interface[i].altsetting[a].endpoint[ep].bEndpointAddress & USB_ENDPOINT_DIR_MASK ) {
60                                         *ep_in = dev->config[c].interface[i].altsetting[a].endpoint[ep].bEndpointAddress ;
61                                     }
62                                     else {
63                                         *ep_out = dev->config[c].interface[i].altsetting[a].endpoint[ep].bEndpointAddress ;
64                                     }
65                                 }
66                             }
67                         }
68                     }
69                 }
70             }
71         }
72     }
73
74     return retval ;
75
76 }
77
78 int is_interface_novacom(struct usb_interface_descriptor *interface)
79 {
80     return interface->bInterfaceClass == NOVACOM_USB_CLASS &&
81            interface->bInterfaceSubClass == NOVACOM_USB_SUBCLASS &&
82            interface->bInterfaceProtocol == NOVACOM_USB_PROTOCOL;
83 }
84
85 int novacom_init( novacom_device_t *dev ) {
86
87     if( (dev->phone=novacom_find_endpoints( &(dev->ep_rx), &(dev->ep_tx) )) == 0 ) {
88         fprintf( stderr, "ERROR: Novacom not found - are you sure your pre is plugged in?\n" ) ;
89         return -1 ;
90     }
91
92     printf( "Novacom found: bulk_ep_in: 0x%2.2x bulk_ep_out: 0x%2.2x\n\n", dev->ep_rx, dev->ep_tx ) ;
93
94     dev->state = STATE_WAIT_ANNOUNCE;
95     
96     return 0 ;
97 }
98
99 void print_buf( char *buf, uint32 size ) {
100     int x ;
101     char c ;
102     printf( "\nhex:\n\n    ") ;
103     for( x = 0 ; x < size ; x++ ) {
104         printf( "%2.2x ", buf[x]&0xff ) ;
105         if( (x+1) % 8 == 0 ) printf("\n    " ) ;
106     }
107     printf( "\nascii:\n\n    ") ;
108     for( x = 0 ; x < size ; x++ ) {
109         c = (char)buf[x]&0xff ;
110         c = (c < 32 || c > 126) ? '.' : c ;
111         printf( "%c ", c ) ;
112         if( (x+1) % 8 == 0 ) printf( "\n    " ) ;
113     }
114     printf( "\n" ) ;
115
116     return ;
117 }
118
119 void novacom_payload_print( uint32 command, uint8 payload[], uint32 size ) {
120     switch( command ) {
121         case NOVACOM_CMD_NOP           :
122             printf( "  nduid: %s\n", ((novacom_nop_t *)payload)->nduid ) ;
123             break ;
124         case NOVACOM_CMD_ANNOUNCEMENT  :
125             printf( "  nduid: %s\n", ((novacom_announcement_t *)payload)->nduid ) ;
126             break ;
127         case NOVACOM_CMD_PMUX         :
128             print_buf( (char *)payload, size ) ;
129     }
130
131     return ;
132
133 }
134
135 int novacom_packet_read( novacom_device_t *dev, uint32 size, uint32 timeout ) {
136     return usb_bulk_read( dev->phone, dev->ep_rx, (int8 *)&dev->packet, size, timeout ) ;
137 }
138
139 int novacom_packet_write( novacom_device_t *dev, uint32 size, uint32 timeout ) {
140     if (dev->state!=STATE_TTY) {
141         fprintf(stderr,"Writing packet of %d bytes\n",size);
142     }
143     return usb_bulk_write( dev->phone, dev->ep_tx, (int8 *)&dev->packet, size, timeout ) ;
144 }
145
146 void novacom_packet_print( novacom_packet_t *packet, uint32 size ) {
147
148     printf( "magic string   : 0x%8.8x\n", packet->magic ) ;
149     printf( "version        : 0x%8.8x\n", packet->version ) ;
150     printf( "sender id      : 0x%8.8x\n", packet->id_tx ) ;
151     printf( "receiver id    : 0x%8.8x\n", packet->id_rx ) ;
152     printf( "command        : " ) ;
153     switch( packet->command ) {
154         case NOVACOM_CMD_NOP            :
155         case NOVACOM_CMD_ANNOUNCEMENT   :
156         case NOVACOM_CMD_PMUX          :
157                                             printf( "%s\n", NOVACOM_COMMANDS[packet->command] ) ;
158                                             novacom_payload_print( packet->command, packet->payload, size-sizeof(novacom_packet_t) ) ;
159                                             break ;
160         default                         :   printf( "UNKNOWN - 0x%8.8xi\n", packet->command ) ;
161     }
162
163     printf( "\n" ) ;
164
165     return ;
166 }
167
168 static const char *dummy_serial = "0c698734f53d02dcf0b0fa2335de38992e65d9ce";
169
170 int novacom_reply_nop( novacom_device_t *dev, uint32 len ) {
171     novacom_nop_t *nop = (novacom_nop_t *) &(dev->packet.payload) ;
172     dev->packet.id_tx = dev->id_host ;
173     dev->packet.id_rx = dev->id_device ;    
174     sprintf( nop->nduid, dummy_serial) ;
175     return novacom_packet_write( dev, len, USB_TIMEOUT ) ;
176 }
177
178 int novacom_reply_announcement( novacom_device_t *dev, uint32 len ) {
179     novacom_announcement_t *announce = (novacom_announcement_t *) &(dev->packet.payload) ;
180     dev->packet.id_tx = dev->id_host ;
181     dev->packet.id_rx = dev->id_device ;
182     sprintf( announce->nduid, dummy_serial) ;
183     announce->mtu = 16384 ;
184     announce->heartbeat = 1000 ;
185     announce->timeout = 10000 ;
186     return novacom_packet_write( dev, len, USB_TIMEOUT ) ;
187 }
188
189 static void print_payload(uint8 *payload, uint32 len)
190 {
191     fprintf(stderr,"payload:");
192     int i = 0;
193     for (;i!=len;++i) {
194     fprintf(stderr," %02x",payload[i]);
195     }
196     fprintf(stderr,"\n");
197 }
198
199 static void print_pmux(pmux_packet_t *pmux)
200 {
201     fprintf(stderr,"pmux version: %02x\n",pmux->version);
202     fprintf(stderr,"pmux ack_synx: %02x\n",pmux->ack_synx);
203     fprintf(stderr,"pmux flags: %04x\n",pmux->flags);
204     fprintf(stderr,"pmux channel_num: %04x\n",pmux->channel_num);
205     fprintf(stderr,"pmux sequence_num: %08x\n",pmux->sequence_num);
206     fprintf(stderr,"pmux length_payload: %08x\n",pmux->length_payload);
207     fprintf(stderr,"pmux length_pmux_packet: %08x\n",pmux->length_pmux_packet);
208     print_payload(pmux->payload,pmux->length_payload);
209 }
210
211
212 static void pmux_ack(novacom_device_t *dev, uint32 seq_num)
213 {
214     if (dev->state!=STATE_TTY) {
215        fprintf(stderr,"Sending ack\n");
216     }
217     int len = 0;
218     dev->packet.id_tx = dev->id_host ;
219     dev->packet.id_rx = dev->id_device ;
220     dev->packet.command = NOVACOM_CMD_PMUX ;
221     len += sizeof(dev->packet);
222     {
223         pmux_packet_t *pmux = (pmux_packet_t *)dev->packet.payload;
224         pmux->magic = PMUX_ASCII_MAGIC;
225         pmux->version = PMUX_MODE_NORMAL;
226         pmux->pad = PMUX_OUT;
227         pmux->ack_synx = PMUX_ACK;
228         pmux->flags = dev->pmux_flags;
229         pmux->channel_num = 0;
230         pmux->sequence_num = seq_num;
231         pmux->length_payload = 0x00;
232         pmux->length_pmux_packet = 0x1c;
233         pmux->zero = 0x00000000;
234         len += sizeof(*pmux);
235     }
236     novacom_packet_write(dev,len,USB_TIMEOUT);
237 }
238
239
240 static void pmux_write_tty(novacom_device_t *dev,char *str,uint32 str_len)
241 {
242     int len = 0;
243
244     if (dev->state!=STATE_TTY) {
245         fprintf(stderr,"Sending text\n");
246     }
247     dev->packet.id_tx = dev->id_host ;
248     dev->packet.id_rx = dev->id_device ;
249     dev->packet.command = NOVACOM_CMD_PMUX ;
250     len += sizeof(dev->packet);
251     int cmd_len = sizeof(pmux_data_payload_t)+str_len;
252     pmux_packet_t *pmux = (pmux_packet_t *)dev->packet.payload;
253     pmux->magic = PMUX_ASCII_MAGIC;
254     pmux->version = PMUX_MODE_NORMAL;
255     pmux->pad = PMUX_OUT;
256     pmux->ack_synx = PMUX_SYN;
257     pmux->flags = PMUX_ESTABLISHED;
258     dev->pmux_flags = PMUX_ESTABLISHED;
259     pmux->channel_num = 0;
260     pmux->sequence_num = dev->pmux_tty_seq_num;
261     pmux->length_payload = cmd_len;
262     pmux->length_pmux_packet = 0x1c+cmd_len;
263     pmux->zero = 0x00000000;
264     pmux_data_payload_t *tty = (pmux_data_payload_t *)pmux->payload;
265     tty->magic = PMUX_DATA_MAGIC;
266     tty->version = 0x00000001;
267     tty->length = str_len;
268     tty->type = 0x00000000;
269     memcpy(tty->payload,str,str_len);
270     len += sizeof(*pmux);
271     len += cmd_len;
272
273     novacom_packet_write(dev,len,USB_TIMEOUT);
274 }
275
276
277 static void pmux_write_command(novacom_device_t *dev,char *cmd)
278 {
279     fprintf(stderr,"Sending %s\n",cmd);
280     int len = 0;
281     dev->packet.id_tx = dev->id_host ;
282     dev->packet.id_rx = dev->id_device ;
283     dev->packet.command = NOVACOM_CMD_PMUX ;
284     len += sizeof(dev->packet);
285     {
286         int cmd_len = strlen((char *)cmd);
287         pmux_packet_t *pmux = (pmux_packet_t *)dev->packet.payload;
288         pmux->magic = PMUX_ASCII_MAGIC;
289         pmux->version = PMUX_MODE_NORMAL;
290         pmux->pad = PMUX_OUT;
291         pmux->ack_synx = PMUX_SYN;
292         pmux->flags = PMUX_ESTABLISHED;
293         dev->pmux_flags = PMUX_ESTABLISHED;
294         pmux->sequence_num = 1;
295         pmux->channel_num = 0;
296         dev->pmux_tty_seq_num = 1;
297         pmux->length_payload = cmd_len;
298         pmux->length_pmux_packet = 0x1c+cmd_len;
299         pmux->zero = 0x00000000;
300         memcpy(pmux->payload,cmd,cmd_len);
301         len += sizeof(*pmux);
302         len += cmd_len;
303     }
304     novacom_packet_write(dev,len,USB_TIMEOUT);
305 }
306
307
308 static void print_payload_str(uint8 *payload,uint32 len)
309 {
310     int i = 0;
311     for (;i<len;++i) {
312         fprintf(stderr,"%c",payload[i]);
313     }
314 }
315
316
317 static int read_input(char *buf,uint32 buf_size)
318 {
319   fd_set readfds;
320   struct timeval timeout;
321   char *p = buf;
322
323   FD_ZERO(&readfds);
324   FD_SET(0,&readfds);
325   timeout.tv_sec = 0;
326   timeout.tv_usec = 0;
327
328   while (p<(buf+buf_size) && select(1,&readfds,0,0,&timeout)) {
329      read(0,p++,1);
330   }
331   return p-buf;
332 }
333
334 int pmux_terminal_open( novacom_device_t *dev ) 
335
336     int len = 0;
337
338     fprintf(stderr,"Opening terminal\n");
339     dev->packet.id_tx = dev->id_host ;
340     dev->packet.id_rx = dev->id_device ;
341     dev->packet.command = NOVACOM_CMD_PMUX ;
342     len += sizeof(dev->packet);
343     {
344         pmux_packet_t *pmux = (pmux_packet_t *)dev->packet.payload;
345         pmux->magic = PMUX_ASCII_MAGIC;
346         pmux->version = PMUX_MODE_NORMAL;
347         pmux->pad = PMUX_OUT;
348         pmux->ack_synx = PMUX_SYN;
349         pmux->flags = PMUX_NOT_CONNECTED;
350         dev->pmux_flags = PMUX_NOT_CONNECTED;
351         pmux->channel_num = 0;
352         pmux->sequence_num = 1;
353         pmux->length_payload = 0x0c;
354         pmux->length_pmux_packet = 0x28;
355         pmux->zero = 0x00000000;
356         len += sizeof(*pmux);
357         {
358             pmux_control_payload_t *pmux_open = (pmux_control_payload_t *)pmux->payload;
359             pmux_open->command = PMUX_CMD_OPEN;
360             pmux_open->hex1000 = 0x00001000;
361             pmux_open->hex000c = 0x0000000c;
362             len += sizeof(*pmux_open);
363         }
364     }
365     dev->state = STATE_OPEN_ACK;
366     return novacom_packet_write( dev, len, USB_TIMEOUT ) ;
367 }
368
369 int pmux_packet_process( novacom_device_t *dev ) { 
370     pmux_packet_t *pmux = (pmux_packet_t *) &(dev->packet.payload) ;
371     
372     if (pmux->magic != PMUX_ASCII_MAGIC) {
373         fprintf(stderr,"Invalid pmux magic: %08x\n",pmux->magic);
374         exit(1) ;
375     }
376
377     if (pmux->version != PMUX_MODE_NORMAL) {
378         fprintf(stderr,"Invalid pmux version\n");
379         exit(1);
380     }
381
382     if (pmux->ack_synx==PMUX_ACK) {
383         if (dev->state!=STATE_TTY) {
384             fprintf(stderr,"Got ack for %d\n",pmux->sequence_num);
385         }
386         if (dev->state==STATE_OPEN_ACK) {
387             fprintf(stderr,"Going to open tty state\n");
388             pmux_write_command(dev,"open tty://");
389             dev->state=STATE_COMMAND_ACK;
390         }
391         else if (dev->state==STATE_COMMAND_ACK) {
392             fprintf(stderr,"Going to wait ok state\n");
393             dev->state=STATE_WAIT_OK;
394             ++dev->pmux_tty_seq_num;
395         }
396         else if (dev->state==STATE_TTY) {
397             ++dev->pmux_tty_seq_num;
398         }
399     }
400     else {
401         if (pmux->payload[0]==PMUX_CMD_CLOSE) {
402             fprintf(stderr,"Channel closed.\n");
403             pmux_ack(dev,pmux->sequence_num);
404             dev->state=STATE_CLOSED;
405         }
406         else if (dev->state==STATE_WAIT_OK) {
407             fprintf(stderr,"Got response: '");
408             print_payload_str(pmux->payload,pmux->length_payload);
409             fprintf(stderr,"'\n");
410             pmux_ack(dev,pmux->sequence_num);
411             dev->state=STATE_TTY;
412             fprintf(stderr,"Going to tty state\n");
413         }
414         else if (dev->state==STATE_TTY) {
415             pmux_data_payload_t *tty = (pmux_data_payload_t *)pmux->payload;
416             if (tty->magic!=PMUX_DATA_MAGIC) {
417                 fprintf(stderr,"Unknown control.\n");
418                 print_pmux(pmux);
419                 dev->pmux_flags = pmux->flags;
420                 pmux_ack(dev,pmux->sequence_num);
421                 dev->state=STATE_LIMBO;
422             }
423             else {
424                 if (tty->type==PMUX_HEADER_TYPE_OOB) {
425                     fprintf(stderr,"Received oob data\n");
426                     print_pmux(pmux);
427                 }
428                 else if (tty->type==PMUX_HEADER_TYPE_ERR) {
429                     fprintf(stderr,"Received error\n");
430                     print_pmux(pmux);
431                 }
432                 else {
433                   int len = tty->length;
434                   print_payload_str(tty->payload,len);
435                 }
436                 pmux_ack(dev,pmux->sequence_num);
437             }
438         }
439         else {
440             fprintf(stderr,"Unexpected syn\n");
441             print_pmux(pmux);
442             pmux_ack(dev,pmux->sequence_num);
443 #if 0
444             pmux_terminal_open(dev);
445 #endif
446         }
447     }
448
449     return 0 ;
450 }
451
452 int novacom_packet_process( novacom_device_t *dev, uint32 len ) {
453     // TODO: Perform checking for magic number and other header
454     // information here
455     switch ( dev->packet.command ) {
456         case NOVACOM_CMD_NOP    :
457             {
458                 novacom_packet_t *packet = &dev->packet;
459                 if (dev->state!=STATE_TTY) {
460                     printf("Got NOP: id_tx=%x, id_rx=%x",packet->id_tx,packet->id_rx);
461                     printf( "Sending NOP reply\n" ) ;
462                 }
463             }
464             novacom_reply_nop( dev, len ) ;
465             if (dev->state==STATE_GOT_ANNOUNCE) {
466                 pmux_terminal_open(dev);
467             }
468             break ;
469
470         case NOVACOM_CMD_ANNOUNCEMENT :
471             // TODO: Randomize this value
472             dev->id_host   = 0x00004ae1 ;
473             dev->id_device = dev->packet.id_tx ;
474             printf( "Sending ANNOUNCEMENT reply\n" ) ;
475             if (novacom_reply_announcement( dev, len )<0) {
476                 return -1;
477             }
478             dev->state = STATE_GOT_ANNOUNCE;
479             break ;
480
481         case NOVACOM_CMD_PMUX :
482             if (dev->state!=STATE_TTY) {
483                 printf( "Processing PMUX packet\n" ) ;
484             }
485             return pmux_packet_process( dev ) ;
486             break ;
487
488         default :
489             printf( "Not sure wtf to do\n" ) ;
490             break ;
491
492     }
493     return -1 ;
494 }
495
496 int error_check( int ret, int quit, char *msg ) {
497     if( ret < 0 ) {
498         fprintf( stderr, "ERROR: %s\n", msg ) ;
499         if( quit ) exit( 1 ) ;
500     }
501     return ret ;
502 }
503
504
505 static void make_raw_tty(int fd,struct termios *orig)
506 {
507     struct termios attr;
508     tcgetattr(fd,&attr);
509     *orig = attr;
510     attr.c_lflag &= ~ICANON;
511     attr.c_lflag &= ~ECHO;
512     attr.c_cc[VMIN] = 1;
513     attr.c_cc[VTIME] = 0;
514     attr.c_cc[VEOF] = 0;
515     attr.c_cc[VINTR] = 0;
516     attr.c_cc[VQUIT] = 0;
517     tcsetattr(fd,TCSANOW,&attr);
518 }
519
520 static void restore_tty(int fd,struct termios *attrs)
521 {
522     tcsetattr(fd,TCSANOW,attrs);
523 }
524
525 int pmux_file_put( novacom_device_t *dev ) { return 0 ; }
526 int pmux_file_get( novacom_device_t *dev ) { return 0 ; }
527
528 int pmux_terminal_close( novacom_device_t *dev ) { return 0 ; }
529 int pmux_terminal_send( novacom_device_t *dev, char *cmd ) { return 0 ; }
530 int pmux_terminal_receive( novacom_device_t *dev, char *buf ) { return 0 ; }
531
532 int pmux_program_run( novacom_device_t *dev, uint32 argc, char **argv ) { return 0 ; }
533
534 int pmux_mem_put( novacom_device_t *dev, uint32 addr, uint32 data ) { return 0 ; }
535 int pmux_mem_boot( novacom_device_t *dev, uint32 addr ) { return 0 ; }
536
537 int main () {
538     
539     int ret;
540     struct termios orig_tty_attr;
541
542     make_raw_tty(0,&orig_tty_attr);
543     
544     /* The USB device with other relavent information */ 
545     novacom_device_t *dev = (novacom_device_t *)malloc( sizeof(novacom_device_t)+USB_BUFLEN ) ;
546     
547     /* Check to see if we're root before we really get into this thing */
548     if( geteuid() != 0 ) {
549         fprintf( stderr, "ERROR: root required for USB privilidges.  Please re-run command as root.\n" ) ;
550         exit( 1 ) ;
551     }
552     
553     /* Initialize novacom communications */
554     error_check( novacom_init( dev ), 1, "Unable to find or initialize Novacom - is your pre plugged in?\n" ) ;
555     
556     /* Iterate through a NOP loop */
557     while (dev->state!=STATE_CLOSED) {
558 #if 0
559         ret = error_check( novacom_packet_read( dev, USB_BUFLEN, USB_TIMEOUT ), 0, "Timeout or error reading USB!\n" ) ;
560 #else
561         ret = novacom_packet_read( dev, USB_BUFLEN, USB_TIMEOUT );
562 #endif
563
564         if( ret > 0 ) {
565             if (dev->state!=STATE_TTY) {
566                     printf( "Read %i bytes - success!\n", ret ) ;
567             }
568             novacom_packet_process( dev, ret ) ;
569         }
570         if (dev->state==STATE_TTY) {
571             char buf[1024];
572             int n = read_input(buf,(sizeof buf)-1);
573             buf[n] = '\0';
574             if (dev->state!=STATE_TTY) {
575                 fprintf(stderr,"terminal input: '%s'\n",buf);
576             }
577             if (n>0) {
578                 pmux_write_tty(dev,buf,n);
579             }
580         }
581     }
582     restore_tty(0,&orig_tty_attr);
583     
584     return 0 ;
585 }