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