don't try to include bluetooth headers
[fw/altos] / altosui / libaltos / libaltos.c
1 /*
2  * Copyright © 2010 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include "libaltos.h"
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #define USB_VENDOR_FSF                  0xfffe
24 #define USB_VENDOR_ALTUSMETRUM          USB_VENDOR_FSF
25 #define USB_PRODUCT_ALTUSMETRUM         0x000a
26 #define USB_PRODUCT_TELEMETRUM          0x000b
27 #define USB_PRODUCT_TELEDONGLE          0x000c
28 #define USB_PRODUCT_TELETERRA           0x000d
29 #define USB_PRODUCT_TELEBT              0x000e
30 #define USB_PRODUCT_ALTUSMETRUM_MIN     0x000a
31 #define USB_PRODUCT_ALTUSMETRUM_MAX     0x0013
32
33 #define USB_IS_ALTUSMETRUM(v,p) ((v) == USB_VENDOR_ALTUSMETRUM && \
34                 (USB_PRODUCT_ALTUSMETRUM_MIN <= (p) && \
35                  (p) <= USB_PRODUCT_ALTUSMETRUM_MAX))
36
37 #define BLUETOOTH_PRODUCT_TELEBT        "TeleBT"
38
39 #define USE_POLL
40
41 PUBLIC int
42 altos_init(void)
43 {
44         return LIBALTOS_SUCCESS;
45 }
46
47 PUBLIC void
48 altos_fini(void)
49 {
50 }
51
52 #ifdef DARWIN
53
54 #undef USE_POLL
55
56 /* Mac OS X don't have strndup even if _GNU_SOURCE is defined */
57 static char *
58 altos_strndup (const char *s, size_t n)
59 {
60     size_t len = strlen (s);
61     char *ret;
62
63     if (len <= n)
64        return strdup (s);
65     ret = malloc(n + 1);
66     strncpy(ret, s, n);
67     ret[n] = '\0';
68     return ret;
69 }
70
71 #else
72 #define altos_strndup strndup
73 #endif
74
75 #ifdef POSIX_TTY
76
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <fcntl.h>
80 #include <termios.h>
81 #include <errno.h>
82
83 #define USB_BUF_SIZE    64
84
85 struct altos_file {
86         int                             fd;
87 #ifdef USE_POLL
88         int                             pipe[2];
89 #else
90         int                             out_fd;
91 #endif
92         unsigned char                   out_data[USB_BUF_SIZE];
93         int                             out_used;
94         unsigned char                   in_data[USB_BUF_SIZE];
95         int                             in_used;
96         int                             in_read;
97 };
98
99 PUBLIC struct altos_file *
100 altos_open(struct altos_device *device)
101 {
102         struct altos_file       *file = calloc (sizeof (struct altos_file), 1);
103         int                     ret;
104         struct termios          term;
105
106         if (!file)
107                 return NULL;
108
109         file->fd = open(device->path, O_RDWR | O_NOCTTY);
110         if (file->fd < 0) {
111                 perror(device->path);
112                 free(file);
113                 return NULL;
114         }
115 #ifdef USE_POLL
116         pipe(file->pipe);
117 #else
118         file->out_fd = open(device->path, O_RDWR | O_NOCTTY);
119         if (file->out_fd < 0) {
120                 perror(device->path);
121                 close(file->fd);
122                 free(file);
123                 return NULL;
124         }
125 #endif
126         ret = tcgetattr(file->fd, &term);
127         if (ret < 0) {
128                 perror("tcgetattr");
129                 close(file->fd);
130 #ifndef USE_POLL
131                 close(file->out_fd);
132 #endif
133                 free(file);
134                 return NULL;
135         }
136         cfmakeraw(&term);
137 #ifdef USE_POLL
138         term.c_cc[VMIN] = 1;
139         term.c_cc[VTIME] = 0;
140 #else
141         term.c_cc[VMIN] = 0;
142         term.c_cc[VTIME] = 1;
143 #endif
144         ret = tcsetattr(file->fd, TCSAFLUSH, &term);
145         if (ret < 0) {
146                 perror("tcsetattr");
147                 close(file->fd);
148 #ifndef USE_POLL
149                 close(file->out_fd);
150 #endif
151                 free(file);
152                 return NULL;
153         }
154         return file;
155 }
156
157 PUBLIC void
158 altos_close(struct altos_file *file)
159 {
160         if (file->fd != -1) {
161                 int     fd = file->fd;
162                 file->fd = -1;
163 #ifdef USE_POLL
164                 write(file->pipe[1], "\r", 1);
165 #else
166                 close(file->out_fd);
167                 file->out_fd = -1;
168 #endif
169                 close(fd);
170         }
171 }
172
173 PUBLIC void
174 altos_free(struct altos_file *file)
175 {
176         altos_close(file);
177         free(file);
178 }
179
180 PUBLIC int
181 altos_flush(struct altos_file *file)
182 {
183         if (file->out_used && 0) {
184                 printf ("flush \"");
185                 fwrite(file->out_data, 1, file->out_used, stdout);
186                 printf ("\"\n");
187         }
188         while (file->out_used) {
189                 int     ret;
190
191                 if (file->fd < 0)
192                         return -EBADF;
193 #ifdef USE_POLL
194                 ret = write (file->fd, file->out_data, file->out_used);
195 #else
196                 ret = write (file->out_fd, file->out_data, file->out_used);
197 #endif
198                 if (ret < 0)
199                         return -errno;
200                 if (ret) {
201                         memmove(file->out_data, file->out_data + ret,
202                                 file->out_used - ret);
203                         file->out_used -= ret;
204                 }
205         }
206         return 0;
207 }
208
209 PUBLIC int
210 altos_putchar(struct altos_file *file, char c)
211 {
212         int     ret;
213
214         if (file->out_used == USB_BUF_SIZE) {
215                 ret = altos_flush(file);
216                 if (ret) {
217                         return ret;
218                 }
219         }
220         file->out_data[file->out_used++] = c;
221         ret = 0;
222         if (file->out_used == USB_BUF_SIZE)
223                 ret = altos_flush(file);
224         return 0;
225 }
226
227 #ifdef USE_POLL
228 #include <poll.h>
229 #endif
230
231 static int
232 altos_fill(struct altos_file *file, int timeout)
233 {
234         int             ret;
235 #ifdef USE_POLL
236         struct pollfd   fd[2];
237 #endif
238
239         if (timeout == 0)
240                 timeout = -1;
241         while (file->in_read == file->in_used) {
242                 if (file->fd < 0)
243                         return LIBALTOS_ERROR;
244 #ifdef USE_POLL
245                 fd[0].fd = file->fd;
246                 fd[0].events = POLLIN|POLLERR|POLLHUP|POLLNVAL;
247                 fd[1].fd = file->pipe[0];
248                 fd[1].events = POLLIN;
249                 ret = poll(fd, 2, timeout);
250                 if (ret < 0) {
251                         perror("altos_getchar");
252                         return LIBALTOS_ERROR;
253                 }
254                 if (ret == 0)
255                         return LIBALTOS_TIMEOUT;
256
257                 if (fd[0].revents & (POLLHUP|POLLERR|POLLNVAL))
258                         return LIBALTOS_ERROR;
259                 if (fd[0].revents & POLLIN)
260 #endif
261                 {
262                         ret = read(file->fd, file->in_data, USB_BUF_SIZE);
263                         if (ret < 0) {
264                                 perror("altos_getchar");
265                                 return LIBALTOS_ERROR;
266                         }
267                         file->in_read = 0;
268                         file->in_used = ret;
269 #ifndef USE_POLL
270                         if (ret == 0 && timeout > 0)
271                                 return LIBALTOS_TIMEOUT;
272 #endif
273                 }
274         }
275         if (file->in_used && 0) {
276                 printf ("fill \"");
277                 fwrite(file->in_data, 1, file->in_used, stdout);
278                 printf ("\"\n");
279         }
280         return 0;
281 }
282
283 PUBLIC int
284 altos_getchar(struct altos_file *file, int timeout)
285 {
286         int     ret;
287         while (file->in_read == file->in_used) {
288                 if (file->fd < 0)
289                         return LIBALTOS_ERROR;
290                 ret = altos_fill(file, timeout);
291                 if (ret)
292                         return ret;
293         }
294         return file->in_data[file->in_read++];
295 }
296
297 #endif /* POSIX_TTY */
298
299 /*
300  * Scan for Altus Metrum devices by looking through /sys
301  */
302
303 #ifdef LINUX
304
305 #define _GNU_SOURCE
306 #include <ctype.h>
307 #include <dirent.h>
308 #include <stdio.h>
309 #include <stdlib.h>
310 #include <string.h>
311
312 static char *
313 cc_fullname (char *dir, char *file)
314 {
315         char    *new;
316         int     dlen = strlen (dir);
317         int     flen = strlen (file);
318         int     slen = 0;
319
320         if (dir[dlen-1] != '/')
321                 slen = 1;
322         new = malloc (dlen + slen + flen + 1);
323         if (!new)
324                 return 0;
325         strcpy(new, dir);
326         if (slen)
327                 strcat (new, "/");
328         strcat(new, file);
329         return new;
330 }
331
332 static char *
333 cc_basename(char *file)
334 {
335         char *b;
336
337         b = strrchr(file, '/');
338         if (!b)
339                 return file;
340         return b + 1;
341 }
342
343 static char *
344 load_string(char *dir, char *file)
345 {
346         char    *full = cc_fullname(dir, file);
347         char    line[4096];
348         char    *r;
349         FILE    *f;
350         int     rlen;
351
352         f = fopen(full, "r");
353         free(full);
354         if (!f)
355                 return NULL;
356         r = fgets(line, sizeof (line), f);
357         fclose(f);
358         if (!r)
359                 return NULL;
360         rlen = strlen(r);
361         if (r[rlen-1] == '\n')
362                 r[rlen-1] = '\0';
363         return strdup(r);
364 }
365
366 static int
367 load_hex(char *dir, char *file)
368 {
369         char    *line;
370         char    *end;
371         long    i;
372
373         line = load_string(dir, file);
374         if (!line)
375                 return -1;
376         i = strtol(line, &end, 16);
377         free(line);
378         if (end == line)
379                 return -1;
380         return i;
381 }
382
383 static int
384 load_dec(char *dir, char *file)
385 {
386         char    *line;
387         char    *end;
388         long    i;
389
390         line = load_string(dir, file);
391         if (!line)
392                 return -1;
393         i = strtol(line, &end, 10);
394         free(line);
395         if (end == line)
396                 return -1;
397         return i;
398 }
399
400 static int
401 dir_filter_tty_colon(const struct dirent *d)
402 {
403         return strncmp(d->d_name, "tty:", 4) == 0;
404 }
405
406 static int
407 dir_filter_tty(const struct dirent *d)
408 {
409         return strncmp(d->d_name, "tty", 3) == 0;
410 }
411
412 struct altos_usbdev {
413         char    *sys;
414         char    *tty;
415         char    *manufacturer;
416         char    *product_name;
417         int     serial; /* AltOS always uses simple integer serial numbers */
418         int     idProduct;
419         int     idVendor;
420 };
421
422 static char *
423 usb_tty(char *sys)
424 {
425         char *base;
426         int num_configs;
427         int config;
428         struct dirent **namelist;
429         int interface;
430         int num_interfaces;
431         char endpoint_base[20];
432         char *endpoint_full;
433         char *tty_dir;
434         int ntty;
435         char *tty;
436
437         base = cc_basename(sys);
438         num_configs = load_hex(sys, "bNumConfigurations");
439         num_interfaces = load_hex(sys, "bNumInterfaces");
440         for (config = 1; config <= num_configs; config++) {
441                 for (interface = 0; interface < num_interfaces; interface++) {
442                         sprintf(endpoint_base, "%s:%d.%d",
443                                 base, config, interface);
444                         endpoint_full = cc_fullname(sys, endpoint_base);
445
446                         /* Check for tty:ttyACMx style names
447                          */
448                         ntty = scandir(endpoint_full, &namelist,
449                                        dir_filter_tty_colon,
450                                        alphasort);
451                         if (ntty > 0) {
452                                 free(endpoint_full);
453                                 tty = cc_fullname("/dev", namelist[0]->d_name + 4);
454                                 free(namelist);
455                                 return tty;
456                         }
457
458                         /* Check for tty/ttyACMx style names
459                          */
460                         tty_dir = cc_fullname(endpoint_full, "tty");
461                         free(endpoint_full);
462                         ntty = scandir(tty_dir, &namelist,
463                                        dir_filter_tty,
464                                        alphasort);
465                         free (tty_dir);
466                         if (ntty > 0) {
467                                 tty = cc_fullname("/dev", namelist[0]->d_name);
468                                 free(namelist);
469                                 return tty;
470                         }
471                 }
472         }
473         return NULL;
474 }
475
476 static struct altos_usbdev *
477 usb_scan_device(char *sys)
478 {
479         struct altos_usbdev *usbdev;
480
481         usbdev = calloc(1, sizeof (struct altos_usbdev));
482         if (!usbdev)
483                 return NULL;
484         usbdev->sys = strdup(sys);
485         usbdev->manufacturer = load_string(sys, "manufacturer");
486         usbdev->product_name = load_string(sys, "product");
487         usbdev->serial = load_dec(sys, "serial");
488         usbdev->idProduct = load_hex(sys, "idProduct");
489         usbdev->idVendor = load_hex(sys, "idVendor");
490         usbdev->tty = usb_tty(sys);
491         return usbdev;
492 }
493
494 static void
495 usbdev_free(struct altos_usbdev *usbdev)
496 {
497         free(usbdev->sys);
498         free(usbdev->manufacturer);
499         free(usbdev->product_name);
500         /* this can get used as a return value */
501         if (usbdev->tty)
502                 free(usbdev->tty);
503         free(usbdev);
504 }
505
506 #define USB_DEVICES     "/sys/bus/usb/devices"
507
508 static int
509 dir_filter_dev(const struct dirent *d)
510 {
511         const char      *n = d->d_name;
512         char    c;
513
514         while ((c = *n++)) {
515                 if (isdigit(c))
516                         continue;
517                 if (c == '-')
518                         continue;
519                 if (c == '.' && n != d->d_name + 1)
520                         continue;
521                 return 0;
522         }
523         return 1;
524 }
525
526 struct altos_list {
527         struct altos_usbdev     **dev;
528         int                     current;
529         int                     ndev;
530 };
531
532 struct altos_list *
533 altos_list_start(void)
534 {
535         int                     e;
536         struct dirent           **ents;
537         char                    *dir;
538         struct altos_usbdev     *dev;
539         struct altos_list       *devs;
540         int                     n;
541
542         devs = calloc(1, sizeof (struct altos_list));
543         if (!devs)
544                 return NULL;
545
546         n = scandir (USB_DEVICES, &ents,
547                      dir_filter_dev,
548                      alphasort);
549         if (!n)
550                 return 0;
551         for (e = 0; e < n; e++) {
552                 dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
553                 dev = usb_scan_device(dir);
554                 free(dir);
555                 if (USB_IS_ALTUSMETRUM(dev->idVendor, dev->idProduct)) {
556                         if (devs->dev)
557                                 devs->dev = realloc(devs->dev,
558                                                     (devs->ndev + 1) * sizeof (struct usbdev *));
559                         else
560                                 devs->dev = malloc (sizeof (struct usbdev *));
561                         devs->dev[devs->ndev++] = dev;
562                 }
563         }
564         free(ents);
565         devs->current = 0;
566         return devs;
567 }
568
569 int
570 altos_list_next(struct altos_list *list, struct altos_device *device)
571 {
572         struct altos_usbdev *dev;
573         if (list->current >= list->ndev)
574                 return 0;
575         dev = list->dev[list->current];
576         strcpy(device->name, dev->product_name);
577         device->vendor = dev->idVendor;
578         device->product = dev->idProduct;
579         strcpy(device->path, dev->tty);
580         device->serial = dev->serial;
581         list->current++;
582         return 1;
583 }
584
585 void
586 altos_list_finish(struct altos_list *usbdevs)
587 {
588         int     i;
589
590         if (!usbdevs)
591                 return;
592         for (i = 0; i < usbdevs->ndev; i++)
593                 usbdev_free(usbdevs->dev[i]);
594         free(usbdevs);
595 }
596
597 #if HAS_BLUETOOTH
598 struct altos_bt_list {
599         inquiry_info    *ii;
600         int             sock;
601         int             dev_id;
602         int             rsp;
603         int             num_rsp;
604 };
605
606 #define INQUIRY_MAX_RSP 255
607
608 struct altos_bt_list *
609 altos_bt_list_start(int inquiry_time)
610 {
611         struct altos_bt_list    *bt_list;
612
613         bt_list = calloc(1, sizeof (struct altos_bt_list));
614         if (!bt_list)
615                 goto no_bt_list;
616
617         bt_list->ii = calloc(INQUIRY_MAX_RSP, sizeof (inquiry_info));
618         if (!bt_list->ii)
619                 goto no_ii;
620         bt_list->dev_id = hci_get_route(NULL);
621         if (bt_list->dev_id < 0)
622                 goto no_dev_id;
623
624         bt_list->sock = hci_open_dev(bt_list->dev_id);
625         if (bt_list->sock < 0)
626                 goto no_sock;
627
628         bt_list->num_rsp = hci_inquiry(bt_list->dev_id,
629                                        inquiry_time,
630                                        INQUIRY_MAX_RSP,
631                                        NULL,
632                                        &bt_list->ii,
633                                        IREQ_CACHE_FLUSH);
634         if (bt_list->num_rsp < 0)
635                 goto no_rsp;
636
637         bt_list->rsp = 0;
638         return bt_list;
639
640 no_rsp:
641         close(bt_list->sock);
642 no_sock:
643 no_dev_id:
644         free(bt_list->ii);
645 no_ii:
646         free(bt_list);
647 no_bt_list:
648         return NULL;
649 }
650
651 int
652 altos_bt_list_next(struct altos_bt_list *bt_list,
653                    struct altos_bt_device *device)
654 {
655         inquiry_info    *ii;
656
657         if (bt_list->rsp >= bt_list->num_rsp)
658                 return 0;
659
660         ii = &bt_list->ii[bt_list->rsp];
661         ba2str(&ii->bdaddr, device->addr);
662         memset(&device->name, '\0', sizeof (device->name));
663         if (hci_read_remote_name(bt_list->sock, &ii->bdaddr,
664                                  sizeof (device->name),
665                                  device->name, 0) < 0) {
666                 strcpy(device->name, "[unknown]");
667         }
668         bt_list->rsp++;
669         return 1;
670 }
671
672 void
673 altos_bt_list_finish(struct altos_bt_list *bt_list)
674 {
675         close(bt_list->sock);
676         free(bt_list->ii);
677         free(bt_list);
678 }
679
680 void
681 altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device)
682 {
683         strncpy(device->name, name, sizeof (device->name));
684         device->name[sizeof(device->name)-1] = '\0';
685         strncpy(device->addr, addr, sizeof (device->addr));
686         device->addr[sizeof(device->addr)-1] = '\0';
687 }
688
689 struct altos_file *
690 altos_bt_open(struct altos_bt_device *device)
691 {
692         struct sockaddr_rc addr = { 0 };
693         int     s, status;
694         struct altos_file *file;
695
696         file = calloc(1, sizeof (struct altos_file));
697         if (!file)
698                 goto no_file;
699         file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
700         if (file->fd < 0)
701                 goto no_sock;
702
703         addr.rc_family = AF_BLUETOOTH;
704         addr.rc_channel = 1;
705         str2ba(device->addr, &addr.rc_bdaddr);
706
707         status = connect(file->fd,
708                          (struct sockaddr *)&addr,
709                          sizeof(addr));
710         if (status < 0) {
711                 perror("connect");
712                 goto no_link;
713         }
714         sleep(1);
715
716 #ifdef USE_POLL
717         pipe(file->pipe);
718 #else
719         file->out_fd = dup(file->fd);
720 #endif
721         return file;
722 no_link:
723         close(s);
724 no_sock:
725         free(file);
726 no_file:
727         return NULL;
728 }
729 #endif /* HAS_BLUETOOTH */
730
731 #endif
732
733 #ifdef DARWIN
734
735 #include <IOKitLib.h>
736 #include <IOKit/usb/USBspec.h>
737 #include <sys/param.h>
738 #include <paths.h>
739 #include <CFNumber.h>
740 #include <IOBSD.h>
741 #include <string.h>
742 #include <stdio.h>
743 #include <stdlib.h>
744
745 struct altos_list {
746         io_iterator_t iterator;
747 };
748
749 static int
750 get_string(io_object_t object, CFStringRef entry, char *result, int result_len)
751 {
752         CFTypeRef entry_as_string;
753         Boolean got_string;
754
755         entry_as_string = IORegistryEntrySearchCFProperty (object,
756                                                            kIOServicePlane,
757                                                            entry,
758                                                            kCFAllocatorDefault,
759                                                            kIORegistryIterateRecursively);
760         if (entry_as_string) {
761                 got_string = CFStringGetCString(entry_as_string,
762                                                 result, result_len,
763                                                 kCFStringEncodingASCII);
764     
765                 CFRelease(entry_as_string);
766                 if (got_string)
767                         return 1;
768         }
769         return 0;
770 }
771
772 static int
773 get_number(io_object_t object, CFStringRef entry, int *result)
774 {
775         CFTypeRef entry_as_number;
776         Boolean got_number;
777         
778         entry_as_number = IORegistryEntrySearchCFProperty (object,
779                                                            kIOServicePlane,
780                                                            entry,
781                                                            kCFAllocatorDefault,
782                                                            kIORegistryIterateRecursively);
783         if (entry_as_number) {
784                 got_number = CFNumberGetValue(entry_as_number,
785                                               kCFNumberIntType,
786                                               result);
787                 if (got_number)
788                         return 1;
789         }
790         return 0;
791 }
792
793 PUBLIC struct altos_list *
794 altos_list_start(void)
795 {
796         struct altos_list *list = calloc (sizeof (struct altos_list), 1);
797         CFMutableDictionaryRef matching_dictionary = IOServiceMatching("IOUSBDevice");
798         io_iterator_t tdIterator;
799         io_object_t tdObject;
800         kern_return_t ret;
801         int i;
802
803         ret = IOServiceGetMatchingServices(kIOMasterPortDefault, matching_dictionary, &list->iterator);
804         if (ret != kIOReturnSuccess)
805                 return NULL;
806         return list;
807 }
808
809 PUBLIC int
810 altos_list_next(struct altos_list *list, struct altos_device *device)
811 {
812         io_object_t object;
813         char serial_string[128];
814
815         for (;;) {
816                 object = IOIteratorNext(list->iterator);
817                 if (!object)
818                         return 0;
819   
820                 if (!get_number (object, CFSTR(kUSBVendorID), &device->vendor) ||
821                     !get_number (object, CFSTR(kUSBProductID), &device->product))
822                         continue;
823                 if (device->vendor != 0xfffe)
824                         continue;
825                 if (device->product < 0x000a || 0x0013 < device->product)
826                         continue;
827                 if (get_string (object, CFSTR("IOCalloutDevice"), device->path, sizeof (device->path)) &&
828                     get_string (object, CFSTR("USB Product Name"), device->name, sizeof (device->name)) &&
829                     get_string (object, CFSTR("USB Serial Number"), serial_string, sizeof (serial_string))) {
830                         device->serial = atoi(serial_string);
831                         return 1;
832                 }
833         }
834 }
835
836 PUBLIC void
837 altos_list_finish(struct altos_list *list)
838 {
839         IOObjectRelease (list->iterator);
840         free(list);
841 }
842
843 #endif
844
845
846 #ifdef WINDOWS
847
848 #include <stdlib.h>
849 #include <windows.h>
850 #include <setupapi.h>
851
852 struct altos_list {
853         HDEVINFO        dev_info;
854         int             index;
855 };
856
857 #define USB_BUF_SIZE    64
858
859 struct altos_file {
860         HANDLE                          handle;
861         unsigned char                   out_data[USB_BUF_SIZE];
862         int                             out_used;
863         unsigned char                   in_data[USB_BUF_SIZE];
864         int                             in_used;
865         int                             in_read;
866         OVERLAPPED                      ov_read;
867         BOOL                            pend_read;
868         OVERLAPPED                      ov_write;
869 };
870
871 PUBLIC struct altos_list *
872 altos_list_start(void)
873 {
874         struct altos_list       *list = calloc(1, sizeof (struct altos_list));
875
876         if (!list)
877                 return NULL;
878         list->dev_info = SetupDiGetClassDevs(NULL, "USB", NULL,
879                                              DIGCF_ALLCLASSES|DIGCF_PRESENT);
880         if (list->dev_info == INVALID_HANDLE_VALUE) {
881                 printf("SetupDiGetClassDevs failed %ld\n", GetLastError());
882                 free(list);
883                 return NULL;
884         }
885         list->index = 0;
886         return list;
887 }
888
889 PUBLIC int
890 altos_list_next(struct altos_list *list, struct altos_device *device)
891 {
892         SP_DEVINFO_DATA dev_info_data;
893         BYTE            port[128];
894         DWORD           port_len;
895         char            friendlyname[256];
896         BYTE            symbolic[256];
897         DWORD           symbolic_len;
898         HKEY            dev_key;
899         unsigned int    vid, pid;
900         int             serial;
901         HRESULT         result;
902         DWORD           friendlyname_type;
903         DWORD           friendlyname_len;
904
905         dev_info_data.cbSize = sizeof (SP_DEVINFO_DATA);
906         while(SetupDiEnumDeviceInfo(list->dev_info, list->index,
907                                     &dev_info_data))
908         {
909                 list->index++;
910
911                 dev_key = SetupDiOpenDevRegKey(list->dev_info, &dev_info_data,
912                                                DICS_FLAG_GLOBAL, 0, DIREG_DEV,
913                                                KEY_READ);
914                 if (dev_key == INVALID_HANDLE_VALUE) {
915                         printf("cannot open device registry key\n");
916                         continue;
917                 }
918
919                 /* Fetch symbolic name for this device and parse out
920                  * the vid/pid/serial info */
921                 symbolic_len = sizeof(symbolic);
922                 result = RegQueryValueEx(dev_key, "SymbolicName", NULL, NULL,
923                                          symbolic, &symbolic_len);
924                 if (result != 0) {
925                         printf("cannot find SymbolicName value\n");
926                         RegCloseKey(dev_key);
927                         continue;
928                 }
929                 vid = pid = serial = 0;
930                 sscanf((char *) symbolic + sizeof("\\??\\USB#VID_") - 1,
931                        "%04X", &vid);
932                 sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_") - 1,
933                        "%04X", &pid);
934                 sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_XXXX#") - 1,
935                        "%d", &serial);
936                 if (!USB_IS_ALTUSMETRUM(vid, pid)) {
937                         RegCloseKey(dev_key);
938                         continue;
939                 }
940
941                 /* Fetch the com port name */
942                 port_len = sizeof (port);
943                 result = RegQueryValueEx(dev_key, "PortName", NULL, NULL,
944                                          port, &port_len);
945                 RegCloseKey(dev_key);
946                 if (result != 0) {
947                         printf("failed to get PortName\n");
948                         continue;
949                 }
950
951                 /* Fetch the device description which is the device name,
952                  * with firmware that has unique USB ids */
953                 friendlyname_len = sizeof (friendlyname);
954                 if(!SetupDiGetDeviceRegistryProperty(list->dev_info,
955                                                      &dev_info_data,
956                                                      SPDRP_FRIENDLYNAME,
957                                                      &friendlyname_type,
958                                                      (BYTE *)friendlyname,
959                                                      sizeof(friendlyname),
960                                                      &friendlyname_len))
961                 {
962                         printf("Failed to get friendlyname\n");
963                         continue;
964                 }
965                 device->vendor = vid;
966                 device->product = pid;
967                 device->serial = serial;
968                 strcpy(device->name, friendlyname);
969
970                 strcpy(device->path, (char *) port);
971                 return 1;
972         }
973         result = GetLastError();
974         if (result != ERROR_NO_MORE_ITEMS)
975                 printf ("SetupDiEnumDeviceInfo failed error %d\n", (int) result);
976         return 0;
977 }
978
979 PUBLIC void
980 altos_list_finish(struct altos_list *list)
981 {
982         SetupDiDestroyDeviceInfoList(list->dev_info);
983         free(list);
984 }
985
986 static int
987 altos_queue_read(struct altos_file *file)
988 {
989         DWORD   got;
990         if (file->pend_read)
991                 return LIBALTOS_SUCCESS;
992
993         if (!ReadFile(file->handle, file->in_data, USB_BUF_SIZE, &got, &file->ov_read)) {
994                 if (GetLastError() != ERROR_IO_PENDING)
995                         return LIBALTOS_ERROR;
996                 file->pend_read = TRUE;
997         } else {
998                 file->pend_read = FALSE;
999                 file->in_read = 0;
1000                 file->in_used = got;
1001         }
1002         return LIBALTOS_SUCCESS;
1003 }
1004
1005 static int
1006 altos_wait_read(struct altos_file *file, int timeout)
1007 {
1008         DWORD   ret;
1009         DWORD   got;
1010
1011         if (!file->pend_read)
1012                 return LIBALTOS_SUCCESS;
1013
1014         if (!timeout)
1015                 timeout = INFINITE;
1016
1017         ret = WaitForSingleObject(file->ov_read.hEvent, timeout);
1018         switch (ret) {
1019         case WAIT_OBJECT_0:
1020                 if (!GetOverlappedResult(file->handle, &file->ov_read, &got, FALSE))
1021                         return LIBALTOS_ERROR;
1022                 file->pend_read = FALSE;
1023                 file->in_read = 0;
1024                 file->in_used = got;
1025                 break;
1026         case WAIT_TIMEOUT:
1027                 return LIBALTOS_TIMEOUT;
1028                 break;
1029         default:
1030                 return LIBALTOS_ERROR;
1031         }
1032         return LIBALTOS_SUCCESS;
1033 }
1034
1035 static int
1036 altos_fill(struct altos_file *file, int timeout)
1037 {
1038         int     ret;
1039
1040         if (file->in_read < file->in_used)
1041                 return LIBALTOS_SUCCESS;
1042
1043         file->in_read = file->in_used = 0;
1044
1045         ret = altos_queue_read(file);
1046         if (ret)
1047                 return ret;
1048         ret = altos_wait_read(file, timeout);
1049         if (ret)
1050                 return ret;
1051
1052         return LIBALTOS_SUCCESS;
1053 }
1054
1055 PUBLIC int
1056 altos_flush(struct altos_file *file)
1057 {
1058         DWORD           put;
1059         unsigned char   *data = file->out_data;
1060         int             used = file->out_used;
1061         DWORD           ret;
1062
1063         while (used) {
1064                 if (!WriteFile(file->handle, data, used, &put, &file->ov_write)) {
1065                         if (GetLastError() != ERROR_IO_PENDING)
1066                                 return LIBALTOS_ERROR;
1067                         ret = WaitForSingleObject(file->ov_write.hEvent, INFINITE);
1068                         switch (ret) {
1069                         case WAIT_OBJECT_0:
1070                                 if (!GetOverlappedResult(file->handle, &file->ov_write, &put, FALSE))
1071                                         return LIBALTOS_ERROR;
1072                                 break;
1073                         default:
1074                                 return LIBALTOS_ERROR;
1075                         }
1076                 }
1077                 data += put;
1078                 used -= put;
1079         }
1080         file->out_used = 0;
1081         return LIBALTOS_SUCCESS;
1082 }
1083
1084 PUBLIC struct altos_file *
1085 altos_open(struct altos_device *device)
1086 {
1087         struct altos_file       *file = calloc (1, sizeof (struct altos_file));
1088         char    full_name[64];
1089         DCB dcbSerialParams = {0};
1090         COMMTIMEOUTS timeouts;
1091
1092         if (!file)
1093                 return NULL;
1094
1095         strcpy(full_name, "\\\\.\\");
1096         strcat(full_name, device->path);
1097         file->handle = CreateFile(full_name, GENERIC_READ|GENERIC_WRITE,
1098                                   0, NULL, OPEN_EXISTING,
1099                                   FILE_FLAG_OVERLAPPED, NULL);
1100         if (file->handle == INVALID_HANDLE_VALUE) {
1101                 free(file);
1102                 return NULL;
1103         }
1104         file->ov_read.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
1105         file->ov_write.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
1106
1107         timeouts.ReadIntervalTimeout = MAXDWORD;
1108         timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
1109         timeouts.ReadTotalTimeoutConstant = 1 << 30;    /* almost forever */
1110         timeouts.WriteTotalTimeoutMultiplier = 0;
1111         timeouts.WriteTotalTimeoutConstant = 0;
1112         SetCommTimeouts(file->handle, &timeouts);
1113
1114         dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
1115         if (!GetCommState(file->handle, &dcbSerialParams)) {
1116                 CloseHandle(file->handle);
1117                 free(file);
1118                 return NULL;
1119         }
1120         dcbSerialParams.BaudRate = CBR_9600;
1121         dcbSerialParams.ByteSize = 8;
1122         dcbSerialParams.StopBits = ONESTOPBIT;
1123         dcbSerialParams.Parity = NOPARITY;
1124         if (!SetCommState(file->handle, &dcbSerialParams)) {
1125                 CloseHandle(file->handle);
1126                 free(file);
1127                 return NULL;
1128         }
1129
1130         return file;
1131 }
1132
1133 PUBLIC void
1134 altos_close(struct altos_file *file)
1135 {
1136         if (file->handle != INVALID_HANDLE_VALUE) {
1137                 CloseHandle(file->handle);
1138                 file->handle = INVALID_HANDLE_VALUE;
1139         }
1140 }
1141
1142 PUBLIC void
1143 altos_free(struct altos_file *file)
1144 {
1145         altos_close(file);
1146         free(file);
1147 }
1148
1149 PUBLIC int
1150 altos_putchar(struct altos_file *file, char c)
1151 {
1152         int     ret;
1153
1154         if (file->out_used == USB_BUF_SIZE) {
1155                 ret = altos_flush(file);
1156                 if (ret)
1157                         return ret;
1158         }
1159         file->out_data[file->out_used++] = c;
1160         if (file->out_used == USB_BUF_SIZE)
1161                 return altos_flush(file);
1162         return LIBALTOS_SUCCESS;
1163 }
1164
1165 PUBLIC int
1166 altos_getchar(struct altos_file *file, int timeout)
1167 {
1168         int     ret;
1169         while (file->in_read == file->in_used) {
1170                 if (file->handle == INVALID_HANDLE_VALUE)
1171                         return LIBALTOS_ERROR;
1172                 ret = altos_fill(file, timeout);
1173                 if (ret)
1174                         return ret;
1175         }
1176         return file->in_data[file->in_read++];
1177 }
1178
1179 #endif