xref: /openbsd/usr.bin/sndiod/dev_sioctl.c (revision 09467b48)
1 /*	$OpenBSD: dev_sioctl.c,v 1.6 2020/06/28 05:21:39 ratchov Exp $	*/
2 /*
3  * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/time.h>
18 #include <sys/types.h>
19 
20 #include <poll.h>
21 #include <sndio.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include "abuf.h"
26 #include "defs.h"
27 #include "dev.h"
28 #include "dsp.h"
29 #include "file.h"
30 #include "dev_sioctl.h"
31 #include "utils.h"
32 
33 void dev_sioctl_ondesc(void *, struct sioctl_desc *, int);
34 void dev_sioctl_onval(void *, unsigned int, unsigned int);
35 int dev_sioctl_pollfd(void *, struct pollfd *);
36 int dev_sioctl_revents(void *, struct pollfd *);
37 void dev_sioctl_in(void *);
38 void dev_sioctl_out(void *);
39 void dev_sioctl_hup(void *);
40 
41 struct fileops dev_sioctl_ops = {
42 	"sioctl",
43 	dev_sioctl_pollfd,
44 	dev_sioctl_revents,
45 	dev_sioctl_in,
46 	dev_sioctl_out,
47 	dev_sioctl_hup
48 };
49 
50 void
51 dev_sioctl_ondesc(void *arg, struct sioctl_desc *desc, int val)
52 {
53 #define GROUP_PREFIX		"hw"
54 	char group_buf[CTL_NAMEMAX], *group;
55 	struct dev *d = arg;
56 	int addr;
57 
58 	if (desc == NULL) {
59 		dev_ctlsync(d);
60 		return;
61 	}
62 
63 	addr = CTLADDR_END + desc->addr;
64 	dev_rmctl(d, addr);
65 
66 	/*
67 	 * prefix with "hw/" group names of controls we expose, to
68 	 * ensure that all controls have unique names when multiple
69 	 * sndiod's are chained
70 	 */
71 	if (strcmp(desc->group, "app") == 0 || (desc->group[0] == 0 &&
72 	    strcmp(desc->node0.name, "server") == 0)) {
73 		group = group_buf;
74 		if (snprintf(group_buf, CTL_NAMEMAX, GROUP_PREFIX "/%s",
75 		    desc->group) >= CTL_NAMEMAX)
76 			return;
77 	} else
78 		group = desc->group;
79 
80 	dev_addctl(d, group, desc->type, addr,
81 	    desc->node0.name, desc->node0.unit, desc->func,
82 	    desc->node1.name, desc->node1.unit, desc->maxval, val);
83 }
84 
85 void
86 dev_sioctl_onval(void *arg, unsigned int addr, unsigned int val)
87 {
88 	struct dev *d = arg;
89 	struct ctl *c;
90 
91 	addr += CTLADDR_END;
92 
93 	dev_log(d);
94 	log_puts(": onctl: addr = ");
95 	log_putu(addr);
96 	log_puts(", val = ");
97 	log_putu(val);
98 	log_puts("\n");
99 
100 	for (c = d->ctl_list; c != NULL; c = c->next) {
101 		if (c->addr != addr)
102 			continue;
103 		ctl_log(c);
104 		log_puts(": new value -> ");
105 		log_putu(val);
106 		log_puts("\n");
107 		c->val_mask = ~0U;
108 		c->curval = val;
109 	}
110 }
111 
112 /*
113  * open the control device.
114  */
115 void
116 dev_sioctl_open(struct dev *d)
117 {
118 	if (d->sioctl.hdl == NULL) {
119 		/*
120 		 * At this point there are clients, for instance if we're
121 		 * called by dev_reopen() but the control device couldn't
122 		 * be opened. In this case controls have changed (thoseof
123 		 * old device are just removed) so we need to notify clients.
124 		 */
125 		dev_ctlsync(d);
126 		return;
127 	}
128 	sioctl_ondesc(d->sioctl.hdl, dev_sioctl_ondesc, d);
129 	sioctl_onval(d->sioctl.hdl, dev_sioctl_onval, d);
130 }
131 
132 /*
133  * close the control device.
134  */
135 void
136 dev_sioctl_close(struct dev *d)
137 {
138 	struct ctl *c, **pc;
139 
140 	/* remove controls */
141 	pc = &d->ctl_list;
142 	while ((c = *pc) != NULL) {
143 		if (c->addr >= CTLADDR_END) {
144 			c->refs_mask &= ~CTL_DEVMASK;
145 			if (c->refs_mask == 0) {
146 				*pc = c->next;
147 				xfree(c);
148 				continue;
149 			}
150 			c->type = CTL_NONE;
151 			c->desc_mask = ~0;
152 		}
153 		pc = &c->next;
154 	}
155 	dev_ctlsync(d);
156 }
157 
158 int
159 dev_sioctl_pollfd(void *arg, struct pollfd *pfd)
160 {
161 	struct dev *d = arg;
162 	struct ctl *c;
163 	int events = 0;
164 
165 	for (c = d->ctl_list; c != NULL; c = c->next) {
166 		if (c->dirty)
167 			events |= POLLOUT;
168 	}
169 	return sioctl_pollfd(d->sioctl.hdl, pfd, events);
170 }
171 
172 int
173 dev_sioctl_revents(void *arg, struct pollfd *pfd)
174 {
175 	struct dev *d = arg;
176 
177 	return sioctl_revents(d->sioctl.hdl, pfd);
178 }
179 
180 void
181 dev_sioctl_in(void *arg)
182 {
183 }
184 
185 void
186 dev_sioctl_out(void *arg)
187 {
188 	struct dev *d = arg;
189 	struct ctl *c;
190 	int cnt;
191 
192 	/*
193 	 * for each dirty ctl, call sioctl_setval() and dev_unref(). As
194 	 * dev_unref() may destroy the ctl_list, we must call it after
195 	 * we've finished iterating on it.
196 	 */
197 	cnt = 0;
198 	for (c = d->ctl_list; c != NULL; c = c->next) {
199 		if (!c->dirty)
200 			continue;
201 		if (!sioctl_setval(d->sioctl.hdl,
202 			c->addr - CTLADDR_END, c->curval)) {
203 			ctl_log(c);
204 			log_puts(": set failed\n");
205 			break;
206 		}
207 		if (log_level >= 2) {
208 			ctl_log(c);
209 			log_puts(": changed\n");
210 		}
211 		c->dirty = 0;
212 		cnt++;
213 	}
214 	while (cnt-- > 0)
215 		dev_unref(d);
216 }
217 
218 void
219 dev_sioctl_hup(void *arg)
220 {
221 	struct dev *d = arg;
222 
223 	dev_sioctl_close(d);
224 	file_del(d->sioctl.file);
225 	sioctl_close(d->sioctl.hdl);
226 	d->sioctl.hdl = NULL;
227 }
228