diff options
Diffstat (limited to 'libaltos/libaltos_linux.c')
| -rw-r--r-- | libaltos/libaltos_linux.c | 528 | 
1 files changed, 528 insertions, 0 deletions
| diff --git a/libaltos/libaltos_linux.c b/libaltos/libaltos_linux.c new file mode 100644 index 00000000..2065d74a --- /dev/null +++ b/libaltos/libaltos_linux.c @@ -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->file; +no_link: +	close(file->fd); +no_sock: +	free(file); +no_file: +	return NULL; +} + | 
