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