libaltos: Add Windows BT support. Split into separate source files.
[fw/altos] / libaltos / libaltos_linux.c
diff --git a/libaltos/libaltos_linux.c b/libaltos/libaltos_linux.c
new file mode 100644 (file)
index 0000000..d7cd15c
--- /dev/null
@@ -0,0 +1,528 @@
+/*
+ * Copyright © 2016 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#define _GNU_SOURCE
+#include "libaltos_private.h"
+#include "libaltos_posix.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+
+static char *
+cc_fullname (char *dir, char *file)
+{
+       char    *new;
+       int     dlen = strlen (dir);
+       int     flen = strlen (file);
+       int     slen = 0;
+
+       if (dir[dlen-1] != '/')
+               slen = 1;
+       new = malloc (dlen + slen + flen + 1);
+       if (!new)
+               return 0;
+       strcpy(new, dir);
+       if (slen)
+               strcat (new, "/");
+       strcat(new, file);
+       return new;
+}
+
+static char *
+cc_basename(char *file)
+{
+       char *b;
+
+       b = strrchr(file, '/');
+       if (!b)
+               return file;
+       return b + 1;
+}
+
+static char *
+load_string(char *dir, char *file)
+{
+       char    *full = cc_fullname(dir, file);
+       char    line[4096];
+       char    *r;
+       FILE    *f;
+       int     rlen;
+
+       f = fopen(full, "r");
+       free(full);
+       if (!f)
+               return NULL;
+       r = fgets(line, sizeof (line), f);
+       fclose(f);
+       if (!r)
+               return NULL;
+       rlen = strlen(r);
+       if (r[rlen-1] == '\n')
+               r[rlen-1] = '\0';
+       return strdup(r);
+}
+
+static int
+load_hex(char *dir, char *file)
+{
+       char    *line;
+       char    *end;
+       long    i;
+
+       line = load_string(dir, file);
+       if (!line)
+               return -1;
+       i = strtol(line, &end, 16);
+       free(line);
+       if (end == line)
+               return -1;
+       return i;
+}
+
+static int
+load_dec(char *dir, char *file)
+{
+       char    *line;
+       char    *end;
+       long    i;
+
+       line = load_string(dir, file);
+       if (!line)
+               return -1;
+       i = strtol(line, &end, 10);
+       free(line);
+       if (end == line)
+               return -1;
+       return i;
+}
+
+static int
+dir_filter_tty_colon(const struct dirent *d)
+{
+       return strncmp(d->d_name, "tty:", 4) == 0;
+}
+
+static int
+dir_filter_tty(const struct dirent *d)
+{
+       return strncmp(d->d_name, "tty", 3) == 0;
+}
+
+struct altos_usbdev {
+       char    *sys;
+       char    *tty;
+       char    *manufacturer;
+       char    *product_name;
+       int     serial; /* AltOS always uses simple integer serial numbers */
+       int     idProduct;
+       int     idVendor;
+};
+
+static char *
+usb_tty(char *sys)
+{
+       char *base;
+       int num_configs;
+       int config;
+       struct dirent **namelist;
+       int interface;
+       int num_interfaces;
+       char endpoint_base[20];
+       char *endpoint_full;
+       char *tty_dir;
+       int ntty;
+       char *tty;
+
+       base = cc_basename(sys);
+       num_configs = load_hex(sys, "bNumConfigurations");
+       num_interfaces = load_hex(sys, "bNumInterfaces");
+       for (config = 1; config <= num_configs; config++) {
+               for (interface = 0; interface < num_interfaces; interface++) {
+                       sprintf(endpoint_base, "%s:%d.%d",
+                               base, config, interface);
+                       endpoint_full = cc_fullname(sys, endpoint_base);
+
+
+                       /* Check for tty:ttyACMx style names
+                        */
+                       ntty = scandir(endpoint_full, &namelist,
+                                      dir_filter_tty_colon,
+                                      alphasort);
+                       if (ntty > 0) {
+                               free(endpoint_full);
+                               tty = cc_fullname("/dev", namelist[0]->d_name + 4);
+                               free(namelist);
+                               return tty;
+                       }
+
+                       /* Check for tty/ttyACMx style names
+                        */
+                       tty_dir = cc_fullname(endpoint_full, "tty");
+                       ntty = scandir(tty_dir, &namelist,
+                                      dir_filter_tty,
+                                      alphasort);
+                       free (tty_dir);
+                       if (ntty > 0) {
+                               tty = cc_fullname("/dev", namelist[0]->d_name);
+                               free(endpoint_full);
+                               free(namelist);
+                               return tty;
+                       }
+
+                       /* Check for ttyACMx style names
+                        */
+                       ntty = scandir(endpoint_full, &namelist,
+                                      dir_filter_tty,
+                                      alphasort);
+                       free(endpoint_full);
+                       if (ntty > 0) {
+                               tty = cc_fullname("/dev", namelist[0]->d_name);
+                               free(namelist);
+                               return tty;
+                       }
+
+               }
+       }
+       return NULL;
+}
+
+static struct altos_usbdev *
+usb_scan_device(char *sys)
+{
+       struct altos_usbdev *usbdev;
+       char *tty;
+
+       tty = usb_tty(sys);
+       if (!tty)
+               return NULL;
+       usbdev = calloc(1, sizeof (struct altos_usbdev));
+       if (!usbdev)
+               return NULL;
+       usbdev->sys = strdup(sys);
+       usbdev->manufacturer = load_string(sys, "manufacturer");
+       usbdev->product_name = load_string(sys, "product");
+       usbdev->serial = load_dec(sys, "serial");
+       usbdev->idProduct = load_hex(sys, "idProduct");
+       usbdev->idVendor = load_hex(sys, "idVendor");
+       usbdev->tty = tty;
+       return usbdev;
+}
+
+static void
+usbdev_free(struct altos_usbdev *usbdev)
+{
+       free(usbdev->sys);
+       free(usbdev->manufacturer);
+       free(usbdev->product_name);
+       /* this can get used as a return value */
+       if (usbdev->tty)
+               free(usbdev->tty);
+       free(usbdev);
+}
+
+#define USB_DEVICES    "/sys/bus/usb/devices"
+
+static int
+dir_filter_dev(const struct dirent *d)
+{
+       const char      *n = d->d_name;
+       char    c;
+
+       while ((c = *n++)) {
+               if (isdigit(c))
+                       continue;
+               if (c == '-')
+                       continue;
+               if (c == '.' && n != d->d_name + 1)
+                       continue;
+               return 0;
+       }
+       return 1;
+}
+
+struct altos_list {
+       struct altos_usbdev     **dev;
+       int                     current;
+       int                     ndev;
+};
+
+struct altos_list *
+altos_list_start(void)
+{
+       int                     e;
+       struct dirent           **ents;
+       char                    *dir;
+       struct altos_usbdev     *dev;
+       struct altos_list       *devs;
+       int                     n;
+
+       devs = calloc(1, sizeof (struct altos_list));
+       if (!devs)
+               return NULL;
+
+       n = scandir (USB_DEVICES, &ents,
+                    dir_filter_dev,
+                    alphasort);
+       if (!n)
+               return 0;
+       for (e = 0; e < n; e++) {
+               dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
+               dev = usb_scan_device(dir);
+               if (!dev)
+                       continue;
+               free(dir);
+               if (devs->dev)
+                       devs->dev = realloc(devs->dev,
+                                           (devs->ndev + 1) * sizeof (struct usbdev *));
+               else
+                       devs->dev = malloc (sizeof (struct usbdev *));
+               devs->dev[devs->ndev++] = dev;
+       }
+       free(ents);
+       devs->current = 0;
+       return devs;
+}
+
+PUBLIC struct altos_list *
+altos_ftdi_list_start(void)
+{
+       return altos_list_start();
+}
+
+int
+altos_list_next(struct altos_list *list, struct altos_device *device)
+{
+       struct altos_usbdev *dev;
+       if (list->current >= list->ndev) {
+               return 0;
+       }
+       dev = list->dev[list->current];
+       strcpy(device->name, dev->product_name);
+       device->vendor = dev->idVendor;
+       device->product = dev->idProduct;
+       strcpy(device->path, dev->tty);
+       device->serial = dev->serial;
+       list->current++;
+       return 1;
+}
+
+void
+altos_list_finish(struct altos_list *usbdevs)
+{
+       int     i;
+
+       if (!usbdevs)
+               return;
+       for (i = 0; i < usbdevs->ndev; i++)
+               usbdev_free(usbdevs->dev[i]);
+       free(usbdevs);
+}
+
+#include <dlfcn.h>
+
+static void *libbt;
+static int bt_initialized;
+
+static int init_bt(void) {
+       if (!bt_initialized) {
+               bt_initialized = 1;
+               libbt = dlopen("libbluetooth.so.3", RTLD_LAZY);
+               if (!libbt)
+                       printf("failed to find bluetooth library\n");
+       }
+       return libbt != NULL;
+}
+
+#define join(a,b)      a ## b
+#define bt_func(name, ret, fail, formals, actuals)                     \
+       static ret join(altos_, name) formals {                         \
+                                     static ret (*name) formals;       \
+                                     if (!init_bt()) return fail;      \
+                                     name = dlsym(libbt, #name);       \
+                                     if (!name) return fail;           \
+                                     return name actuals;              \
+                                     }
+
+bt_func(ba2str, int, -1, (const bdaddr_t *ba, char *str), (ba, str))
+#define ba2str altos_ba2str
+
+bt_func(str2ba, int, -1, (const char *str, bdaddr_t *ba), (str, ba))
+#define str2ba altos_str2ba
+
+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))
+#define hci_read_remote_name altos_hci_read_remote_name
+
+bt_func(hci_open_dev, int, -1, (int dev_id), (dev_id))
+#define hci_open_dev altos_hci_open_dev
+
+bt_func(hci_get_route, int, -1, (bdaddr_t *bdaddr), (bdaddr))
+#define hci_get_route altos_hci_get_route
+
+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))
+#define hci_inquiry altos_hci_inquiry
+
+struct altos_bt_list {
+       inquiry_info    *ii;
+       int             sock;
+       int             dev_id;
+       int             rsp;
+       int             num_rsp;
+};
+
+#define INQUIRY_MAX_RSP        255
+
+struct altos_bt_list *
+altos_bt_list_start(int inquiry_time)
+{
+       struct altos_bt_list    *bt_list;
+
+       bt_list = calloc(1, sizeof (struct altos_bt_list));
+       if (!bt_list)
+               goto no_bt_list;
+
+       bt_list->ii = calloc(INQUIRY_MAX_RSP, sizeof (inquiry_info));
+       if (!bt_list->ii)
+               goto no_ii;
+       bt_list->dev_id = hci_get_route(NULL);
+       if (bt_list->dev_id < 0)
+               goto no_dev_id;
+
+       bt_list->sock = hci_open_dev(bt_list->dev_id);
+       if (bt_list->sock < 0)
+               goto no_sock;
+
+       bt_list->num_rsp = hci_inquiry(bt_list->dev_id,
+                                      inquiry_time,
+                                      INQUIRY_MAX_RSP,
+                                      NULL,
+                                      &bt_list->ii,
+                                      IREQ_CACHE_FLUSH);
+       if (bt_list->num_rsp < 0)
+               goto no_rsp;
+
+       bt_list->rsp = 0;
+       return bt_list;
+
+no_rsp:
+       close(bt_list->sock);
+no_sock:
+no_dev_id:
+       free(bt_list->ii);
+no_ii:
+       free(bt_list);
+no_bt_list:
+       return NULL;
+}
+
+int
+altos_bt_list_next(struct altos_bt_list *bt_list,
+                  struct altos_bt_device *device)
+{
+       inquiry_info    *ii;
+
+       if (bt_list->rsp >= bt_list->num_rsp)
+               return 0;
+
+       ii = &bt_list->ii[bt_list->rsp];
+       if (ba2str(&ii->bdaddr, device->addr) < 0)
+               return 0;
+       memset(&device->name, '\0', sizeof (device->name));
+       if (hci_read_remote_name(bt_list->sock, &ii->bdaddr,
+                                sizeof (device->name),
+                                device->name, 0) < 0) {
+               strcpy(device->name, "[unknown]");
+       }
+       bt_list->rsp++;
+       return 1;
+}
+
+void
+altos_bt_list_finish(struct altos_bt_list *bt_list)
+{
+       close(bt_list->sock);
+       free(bt_list->ii);
+       free(bt_list);
+}
+
+void
+altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device)
+{
+       strncpy(device->name, name, sizeof (device->name));
+       device->name[sizeof(device->name)-1] = '\0';
+       strncpy(device->addr, addr, sizeof (device->addr));
+       device->addr[sizeof(device->addr)-1] = '\0';
+}
+
+struct altos_file *
+altos_bt_open(struct altos_bt_device *device)
+{
+       struct sockaddr_rc      addr = { 0 };
+       int                     status, i;
+       struct altos_file_posix *file;
+
+       file = calloc(1, sizeof (struct altos_file_posix));
+       if (!file) {
+               errno = ENOMEM;
+               altos_set_last_posix_error();
+               goto no_file;
+       }
+       addr.rc_family = AF_BLUETOOTH;
+       addr.rc_channel = 1;
+       if (str2ba(device->addr, &addr.rc_bdaddr) < 0) {
+               altos_set_last_posix_error();
+               goto no_sock;
+       }
+
+       for (i = 0; i < 5; i++) {
+               file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+               if (file->fd < 0) {
+                       altos_set_last_posix_error();
+                       goto no_sock;
+               }
+
+               status = connect(file->fd,
+                                (struct sockaddr *)&addr,
+                                sizeof(addr));
+               if (status >= 0 || errno != EBUSY)
+                       break;
+               close(file->fd);
+               usleep(100 * 1000);
+       }
+       if (status < 0) {
+               altos_set_last_posix_error();
+               goto no_link;
+       }
+       usleep(100 * 1000);
+
+#ifdef USE_POLL
+       pipe(file->pipe);
+#else
+       file->out_fd = dup(file->fd);
+#endif
+       return file;
+no_link:
+       close(file->fd);
+no_sock:
+       free(file);
+no_file:
+       return NULL;
+}
+