1/*
2 * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16#include <errno.h>
17#include <poll.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <sndio.h>
22#include <unistd.h>
23#include <arpa/inet.h>
24#include "debug.h"
25#include "aucat.h"
26#include "sioctl_priv.h"
27
28struct sioctl_aucat_hdl {
29	struct sioctl_hdl sioctl;
30	struct aucat aucat;
31	struct sioctl_desc desc;
32	struct amsg_ctl_desc buf[16];
33	size_t buf_wpos;
34	int dump_wait;
35};
36
37static void sioctl_aucat_close(struct sioctl_hdl *);
38static int sioctl_aucat_nfds(struct sioctl_hdl *);
39static int sioctl_aucat_pollfd(struct sioctl_hdl *, struct pollfd *, int);
40static int sioctl_aucat_revents(struct sioctl_hdl *, struct pollfd *);
41static int sioctl_aucat_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
42static int sioctl_aucat_onval(struct sioctl_hdl *);
43static int sioctl_aucat_ondesc(struct sioctl_hdl *);
44
45/*
46 * operations every device should support
47 */
48struct sioctl_ops sioctl_aucat_ops = {
49	sioctl_aucat_close,
50	sioctl_aucat_nfds,
51	sioctl_aucat_pollfd,
52	sioctl_aucat_revents,
53	sioctl_aucat_setctl,
54	sioctl_aucat_onval,
55	sioctl_aucat_ondesc
56};
57
58static int
59sioctl_aucat_rdata(struct sioctl_aucat_hdl *hdl)
60{
61	struct sioctl_desc desc;
62	struct amsg_ctl_desc *c;
63	size_t rpos;
64	int n;
65
66	while (hdl->aucat.rstate == RSTATE_DATA) {
67
68		/* read entries */
69		while (hdl->buf_wpos < sizeof(hdl->buf) &&
70		    hdl->aucat.rstate == RSTATE_DATA) {
71			n = _aucat_rdata(&hdl->aucat,
72			    (unsigned char *)hdl->buf + hdl->buf_wpos,
73			    sizeof(hdl->buf) - hdl->buf_wpos,
74			    &hdl->sioctl.eof);
75			if (n == 0 || hdl->sioctl.eof)
76				return 0;
77			hdl->buf_wpos += n;
78		}
79
80		/* parse entries */
81		c = hdl->buf;
82		rpos = 0;
83		while (rpos < hdl->buf_wpos) {
84			strlcpy(desc.group, c->group, SIOCTL_NAMEMAX);
85			strlcpy(desc.node0.name, c->node0.name, SIOCTL_NAMEMAX);
86			desc.node0.unit = (int16_t)ntohs(c->node0.unit);
87			strlcpy(desc.node1.name, c->node1.name, SIOCTL_NAMEMAX);
88			desc.node1.unit = (int16_t)ntohs(c->node1.unit);
89			strlcpy(desc.func, c->func, SIOCTL_NAMEMAX);
90			desc.type = c->type;
91			desc.addr = ntohs(c->addr);
92			desc.maxval = ntohs(c->maxval);
93			_sioctl_ondesc_cb(&hdl->sioctl,
94			    &desc, ntohs(c->curval));
95			rpos += sizeof(struct amsg_ctl_desc);
96			c++;
97		}
98		hdl->buf_wpos = 0;
99	}
100	return 1;
101}
102
103/*
104 * execute the next message, return 0 if blocked
105 */
106static int
107sioctl_aucat_runmsg(struct sioctl_aucat_hdl *hdl)
108{
109	if (!_aucat_rmsg(&hdl->aucat, &hdl->sioctl.eof))
110		return 0;
111	switch (ntohl(hdl->aucat.rmsg.cmd)) {
112	case AMSG_DATA:
113		hdl->buf_wpos = 0;
114		if (!sioctl_aucat_rdata(hdl))
115			return 0;
116		break;
117	case AMSG_CTLSET:
118		DPRINTF("sioctl_aucat_runmsg: got CTLSET\n");
119		_sioctl_onval_cb(&hdl->sioctl,
120		    ntohs(hdl->aucat.rmsg.u.ctlset.addr),
121		    ntohs(hdl->aucat.rmsg.u.ctlset.val));
122		break;
123	case AMSG_CTLSYNC:
124		DPRINTF("sioctl_aucat_runmsg: got CTLSYNC\n");
125		hdl->dump_wait = 0;
126		_sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
127		break;
128	default:
129		DPRINTF("sio_aucat_runmsg: unhandled message %u\n",
130		    hdl->aucat.rmsg.cmd);
131		hdl->sioctl.eof = 1;
132		return 0;
133	}
134	hdl->aucat.rstate = RSTATE_MSG;
135	hdl->aucat.rtodo = sizeof(struct amsg);
136	return 1;
137}
138
139struct sioctl_hdl *
140_sioctl_aucat_open(const char *str, unsigned int mode, int nbio)
141{
142	struct sioctl_aucat_hdl *hdl;
143
144	hdl = malloc(sizeof(struct sioctl_aucat_hdl));
145	if (hdl == NULL)
146		return NULL;
147	if (!_aucat_open(&hdl->aucat, str, mode))
148		goto bad;
149	_sioctl_create(&hdl->sioctl, &sioctl_aucat_ops, mode, nbio);
150	if (!_aucat_setfl(&hdl->aucat, 1, &hdl->sioctl.eof))
151		goto bad;
152	hdl->dump_wait = 0;
153	return (struct sioctl_hdl *)hdl;
154bad:
155	free(hdl);
156	return NULL;
157}
158
159static void
160sioctl_aucat_close(struct sioctl_hdl *addr)
161{
162	struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
163
164	if (!hdl->sioctl.eof)
165		_aucat_setfl(&hdl->aucat, 0, &hdl->sioctl.eof);
166	_aucat_close(&hdl->aucat, hdl->sioctl.eof);
167	free(hdl);
168}
169
170static int
171sioctl_aucat_ondesc(struct sioctl_hdl *addr)
172{
173	struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
174
175	while (hdl->aucat.wstate != WSTATE_IDLE) {
176		if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
177			return 0;
178	}
179	AMSG_INIT(&hdl->aucat.wmsg);
180	hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
181	hdl->aucat.wmsg.u.ctlsub.desc = 1;
182	hdl->aucat.wmsg.u.ctlsub.val = 0;
183	hdl->aucat.wtodo = sizeof(struct amsg);
184	if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
185		return 0;
186	hdl->dump_wait = 1;
187	while (hdl->dump_wait) {
188		DPRINTF("psleeping...\n");
189		if (!_sioctl_psleep(&hdl->sioctl, 0))
190			return 0;
191		DPRINTF("psleeping done\n");
192	}
193	DPRINTF("done\n");
194	return 1;
195}
196
197static int
198sioctl_aucat_onval(struct sioctl_hdl *addr)
199{
200	struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
201
202	while (hdl->aucat.wstate != WSTATE_IDLE) {
203		if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
204			return 0;
205	}
206	AMSG_INIT(&hdl->aucat.wmsg);
207	hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
208	hdl->aucat.wmsg.u.ctlsub.desc = 1;
209	hdl->aucat.wmsg.u.ctlsub.val = 1;
210	hdl->aucat.wtodo = sizeof(struct amsg);
211	if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
212		return 0;
213	return 1;
214}
215
216static int
217sioctl_aucat_setctl(struct sioctl_hdl *addr, unsigned int a, unsigned int v)
218{
219	struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
220
221	hdl->aucat.wstate = WSTATE_MSG;
222	hdl->aucat.wtodo = sizeof(struct amsg);
223	hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSET);
224	hdl->aucat.wmsg.u.ctlset.addr = htons(a);
225	hdl->aucat.wmsg.u.ctlset.val = htons(v);
226	while (hdl->aucat.wstate != WSTATE_IDLE) {
227		if (_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
228			break;
229		if (hdl->sioctl.nbio || !_sioctl_psleep(&hdl->sioctl, POLLOUT))
230			return 0;
231	}
232	return 1;
233}
234
235static int
236sioctl_aucat_nfds(struct sioctl_hdl *addr)
237{
238	return 1;
239}
240
241static int
242sioctl_aucat_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
243{
244	struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
245
246	return _aucat_pollfd(&hdl->aucat, pfd, events | POLLIN);
247}
248
249static int
250sioctl_aucat_revents(struct sioctl_hdl *addr, struct pollfd *pfd)
251{
252	struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
253	int revents;
254
255	revents = _aucat_revents(&hdl->aucat, pfd);
256	if (revents & POLLIN) {
257		while (1) {
258			if (hdl->aucat.rstate == RSTATE_MSG) {
259				if (!sioctl_aucat_runmsg(hdl))
260					break;
261			}
262			if (hdl->aucat.rstate == RSTATE_DATA) {
263				if (!sioctl_aucat_rdata(hdl))
264					break;
265			}
266		}
267		revents &= ~POLLIN;
268	}
269	if (hdl->sioctl.eof)
270		return POLLHUP;
271	DPRINTFN(3, "sioctl_aucat_revents: revents = 0x%x\n", revents);
272	return revents;
273}
274