altos: Remove pdclib bits from Makefile
[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 #include <bluetooth/sdp.h>
30 #include <bluetooth/sdp_lib.h>
31
32 static char *
33 cc_fullname (char *dir, char *file)
34 {
35         char    *new;
36         int     dlen = strlen (dir);
37         int     flen = strlen (file);
38         int     slen = 0;
39
40         if (dir[dlen-1] != '/')
41                 slen = 1;
42         new = malloc (dlen + slen + flen + 1);
43         if (!new)
44                 return 0;
45         strcpy(new, dir);
46         if (slen)
47                 strcat (new, "/");
48         strcat(new, file);
49         return new;
50 }
51
52 static char *
53 cc_basename(char *file)
54 {
55         char *b;
56
57         b = strrchr(file, '/');
58         if (!b)
59                 return file;
60         return b + 1;
61 }
62
63 static char *
64 load_string(char *dir, char *file)
65 {
66         char    *full = cc_fullname(dir, file);
67         char    line[4096];
68         char    *r;
69         FILE    *f;
70         int     rlen;
71
72         f = fopen(full, "r");
73         free(full);
74         if (!f)
75                 return NULL;
76         r = fgets(line, sizeof (line), f);
77         fclose(f);
78         if (!r)
79                 return NULL;
80         rlen = strlen(r);
81         if (r[rlen-1] == '\n')
82                 r[rlen-1] = '\0';
83         return strdup(r);
84 }
85
86 static int
87 load_hex(char *dir, char *file)
88 {
89         char    *line;
90         char    *end;
91         long    i;
92
93         line = load_string(dir, file);
94         if (!line)
95                 return -1;
96         i = strtol(line, &end, 16);
97         free(line);
98         if (end == line)
99                 return -1;
100         return i;
101 }
102
103 static int
104 load_dec(char *dir, char *file)
105 {
106         char    *line;
107         char    *end;
108         long    i;
109
110         line = load_string(dir, file);
111         if (!line)
112                 return -1;
113         i = strtol(line, &end, 10);
114         free(line);
115         if (end == line)
116                 return -1;
117         return i;
118 }
119
120 static int
121 dir_filter_tty_colon(const struct dirent *d)
122 {
123         return strncmp(d->d_name, "tty:", 4) == 0;
124 }
125
126 static int
127 dir_filter_tty(const struct dirent *d)
128 {
129         return strncmp(d->d_name, "tty", 3) == 0;
130 }
131
132 struct altos_usbdev {
133         char    *sys;
134         char    *tty;
135         char    *manufacturer;
136         char    *product_name;
137         int     serial; /* AltOS always uses simple integer serial numbers */
138         int     idProduct;
139         int     idVendor;
140 };
141
142 static char *
143 usb_tty(char *sys)
144 {
145         char *base;
146         int num_configs;
147         int config;
148         struct dirent **namelist;
149         int interface;
150         int num_interfaces;
151         char endpoint_base[20];
152         char *endpoint_full;
153         char *tty_dir;
154         int ntty;
155         char *tty;
156
157         base = cc_basename(sys);
158         num_configs = load_hex(sys, "bNumConfigurations");
159         num_interfaces = load_hex(sys, "bNumInterfaces");
160         for (config = 1; config <= num_configs; config++) {
161                 for (interface = 0; interface < num_interfaces; interface++) {
162                         sprintf(endpoint_base, "%s:%d.%d",
163                                 base, config, interface);
164                         endpoint_full = cc_fullname(sys, endpoint_base);
165
166
167                         /* Check for tty:ttyACMx style names
168                          */
169                         ntty = scandir(endpoint_full, &namelist,
170                                        dir_filter_tty_colon,
171                                        alphasort);
172                         if (ntty > 0) {
173                                 free(endpoint_full);
174                                 tty = cc_fullname("/dev", namelist[0]->d_name + 4);
175                                 free(namelist);
176                                 return tty;
177                         }
178
179                         /* Check for tty/ttyACMx style names
180                          */
181                         tty_dir = cc_fullname(endpoint_full, "tty");
182                         ntty = scandir(tty_dir, &namelist,
183                                        dir_filter_tty,
184                                        alphasort);
185                         free (tty_dir);
186                         if (ntty > 0) {
187                                 tty = cc_fullname("/dev", namelist[0]->d_name);
188                                 free(endpoint_full);
189                                 free(namelist);
190                                 return tty;
191                         }
192
193                         /* Check for ttyACMx style names
194                          */
195                         ntty = scandir(endpoint_full, &namelist,
196                                        dir_filter_tty,
197                                        alphasort);
198                         free(endpoint_full);
199                         if (ntty > 0) {
200                                 tty = cc_fullname("/dev", namelist[0]->d_name);
201                                 free(namelist);
202                                 return tty;
203                         }
204
205                 }
206         }
207         return NULL;
208 }
209
210 static struct altos_usbdev *
211 usb_scan_device(char *sys)
212 {
213         struct altos_usbdev *usbdev;
214         char *tty;
215
216         tty = usb_tty(sys);
217         if (!tty)
218                 return NULL;
219         usbdev = calloc(1, sizeof (struct altos_usbdev));
220         if (!usbdev)
221                 return NULL;
222         usbdev->sys = strdup(sys);
223         usbdev->manufacturer = load_string(sys, "manufacturer");
224         usbdev->product_name = load_string(sys, "product");
225         usbdev->serial = load_dec(sys, "serial");
226         usbdev->idProduct = load_hex(sys, "idProduct");
227         usbdev->idVendor = load_hex(sys, "idVendor");
228         usbdev->tty = tty;
229         return usbdev;
230 }
231
232 static void
233 usbdev_free(struct altos_usbdev *usbdev)
234 {
235         free(usbdev->sys);
236         free(usbdev->manufacturer);
237         free(usbdev->product_name);
238         /* this can get used as a return value */
239         if (usbdev->tty)
240                 free(usbdev->tty);
241         free(usbdev);
242 }
243
244 #define USB_DEVICES     "/sys/bus/usb/devices"
245
246 static int
247 dir_filter_dev(const struct dirent *d)
248 {
249         const char      *n = d->d_name;
250         char    c;
251
252         while ((c = *n++)) {
253                 if (isdigit(c))
254                         continue;
255                 if (c == '-')
256                         continue;
257                 if (c == '.' && n != d->d_name + 1)
258                         continue;
259                 return 0;
260         }
261         return 1;
262 }
263
264 struct altos_list {
265         struct altos_usbdev     **dev;
266         int                     current;
267         int                     ndev;
268 };
269
270 struct altos_list *
271 altos_list_start(void)
272 {
273         int                     e;
274         struct dirent           **ents;
275         char                    *dir;
276         struct altos_usbdev     *dev;
277         struct altos_list       *devs;
278         int                     n;
279
280         devs = calloc(1, sizeof (struct altos_list));
281         if (!devs)
282                 return NULL;
283
284         n = scandir (USB_DEVICES, &ents,
285                      dir_filter_dev,
286                      alphasort);
287         if (!n)
288                 return 0;
289         for (e = 0; e < n; e++) {
290                 dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
291                 dev = usb_scan_device(dir);
292                 if (!dev)
293                         continue;
294                 free(dir);
295                 if (devs->dev)
296                         devs->dev = realloc(devs->dev,
297                                             (devs->ndev + 1) * sizeof (struct usbdev *));
298                 else
299                         devs->dev = malloc (sizeof (struct usbdev *));
300                 devs->dev[devs->ndev++] = dev;
301         }
302         free(ents);
303         devs->current = 0;
304         return devs;
305 }
306
307 PUBLIC struct altos_list *
308 altos_ftdi_list_start(void)
309 {
310         return altos_list_start();
311 }
312
313 int
314 altos_list_next(struct altos_list *list, struct altos_device *device)
315 {
316         struct altos_usbdev *dev;
317         if (list->current >= list->ndev) {
318                 return 0;
319         }
320         dev = list->dev[list->current];
321         strcpy(device->name, dev->product_name);
322         device->vendor = dev->idVendor;
323         device->product = dev->idProduct;
324         strcpy(device->path, dev->tty);
325         device->serial = dev->serial;
326         list->current++;
327         return 1;
328 }
329
330 void
331 altos_list_finish(struct altos_list *usbdevs)
332 {
333         int     i;
334
335         if (!usbdevs)
336                 return;
337         for (i = 0; i < usbdevs->ndev; i++)
338                 usbdev_free(usbdevs->dev[i]);
339         free(usbdevs);
340 }
341
342 #include <dlfcn.h>
343
344 static void *libbt;
345 static int bt_initialized;
346
347 static int init_bt(void) {
348         if (!bt_initialized) {
349                 bt_initialized = 1;
350                 libbt = dlopen("libbluetooth.so.3", RTLD_LAZY);
351                 if (!libbt)
352                         printf("failed to find bluetooth library\n");
353         }
354         return libbt != NULL;
355 }
356
357 #define join(a,b)       a ## b
358 #define bt_func(name, ret, fail, formals, actuals)                      \
359         static ret join(altos_, name) formals {                         \
360                                       static ret (*name) formals;       \
361                                       if (!init_bt()) return fail;      \
362                                       name = dlsym(libbt, #name);       \
363                                       if (!name) return fail;           \
364                                       return name actuals;              \
365                                       }
366
367 bt_func(ba2str, int, -1, (const bdaddr_t *ba, char *str), (ba, str))
368 #define ba2str altos_ba2str
369
370 bt_func(str2ba, int, -1, (const char *str, bdaddr_t *ba), (str, ba))
371 #define str2ba altos_str2ba
372
373 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))
374 #define hci_read_remote_name altos_hci_read_remote_name
375
376 bt_func(hci_open_dev, int, -1, (int dev_id), (dev_id))
377 #define hci_open_dev altos_hci_open_dev
378
379 bt_func(hci_get_route, int, -1, (bdaddr_t *bdaddr), (bdaddr))
380 #define hci_get_route altos_hci_get_route
381
382 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))
383 #define hci_inquiry altos_hci_inquiry
384
385 bt_func(sdp_connect, sdp_session_t *, 0, (const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags), (src, dst, flags))
386 #define sdp_connect altos_sdp_connect
387
388 bt_func(sdp_uuid16_create, uuid_t *, 0, (uuid_t *uuid, uint16_t data), (uuid, data))
389 #define sdp_uuid16_create altos_sdp_uuid16_create
390
391 bt_func(sdp_list_append, sdp_list_t *, 0, (sdp_list_t *list, void *d), (list, d))
392 #define sdp_list_append altos_sdp_list_append
393
394 bt_func(sdp_service_search_attr_req, int, -1, (sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list, sdp_list_t **rsp_list), (session, search, reqtype, attrid_list, rsp_list))
395 #define sdp_service_search_attr_req altos_sdp_service_search_attr_req
396
397 bt_func(sdp_uuid_to_proto, int, 0, (uuid_t *uuid), (uuid))
398 #define sdp_uuid_to_proto altos_sdp_uuid_to_proto
399
400 bt_func(sdp_get_access_protos, int, 0, (const sdp_record_t *rec, sdp_list_t **protos), (rec, protos))
401 #define sdp_get_access_protos altos_sdp_get_access_protos
402
403 bt_func(sdp_get_proto_port, int, 0, (const sdp_list_t *list, int proto), (list, proto))
404 #define sdp_get_proto_port altos_sdp_get_proto_port
405
406 bt_func(sdp_close, int, 0, (sdp_session_t *session), (session))
407 #define sdp_close altos_sdp_close
408
409 struct altos_bt_list {
410         inquiry_info    *ii;
411         int             sock;
412         int             dev_id;
413         int             rsp;
414         int             num_rsp;
415 };
416
417 #define INQUIRY_MAX_RSP 255
418
419 struct altos_bt_list *
420 altos_bt_list_start(int inquiry_time)
421 {
422         struct altos_bt_list    *bt_list;
423
424         bt_list = calloc(1, sizeof (struct altos_bt_list));
425         if (!bt_list)
426                 goto no_bt_list;
427
428         bt_list->ii = calloc(INQUIRY_MAX_RSP, sizeof (inquiry_info));
429         if (!bt_list->ii)
430                 goto no_ii;
431         bt_list->dev_id = hci_get_route(NULL);
432         if (bt_list->dev_id < 0)
433                 goto no_dev_id;
434
435         bt_list->sock = hci_open_dev(bt_list->dev_id);
436         if (bt_list->sock < 0)
437                 goto no_sock;
438
439         bt_list->num_rsp = hci_inquiry(bt_list->dev_id,
440                                        inquiry_time,
441                                        INQUIRY_MAX_RSP,
442                                        NULL,
443                                        &bt_list->ii,
444                                        IREQ_CACHE_FLUSH);
445         if (bt_list->num_rsp < 0)
446                 goto no_rsp;
447
448         bt_list->rsp = 0;
449         return bt_list;
450
451 no_rsp:
452         close(bt_list->sock);
453 no_sock:
454 no_dev_id:
455         free(bt_list->ii);
456 no_ii:
457         free(bt_list);
458 no_bt_list:
459         return NULL;
460 }
461
462 int
463 altos_bt_list_next(struct altos_bt_list *bt_list,
464                    struct altos_bt_device *device)
465 {
466         inquiry_info    *ii;
467
468         if (bt_list->rsp >= bt_list->num_rsp)
469                 return 0;
470
471         ii = &bt_list->ii[bt_list->rsp];
472         if (ba2str(&ii->bdaddr, device->addr) < 0)
473                 return 0;
474         memset(&device->name, '\0', sizeof (device->name));
475         if (hci_read_remote_name(bt_list->sock, &ii->bdaddr,
476                                  sizeof (device->name),
477                                  device->name, 0) < 0) {
478                 strcpy(device->name, "[unknown]");
479         }
480         bt_list->rsp++;
481         return 1;
482 }
483
484 void
485 altos_bt_list_finish(struct altos_bt_list *bt_list)
486 {
487         close(bt_list->sock);
488         free(bt_list->ii);
489         free(bt_list);
490 }
491
492 void
493 altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device)
494 {
495         strncpy(device->name, name, sizeof (device->name));
496         device->name[sizeof(device->name)-1] = '\0';
497         strncpy(device->addr, addr, sizeof (device->addr));
498         device->addr[sizeof(device->addr)-1] = '\0';
499 }
500
501 struct altos_file *
502 altos_bt_open(struct altos_bt_device *device)
503 {
504         struct sockaddr_rc      addr = { 0 };
505         int                     status, i;
506         struct altos_file_posix *file;
507         sdp_session_t           *session = NULL;
508         int                     channel = 0;
509
510         if (str2ba(device->addr, &addr.rc_bdaddr) < 0) {
511                 altos_set_last_posix_error();
512                 goto no_file;
513         }
514
515 #if 0
516         /*
517          * Search for the RFCOMM service to get the right channel
518          */
519         session = sdp_connect(BDADDR_ANY, &addr.rc_bdaddr, SDP_RETRY_IF_BUSY);
520
521         if (session) {
522                 static const uint8_t svc_uuid_int[] = {
523                         0, 0, 0, 0, 0, 0, 0, 0,
524                         0, 0, 0, 0, 0, 0, 0x11, 0x01
525                 };
526                 int                     err;
527                 uuid_t                  svc_uuid;
528                 uint32_t                range;
529                 sdp_list_t              *search_list, *attrid_list;
530                 sdp_list_t              *response_list = NULL, *r;
531                 sdp_uuid16_create(&svc_uuid, PUBLIC_BROWSE_GROUP);
532                 search_list = sdp_list_append(NULL, &svc_uuid);
533
534                 range = 0x0000ffff;
535                 attrid_list = sdp_list_append(NULL, &range);
536
537                 err = sdp_service_search_attr_req(session, search_list,
538                                                   SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
539
540                 if (err >= 0) {
541                         for (r = response_list; r; r = r->next) {
542                                 sdp_record_t *rec = (sdp_record_t*) r->data;
543                                 sdp_list_t *proto_list;
544                                 sdp_list_t *access = NULL;
545                                 int proto;
546
547                                 proto = sdp_uuid_to_proto(&rec->svclass);
548
549                                 if (proto == SERIAL_PORT_SVCLASS_ID) {
550                                         sdp_get_access_protos(rec, &access);
551                                         if (access) {
552                                                 int this_chan = sdp_get_proto_port(access, RFCOMM_UUID);
553                                                 if (this_chan)
554                                                         channel = this_chan;
555                                         }
556                                 }
557                         }
558                 }
559
560                 /* Leave the session open so we don't disconnect from the device before opening
561                  * the RFCOMM channel
562                  */
563         }
564 #endif
565         if (channel == 0)
566                 channel = altos_bt_port(device);
567
568         /* Connect to the channel */
569         file = calloc(1, sizeof (struct altos_file_posix));
570         if (!file) {
571                 errno = ENOMEM;
572                 altos_set_last_posix_error();
573                 goto no_file;
574         }
575         addr.rc_family = AF_BLUETOOTH;
576         addr.rc_channel = channel;
577
578         for (i = 0; i < 5; i++) {
579                 file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
580                 if (file->fd < 0) {
581                         altos_set_last_posix_error();
582                         goto no_sock;
583                 }
584
585                 status = connect(file->fd,
586                                  (struct sockaddr *)&addr,
587                                  sizeof(addr));
588                 if (status >= 0 || errno != EBUSY)
589                         break;
590                 close(file->fd);
591                 usleep(100 * 1000);
592         }
593
594
595         if (status < 0) {
596                 altos_set_last_posix_error();
597                 goto no_link;
598         }
599
600         if (session)
601                 sdp_close(session);
602
603         usleep(100 * 1000);
604
605 #ifdef USE_POLL
606         pipe(file->pipe);
607 #else
608         file->out_fd = dup(file->fd);
609 #endif
610         return &file->file;
611 no_link:
612         close(file->fd);
613 no_sock:
614         free(file);
615 no_file:
616         if (session)
617                 sdp_close(session);
618         return NULL;
619 }
620