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