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