Switch from GPLv2 to GPLv2+
[fw/altos] / libaltos / libaltos_linux.c
1 /*
2  * Copyright © 2016 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 "libaltos_private.h"
21 #include "libaltos_posix.h"
22
23 #include <ctype.h>
24 #include <dirent.h>
25 #include <bluetooth/bluetooth.h>
26 #include <bluetooth/hci.h>
27 #include <bluetooth/hci_lib.h>
28 #include <bluetooth/rfcomm.h>
29
30 static char *
31 cc_fullname (char *dir, char *file)
32 {
33         char    *new;
34         int     dlen = strlen (dir);
35         int     flen = strlen (file);
36         int     slen = 0;
37
38         if (dir[dlen-1] != '/')
39                 slen = 1;
40         new = malloc (dlen + slen + flen + 1);
41         if (!new)
42                 return 0;
43         strcpy(new, dir);
44         if (slen)
45                 strcat (new, "/");
46         strcat(new, file);
47         return new;
48 }
49
50 static char *
51 cc_basename(char *file)
52 {
53         char *b;
54
55         b = strrchr(file, '/');
56         if (!b)
57                 return file;
58         return b + 1;
59 }
60
61 static char *
62 load_string(char *dir, char *file)
63 {
64         char    *full = cc_fullname(dir, file);
65         char    line[4096];
66         char    *r;
67         FILE    *f;
68         int     rlen;
69
70         f = fopen(full, "r");
71         free(full);
72         if (!f)
73                 return NULL;
74         r = fgets(line, sizeof (line), f);
75         fclose(f);
76         if (!r)
77                 return NULL;
78         rlen = strlen(r);
79         if (r[rlen-1] == '\n')
80                 r[rlen-1] = '\0';
81         return strdup(r);
82 }
83
84 static int
85 load_hex(char *dir, char *file)
86 {
87         char    *line;
88         char    *end;
89         long    i;
90
91         line = load_string(dir, file);
92         if (!line)
93                 return -1;
94         i = strtol(line, &end, 16);
95         free(line);
96         if (end == line)
97                 return -1;
98         return i;
99 }
100
101 static int
102 load_dec(char *dir, char *file)
103 {
104         char    *line;
105         char    *end;
106         long    i;
107
108         line = load_string(dir, file);
109         if (!line)
110                 return -1;
111         i = strtol(line, &end, 10);
112         free(line);
113         if (end == line)
114                 return -1;
115         return i;
116 }
117
118 static int
119 dir_filter_tty_colon(const struct dirent *d)
120 {
121         return strncmp(d->d_name, "tty:", 4) == 0;
122 }
123
124 static int
125 dir_filter_tty(const struct dirent *d)
126 {
127         return strncmp(d->d_name, "tty", 3) == 0;
128 }
129
130 struct altos_usbdev {
131         char    *sys;
132         char    *tty;
133         char    *manufacturer;
134         char    *product_name;
135         int     serial; /* AltOS always uses simple integer serial numbers */
136         int     idProduct;
137         int     idVendor;
138 };
139
140 static char *
141 usb_tty(char *sys)
142 {
143         char *base;
144         int num_configs;
145         int config;
146         struct dirent **namelist;
147         int interface;
148         int num_interfaces;
149         char endpoint_base[20];
150         char *endpoint_full;
151         char *tty_dir;
152         int ntty;
153         char *tty;
154
155         base = cc_basename(sys);
156         num_configs = load_hex(sys, "bNumConfigurations");
157         num_interfaces = load_hex(sys, "bNumInterfaces");
158         for (config = 1; config <= num_configs; config++) {
159                 for (interface = 0; interface < num_interfaces; interface++) {
160                         sprintf(endpoint_base, "%s:%d.%d",
161                                 base, config, interface);
162                         endpoint_full = cc_fullname(sys, endpoint_base);
163
164
165                         /* Check for tty:ttyACMx style names
166                          */
167                         ntty = scandir(endpoint_full, &namelist,
168                                        dir_filter_tty_colon,
169                                        alphasort);
170                         if (ntty > 0) {
171                                 free(endpoint_full);
172                                 tty = cc_fullname("/dev", namelist[0]->d_name + 4);
173                                 free(namelist);
174                                 return tty;
175                         }
176
177                         /* Check for tty/ttyACMx style names
178                          */
179                         tty_dir = cc_fullname(endpoint_full, "tty");
180                         ntty = scandir(tty_dir, &namelist,
181                                        dir_filter_tty,
182                                        alphasort);
183                         free (tty_dir);
184                         if (ntty > 0) {
185                                 tty = cc_fullname("/dev", namelist[0]->d_name);
186                                 free(endpoint_full);
187                                 free(namelist);
188                                 return tty;
189                         }
190
191                         /* Check for ttyACMx style names
192                          */
193                         ntty = scandir(endpoint_full, &namelist,
194                                        dir_filter_tty,
195                                        alphasort);
196                         free(endpoint_full);
197                         if (ntty > 0) {
198                                 tty = cc_fullname("/dev", namelist[0]->d_name);
199                                 free(namelist);
200                                 return tty;
201                         }
202
203                 }
204         }
205         return NULL;
206 }
207
208 static struct altos_usbdev *
209 usb_scan_device(char *sys)
210 {
211         struct altos_usbdev *usbdev;
212         char *tty;
213
214         tty = usb_tty(sys);
215         if (!tty)
216                 return NULL;
217         usbdev = calloc(1, sizeof (struct altos_usbdev));
218         if (!usbdev)
219                 return NULL;
220         usbdev->sys = strdup(sys);
221         usbdev->manufacturer = load_string(sys, "manufacturer");
222         usbdev->product_name = load_string(sys, "product");
223         usbdev->serial = load_dec(sys, "serial");
224         usbdev->idProduct = load_hex(sys, "idProduct");
225         usbdev->idVendor = load_hex(sys, "idVendor");
226         usbdev->tty = tty;
227         return usbdev;
228 }
229
230 static void
231 usbdev_free(struct altos_usbdev *usbdev)
232 {
233         free(usbdev->sys);
234         free(usbdev->manufacturer);
235         free(usbdev->product_name);
236         /* this can get used as a return value */
237         if (usbdev->tty)
238                 free(usbdev->tty);
239         free(usbdev);
240 }
241
242 #define USB_DEVICES     "/sys/bus/usb/devices"
243
244 static int
245 dir_filter_dev(const struct dirent *d)
246 {
247         const char      *n = d->d_name;
248         char    c;
249
250         while ((c = *n++)) {
251                 if (isdigit(c))
252                         continue;
253                 if (c == '-')
254                         continue;
255                 if (c == '.' && n != d->d_name + 1)
256                         continue;
257                 return 0;
258         }
259         return 1;
260 }
261
262 struct altos_list {
263         struct altos_usbdev     **dev;
264         int                     current;
265         int                     ndev;
266 };
267
268 struct altos_list *
269 altos_list_start(void)
270 {
271         int                     e;
272         struct dirent           **ents;
273         char                    *dir;
274         struct altos_usbdev     *dev;
275         struct altos_list       *devs;
276         int                     n;
277
278         devs = calloc(1, sizeof (struct altos_list));
279         if (!devs)
280                 return NULL;
281
282         n = scandir (USB_DEVICES, &ents,
283                      dir_filter_dev,
284                      alphasort);
285         if (!n)
286                 return 0;
287         for (e = 0; e < n; e++) {
288                 dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
289                 dev = usb_scan_device(dir);
290                 if (!dev)
291                         continue;
292                 free(dir);
293                 if (devs->dev)
294                         devs->dev = realloc(devs->dev,
295                                             (devs->ndev + 1) * sizeof (struct usbdev *));
296                 else
297                         devs->dev = malloc (sizeof (struct usbdev *));
298                 devs->dev[devs->ndev++] = dev;
299         }
300         free(ents);
301         devs->current = 0;
302         return devs;
303 }
304
305 PUBLIC struct altos_list *
306 altos_ftdi_list_start(void)
307 {
308         return altos_list_start();
309 }
310
311 int
312 altos_list_next(struct altos_list *list, struct altos_device *device)
313 {
314         struct altos_usbdev *dev;
315         if (list->current >= list->ndev) {
316                 return 0;
317         }
318         dev = list->dev[list->current];
319         strcpy(device->name, dev->product_name);
320         device->vendor = dev->idVendor;
321         device->product = dev->idProduct;
322         strcpy(device->path, dev->tty);
323         device->serial = dev->serial;
324         list->current++;
325         return 1;
326 }
327
328 void
329 altos_list_finish(struct altos_list *usbdevs)
330 {
331         int     i;
332
333         if (!usbdevs)
334                 return;
335         for (i = 0; i < usbdevs->ndev; i++)
336                 usbdev_free(usbdevs->dev[i]);
337         free(usbdevs);
338 }
339
340 #include <dlfcn.h>
341
342 static void *libbt;
343 static int bt_initialized;
344
345 static int init_bt(void) {
346         if (!bt_initialized) {
347                 bt_initialized = 1;
348                 libbt = dlopen("libbluetooth.so.3", RTLD_LAZY);
349                 if (!libbt)
350                         printf("failed to find bluetooth library\n");
351         }
352         return libbt != NULL;
353 }
354
355 #define join(a,b)       a ## b
356 #define bt_func(name, ret, fail, formals, actuals)                      \
357         static ret join(altos_, name) formals {                         \
358                                       static ret (*name) formals;       \
359                                       if (!init_bt()) return fail;      \
360                                       name = dlsym(libbt, #name);       \
361                                       if (!name) return fail;           \
362                                       return name actuals;              \
363                                       }
364
365 bt_func(ba2str, int, -1, (const bdaddr_t *ba, char *str), (ba, str))
366 #define ba2str altos_ba2str
367
368 bt_func(str2ba, int, -1, (const char *str, bdaddr_t *ba), (str, ba))
369 #define str2ba altos_str2ba
370
371 bt_func(hci_read_remote_name, int, -1, (int sock, const bdaddr_t *ba, int len, char *name, int timeout), (sock, ba, len, name, timeout))
372 #define hci_read_remote_name altos_hci_read_remote_name
373
374 bt_func(hci_open_dev, int, -1, (int dev_id), (dev_id))
375 #define hci_open_dev altos_hci_open_dev
376
377 bt_func(hci_get_route, int, -1, (bdaddr_t *bdaddr), (bdaddr))
378 #define hci_get_route altos_hci_get_route
379
380 bt_func(hci_inquiry, int, -1, (int adapter_id, int len, int max_rsp, const uint8_t *lap, inquiry_info **devs, long flags), (adapter_id, len, max_rsp, lap, devs, flags))
381 #define hci_inquiry altos_hci_inquiry
382
383 struct altos_bt_list {
384         inquiry_info    *ii;
385         int             sock;
386         int             dev_id;
387         int             rsp;
388         int             num_rsp;
389 };
390
391 #define INQUIRY_MAX_RSP 255
392
393 struct altos_bt_list *
394 altos_bt_list_start(int inquiry_time)
395 {
396         struct altos_bt_list    *bt_list;
397
398         bt_list = calloc(1, sizeof (struct altos_bt_list));
399         if (!bt_list)
400                 goto no_bt_list;
401
402         bt_list->ii = calloc(INQUIRY_MAX_RSP, sizeof (inquiry_info));
403         if (!bt_list->ii)
404                 goto no_ii;
405         bt_list->dev_id = hci_get_route(NULL);
406         if (bt_list->dev_id < 0)
407                 goto no_dev_id;
408
409         bt_list->sock = hci_open_dev(bt_list->dev_id);
410         if (bt_list->sock < 0)
411                 goto no_sock;
412
413         bt_list->num_rsp = hci_inquiry(bt_list->dev_id,
414                                        inquiry_time,
415                                        INQUIRY_MAX_RSP,
416                                        NULL,
417                                        &bt_list->ii,
418                                        IREQ_CACHE_FLUSH);
419         if (bt_list->num_rsp < 0)
420                 goto no_rsp;
421
422         bt_list->rsp = 0;
423         return bt_list;
424
425 no_rsp:
426         close(bt_list->sock);
427 no_sock:
428 no_dev_id:
429         free(bt_list->ii);
430 no_ii:
431         free(bt_list);
432 no_bt_list:
433         return NULL;
434 }
435
436 int
437 altos_bt_list_next(struct altos_bt_list *bt_list,
438                    struct altos_bt_device *device)
439 {
440         inquiry_info    *ii;
441
442         if (bt_list->rsp >= bt_list->num_rsp)
443                 return 0;
444
445         ii = &bt_list->ii[bt_list->rsp];
446         if (ba2str(&ii->bdaddr, device->addr) < 0)
447                 return 0;
448         memset(&device->name, '\0', sizeof (device->name));
449         if (hci_read_remote_name(bt_list->sock, &ii->bdaddr,
450                                  sizeof (device->name),
451                                  device->name, 0) < 0) {
452                 strcpy(device->name, "[unknown]");
453         }
454         bt_list->rsp++;
455         return 1;
456 }
457
458 void
459 altos_bt_list_finish(struct altos_bt_list *bt_list)
460 {
461         close(bt_list->sock);
462         free(bt_list->ii);
463         free(bt_list);
464 }
465
466 void
467 altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device)
468 {
469         strncpy(device->name, name, sizeof (device->name));
470         device->name[sizeof(device->name)-1] = '\0';
471         strncpy(device->addr, addr, sizeof (device->addr));
472         device->addr[sizeof(device->addr)-1] = '\0';
473 }
474
475 struct altos_file *
476 altos_bt_open(struct altos_bt_device *device)
477 {
478         struct sockaddr_rc      addr = { 0 };
479         int                     status, i;
480         struct altos_file_posix *file;
481
482         file = calloc(1, sizeof (struct altos_file_posix));
483         if (!file) {
484                 errno = ENOMEM;
485                 altos_set_last_posix_error();
486                 goto no_file;
487         }
488         addr.rc_family = AF_BLUETOOTH;
489         addr.rc_channel = 1;
490         if (str2ba(device->addr, &addr.rc_bdaddr) < 0) {
491                 altos_set_last_posix_error();
492                 goto no_sock;
493         }
494
495         for (i = 0; i < 5; i++) {
496                 file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
497                 if (file->fd < 0) {
498                         altos_set_last_posix_error();
499                         goto no_sock;
500                 }
501
502                 status = connect(file->fd,
503                                  (struct sockaddr *)&addr,
504                                  sizeof(addr));
505                 if (status >= 0 || errno != EBUSY)
506                         break;
507                 close(file->fd);
508                 usleep(100 * 1000);
509         }
510         if (status < 0) {
511                 altos_set_last_posix_error();
512                 goto no_link;
513         }
514         usleep(100 * 1000);
515
516 #ifdef USE_POLL
517         pipe(file->pipe);
518 #else
519         file->out_fd = dup(file->fd);
520 #endif
521         return &file->file;
522 no_link:
523         close(file->fd);
524 no_sock:
525         free(file);
526 no_file:
527         return NULL;
528 }
529