1/*	$NetBSD: bthset.c,v 1.6 2011/08/25 16:19:23 joerg Exp $	*/
2
3/*-
4 * Copyright (c) 2006 Itronix Inc.
5 * All rights reserved.
6 *
7 * Written by Iain Hibbert for Itronix Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. The name of Itronix Inc. may not be used to endorse
18 *    or promote products derived from this software without specific
19 *    prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
25 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28 * ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <sys/cdefs.h>
35__COPYRIGHT("@(#) Copyright (c) 2006 Itronix, Inc.  All rights reserved.");
36__RCSID("$NetBSD: bthset.c,v 1.6 2011/08/25 16:19:23 joerg Exp $");
37
38#include <sys/types.h>
39#include <sys/audioio.h>
40#include <sys/ioctl.h>
41#include <sys/time.h>
42#include <sys/uio.h>
43
44#include <assert.h>
45#include <bluetooth.h>
46#include <err.h>
47#include <event.h>
48#include <fcntl.h>
49#include <sdp.h>
50#include <signal.h>
51#include <stdarg.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56#include <errno.h>
57
58#include <dev/bluetooth/btdev.h>
59#include <dev/bluetooth/btsco.h>
60
61#include <netbt/rfcomm.h>
62
63#define RING_INTERVAL	5	/* seconds */
64
65__dead static void usage(void);
66
67static void do_signal(int, short, void *);
68static void do_ring(int, short, void *);
69static void do_mixer(int, short, void *);
70static void do_rfcomm(int, short, void *);
71static void do_server(int, short, void *);
72static int send_rfcomm(const char *, ...);
73
74static int init_mixer(struct btsco_info *, const char *);
75static int init_rfcomm(struct btsco_info *);
76static int init_server(struct btsco_info *, int);
77
78static void remove_pid(void);
79static int write_pid(void);
80
81static struct event sigint_ev;		/* bye bye */
82static struct event sigusr1_ev;	/* start ringing */
83static struct event sigusr2_ev;	/* stop ringing */
84static struct event mixer_ev;		/* mixer changed */
85static struct event rfcomm_ev;		/* headset speaks */
86static struct event server_ev;		/* headset connecting */
87static struct event ring_ev;		/* ring timer */
88
89static mixer_ctrl_t vgs;	/* speaker control */
90static mixer_ctrl_t vgm;	/* mic control */
91static int ringing;		/* we are ringing */
92static int verbose;		/* copy to stdout */
93static int mx;			/* mixer fd */
94static int rf;			/* rfcomm connection fd */
95static int ag;			/* rfcomm gateway fd */
96static sdp_session_t ss;	/* SDP server session */
97
98static char *command;		/* answer command */
99static char *pidfile;		/* PID file name */
100
101/* Headset Audio Gateway service record */
102static uint8_t hset_data[] = {
103	0x09, 0x00, 0x00,	//  uint16	ServiceRecordHandle
104	0x0a, 0x00, 0x00, 0x00,	//  uint32	0x00000000
105	0x00,
106
107	0x09, 0x00, 0x01,	//  uint16	ServiceClassIDList
108	0x35, 0x06,		//  seq8(6)
109	0x19, 0x11, 0x12,	//   uuid16	HeadsetAudioGateway
110	0x19, 0x12, 0x03,	//   uuid16	GenericAudio
111
112	0x09, 0x00, 0x04,	//  uint16	ProtocolDescriptorList
113	0x35, 0x0c,		//  seq8(12)
114	0x35, 0x03,		//   seq8(3)
115	0x19, 0x01, 0x00,	//    uuid16	L2CAP
116	0x35, 0x05,		//   seq8(5)
117	0x19, 0x00, 0x03,	//    uuid16	RFCOMM
118	0x08, 0x00,		//    uint8	%hset_channel%
119
120	0x09, 0x00, 0x05,	//  uint16	BrowseGroupList
121	0x35, 0x03,		//  seq8(3)
122	0x19, 0x10, 0x02,	//   uuid16	PublicBrowseGroup
123
124	0x09, 0x00, 0x06,	//  uint16	LanguageBaseAttributeIDList
125	0x35, 0x09,		//  seq8(9)
126	0x09, 0x65, 0x6e,	//   uint16	0x656e	("en")
127	0x09, 0x00, 0x6a,	//   uint16	106	(UTF-8)
128	0x09, 0x01, 0x00,	//   uint16	PrimaryLanguageBaseID
129
130	0x09, 0x00, 0x09,	//  uint16	BluetoothProfileDescriptorList
131	0x35, 0x08,		//  seq8(8)
132	0x35, 0x06,		//   seq8(6)
133	0x19, 0x11, 0x08,	//    uuid16	Headset
134	0x09, 0x01, 0x00,	//    uint16	v1.0
135
136	0x09, 0x01, 0x00,	//  uint16	PrimaryLanguageBaseID + ServiceNameOffset
137	0x25, 0x0d, 0x56, 0x6f,	//  str8(13)	"Voice Gateway"
138	0x69, 0x63, 0x65, 0x20,
139	0x47, 0x61, 0x74, 0x65,
140	0x77, 0x61, 0x79
141};
142
143static sdp_data_t hset_record =	{ hset_data + 0, hset_data + 91 };
144static sdp_data_t hset_channel =	{ hset_data + 36, hset_data + 37 };
145
146int
147main(int ac, char *av[])
148{
149	struct btsco_info	info;
150	const char		*mixer;
151	int			ch, channel;
152
153	ag = rf = -1;
154	verbose = 0;
155	channel = 0;
156	pidfile = getenv("BTHSET_PIDFILE");
157	command = getenv("BTHSET_COMMAND");
158	mixer = getenv("BTHSET_MIXER");
159	if (mixer == NULL)
160		mixer = "/dev/mixer";
161
162	while ((ch = getopt(ac, av, "hc:m:p:s:v")) != EOF) {
163		switch (ch) {
164		case 'c':
165			command = optarg;
166			break;
167
168		case 'm':
169			mixer = optarg;
170			break;
171
172		case 'p':
173			pidfile = optarg;
174			break;
175
176		case 's':
177			channel = atoi(optarg);
178			break;
179
180		case 'v':
181			verbose = 1;
182			break;
183
184		case 'h':
185		default:
186			usage();
187		}
188	}
189
190	if (mixer == NULL)
191		usage();
192
193	if ((channel < RFCOMM_CHANNEL_MIN || channel > RFCOMM_CHANNEL_MAX)
194	    && channel != 0)
195		usage();
196
197	if (write_pid() < 0)
198		err(EXIT_FAILURE, "%s", pidfile);
199
200	event_init();
201
202	ringing = 0;
203	evtimer_set(&ring_ev, do_ring, NULL);
204
205	signal_set(&sigusr1_ev, SIGUSR1, do_signal, NULL);
206	if (signal_add(&sigusr1_ev, NULL) < 0)
207		err(EXIT_FAILURE, "SIGUSR1");
208
209	signal_set(&sigusr2_ev, SIGUSR2, do_signal, NULL);
210	if (signal_add(&sigusr2_ev, NULL) < 0)
211		err(EXIT_FAILURE, "SIGUSR2");
212
213	signal_set(&sigint_ev, SIGINT, do_signal, NULL);
214	if (signal_add(&sigint_ev, NULL) < 0)
215		err(EXIT_FAILURE, "SIGINT");
216
217	if (init_mixer(&info, mixer) < 0)
218		err(EXIT_FAILURE, "%s", mixer);
219
220	if (channel == 0 && init_rfcomm(&info) < 0)
221		err(EXIT_FAILURE, "%s", bt_ntoa(&info.raddr, NULL));
222
223	if (channel && init_server(&info, channel) < 0)
224		err(EXIT_FAILURE, "%d", channel);
225
226	if (verbose) {
227		printf("Headset Info:\n");
228		printf("\tmixer: %s\n", mixer);
229		printf("\tladdr: %s\n", bt_ntoa(&info.laddr, NULL));
230		printf("\traddr: %s\n", bt_ntoa(&info.raddr, NULL));
231		printf("\tchannel: %d\n", info.channel);
232		printf("\tvgs.dev: %d, vgm.dev: %d\n", vgs.dev, vgm.dev);
233		if (channel) printf("\tserver channel: %d\n", channel);
234	}
235
236	event_dispatch();
237
238	err(EXIT_FAILURE, "event_dispatch");
239}
240
241static void
242usage(void)
243{
244
245	fprintf(stderr,
246		"usage: %s [-hv] [-c command] [-m mixer] [-p file] [-s channel]\n"
247		"Where:\n"
248		"\t-h           display this message\n"
249		"\t-v           verbose output\n"
250		"\t-c command   command to execute on answer\n"
251		"\t-m mixer     mixer path\n"
252		"\t-p file      write PID to file\n"
253		"\t-s channel   register as audio gateway on channel\n"
254		"", getprogname());
255
256	exit(EXIT_FAILURE);
257}
258
259static void
260do_signal(int s, short ev, void *arg)
261{
262
263	switch (s) {
264	case SIGUSR1:
265		ringing = 1;	/* start ringing */
266		do_ring(0, 0, NULL);
267		break;
268
269	case SIGUSR2:
270		ringing = 0;
271		break;
272
273	case SIGINT:
274	default:
275		exit(EXIT_SUCCESS);
276	}
277}
278
279static void
280do_ring(int s, short ev, void *arg)
281{
282	static struct timeval tv = { RING_INTERVAL, 0 };
283
284	if (!ringing)
285		return;
286
287	send_rfcomm("RING");
288	evtimer_add(&ring_ev, &tv);
289}
290
291/*
292 * The mixer device has been twiddled. We check mic and speaker
293 * settings and send the appropriate commands to the headset,
294 */
295static void
296do_mixer(int s, short ev, void *arg)
297{
298	mixer_ctrl_t mc;
299	int level;
300
301	memcpy(&mc, &vgs, sizeof(mc));
302	if (ioctl(mx, AUDIO_MIXER_READ, &mc) < 0)
303		return;
304
305	if (memcmp(&vgs, &mc, sizeof(mc))) {
306		memcpy(&vgs, &mc, sizeof(mc));
307		level = mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] / BTSCO_DELTA;
308
309		send_rfcomm("+VGS=%d", level);
310	}
311
312	memcpy(&mc, &vgm, sizeof(mc));
313	if (ioctl(mx, AUDIO_MIXER_READ, &mc) < 0)
314		return;
315
316	if (memcmp(&vgm, &mc, sizeof(mc))) {
317		memcpy(&vgm, &mc, sizeof(mc));
318		level = mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] / BTSCO_DELTA;
319
320		send_rfcomm("+VGM=%d", level);
321	}
322}
323
324/*
325 * RFCOMM socket event.
326 */
327static void
328do_rfcomm(int fd, short ev, void *arg)
329{
330	char buf[128];
331	int len, level;
332
333	memset(buf, 0, sizeof(buf));
334	len = recv(rf, buf, sizeof(buf), 0);
335	if (len <= 0) {
336		if (ag < 0)
337			errx(EXIT_FAILURE, "Connection Lost");
338
339		event_del(&rfcomm_ev);
340		close(rf);
341		rf = -1;
342		ringing = 0;
343		return;
344	}
345
346	if (verbose)
347		printf("> %.*s\n", len, buf);
348
349	if (len >= 7 && strncmp(buf, "AT+CKPD", 7) == 0) {
350		if (ringing && command != NULL) {
351			if (verbose)
352				printf("%% %s\n", command);
353
354			system(command);
355		}
356
357		ringing = 0;
358		send_rfcomm("OK");
359		return;
360	}
361
362	if (len >= 7 && strncmp(buf, "AT+VGS=", 7) == 0) {
363		level = atoi(buf + 7);
364		if (level < 0 || level > 15)
365			return;
366
367		vgs.un.value.level[AUDIO_MIXER_LEVEL_MONO] = level * BTSCO_DELTA;
368		if (ioctl(mx, AUDIO_MIXER_WRITE, &vgs) < 0)
369			return;
370
371		send_rfcomm("OK");
372		return;
373	}
374
375	if (len >= 7 && strncmp(buf, "AT+VGM=", 7) == 0) {
376		level = atoi(buf + 7);
377		if (level < 0 || level > 15)
378			return;
379
380		vgm.un.value.level[AUDIO_MIXER_LEVEL_MONO] = level * BTSCO_DELTA;
381		if (ioctl(mx, AUDIO_MIXER_WRITE, &vgm) < 0)
382			return;
383
384		send_rfcomm("OK");
385		return;
386	}
387
388	send_rfcomm("ERROR");
389}
390
391/*
392 * got an incoming connection on the AG socket.
393 */
394static void
395do_server(int fd, short ev, void *arg)
396{
397	bdaddr_t *raddr = arg;
398	struct sockaddr_bt addr;
399	socklen_t len;
400	int s;
401
402	assert(raddr != NULL);
403
404	len = sizeof(addr);
405	s = accept(fd, (struct sockaddr *)&addr, &len);
406	if (s < 0)
407		return;
408
409	if (rf >= 0
410	    || len != sizeof(addr)
411	    || addr.bt_len != sizeof(addr)
412	    || addr.bt_family != AF_BLUETOOTH
413	    || !bdaddr_same(raddr, &addr.bt_bdaddr)) {
414		close(s);
415		return;
416	}
417
418	rf = s;
419	event_set(&rfcomm_ev, rf, EV_READ | EV_PERSIST, do_rfcomm, NULL);
420	if (event_add(&rfcomm_ev, NULL) < 0)
421		err(EXIT_FAILURE, "rfcomm_ev");
422}
423
424/*
425 * send a message to the RFCOMM socket
426 */
427static int
428send_rfcomm(const char *msg, ...)
429{
430	struct iovec iov[3];
431	char buf[128];
432	va_list ap;
433
434	if (verbose) {
435		fputs("< ", stdout);
436		va_start(ap, msg);
437		vprintf(msg, ap);
438		va_end(ap);
439		putchar('\n');
440	}
441
442	iov[0].iov_base = iov[2].iov_base = __UNCONST("\r\n");
443	iov[0].iov_len = iov[2].iov_len = 2;
444	va_start(ap, msg);
445	iov[1].iov_base = buf;
446	iov[1].iov_len = vsnprintf(buf, sizeof(buf), msg, ap);
447	va_end(ap);
448
449	return writev(rf, iov, __arraycount(iov));
450}
451
452/*
453 * Initialise mixer event
454 */
455static int
456init_mixer(struct btsco_info *info, const char *mixer)
457{
458
459	mx = open(mixer, O_WRONLY, 0);
460	if (mx < 0)
461		return -1;
462
463	if (ioctl(mx, BTSCO_GETINFO, info) < 0)
464		return -1;
465
466	/* get initial vol settings */
467	memset(&vgs, 0, sizeof(vgs));
468	vgs.dev = info->vgs;
469	if (ioctl(mx, AUDIO_MIXER_READ, &vgs) < 0)
470		return -1;
471
472	memset(&vgm, 0, sizeof(vgm));
473	vgm.dev = info->vgm;
474	if (ioctl(mx, AUDIO_MIXER_READ, &vgm) < 0)
475		return -1;
476
477	/* set up mixer changed event */
478	if (fcntl(mx, F_SETFL, O_ASYNC) < 0)
479		return -1;
480
481	signal_set(&mixer_ev, SIGIO, do_mixer, NULL);
482	if (signal_add(&mixer_ev, NULL) < 0)
483		return -1;
484
485	return 0;
486}
487
488/*
489 * Initialise RFCOMM socket
490 */
491static int
492init_rfcomm(struct btsco_info *info)
493{
494	struct sockaddr_bt addr;
495
496	rf = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
497	if (rf < 0)
498		return -1;
499
500	memset(&addr, 0, sizeof(addr));
501	addr.bt_len = sizeof(addr);
502	addr.bt_family = AF_BLUETOOTH;
503	bdaddr_copy(&addr.bt_bdaddr, &info->laddr);
504
505	if (bind(rf, (struct sockaddr *)&addr, sizeof(addr)) < 0)
506		return -1;
507
508	bdaddr_copy(&addr.bt_bdaddr, &info->raddr);
509	addr.bt_channel = info->channel;
510
511	if (connect(rf, (struct sockaddr *)&addr, sizeof(addr)) < 0)
512		return -1;
513
514	event_set(&rfcomm_ev, rf, EV_READ | EV_PERSIST, do_rfcomm, NULL);
515	if (event_add(&rfcomm_ev, NULL) < 0)
516		return -1;
517
518	return 0;
519}
520
521/*
522 * Initialise server socket
523 */
524static int
525init_server(struct btsco_info *info, int channel)
526{
527	struct sockaddr_bt addr;
528
529	ag = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
530	if (ag < 0)
531		return -1;
532
533	memset(&addr, 0, sizeof(addr));
534	addr.bt_len = sizeof(addr);
535	addr.bt_family = AF_BLUETOOTH;
536	addr.bt_channel = channel;
537	bdaddr_copy(&addr.bt_bdaddr, &info->laddr);
538
539	if (bind(ag, (struct sockaddr *)&addr, sizeof(addr)) < 0)
540		return -1;
541
542	if (listen(ag, 1) < 0)
543		return -1;
544
545	event_set(&server_ev, ag, EV_READ | EV_PERSIST, do_server, &info->raddr);
546	if (event_add(&server_ev, NULL) < 0)
547		return -1;
548
549	sdp_set_uint(&hset_channel, channel);
550
551	ss = sdp_open_local(NULL);
552	if (ss == NULL)
553		return -1;
554
555	if (!sdp_record_insert(ss, &info->laddr, NULL, &hset_record)) {
556		sdp_close(ss);
557		return -1;
558	}
559
560	return 0;
561}
562
563static void
564remove_pid(void)
565{
566
567	if (pidfile == NULL)
568		return;
569
570	unlink(pidfile);
571}
572
573static int
574write_pid(void)
575{
576	char *buf;
577	int fd, len;
578
579	if (pidfile == NULL)
580		return 0;
581
582	fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
583	if (fd < 0)
584		return -1;
585
586	len = asprintf(&buf, "%d\n", getpid());
587	if (len > 0)
588		write(fd, buf, len);
589
590	if (len >= 0 && buf != NULL)
591		free(buf);
592
593	close (fd);
594
595	return atexit(remove_pid);
596}
597