• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  RawMIDI - Hardware
3  *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
4  *                        Abramo Bagnara <abramo@alsa-project.org>
5  *
6  *
7  *   This library is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU Lesser General Public License as
9  *   published by the Free Software Foundation; either version 2.1 of
10  *   the License, or (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU Lesser General Public License for more details.
16  *
17  *   You should have received a copy of the GNU Lesser General Public
18  *   License along with this library; if not, write to the Free Software
19  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <fcntl.h>
28 #include <sys/ioctl.h>
29 #include "../control/control_local.h"
30 #include "rawmidi_local.h"
31 
32 #ifndef PIC
33 /* entry for static linking */
34 const char *_snd_module_rawmidi_hw = "";
35 #endif
36 
37 #define SNDRV_FILE_RAWMIDI		ALSA_DEVICE_DIRECTORY "midiC%iD%i"
38 #define SNDRV_RAWMIDI_VERSION_MAX	SNDRV_PROTOCOL_VERSION(2, 0, 0)
39 
40 #ifndef DOC_HIDDEN
41 typedef struct {
42 	int open;
43 	int fd;
44 	int card, device, subdevice;
45 	unsigned char *buf;
46 	size_t buf_size;	/* total buffer size in bytes */
47 	size_t buf_fill;	/* filled buffer size in bytes */
48 	size_t buf_pos;		/* offset to frame in the read buffer (bytes) */
49 	size_t buf_fpos;	/* offset to the frame data array (bytes 0-16) */
50 } snd_rawmidi_hw_t;
51 #endif
52 
buf_reset(snd_rawmidi_hw_t * hw)53 static void buf_reset(snd_rawmidi_hw_t *hw)
54 {
55 	hw->buf_fill = 0;
56 	hw->buf_pos = 0;
57 	hw->buf_fpos = 0;
58 }
59 
snd_rawmidi_hw_close(snd_rawmidi_t * rmidi)60 static int snd_rawmidi_hw_close(snd_rawmidi_t *rmidi)
61 {
62 	snd_rawmidi_hw_t *hw = rmidi->private_data;
63 	int err = 0;
64 
65 	hw->open--;
66 	if (hw->open)
67 		return 0;
68 	if (close(hw->fd)) {
69 		err = -errno;
70 		SYSERR("close failed\n");
71 	}
72 	free(hw->buf);
73 	free(hw);
74 	return err;
75 }
76 
snd_rawmidi_hw_nonblock(snd_rawmidi_t * rmidi,int nonblock)77 static int snd_rawmidi_hw_nonblock(snd_rawmidi_t *rmidi, int nonblock)
78 {
79 	snd_rawmidi_hw_t *hw = rmidi->private_data;
80 	long flags;
81 
82 	if ((flags = fcntl(hw->fd, F_GETFL)) < 0) {
83 		SYSERR("F_GETFL failed");
84 		return -errno;
85 	}
86 	if (nonblock)
87 		flags |= O_NONBLOCK;
88 	else
89 		flags &= ~O_NONBLOCK;
90 	if (fcntl(hw->fd, F_SETFL, flags) < 0) {
91 		SYSERR("F_SETFL for O_NONBLOCK failed");
92 		return -errno;
93 	}
94 	return 0;
95 }
96 
snd_rawmidi_hw_info(snd_rawmidi_t * rmidi,snd_rawmidi_info_t * info)97 static int snd_rawmidi_hw_info(snd_rawmidi_t *rmidi, snd_rawmidi_info_t * info)
98 {
99 	snd_rawmidi_hw_t *hw = rmidi->private_data;
100 	info->stream = rmidi->stream;
101 	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_INFO, info) < 0) {
102 		SYSERR("SNDRV_RAWMIDI_IOCTL_INFO failed");
103 		return -errno;
104 	}
105 	return 0;
106 }
107 
snd_rawmidi_hw_params(snd_rawmidi_t * rmidi,snd_rawmidi_params_t * params)108 static int snd_rawmidi_hw_params(snd_rawmidi_t *rmidi, snd_rawmidi_params_t * params)
109 {
110 	snd_rawmidi_hw_t *hw = rmidi->private_data;
111 	int tstamp;
112 	params->stream = rmidi->stream;
113 	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_PARAMS, params) < 0) {
114 		SYSERR("SNDRV_RAWMIDI_IOCTL_PARAMS failed");
115 		return -errno;
116 	}
117 	buf_reset(hw);
118 	tstamp = (params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK) == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP;
119 	if (hw->buf && !tstamp) {
120 		free(hw->buf);
121 		hw->buf = NULL;
122 		hw->buf_size = 0;
123 	} else if (tstamp) {
124 		size_t alloc_size;
125 		void *buf;
126 
127 		alloc_size = page_size();
128 		if (params->buffer_size > alloc_size)
129 			alloc_size = params->buffer_size;
130 		if (alloc_size != hw->buf_size) {
131 			buf = realloc(hw->buf, alloc_size);
132 			if (buf == NULL)
133 				return -ENOMEM;
134 			hw->buf = buf;
135 			hw->buf_size = alloc_size;
136 		}
137 	}
138 	return 0;
139 }
140 
snd_rawmidi_hw_status(snd_rawmidi_t * rmidi,snd_rawmidi_status_t * status)141 static int snd_rawmidi_hw_status(snd_rawmidi_t *rmidi, snd_rawmidi_status_t * status)
142 {
143 	snd_rawmidi_hw_t *hw = rmidi->private_data;
144 	status->stream = rmidi->stream;
145 	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_STATUS, status) < 0) {
146 		SYSERR("SNDRV_RAWMIDI_IOCTL_STATUS failed");
147 		return -errno;
148 	}
149 	return 0;
150 }
151 
snd_rawmidi_hw_drop(snd_rawmidi_t * rmidi)152 static int snd_rawmidi_hw_drop(snd_rawmidi_t *rmidi)
153 {
154 	snd_rawmidi_hw_t *hw = rmidi->private_data;
155 	int str = rmidi->stream;
156 	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_DROP, &str) < 0) {
157 		SYSERR("SNDRV_RAWMIDI_IOCTL_DROP failed");
158 		return -errno;
159 	}
160 	buf_reset(hw);
161 	return 0;
162 }
163 
snd_rawmidi_hw_drain(snd_rawmidi_t * rmidi)164 static int snd_rawmidi_hw_drain(snd_rawmidi_t *rmidi)
165 {
166 	snd_rawmidi_hw_t *hw = rmidi->private_data;
167 	int str = rmidi->stream;
168 	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_DRAIN, &str) < 0) {
169 		SYSERR("SNDRV_RAWMIDI_IOCTL_DRAIN failed");
170 		return -errno;
171 	}
172 	return 0;
173 }
174 
snd_rawmidi_hw_write(snd_rawmidi_t * rmidi,const void * buffer,size_t size)175 static ssize_t snd_rawmidi_hw_write(snd_rawmidi_t *rmidi, const void *buffer, size_t size)
176 {
177 	snd_rawmidi_hw_t *hw = rmidi->private_data;
178 	ssize_t result;
179 	result = write(hw->fd, buffer, size);
180 	if (result < 0)
181 		return -errno;
182 	return result;
183 }
184 
snd_rawmidi_hw_read(snd_rawmidi_t * rmidi,void * buffer,size_t size)185 static ssize_t snd_rawmidi_hw_read(snd_rawmidi_t *rmidi, void *buffer, size_t size)
186 {
187 	snd_rawmidi_hw_t *hw = rmidi->private_data;
188 	ssize_t result;
189 	result = read(hw->fd, buffer, size);
190 	if (result < 0)
191 		return -errno;
192 	return result;
193 }
194 
read_from_ts_buf(snd_rawmidi_hw_t * hw,struct timespec * tstamp,void * buffer,size_t size)195 static ssize_t read_from_ts_buf(snd_rawmidi_hw_t *hw, struct timespec *tstamp,
196 				void *buffer, size_t size)
197 {
198 	struct snd_rawmidi_framing_tstamp *f;
199 	size_t flen;
200 	ssize_t result = 0;
201 
202 	f = (struct snd_rawmidi_framing_tstamp *)(hw->buf + hw->buf_pos);
203 	while (hw->buf_fill >= sizeof(*f)) {
204 		if (f->frame_type == 0) {
205 			tstamp->tv_sec = f->tv_sec;
206 			tstamp->tv_nsec = f->tv_nsec;
207 			break;
208 		}
209 		hw->buf_pos += sizeof(*f);
210 		hw->buf_fill -= sizeof(*f);
211 		f++;
212 	}
213 	while (size > 0 && hw->buf_fill >= sizeof(*f)) {
214 		/* skip other frames */
215 		if (f->frame_type != 0)
216 			goto __next;
217 		if (f->length == 0 || f->length > SNDRV_RAWMIDI_FRAMING_DATA_LENGTH)
218 			return -EINVAL;
219 		if (tstamp->tv_sec != (time_t)f->tv_sec ||
220 		    tstamp->tv_nsec != f->tv_nsec)
221 			break;
222 		flen = f->length - hw->buf_fpos;
223 		if (size < flen) {
224 			/* partial copy */
225 			memcpy(buffer, f->data + hw->buf_fpos, size);
226 			hw->buf_fpos += size;
227 			result += size;
228 			break;
229 		}
230 		memcpy(buffer, f->data + hw->buf_fpos, flen);
231 		hw->buf_fpos = 0;
232 		buffer += flen;
233 		size -= flen;
234 		result += flen;
235 	     __next:
236 		hw->buf_pos += sizeof(*f);
237 		hw->buf_fill -= sizeof(*f);
238 		f++;
239 	}
240 	return result;
241 }
242 
snd_rawmidi_hw_tread(snd_rawmidi_t * rmidi,struct timespec * tstamp,void * buffer,size_t size)243 static ssize_t snd_rawmidi_hw_tread(snd_rawmidi_t *rmidi, struct timespec *tstamp,
244 				    void *buffer, size_t size)
245 {
246 	snd_rawmidi_hw_t *hw = rmidi->private_data;
247 	ssize_t result = 0, ret;
248 
249 	/* no timestamp */
250 	tstamp->tv_sec = tstamp->tv_nsec = 0;
251 
252 	/* copy buffered frames */
253 	if (hw->buf_fill > 0) {
254 		result = read_from_ts_buf(hw, tstamp, buffer, size);
255 		if (result < 0 || size == (size_t)result ||
256 		    hw->buf_fill >= sizeof(struct snd_rawmidi_framing_tstamp))
257 			return result;
258 		buffer += result;
259 		size -= result;
260 	}
261 
262 	buf_reset(hw);
263 	ret = read(hw->fd, hw->buf, hw->buf_size);
264 	if (ret < 0)
265 		return result > 0 ? result : -errno;
266 	if (ret < (ssize_t)sizeof(struct snd_rawmidi_framing_tstamp))
267 		return result;
268 	hw->buf_fill = ret;
269 	ret = read_from_ts_buf(hw, tstamp, buffer, size);
270 	if (ret < 0 && result > 0)
271 		return result;
272 	return ret + result;
273 }
274 
275 static const snd_rawmidi_ops_t snd_rawmidi_hw_ops = {
276 	.close = snd_rawmidi_hw_close,
277 	.nonblock = snd_rawmidi_hw_nonblock,
278 	.info = snd_rawmidi_hw_info,
279 	.params = snd_rawmidi_hw_params,
280 	.status = snd_rawmidi_hw_status,
281 	.drop = snd_rawmidi_hw_drop,
282 	.drain = snd_rawmidi_hw_drain,
283 	.write = snd_rawmidi_hw_write,
284 	.read = snd_rawmidi_hw_read,
285 	.tread = snd_rawmidi_hw_tread
286 };
287 
288 
snd_rawmidi_hw_open(snd_rawmidi_t ** inputp,snd_rawmidi_t ** outputp,const char * name,int card,int device,int subdevice,int mode)289 int snd_rawmidi_hw_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
290 			const char *name, int card, int device, int subdevice,
291 			int mode)
292 {
293 	int fd, ver, ret;
294 	int attempt = 0;
295 	char filename[sizeof(SNDRV_FILE_RAWMIDI) + 20];
296 	snd_ctl_t *ctl;
297 	snd_rawmidi_t *rmidi;
298 	snd_rawmidi_hw_t *hw = NULL;
299 	snd_rawmidi_info_t info;
300 	int fmode;
301 
302 	if (inputp)
303 		*inputp = NULL;
304 	if (outputp)
305 		*outputp = NULL;
306 	if (!inputp && !outputp)
307 		return -EINVAL;
308 
309 	if ((ret = snd_ctl_hw_open(&ctl, NULL, card, 0)) < 0)
310 		return ret;
311 	sprintf(filename, SNDRV_FILE_RAWMIDI, card, device);
312 
313       __again:
314       	if (attempt++ > 3) {
315       		snd_ctl_close(ctl);
316       		return -EBUSY;
317       	}
318       	ret = snd_ctl_rawmidi_prefer_subdevice(ctl, subdevice);
319 	if (ret < 0) {
320 		snd_ctl_close(ctl);
321 		return ret;
322 	}
323 
324 	if (!inputp)
325 		fmode = O_WRONLY;
326 	else if (!outputp)
327 		fmode = O_RDONLY;
328 	else
329 		fmode = O_RDWR;
330 
331 	if (mode & SND_RAWMIDI_APPEND) {
332 		assert(outputp);
333 		fmode |= O_APPEND;
334 	}
335 
336 	if (mode & SND_RAWMIDI_NONBLOCK) {
337 		fmode |= O_NONBLOCK;
338 	}
339 
340 	if (mode & SND_RAWMIDI_SYNC) {
341 		fmode |= O_SYNC;
342 	}
343 
344 	assert(!(mode & ~(SND_RAWMIDI_APPEND|SND_RAWMIDI_NONBLOCK|SND_RAWMIDI_SYNC)));
345 
346 	fd = snd_open_device(filename, fmode);
347 	if (fd < 0) {
348 		snd_card_load(card);
349 		fd = snd_open_device(filename, fmode);
350 		if (fd < 0) {
351 			snd_ctl_close(ctl);
352 			SYSERR("open %s failed", filename);
353 			return -errno;
354 		}
355 	}
356 	if (ioctl(fd, SNDRV_RAWMIDI_IOCTL_PVERSION, &ver) < 0) {
357 		ret = -errno;
358 		SYSERR("SNDRV_RAWMIDI_IOCTL_PVERSION failed");
359 		close(fd);
360 		snd_ctl_close(ctl);
361 		return ret;
362 	}
363 	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_RAWMIDI_VERSION_MAX)) {
364 		close(fd);
365 		snd_ctl_close(ctl);
366 		return -SND_ERROR_INCOMPATIBLE_VERSION;
367 	}
368 	if (SNDRV_PROTOCOL_VERSION(2, 0, 2) <= ver) {
369 		/* inform the protocol version we're supporting */
370 		unsigned int user_ver = SNDRV_RAWMIDI_VERSION;
371 		ioctl(fd, SNDRV_RAWMIDI_IOCTL_USER_PVERSION, &user_ver);
372 	}
373 	if (subdevice >= 0) {
374 		memset(&info, 0, sizeof(info));
375 		info.stream = outputp ? SNDRV_RAWMIDI_STREAM_OUTPUT : SNDRV_RAWMIDI_STREAM_INPUT;
376 		if (ioctl(fd, SNDRV_RAWMIDI_IOCTL_INFO, &info) < 0) {
377 			SYSERR("SNDRV_RAWMIDI_IOCTL_INFO failed");
378 			ret = -errno;
379 			close(fd);
380 			snd_ctl_close(ctl);
381 			return ret;
382 		}
383 		if (info.subdevice != (unsigned int) subdevice) {
384 			close(fd);
385 			goto __again;
386 		}
387 	}
388 	snd_ctl_close(ctl);
389 
390 	hw = calloc(1, sizeof(snd_rawmidi_hw_t));
391 	if (hw == NULL)
392 		goto _nomem;
393 	hw->card = card;
394 	hw->device = device;
395 	hw->subdevice = subdevice;
396 	hw->fd = fd;
397 
398 	if (inputp) {
399 		rmidi = calloc(1, sizeof(snd_rawmidi_t));
400 		if (rmidi == NULL)
401 			goto _nomem;
402 		if (name)
403 			rmidi->name = strdup(name);
404 		rmidi->type = SND_RAWMIDI_TYPE_HW;
405 		rmidi->stream = SND_RAWMIDI_STREAM_INPUT;
406 		rmidi->mode = mode;
407 		rmidi->poll_fd = fd;
408 		rmidi->ops = &snd_rawmidi_hw_ops;
409 		rmidi->private_data = hw;
410 		rmidi->version = ver;
411 		hw->open++;
412 		*inputp = rmidi;
413 	}
414 	if (outputp) {
415 		rmidi = calloc(1, sizeof(snd_rawmidi_t));
416 		if (rmidi == NULL)
417 			goto _nomem;
418 		if (name)
419 			rmidi->name = strdup(name);
420 		rmidi->type = SND_RAWMIDI_TYPE_HW;
421 		rmidi->stream = SND_RAWMIDI_STREAM_OUTPUT;
422 		rmidi->mode = mode;
423 		rmidi->poll_fd = fd;
424 		rmidi->ops = &snd_rawmidi_hw_ops;
425 		rmidi->private_data = hw;
426 		rmidi->version = ver;
427 		hw->open++;
428 		*outputp = rmidi;
429 	}
430 	return 0;
431 
432  _nomem:
433 	close(fd);
434 	free(hw);
435 	if (inputp)
436 		free(*inputp);
437 	if (outputp)
438 		free(*outputp);
439 	return -ENOMEM;
440 }
441 
_snd_rawmidi_hw_open(snd_rawmidi_t ** inputp,snd_rawmidi_t ** outputp,char * name,snd_config_t * root ATTRIBUTE_UNUSED,snd_config_t * conf,int mode)442 int _snd_rawmidi_hw_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
443 			 char *name, snd_config_t *root ATTRIBUTE_UNUSED,
444 			 snd_config_t *conf, int mode)
445 {
446 	snd_config_iterator_t i, next;
447 	long card = -1, device = 0, subdevice = -1;
448 	int err;
449 	snd_config_for_each(i, next, conf) {
450 		snd_config_t *n = snd_config_iterator_entry(i);
451 		const char *id;
452 		if (snd_config_get_id(n, &id) < 0)
453 			continue;
454 		if (snd_rawmidi_conf_generic_id(id))
455 			continue;
456 		if (strcmp(id, "card") == 0) {
457 			err = snd_config_get_card(n);
458 			if (err < 0)
459 				return err;
460 			card = err;
461 			continue;
462 		}
463 		if (strcmp(id, "device") == 0) {
464 			err = snd_config_get_integer(n, &device);
465 			if (err < 0)
466 				return err;
467 			continue;
468 		}
469 		if (strcmp(id, "subdevice") == 0) {
470 			err = snd_config_get_integer(n, &subdevice);
471 			if (err < 0)
472 				return err;
473 			continue;
474 		}
475 		return -EINVAL;
476 	}
477 	if (card < 0)
478 		return -EINVAL;
479 	return snd_rawmidi_hw_open(inputp, outputp, name, card, device, subdevice, mode);
480 }
481 SND_DLSYM_BUILD_VERSION(_snd_rawmidi_hw_open, SND_RAWMIDI_DLSYM_VERSION);
482