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