1 /*	$OpenBSD$	*/
2 /*
3  * Copyright (c) 2008-2011 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 #ifdef USE_ALSA
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <poll.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <alsa/asoundlib.h>
30 
31 #include "debug.h"
32 #include "mio_priv.h"
33 
34 #define DEVNAME_PREFIX "hw:"
35 
36 #ifdef DEBUG
37 static snd_output_t *output = NULL;
38 #define DALSA(str, err) fprintf(stderr, "%s: %s\n", str, snd_strerror(err))
39 #else
40 #define DALSA(str, err) do {} while (0)
41 #endif
42 
43 struct mio_alsa_hdl {
44 	struct mio_hdl mio;
45 	char *devname;
46 	snd_rawmidi_t *in, *out;
47 	int infds, onfds, nfds, events;
48 };
49 
50 static void mio_alsa_close(struct mio_hdl *);
51 static size_t mio_alsa_read(struct mio_hdl *, void *, size_t);
52 static size_t mio_alsa_write(struct mio_hdl *, const void *, size_t);
53 static int mio_alsa_nfds(struct mio_hdl *);
54 static int mio_alsa_pollfd(struct mio_hdl *, struct pollfd *, int);
55 static int mio_alsa_revents(struct mio_hdl *, struct pollfd *);
56 
57 static struct mio_ops mio_alsa_ops = {
58 	mio_alsa_close,
59 	mio_alsa_write,
60 	mio_alsa_read,
61 	mio_alsa_nfds,
62 	mio_alsa_pollfd,
63 	mio_alsa_revents
64 };
65 
66 struct mio_hdl *
_mio_alsa_open(const char * _str,unsigned int mode,int nbio)67 _mio_alsa_open(const char *_str, unsigned int mode, int nbio)
68 {
69 	const char *p;
70 	struct mio_alsa_hdl *hdl;
71 	size_t len;
72 	int rc;
73 
74 	p = _sndio_parsetype(_str, "rmidi");
75 	if (p == NULL) {
76 		DPRINTF("_mio_alsa_open: %s: \"rsnd\" expected\n", _str);
77 		return NULL;
78 	}
79 	switch (*p) {
80 	case '/':
81 		p++;
82 		break;
83 	default:
84 		DPRINTF("_mio_alsa_open: %s: '/' expected\n", _str);
85 		return NULL;
86 	}
87 	hdl = malloc(sizeof(struct mio_alsa_hdl));
88 	if (hdl == NULL)
89 		return NULL;
90 	_mio_create(&hdl->mio, &mio_alsa_ops, mode, nbio);
91 #ifdef DEBUG
92 	rc = snd_output_stdio_attach(&output, stderr, 0);
93 	if (rc < 0)
94 		DALSA("couldn't attach to stderr", rc);
95 #endif
96 	len = strlen(p);
97 	hdl->devname = malloc(len + sizeof(DEVNAME_PREFIX));
98 	if (hdl->devname == NULL) {
99 		free(hdl);
100 		return NULL;
101 	}
102 	memcpy(hdl->devname, DEVNAME_PREFIX, sizeof(DEVNAME_PREFIX) - 1);
103 	memcpy(hdl->devname + sizeof(DEVNAME_PREFIX) - 1, p, len + 1);
104 	hdl->in = hdl->out = NULL;
105 	rc = snd_rawmidi_open(&hdl->in, &hdl->out,
106 	    hdl->devname, SND_RAWMIDI_NONBLOCK);
107 	if (rc) {
108 		DALSA("could't open port", rc);
109 		free(hdl->devname);
110 		free(hdl);
111 		return NULL;
112 	}
113 	hdl->nfds = 0;
114 	if (hdl->in)
115 		hdl->nfds += snd_rawmidi_poll_descriptors_count(hdl->in);
116 	if (hdl->out)
117 		hdl->nfds += snd_rawmidi_poll_descriptors_count(hdl->out);
118 	return (struct mio_hdl *)hdl;
119 }
120 
121 static void
mio_alsa_close(struct mio_hdl * sh)122 mio_alsa_close(struct mio_hdl *sh)
123 {
124 	struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh;
125 
126 	if (hdl->in)
127 		snd_rawmidi_close(hdl->in);
128 	if (hdl->out) {
129 		snd_rawmidi_drain(hdl->out);
130 		snd_rawmidi_close(hdl->out);
131 	}
132 	free(hdl->devname);
133 	free(hdl);
134 }
135 
136 static size_t
mio_alsa_read(struct mio_hdl * sh,void * buf,size_t len)137 mio_alsa_read(struct mio_hdl *sh, void *buf, size_t len)
138 {
139 	struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh;
140 	ssize_t n;
141 
142 	for (;;) {
143 		n = snd_rawmidi_read(hdl->in, buf, len);
144 		if (n > 0)
145 			return n;
146 		if (n == -EINTR)
147 			continue;
148 		if (n == -EAGAIN)
149 			return 0;
150 		if (n == 0)
151 			DPRINTF("mio_alsa_read: eof\n");
152 		if (n < 0)
153 			DALSA("mio_alsa_read", n);
154 		hdl->mio.eof = 1;
155 		return 0;
156 	}
157 }
158 
159 static size_t
mio_alsa_write(struct mio_hdl * sh,const void * buf,size_t len)160 mio_alsa_write(struct mio_hdl *sh, const void *buf, size_t len)
161 {
162 	struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh;
163 	ssize_t n;
164 
165 	for (;;) {
166 		n = snd_rawmidi_write(hdl->out, buf, len);
167 		if (n > 0)
168 			return n;
169 		if (n == -EINTR)
170 			continue;
171 		if (n == -EAGAIN)
172 			return 0;
173 		if (n == 0)
174 			DPRINTF("mio_alsa_write: eof\n");
175 		if (n < 0)
176 			DALSA("mio_alsa_write", n);
177 		hdl->mio.eof = 1;
178 		return 0;
179 	}
180 }
181 
182 static int
mio_alsa_nfds(struct mio_hdl * sh)183 mio_alsa_nfds(struct mio_hdl *sh)
184 {
185 	struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh;
186 
187 	return hdl->nfds;
188 }
189 
190 static int
mio_alsa_pollfd(struct mio_hdl * sh,struct pollfd * pfd,int events)191 mio_alsa_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events)
192 {
193 	struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh;
194 
195 	if (!hdl->in)
196 		events &= ~POLLIN;
197 	if (!hdl->out)
198 		events &= ~POLLOUT;
199 	hdl->events = events;
200 	if (events & POLLIN) {
201 		hdl->infds = snd_rawmidi_poll_descriptors(hdl->in,
202 		    pfd, hdl->nfds);
203 	} else
204 		hdl->infds = 0;
205 	if (events & POLLOUT) {
206 		hdl->onfds += snd_rawmidi_poll_descriptors(hdl->out,
207 		    pfd + hdl->infds, hdl->nfds - hdl->infds);
208 	} else
209 		hdl->onfds = 0;
210 	return hdl->infds + hdl->onfds;
211 }
212 
213 static int
mio_alsa_revents(struct mio_hdl * sh,struct pollfd * pfd)214 mio_alsa_revents(struct mio_hdl *sh, struct pollfd *pfd)
215 {
216 	struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh;
217 	unsigned short r;
218 	int revents = 0, rc;
219 
220 	if (hdl->events & POLLIN) {
221 		rc = snd_rawmidi_poll_descriptors_revents(hdl->in,
222 		    pfd, hdl->infds, &r);
223 		if (rc < 0) {
224 			DALSA("snd_rawmidi_poll_descriptors_revents", rc);
225 			hdl->mio.eof = 1;
226 			return POLLHUP;
227 		}
228 		revents |= r & (POLLIN | POLLHUP);
229 	}
230 	if (hdl->events & POLLOUT) {
231 		rc = snd_rawmidi_poll_descriptors_revents(hdl->in,
232 		    pfd + hdl->infds, hdl->onfds, &r);
233 		if (rc < 0) {
234 			DALSA("snd_rawmidi_poll_descriptors_revents", rc);
235 			hdl->mio.eof = 1;
236 			return POLLHUP;
237 		}
238 		revents |= r & (POLLOUT | POLLHUP);
239 	}
240 	return revents;
241 }
242 #endif /* defined USE_ALSA */
243