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