Merge remote branch 'origin/master' into new-packet-format
[fw/altos] / ao-tools / lib / cc-usbdev.c
1 /*
2  * Copyright © 2009 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; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 #define _GNU_SOURCE
20 #include "cc.h"
21 #include <ctype.h>
22 #include <dirent.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 static char *
28 load_string(char *dir, char *file)
29 {
30         char    *full = cc_fullname(dir, file);
31         char    line[4096];
32         char    *r;
33         FILE    *f;
34         int     rlen;
35
36         f = fopen(full, "r");
37         free(full);
38         if (!f)
39                 return NULL;
40         r = fgets(line, sizeof (line), f);
41         fclose(f);
42         if (!r)
43                 return NULL;
44         rlen = strlen(r);
45         if (r[rlen-1] == '\n')
46                 r[rlen-1] = '\0';
47         return strdup(r);
48 }
49
50 static int
51 load_hex(char *dir, char *file)
52 {
53         char    *line;
54         char    *end;
55         long    i;
56
57         line = load_string(dir, file);
58         if (!line)
59                 return -1;
60         i = strtol(line, &end, 16);
61         free(line);
62         if (end == line)
63                 return -1;
64         return i;
65 }
66
67 static int
68 load_dec(char *dir, char *file)
69 {
70         char    *line;
71         char    *end;
72         long    i;
73
74         line = load_string(dir, file);
75         if (!line)
76                 return -1;
77         i = strtol(line, &end, 10);
78         free(line);
79         if (end == line)
80                 return -1;
81         return i;
82 }
83
84 static int
85 dir_filter_tty_colon(const struct dirent *d)
86 {
87         return strncmp(d->d_name, "tty:", 4) == 0;
88 }
89
90 static int
91 dir_filter_tty(const struct dirent *d)
92 {
93         return strncmp(d->d_name, "tty", 3) == 0;
94 }
95
96 static char *
97 usb_tty(char *sys)
98 {
99         char *base;
100         int num_configs;
101         int config;
102         struct dirent **namelist;
103         int interface;
104         int num_interfaces;
105         char endpoint_base[20];
106         char *endpoint_full;
107         char *tty_dir;
108         int ntty;
109         char *tty;
110
111         base = cc_basename(sys);
112         num_configs = load_hex(sys, "bNumConfigurations");
113         num_interfaces = load_hex(sys, "bNumInterfaces");
114         for (config = 1; config <= num_configs; config++) {
115                 for (interface = 0; interface < num_interfaces; interface++) {
116                         sprintf(endpoint_base, "%s:%d.%d",
117                                 base, config, interface);
118                         endpoint_full = cc_fullname(sys, endpoint_base);
119
120                         /* Check for tty:ttyACMx style names
121                          */
122                         ntty = scandir(endpoint_full, &namelist,
123                                        dir_filter_tty_colon,
124                                        alphasort);
125                         if (ntty > 0) {
126                                 free(endpoint_full);
127                                 tty = cc_fullname("/dev", namelist[0]->d_name + 4);
128                                 free(namelist);
129                                 return tty;
130                         }
131
132                         /* Check for tty/ttyACMx style names
133                          */
134                         tty_dir = cc_fullname(endpoint_full, "tty");
135                         free(endpoint_full);
136                         ntty = scandir(tty_dir, &namelist,
137                                        dir_filter_tty,
138                                        alphasort);
139                         free (tty_dir);
140                         if (ntty > 0) {
141                                 tty = cc_fullname("/dev", namelist[0]->d_name);
142                                 free(namelist);
143                                 return tty;
144                         }
145                 }
146         }
147         return NULL;
148 }
149
150 static struct cc_usbdev *
151 usb_scan_device(char *sys)
152 {
153         struct cc_usbdev *usbdev;
154
155         usbdev = calloc(1, sizeof (struct cc_usbdev));
156         if (!usbdev)
157                 return NULL;
158         usbdev->sys = strdup(sys);
159         usbdev->manufacturer = load_string(sys, "manufacturer");
160         usbdev->product = load_string(sys, "product");
161         usbdev->serial = load_dec(sys, "serial");
162         usbdev->idProduct = load_hex(sys, "idProduct");
163         usbdev->idVendor = load_hex(sys, "idVendor");
164         usbdev->tty = usb_tty(sys);
165         return usbdev;
166 }
167
168 static void
169 usbdev_free(struct cc_usbdev *usbdev)
170 {
171         free(usbdev->sys);
172         free(usbdev->manufacturer);
173         free(usbdev->product);
174         /* this can get used as a return value */
175         if (usbdev->tty)
176                 free(usbdev->tty);
177         free(usbdev);
178 }
179
180 #define USB_DEVICES     "/sys/bus/usb/devices"
181
182 static int
183 dir_filter_dev(const struct dirent *d)
184 {
185         const char      *n = d->d_name;
186         char    c;
187
188         while ((c = *n++)) {
189                 if (isdigit(c))
190                         continue;
191                 if (c == '-')
192                         continue;
193                 if (c == '.' && n != d->d_name + 1)
194                         continue;
195                 return 0;
196         }
197         return 1;
198 }
199
200 struct cc_usbdevs *
201 cc_usbdevs_scan(void)
202 {
203         int                     e;
204         struct dirent           **ents;
205         char                    *dir;
206         struct cc_usbdev        *dev;
207         struct cc_usbdevs       *devs;
208         int                     n;
209
210         devs = calloc(1, sizeof (struct cc_usbdevs));
211         if (!devs)
212                 return NULL;
213
214         n = scandir (USB_DEVICES, &ents,
215                      dir_filter_dev,
216                      alphasort);
217         if (!n)
218                 return 0;
219         for (e = 0; e < n; e++) {
220                 dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
221                 dev = usb_scan_device(dir);
222                 free(dir);
223                 if (dev->idVendor == 0xfffe && dev->tty) {
224                         if (devs->dev)
225                                 devs->dev = realloc(devs->dev,
226                                                     devs->ndev + 1 * sizeof (struct usbdev *));
227                         else
228                                 devs->dev = malloc (sizeof (struct usbdev *));
229                         devs->dev[devs->ndev++] = dev;
230                 }
231         }
232         free(ents);
233         return devs;
234 }
235
236 void
237 cc_usbdevs_free(struct cc_usbdevs *usbdevs)
238 {
239         int     i;
240
241         if (!usbdevs)
242                 return;
243         for (i = 0; i < usbdevs->ndev; i++)
244                 usbdev_free(usbdevs->dev[i]);
245         free(usbdevs);
246 }
247
248 static char *
249 match_dev(char *product, int serial)
250 {
251         struct cc_usbdevs       *devs;
252         struct cc_usbdev        *dev;
253         int                     i;
254         char                    *tty = NULL;
255
256         devs = cc_usbdevs_scan();
257         if (!devs)
258                 return NULL;
259         for (i = 0; i < devs->ndev; i++) {
260                 dev = devs->dev[i];
261                 if (product && strncmp (product, dev->product, strlen(product)) != 0)
262                         continue;
263                 if (serial && serial != dev->serial)
264                         continue;
265                 break;
266         }
267         if (i < devs->ndev) {
268                 tty = devs->dev[i]->tty;
269                 devs->dev[i]->tty = NULL;
270         }
271         cc_usbdevs_free(devs);
272         return tty;
273 }
274
275 char *
276 cc_usbdevs_find_by_arg(char *arg, char *default_product)
277 {
278         char    *product;
279         int     serial;
280         char    *end;
281         char    *colon;
282         char    *tty;
283
284         if (arg)
285         {
286                 /* check for <serial> */
287                 serial = strtol(arg, &end, 0);
288                 if (end != arg) {
289                         if (*end != '\0')
290                                 return NULL;
291                         product = NULL;
292                 } else {
293                         /* check for <product>:<serial> */
294                         colon = strchr(arg, ':');
295                         if (colon) {
296                                 product = strndup(arg, colon - arg);
297                                 serial = strtol(colon + 1, &end, 0);
298                                 if (*end != '\0')
299                                         return NULL;
300                         } else {
301                                 product = arg;
302                                 serial = 0;
303                         }
304                 }
305         } else {
306                 product = NULL;
307                 serial = 0;
308         }
309         tty = NULL;
310         if (!product && default_product)
311                 tty = match_dev(default_product, serial);
312         if (!tty)
313                 tty = match_dev(product, serial);
314         if (product && product != arg)
315                 free(product);
316         return tty;
317 }