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