xref: /openbsd/usr.bin/radioctl/radioctl.c (revision 404b540a)
1 /* $OpenBSD: radioctl.c,v 1.16 2008/10/16 14:32:57 jmc Exp $ */
2 /* $RuOBSD: radioctl.c,v 1.4 2001/10/20 18:09:10 pva Exp $ */
3 
4 /*
5  * Copyright (c) 2001 Vladimir Popov <jumbo@narod.ru>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/ioctl.h>
30 #include <sys/radioio.h>
31 
32 #include <dev/ic/bt8xx.h>
33 
34 #include <err.h>
35 #include <fcntl.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 #define RADIO_ENV	"RADIODEVICE"
42 #define RADIODEVICE	"/dev/radio"
43 
44 const char *varname[] = {
45 	"search",
46 #define OPTION_SEARCH		0x00
47 	"volume",
48 #define OPTION_VOLUME		0x01
49 	"frequency",
50 #define OPTION_FREQUENCY	0x02
51 	"mute",
52 #define OPTION_MUTE		0x03
53 	"reference",
54 #define OPTION_REFERENCE	0x04
55 	"mono",
56 #define OPTION_MONO		0x05
57 	"stereo",
58 #define	OPTION_STEREO		0x06
59 	"sensitivity",
60 #define	OPTION_SENSITIVITY	0x07
61 	"channel",
62 #define OPTION_CHANNEL		0x08
63 	"chnlset"
64 #define OPTION_CHNLSET		0x09
65 };
66 
67 #define OPTION_NONE		~0u
68 #define VALUE_NONE		~0u
69 
70 struct opt_t {
71 	char *string;
72 	int option;
73 	int sign;
74 #define SIGN_NONE	0
75 #define SIGN_PLUS	1
76 #define SIGN_MINUS	-1
77 	u_int32_t value;
78 };
79 
80 struct chansets {
81 	int value;
82 	char *name;
83 } chansets[] = {
84 { CHNLSET_NABCST,	"nabcst",	},
85 { CHNLSET_CABLEIRC,	"cableirc",	},
86 { CHNLSET_CABLEHRC,	"cablehrc",	},
87 { CHNLSET_WEUROPE,	"weurope",	},
88 { CHNLSET_JPNBCST,	"jpnbcst",	},
89 { CHNLSET_JPNCABLE,	"jpncable",	},
90 { CHNLSET_XUSSR,	"xussr",	},
91 { CHNLSET_AUSTRALIA,	"australia",	},
92 { CHNLSET_FRANCE,	"france",	},
93 { 0, NULL }
94 };
95 
96 extern char *__progname;
97 const char *onchar = "on";
98 #define ONCHAR_LEN	2
99 const char *offchar = "off";
100 #define OFFCHAR_LEN	3
101 
102 struct radio_info ri;
103 unsigned int i = 0;
104 
105 int	parse_opt(char *, struct opt_t *);
106 
107 void	print_vars(int, int);
108 void	do_ioctls(int, struct opt_t *, int);
109 
110 void	print_value(int, int);
111 void	change_value(const struct opt_t);
112 void	update_value(int, int *, int);
113 
114 void	warn_unsupported(int);
115 void	usage(void);
116 
117 void	show_verbose(const char *, int);
118 void	show_int_val(int, const char *, char *, int);
119 void	show_float_val(float, const char *, char *, int);
120 void	show_char_val(const char *, const char *, int);
121 int	str_to_opt(const char *);
122 u_int	str_to_int(char *, int);
123 
124 /*
125  * Control behavior of a FM tuner - set frequency, volume etc
126  */
127 int
128 main(int argc, char **argv)
129 {
130 	struct opt_t opt;
131 	char **avp;
132 
133 	char *radiodev = NULL;
134 	int rd = -1;
135 	int optchar;
136 	int show_vars = 0;
137 	int show_choices = 0;
138 	int silent = 0;
139 	int mode = O_RDONLY;
140 
141 	radiodev = getenv(RADIO_ENV);
142 	if (radiodev == NULL)
143 		radiodev = RADIODEVICE;
144 
145 	while ((optchar = getopt(argc, argv, "af:nvw")) != -1) {
146 		switch (optchar) {
147 		case 'a':
148 			show_vars = 1;
149 			break;
150 		case 'f':
151 			radiodev = optarg;
152 			break;
153 		case 'n':
154 			silent = 1;
155 			break;
156 		case 'v':
157 			show_choices = 1;
158 			break;
159 		case 'w':
160 			/* backwards compatibility */
161 			break;
162 		default:
163 			usage();
164 			/* NOTREACHED */
165 		}
166 	}
167 
168 	argc -= optind;
169 	argv += optind;
170 
171 	if (argc == 0)
172 		show_vars = 1;
173 
174 	/*
175 	 * Scan the options for `name=value` so the
176 	 * device can be opened in the proper mode.
177 	 */
178 	for (avp = argv; *avp != NULL; avp++)
179 		if (strchr(*avp, '=') != NULL) {
180 			mode = O_RDWR;
181 			break;
182 		}
183 
184 	rd = open(radiodev, mode);
185 	if (rd < 0)
186 		err(1, "%s open error", radiodev);
187 
188 	if (ioctl(rd, RIOCGINFO, &ri) < 0)
189 		err(1, "RIOCGINFO");
190 
191 	if (!argc && show_vars)
192 		print_vars(silent, show_choices);
193 	else if (argc > 0 && !show_vars) {
194 		if (mode == O_RDWR) {
195 			for (; argc--; argv++)
196 				if (parse_opt(*argv, &opt))
197 					do_ioctls(rd, &opt, silent);
198 		} else {
199 			for (; argc--; argv++)
200 				if (parse_opt(*argv, &opt)) {
201 					show_verbose(varname[opt.option],
202 					    silent);
203 					print_value(opt.option, show_choices);
204 					free(opt.string);
205 					putchar('\n');
206 				}
207 		}
208 	}
209 
210 	if (close(rd) < 0)
211 		warn("%s close error", radiodev);
212 
213 	return 0;
214 }
215 
216 void
217 usage(void)
218 {
219 	fprintf(stderr,
220 	    "usage: %s [-anv] [-f file]\n"
221 	    "       %s [-nv] [-f file] name\n"
222 	    "       %s [-n] [-f file] name=value\n",
223 	    __progname, __progname, __progname);
224 	exit(1);
225 }
226 
227 void
228 show_verbose(const char *nick, int silent)
229 {
230 	if (!silent)
231 		printf("%s=", nick);
232 }
233 
234 void
235 warn_unsupported(int optval)
236 {
237 	warnx("driver does not support `%s'", varname[optval]);
238 }
239 
240 void
241 do_ioctls(int fd, struct opt_t *o, int silent)
242 {
243 	int oval;
244 
245 	if (fd < 0 || o == NULL)
246 		return;
247 
248 	if (o->option == OPTION_SEARCH && !(ri.caps & RADIO_CAPS_HW_SEARCH)) {
249 		warn_unsupported(o->option);
250 		return;
251 	}
252 
253 	oval = o->option == OPTION_SEARCH ? OPTION_FREQUENCY : o->option;
254 	if (!silent)
255 		printf("%s: ", varname[oval]);
256 
257 	print_value(o->option, 0);
258 	printf(" -> ");
259 
260 	if (o->option == OPTION_SEARCH) {
261 
262 		if (ioctl(fd, RIOCSSRCH, &o->value) < 0) {
263 			warn("RIOCSSRCH");
264 			return;
265 		}
266 
267 	} else {
268 
269 		change_value(*o);
270 		if (ioctl(fd, RIOCSINFO, &ri) < 0) {
271 			warn("RIOCSINFO");
272 			return;
273 		}
274 
275 	}
276 
277 	if (ioctl(fd, RIOCGINFO, &ri) < 0) {
278 		warn("RIOCGINFO");
279 		return;
280 	}
281 
282 	print_value(o->option, 0);
283 	putchar('\n');
284 }
285 
286 void
287 change_value(const struct opt_t o)
288 {
289 	int unsupported = 0;
290 
291 	if (o.value == VALUE_NONE)
292 		return;
293 
294 	switch (o.option) {
295 	case OPTION_VOLUME:
296 		update_value(o.sign, &ri.volume, o.value);
297 		break;
298 	case OPTION_FREQUENCY:
299 		ri.tuner_mode = RADIO_TUNER_MODE_RADIO;
300 		update_value(o.sign, &ri.freq, o.value);
301 		break;
302 	case OPTION_REFERENCE:
303 		if (ri.caps & RADIO_CAPS_REFERENCE_FREQ)
304 			update_value(o.sign, &ri.rfreq, o.value);
305 		else
306 			unsupported++;
307 		break;
308 	case OPTION_MONO:
309 		/* FALLTHROUGH */
310 	case OPTION_STEREO:
311 		if (ri.caps & RADIO_CAPS_SET_MONO)
312 			ri.stereo = o.option == OPTION_MONO ? !o.value : o.value;
313 		else
314 			unsupported++;
315 		break;
316 	case OPTION_SENSITIVITY:
317 		if (ri.caps & RADIO_CAPS_LOCK_SENSITIVITY)
318 			update_value(o.sign, &ri.lock, o.value);
319 		else
320 			unsupported++;
321 		break;
322 	case OPTION_MUTE:
323 		ri.mute = o.value;
324 		break;
325 	case OPTION_CHANNEL:
326 		ri.tuner_mode = RADIO_TUNER_MODE_TV;
327 		update_value(o.sign, &ri.chan, o.value);
328 		break;
329 	case OPTION_CHNLSET:
330 		ri.chnlset = o.value;
331 		break;
332 	}
333 
334 	if (unsupported)
335 		warn_unsupported(o.option);
336 }
337 
338 /*
339  * Convert string to integer representation of a parameter
340  */
341 int
342 str_to_opt(const char *topt)
343 {
344 	int res, toptlen, varlen, len, varsize;
345 
346 	if (topt == NULL || *topt == '\0')
347 		return OPTION_NONE;
348 
349 	varsize = sizeof(varname) / sizeof(varname[0]);
350 	toptlen = strlen(topt);
351 
352 	for (res = 0; res < varsize; res++) {
353 		varlen = strlen(varname[res]);
354 		len = toptlen > varlen ? toptlen : varlen;
355 		if (strncmp(topt, varname[res], len) == 0)
356 			return res;
357 	}
358 
359 	warnx("bad name `%s'", topt);
360 	return OPTION_NONE;
361 }
362 
363 void
364 update_value(int sign, int *value, int update)
365 {
366 	switch (sign) {
367 	case SIGN_NONE:
368 		*value  = update;
369 		break;
370 	case SIGN_PLUS:
371 		*value += update;
372 		break;
373 	case SIGN_MINUS:
374 		*value -= update;
375 		break;
376 	}
377 }
378 
379 /*
380  * Convert string to unsigned integer
381  */
382 u_int
383 str_to_int(char *str, int optval)
384 {
385 	int val;
386 
387 	if (str == NULL || *str == '\0')
388 		return VALUE_NONE;
389 
390 	if (optval == OPTION_FREQUENCY)
391 		val = (int)(1000 * atof(str));
392 	else
393 		val = (int)strtol(str, (char **)NULL, 10);
394 
395 	return val;
396 }
397 
398 /*
399  * parse string s into struct opt_t
400  * return true on success, false on failure
401  */
402 int
403 parse_opt(char *s, struct opt_t *o) {
404 	const char *badvalue = "bad value `%s'";
405 	char *topt = NULL;
406 	int slen, optlen;
407 
408 	if (s == NULL || *s == '\0' || o == NULL)
409 		return 0;
410 
411 	o->string = NULL;
412 	o->option = OPTION_NONE;
413 	o->value = VALUE_NONE;
414 	o->sign = SIGN_NONE;
415 
416 	slen = strlen(s);
417 	optlen = strcspn(s, "=");
418 
419 	/* Set only o->optval, the rest is missing */
420 	if (slen == optlen) {
421 		o->option = str_to_opt(s);
422 		return o->option == OPTION_NONE ? 0 : 1;
423 	}
424 
425 	if (optlen > slen - 2) {
426 		warnx(badvalue, s);
427 		return 0;
428 	}
429 
430 	slen -= ++optlen;
431 
432 	if ((topt = (char *)malloc(optlen)) == NULL) {
433 		warn("memory allocation error");
434 		return 0;
435 	}
436 	strlcpy(topt, s, optlen);
437 
438 	if ((o->option = str_to_opt(topt)) == OPTION_NONE) {
439 		free(topt);
440 		return 0;
441 	}
442 	o->string = topt;
443 
444 	topt = &s[optlen];
445 
446 	if (strcmp(o->string, "chnlset") == 0) {
447 		for (i = 0; chansets[i].name; i++)
448 			if (strncmp(chansets[i].name, topt,
449 				strlen(chansets[i].name)) == 0)
450 					break;
451 		if (chansets[i].name != NULL) {
452 			o->value = chansets[i].value;
453 			return 1;
454 		} else {
455 			warnx(badvalue, topt);
456 			return 0;
457 		}
458 	}
459 
460 	switch (*topt) {
461 	case '+':
462 	case '-':
463 		o->sign = (*topt == '+') ? SIGN_PLUS : SIGN_MINUS;
464 		o->value = str_to_int(&topt[1], o->option);
465 		break;
466 	case 'o':
467 		if (strncmp(topt, offchar,
468 			slen > OFFCHAR_LEN ? slen : OFFCHAR_LEN) == 0)
469 			o->value = 0;
470 		else if (strncmp(topt, onchar,
471 				slen > ONCHAR_LEN ? slen : ONCHAR_LEN) == 0)
472 				o->value = 1;
473 		break;
474 	case 'u':
475 		if (strncmp(topt, "up", slen > 2 ? slen : 2) == 0)
476 			o->value = 1;
477 		break;
478 	case 'd':
479 		if (strncmp(topt, "down", slen > 4 ? slen : 4) == 0)
480 			o->value = 0;
481 		break;
482 	default:
483 		if (*topt > 47 && *topt < 58)
484 			o->value = str_to_int(topt, o->option);
485 		break;
486 	}
487 
488 	if (o->value == VALUE_NONE) {
489 		warnx(badvalue, topt);
490 		return 0;
491 	}
492 
493 	return 1;
494 }
495 
496 /*
497  * Print current value of the parameter.
498  */
499 void
500 print_value(int optval, int show_choices)
501 {
502 	if (optval == OPTION_NONE)
503 		return;
504 
505 	switch (optval) {
506 	case OPTION_SEARCH:
507 		/* FALLTHROUGH */
508 	case OPTION_FREQUENCY:
509 		printf("%.2fMHz", (float)ri.freq / 1000.);
510 		break;
511 	case OPTION_REFERENCE:
512 		printf("%ukHz", ri.rfreq);
513 		break;
514 	case OPTION_SENSITIVITY:
515 		printf("%umkV", ri.lock);
516 		break;
517 	case OPTION_MUTE:
518 		printf(ri.mute ? onchar : offchar);
519 		break;
520 	case OPTION_MONO:
521 		printf(ri.stereo ? offchar : onchar);
522 		break;
523 	case OPTION_STEREO:
524 		printf(ri.stereo ? onchar : offchar);
525 		break;
526 	case OPTION_CHANNEL:
527 		printf("%u", ri.chan);
528 		break;
529 	case OPTION_CHNLSET:
530 		for (i = 0; chansets[i].name; i++) {
531 			if (chansets[i].value == ri.chnlset)
532 				printf("%s", chansets[i].name);
533 		}
534 		if (show_choices) {
535 			printf("\n\t[");
536 			for (i = 0; chansets[i].name; i++)
537 				printf("%s ", chansets[i].name);
538 			printf("]");
539 		}
540 		break;
541 	case OPTION_VOLUME:
542 	default:
543 		printf("%u", ri.volume);
544 		break;
545 	}
546 }
547 
548 void
549 show_int_val(int val, const char *nick, char *append, int silent)
550 {
551 	show_verbose(nick, silent);
552 	printf("%u%s\n", val, append);
553 }
554 
555 void
556 show_float_val(float val, const char *nick, char *append, int silent)
557 {
558 	show_verbose(nick, silent);
559 	printf("%.2f%s\n", val, append);
560 }
561 
562 void
563 show_char_val(const char *val, const char *nick, int silent)
564 {
565 	show_verbose(nick, silent);
566 	printf("%s\n", val);
567 }
568 
569 /*
570  * Print all available parameters
571  */
572 void
573 print_vars(int silent, int show_choices)
574 {
575 	show_int_val(ri.volume, varname[OPTION_VOLUME], "", silent);
576 	show_int_val(ri.chan, varname[OPTION_CHANNEL], "", silent);
577 	for (i = 0; chansets[i].name; i++) {
578 		if (chansets[i].value == ri.chnlset)
579 			show_char_val(chansets[i].name, varname[OPTION_CHNLSET], silent);
580 	}
581 	if (show_choices) {
582 		printf("\t[ ");
583 		for (i = 0; chansets[i].name; i++)
584 			printf("%s ", chansets[i].name);
585 		printf("]\n");
586 	}
587 	show_float_val((float)ri.freq / 1000., varname[OPTION_FREQUENCY],
588 	    "MHz", silent);
589 	show_char_val(ri.mute ? onchar : offchar, varname[OPTION_MUTE], silent);
590 
591 	if (ri.caps & RADIO_CAPS_REFERENCE_FREQ)
592 		show_int_val(ri.rfreq, varname[OPTION_REFERENCE], "kHz", silent);
593 	if (ri.caps & RADIO_CAPS_LOCK_SENSITIVITY)
594 		show_int_val(ri.lock, varname[OPTION_SENSITIVITY], "mkV", silent);
595 
596 	if (ri.caps & RADIO_CAPS_DETECT_SIGNAL) {
597 		show_verbose("signal", silent);
598 		printf("%s\n", ri.info & RADIO_INFO_SIGNAL ? onchar : offchar);
599 	}
600 	if (ri.caps & RADIO_CAPS_DETECT_STEREO) {
601 		show_verbose(varname[OPTION_STEREO], silent);
602 		printf("%s\n", ri.info & RADIO_INFO_STEREO ? onchar : offchar);
603 	}
604 
605 	if (!silent) {
606 		printf("mode: %s\n",
607 		    ri.tuner_mode == RADIO_TUNER_MODE_TV ? "TV" : "radio");
608 
609 		puts("card capabilities:");
610 	}
611 
612 	if (ri.caps & RADIO_CAPS_SET_MONO)
613 		puts("\tmanageable mono/stereo");
614 	if (ri.caps & RADIO_CAPS_HW_SEARCH)
615 		puts("\thardware search");
616 	if (ri.caps & RADIO_CAPS_HW_AFC)
617 		puts("\thardware AFC");
618 }
619