Merge branch 'macos'
[fw/altos] / ao-tools / 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 static int
24 match_dev(char *product, int serial, struct altos_device *device)
25 {
26         struct altos_list       *list;
27         int                     i;
28
29         list = altos_list_start();
30         if (!list)
31                 return 0;
32         while ((i = altos_list_next(list, device)) != 0) {
33                 if (product && strncmp (product, device->product, strlen(product)) != 0)
34                         continue;
35                 if (serial && serial != device->serial)
36                         continue;
37                 break;
38         }
39         altos_list_finish(list);
40         return i;
41 }
42
43 #ifdef DARWIN
44 /* Mac OS X don't have strndup even if _GNU_SOURCE is defined */
45 static char *
46 altos_strndup (const char *s, size_t n)
47 {
48     size_t len = strlen (s);
49     char *ret;
50
51     if (len <= n)
52        return strdup (s);
53     ret = malloc(n + 1);
54     strncpy(ret, s, n);
55     ret[n] = '\0';
56     return ret;
57 }
58
59 #else
60 #define altos_strndup strndup
61 #endif
62
63 int
64 altos_find_by_arg(char *arg, char *default_product, struct altos_device *device)
65 {
66         char    *product;
67         int     serial;
68         char    *end;
69         char    *colon;
70         int     ret;
71
72         if (arg)
73         {
74                 /* check for <serial> */
75                 serial = strtol(arg, &end, 0);
76                 if (end != arg) {
77                         if (*end != '\0')
78                                 return 0;
79                         product = NULL;
80                 } else {
81                         /* check for <product>:<serial> */
82                         colon = strchr(arg, ':');
83                         if (colon) {
84                                 product = altos_strndup(arg, colon - arg);
85                                 serial = strtol(colon + 1, &end, 0);
86                                 if (*end != '\0')
87                                         return 0;
88                         } else {
89                                 product = arg;
90                                 serial = 0;
91                         }
92                 }
93         } else {
94                 product = NULL;
95                 serial = 0;
96         }
97         if (!product && default_product)
98                 ret = match_dev(default_product, serial, device);
99         if (!ret)
100                 ret = match_dev(product, serial, device);
101         if (product && product != arg)
102                 free(product);
103         return ret;
104 }
105
106 #ifdef LINUX
107
108 #define _GNU_SOURCE
109 #include <ctype.h>
110 #include <dirent.h>
111 #include <stdio.h>
112 #include <stdlib.h>
113 #include <string.h>
114
115 static char *
116 cc_fullname (char *dir, char *file)
117 {
118         char    *new;
119         int     dlen = strlen (dir);
120         int     flen = strlen (file);
121         int     slen = 0;
122
123         if (dir[dlen-1] != '/')
124                 slen = 1;
125         new = malloc (dlen + slen + flen + 1);
126         if (!new)
127                 return 0;
128         strcpy(new, dir);
129         if (slen)
130                 strcat (new, "/");
131         strcat(new, file);
132         return new;
133 }
134
135 static char *
136 cc_basename(char *file)
137 {
138         char *b;
139
140         b = strrchr(file, '/');
141         if (!b)
142                 return file;
143         return b + 1;
144 }
145
146 static char *
147 load_string(char *dir, char *file)
148 {
149         char    *full = cc_fullname(dir, file);
150         char    line[4096];
151         char    *r;
152         FILE    *f;
153         int     rlen;
154
155         f = fopen(full, "r");
156         free(full);
157         if (!f)
158                 return NULL;
159         r = fgets(line, sizeof (line), f);
160         fclose(f);
161         if (!r)
162                 return NULL;
163         rlen = strlen(r);
164         if (r[rlen-1] == '\n')
165                 r[rlen-1] = '\0';
166         return strdup(r);
167 }
168
169 static int
170 load_hex(char *dir, char *file)
171 {
172         char    *line;
173         char    *end;
174         long    i;
175
176         line = load_string(dir, file);
177         if (!line)
178                 return -1;
179         i = strtol(line, &end, 16);
180         free(line);
181         if (end == line)
182                 return -1;
183         return i;
184 }
185
186 static int
187 load_dec(char *dir, char *file)
188 {
189         char    *line;
190         char    *end;
191         long    i;
192
193         line = load_string(dir, file);
194         if (!line)
195                 return -1;
196         i = strtol(line, &end, 10);
197         free(line);
198         if (end == line)
199                 return -1;
200         return i;
201 }
202
203 static int
204 dir_filter_tty_colon(const struct dirent *d)
205 {
206         return strncmp(d->d_name, "tty:", 4) == 0;
207 }
208
209 static int
210 dir_filter_tty(const struct dirent *d)
211 {
212         return strncmp(d->d_name, "tty", 3) == 0;
213 }
214
215 struct altos_usbdev {
216         char    *sys;
217         char    *tty;
218         char    *manufacturer;
219         char    *product;
220         int     serial; /* AltOS always uses simple integer serial numbers */
221         int     idProduct;
222         int     idVendor;
223 };
224
225 static char *
226 usb_tty(char *sys)
227 {
228         char *base;
229         int num_configs;
230         int config;
231         struct dirent **namelist;
232         int interface;
233         int num_interfaces;
234         char endpoint_base[20];
235         char *endpoint_full;
236         char *tty_dir;
237         int ntty;
238         char *tty;
239
240         base = cc_basename(sys);
241         num_configs = load_hex(sys, "bNumConfigurations");
242         num_interfaces = load_hex(sys, "bNumInterfaces");
243         for (config = 1; config <= num_configs; config++) {
244                 for (interface = 0; interface < num_interfaces; interface++) {
245                         sprintf(endpoint_base, "%s:%d.%d",
246                                 base, config, interface);
247                         endpoint_full = cc_fullname(sys, endpoint_base);
248
249                         /* Check for tty:ttyACMx style names
250                          */
251                         ntty = scandir(endpoint_full, &namelist,
252                                        dir_filter_tty_colon,
253                                        alphasort);
254                         if (ntty > 0) {
255                                 free(endpoint_full);
256                                 tty = cc_fullname("/dev", namelist[0]->d_name + 4);
257                                 free(namelist);
258                                 return tty;
259                         }
260
261                         /* Check for tty/ttyACMx style names
262                          */
263                         tty_dir = cc_fullname(endpoint_full, "tty");
264                         free(endpoint_full);
265                         ntty = scandir(tty_dir, &namelist,
266                                        dir_filter_tty,
267                                        alphasort);
268                         free (tty_dir);
269                         if (ntty > 0) {
270                                 tty = cc_fullname("/dev", namelist[0]->d_name);
271                                 free(namelist);
272                                 return tty;
273                         }
274                 }
275         }
276         return NULL;
277 }
278
279 static struct altos_usbdev *
280 usb_scan_device(char *sys)
281 {
282         struct altos_usbdev *usbdev;
283
284         usbdev = calloc(1, sizeof (struct altos_usbdev));
285         if (!usbdev)
286                 return NULL;
287         usbdev->sys = strdup(sys);
288         usbdev->manufacturer = load_string(sys, "manufacturer");
289         usbdev->product = load_string(sys, "product");
290         usbdev->serial = load_dec(sys, "serial");
291         usbdev->idProduct = load_hex(sys, "idProduct");
292         usbdev->idVendor = load_hex(sys, "idVendor");
293         usbdev->tty = usb_tty(sys);
294         return usbdev;
295 }
296
297 static void
298 usbdev_free(struct altos_usbdev *usbdev)
299 {
300         free(usbdev->sys);
301         free(usbdev->manufacturer);
302         free(usbdev->product);
303         /* this can get used as a return value */
304         if (usbdev->tty)
305                 free(usbdev->tty);
306         free(usbdev);
307 }
308
309 #define USB_DEVICES     "/sys/bus/usb/devices"
310
311 static int
312 dir_filter_dev(const struct dirent *d)
313 {
314         const char      *n = d->d_name;
315         char    c;
316
317         while ((c = *n++)) {
318                 if (isdigit(c))
319                         continue;
320                 if (c == '-')
321                         continue;
322                 if (c == '.' && n != d->d_name + 1)
323                         continue;
324                 return 0;
325         }
326         return 1;
327 }
328
329 struct altos_list {
330         struct altos_usbdev     **dev;
331         int                     current;
332         int                     ndev;
333 };
334
335 int
336 altos_init(void)
337 {
338         return 1;
339 }
340
341 void
342 altos_fini(void)
343 {
344 }
345
346 struct altos_list *
347 altos_list_start(void)
348 {
349         int                     e;
350         struct dirent           **ents;
351         char                    *dir;
352         struct altos_usbdev     *dev;
353         struct altos_list       *devs;
354         int                     n;
355
356         devs = calloc(1, sizeof (struct altos_list));
357         if (!devs)
358                 return NULL;
359
360         n = scandir (USB_DEVICES, &ents,
361                      dir_filter_dev,
362                      alphasort);
363         if (!n)
364                 return 0;
365         for (e = 0; e < n; e++) {
366                 dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
367                 dev = usb_scan_device(dir);
368                 free(dir);
369                 if (dev->idVendor == 0xfffe && dev->tty) {
370                         if (devs->dev)
371                                 devs->dev = realloc(devs->dev,
372                                                     devs->ndev + 1 * sizeof (struct usbdev *));
373                         else
374                                 devs->dev = malloc (sizeof (struct usbdev *));
375                         devs->dev[devs->ndev++] = dev;
376                 }
377         }
378         free(ents);
379         devs->current = 0;
380         return devs;
381 }
382
383 int
384 altos_list_next(struct altos_list *list, struct altos_device *device)
385 {
386         struct altos_usbdev *dev;
387         if (list->current >= list->ndev)
388                 return 0;
389         dev = list->dev[list->current];
390         strcpy(device->product, dev->product);
391         strcpy(device->path, dev->tty);
392         device->serial = dev->serial;
393         list->current++;
394         return 1;
395 }
396
397 void
398 altos_list_finish(struct altos_list *usbdevs)
399 {
400         int     i;
401
402         if (!usbdevs)
403                 return;
404         for (i = 0; i < usbdevs->ndev; i++)
405                 usbdev_free(usbdevs->dev[i]);
406         free(usbdevs);
407 }
408
409 #endif
410
411 #ifdef DARWIN
412
413 #include <IOKitLib.h>
414 #include <IOKit/usb/USBspec.h>
415 #include <sys/param.h>
416 #include <paths.h>
417 #include <CFNumber.h>
418 #include <IOBSD.h>
419 #include <string.h>
420 #include <stdio.h>
421 #include <stdlib.h>
422
423 struct altos_list {
424         io_iterator_t iterator;
425 };
426
427 static int
428 get_string(io_object_t object, CFStringRef entry, char *result, int result_len)
429 {
430         CFTypeRef entry_as_string;
431         Boolean got_string;
432
433         entry_as_string = IORegistryEntrySearchCFProperty (object,
434                                                            kIOServicePlane,
435                                                            entry,
436                                                            kCFAllocatorDefault,
437                                                            kIORegistryIterateRecursively);
438         if (entry_as_string) {
439                 got_string = CFStringGetCString(entry_as_string,
440                                                 result, result_len,
441                                                 kCFStringEncodingASCII);
442     
443                 CFRelease(entry_as_string);
444                 if (got_string)
445                         return 1;
446         }
447         return 0;
448 }
449
450 int
451 altos_init(void)
452 {
453         return 1;
454 }
455
456 void
457 altos_fini(void)
458 {
459 }
460
461 struct altos_list *
462 altos_list_start(void)
463 {
464         struct altos_list *list = calloc (sizeof (struct altos_list), 1);
465         CFMutableDictionaryRef matching_dictionary = IOServiceMatching("IOUSBDevice");
466         UInt32 vendor = 0xfffe, product = 0x000a;
467         CFNumberRef vendor_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendor);
468         CFNumberRef product_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &product);
469         io_iterator_t tdIterator;
470         io_object_t tdObject;
471   
472         CFDictionaryAddValue(matching_dictionary, CFSTR(kUSBVendorID), vendor_ref);
473         CFDictionaryAddValue(matching_dictionary, CFSTR(kUSBProductID), product_ref);
474
475         IOServiceGetMatchingServices(kIOMasterPortDefault, matching_dictionary, &list->iterator);
476   
477         CFRelease(vendor_ref);
478         CFRelease(product_ref);
479         return list;
480 }
481
482 int
483 altos_list_next(struct altos_list *list, struct altos_device *device)
484 {
485         io_object_t object;
486         char serial_string[128];
487
488         for (;;) {
489                 object = IOIteratorNext(list->iterator);
490                 if (!object)
491                         return 0;
492   
493                 if (get_string (object, CFSTR("IOCalloutDevice"), device->path, sizeof (device->path)) &&
494                     get_string (object, CFSTR("USB Product Name"), device->product, sizeof (device->product)) &&
495                     get_string (object, CFSTR("USB Serial Number"), serial_string, sizeof (serial_string))) {
496                         device->serial = atoi(serial_string);
497                         return 1;
498                 }
499         }
500 }
501
502 void
503 altos_list_finish(struct altos_list *list)
504 {
505         IOObjectRelease (list->iterator);
506         free(list);
507 }
508
509 #endif
510
511 #ifdef POSIX_TTY
512
513 #include <stdio.h>
514 #include <stdlib.h>
515 #include <fcntl.h>
516 #include <termios.h>
517 #include <errno.h>
518
519 #define USB_BUF_SIZE    64
520
521 struct altos_file {
522         int                             fd;
523         unsigned char                   out_data[USB_BUF_SIZE];
524         int                             out_used;
525         unsigned char                   in_data[USB_BUF_SIZE];
526         int                             in_used;
527         int                             in_read;
528 };
529
530 struct altos_file *
531 altos_open(struct altos_device *device)
532 {
533         struct altos_file       *file = calloc (sizeof (struct altos_file), 1);
534         int                     ret;
535         struct termios          term;
536
537         if (!file)
538                 return NULL;
539
540         file->fd = open(device->path, O_RDWR | O_NOCTTY);
541         if (file->fd < 0) {
542                 perror(device->path);
543                 free(file);
544                 return NULL;
545         }
546         ret = tcgetattr(file->fd, &term);
547         if (ret < 0) {
548                 perror("tcgetattr");
549                 close(file->fd);
550                 free(file);
551                 return NULL;
552         }
553         cfmakeraw(&term);
554         term.c_cc[VMIN] = 0;
555         term.c_cc[VTIME] = 1;
556         ret = tcsetattr(file->fd, TCSAFLUSH, &term);
557         if (ret < 0) {
558                 perror("tcsetattr");
559                 close(file->fd);
560                 free(file);
561                 return NULL;
562         }
563         return file;
564 }
565
566 void
567 altos_close(struct altos_file *file)
568 {
569         close(file->fd);
570         free(file);
571 }
572
573 int
574 altos_putchar(struct altos_file *file, char c)
575 {
576         int     ret;
577
578         if (file->out_used == USB_BUF_SIZE) {
579                 ret = altos_flush(file);
580                 if (ret)
581                         return ret;
582         }
583         file->out_data[file->out_used++] = c;
584         if (file->out_used == USB_BUF_SIZE)
585                 return altos_flush(file);
586         return 0;
587 }
588
589 int
590 altos_flush(struct altos_file *file)
591 {
592         while (file->out_used) {
593                 int     ret;
594
595                 ret = write (file->fd, file->out_data, file->out_used);
596                 if (ret < 0)
597                         return -errno;
598                 if (ret) {
599                         memmove(file->out_data, file->out_data + ret,
600                                 file->out_used - ret);
601                         file->out_used -= ret;
602                 }
603         }
604 }
605
606 int
607 altos_getchar(struct altos_file *file, int timeout)
608 {
609         while (file->in_read == file->in_used) {
610                 int     ret;
611
612                 altos_flush(file);
613                 ret = read(file->fd, file->in_data, USB_BUF_SIZE);
614                 if (ret < 0)
615                         return -errno;
616                 file->in_read = 0;
617                 file->in_used = ret;
618         }
619         return file->in_data[file->in_read++];
620 }
621
622 #endif /* POSIX_TTY */
623
624 #ifdef USE_LIBUSB
625 #include <libusb.h>
626 #include <stdio.h>
627 #include <stdlib.h>
628 #include <string.h>
629
630 libusb_context  *usb_context;
631
632 int altos_init(void)
633 {
634         int     ret;
635         ret = libusb_init(&usb_context);
636         if (ret)
637                 return ret;
638         libusb_set_debug(usb_context, 3);
639         return 0;
640 }
641
642 void altos_fini(void)
643 {
644         libusb_exit(usb_context);
645         usb_context = NULL;
646 }
647
648 static libusb_device **list;
649 static ssize_t num, current;
650
651 int altos_list_start(void)
652 {
653         if (list)
654                 altos_list_finish();
655         current = 0;
656         num = libusb_get_device_list(usb_context, &list);
657         if (num == 0) {
658                 current = num = 0;
659                 list = NULL;
660                 return 0;
661         }
662         return 1;
663 }
664
665 int altos_list_next(struct altos_device *device)
666 {
667         while (current < num) {
668                 struct libusb_device_descriptor descriptor;
669                 libusb_device *usb_device = list[current++];
670
671                 if (libusb_get_device_descriptor(usb_device, &descriptor) == 0) {
672                         if (descriptor.idVendor == 0xfffe)
673                         {
674                                 libusb_device_handle    *handle;
675                                 if (libusb_open(usb_device, &handle) == 0) {
676                                         char    serial_number[256];
677                                         libusb_get_string_descriptor_ascii(handle, descriptor.iProduct,
678                                                                            device->product,
679                                                                            sizeof(device->product));
680                                         libusb_get_string_descriptor_ascii(handle, descriptor.iSerialNumber,
681                                                                            serial_number,
682                                                                            sizeof (serial_number));
683                                         libusb_close(handle);
684                                         device->serial = atoi(serial_number);
685                                         device->device = usb_device;
686                                         return 1;
687                                 }
688                         }
689                 }
690         }
691         return 0;
692 }
693
694 void altos_list_finish(void)
695 {
696         if (list) {
697                 libusb_free_device_list(list, 1);
698                 list = NULL;
699         }
700 }
701
702 #define USB_BUF_SIZE    64
703
704 struct altos_file {
705         struct libusb_device            *device;
706         struct libusb_device_handle     *handle;
707         int                             out_ep;
708         int                             out_size;
709         int                             in_ep;
710         int                             in_size;
711         unsigned char                   out_data[USB_BUF_SIZE];
712         int                             out_used;
713         unsigned char                   in_data[USB_BUF_SIZE];
714         int                             in_used;
715         int                             in_read;
716 };
717
718 struct altos_file *
719 altos_open(struct altos_device *device)
720 {
721         struct altos_file               *file;
722         struct libusb_device_handle     *handle;
723         if (libusb_open(device->device, &handle) == 0) {
724                 int     ret;
725
726                 ret = libusb_claim_interface(handle, 1);
727 #if 0
728                 if (ret) {
729                         libusb_close(handle);
730                         return NULL;
731                 }
732 #endif
733                 ret = libusb_detach_kernel_driver(handle, 1);
734 #if 0
735                 if (ret) {
736                         libusb_close(handle);
737                         return NULL;
738                 }
739 #endif
740
741                 file = calloc(sizeof (struct altos_file), 1);
742                 file->device = libusb_ref_device(device->device);
743                 file->handle = handle;
744                 /* XXX should get these from the endpoint descriptors */
745                 file->out_ep = 4 | LIBUSB_ENDPOINT_OUT;
746                 file->out_size = 64;
747                 file->in_ep = 5 | LIBUSB_ENDPOINT_IN;
748                 file->in_size = 64;
749
750                 return file;
751         }
752         return NULL;
753 }
754
755 void
756 altos_close(struct altos_file *file)
757 {
758         libusb_close(file->handle);
759         libusb_unref_device(file->device);
760         file->handle = NULL;
761         free(file);
762 }
763
764 int
765 altos_putchar(struct altos_file *file, char c)
766 {
767         int     ret;
768
769         if (file->out_used == file->out_size) {
770                 ret = altos_flush(file);
771                 if (ret)
772                         return ret;
773         }
774         file->out_data[file->out_used++] = c;
775         if (file->out_used == file->out_size)
776                 return altos_flush(file);
777         return 0;
778 }
779
780 int
781 altos_flush(struct altos_file *file)
782 {
783         while (file->out_used) {
784                 int     transferred;
785                 int     ret;
786
787                 ret = libusb_bulk_transfer(file->handle,
788                                            file->out_ep,
789                                            file->out_data,
790                                            file->out_used,
791                                            &transferred,
792                                            0);
793                 if (ret)
794                         return ret;
795                 if (transferred) {
796                         memmove(file->out_data, file->out_data + transferred,
797                                 file->out_used - transferred);
798                         file->out_used -= transferred;
799                 }
800         }
801 }
802
803 int
804 altos_getchar(struct altos_file *file, int timeout)
805 {
806         while (file->in_read == file->in_used) {
807                 int     ret;
808                 int     transferred;
809
810                 altos_flush(file);
811                 ret = libusb_bulk_transfer(file->handle,
812                                            file->in_ep,
813                                            file->in_data,
814                                            file->in_size,
815                                            &transferred,
816                                            (unsigned int) timeout);
817                 if (ret)
818                         return ret;
819                 file->in_read = 0;
820                 file->in_used = transferred;
821         }
822         return file->in_data[file->in_read++];
823 }
824
825 #endif /* USE_LIBUSB */