/* * Purpose: MIDI support. */ /* * * This file is part of Open Sound System. * * Copyright (C) 4Front Technologies 1996-2008. * * This this source file is released under GPL v2 license (no other versions). * See the COPYING file included in the main directory of this source * distribution for the license terms and conditions. * */ #include "oss_config.h" #include "midi_core.h" #define MDB(x) oss_mutex_t midi_mutex; static oss_device_t *osscore_osdev = NULL; /* * List of MIDI devices. */ mididev_t **midi_devs = NULL; int num_mididevs = 0; /* * List of MIDI clients (/dev/midi* device files). */ int oss_num_midi_clients = 0; oss_midi_client_t *oss_midi_clients[MAX_MIDI_CLIENTS] = { NULL }; static int queue_midi_input (mididev_t * mididev, unsigned char *data, int len, oss_native_word * wait_flags) { int ret; midi_packet_header_t hdr, *hdrp = NULL; if (mididev->working_mode != MIDI_MODE_TRADITIONAL) { int use_abs = 0; hdrp = &hdr; memset (&hdr, 0, sizeof (hdr)); hdr.magic = MIDI_HDR_MAGIC; hdr.event_type = MIDI_EV_WRITE; hdr.options |= MIDI_OPT_TIMED; if (mididev->working_mode == MIDI_MODE_TIMED_ABS) { use_abs = 1; hdr.options |= MIDI_OPT_USECTIME; } hdr.time = oss_timer_get_current_time (mididev->timer_dev, mididev->latency, use_abs); } if ((ret = midi_queue_put (mididev->in_queue, data, len, hdrp)) < 0) { if (len == 1) cmn_err (CE_CONT, "/dev/midi%02d: Input buffer overflow\n", mididev->dev); ret = 0; } oss_wakeup (mididev->in_wq, &mididev->mutex, wait_flags, POLLIN); return ret; } static void mtc_generator (void *d) { #if 1 oss_native_word dev = (oss_native_word) d; oss_native_word flags; unsigned char data, buf[2]; mididev_p mididev = midi_devs[dev]; int t; int step; int h, m, s, f; t = (int) (GET_JIFFIES () - mididev->mtc_t0); if (mididev->mtc_timebase < 1) return; mididev->mtc_timeout_id = timeout (mtc_generator, (void *) dev, OSS_HZ / 100); step = OSS_HZ / (mididev->mtc_timebase * 4); /* System ticks per quarter frame */ if (step < 1) { /* System timer has too low resolution so do nothing */ return; } t /= step; if (t <= mididev->mtc_prev_t) /* Not enough time elapsed yet (for some reason) */ { return; } mididev->mtc_prev_t = t; if (mididev->mtc_phase == 0) mididev->mtc_current = t; else t = mididev->mtc_current; t /= 4; /* Ignore quarter frames */ f = t % mididev->mtc_timebase; t /= mididev->mtc_timebase; s = t % 60; t /= 60; m = t % 60; t /= 60; h = t % 0x24; data = 0x00; switch (mididev->mtc_phase) { case 0: data = 0x00 | (f % 0x0f); break; case 1: data = 0x10 | ((f >> 4) % 0x0f); break; case 2: data = 0x20 | (s % 0x0f); break; case 3: data = 0x30 | ((s >> 4) % 0x0f); break; case 4: data = 0x40 | (m % 0x0f); break; case 5: data = 0x50 | ((m >> 4) % 0x0f); break; case 6: data = 0x60 | (h % 0x0f); break; case 7: data = 0x70 | ((h >> 4) % 0x03) | (mididev->mtc_codetype << 1); break; } mididev->mtc_phase = (mididev->mtc_phase + 1) % 8; #if 0 cmn_err (CE_CONT, "MTC = %02d:%02d:%02d %02d.x -> %02x\n", h, m, s, f, data); #endif MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); buf[0] = 0xf1; buf[1] = data; queue_midi_input (mididev, buf, 2, &flags); MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); #endif } int oss_midi_input_byte (int dev, unsigned char data) { oss_native_word flags; mididev_t *mididev; int ret; if (dev < 0 || dev >= num_mididevs) cmn_err (CE_PANIC, "MIDI driver bug - bad MIDI device %d\n", dev); mididev = midi_devs[dev]; if (data == 0xf1) { mididev->mtc_timebase = -1; } MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); ret = queue_midi_input (mididev, &data, 1, &flags); MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return ret; } int oss_midi_input_buf (int dev, unsigned char *data, int len) { oss_native_word flags; mididev_t *mididev; int ret, n = 0; if (dev < 0 || dev >= num_mididevs) cmn_err (CE_PANIC, "MIDI driver bug - bad MIDI device %d\n", dev); mididev = midi_devs[dev]; MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); while (len > 0) { int i, l = len; if (l > MIDI_PAYLOAD_SIZE) l = MIDI_PAYLOAD_SIZE; for (i = 0; i < l; i++) { /* Turn off MTC generation if MTC data is received */ if (data[i] == 0xf1) { mididev->mtc_timebase = -1; } } ret = queue_midi_input (mididev, data, l, &flags); if (ret < 0) { ret = 0; cmn_err (CE_WARN, "MIDI input bytes dropped\n"); } if (ret == 0) /* Cannot queue more bytes */ break; len -= ret; data += ret; n += ret; } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return n; } static void oss_midi_input_event (int dev, unsigned char *data, int len) { oss_native_word flags; mididev_t *mididev; int ret, n = 0, i; if (dev < 0 || dev >= num_mididevs) cmn_err (CE_PANIC, "MIDI driver bug - bad MIDI device %d\n", dev); mididev = midi_devs[dev]; MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); for (i = 0; i < len; i += 4) { int l; unsigned char *d = data + i; switch (d[0] & 0x0f) { case 0x0: /* End marker */ MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return; break; case 0x2: /* Two byte real time messages TODO? */ l = 2; break; case 0x4: /* Sysex start/continuation */ l = 3; break; case 0x5: /* Sysex termination records */ case 0x6: case 0x7: l = (d[0] & 0x0f) - 4; break; case 0x8: case 0x9: case 0xa: case 0xb: case 0xe: l = 3; break; case 0x0c: case 0x0d: l = 2; break; case 0xf: /* System real time messages */ l = 1; break; default: l = 3; } if (l == 0) /* Nothing to send */ continue; if (l > MIDI_PAYLOAD_SIZE) l = MIDI_PAYLOAD_SIZE; /* * Ignore active sensing */ if (d[1] == 0xfe) continue; ret = queue_midi_input (mididev, d + 1, l, &flags); if (ret < 0) { ret = 0; cmn_err (CE_WARN, "MIDI input bytes dropped\n"); } if (ret == 0) /* Cannot queue more bytes */ break; len -= ret; data += ret; n += ret; } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return; } static void oss_midi_callback (void *arg); static int setup_tempo (mididev_t * mididev); void oss_midi_copy_timer (int dev, int source_dev) { mididev_t *mididev; int err; if (dev < 0 || dev >= num_mididevs) return; mididev = midi_devs[dev]; if (mididev->timer_dev >= 0) /* Detach the old client */ oss_timer_detach_client (mididev->timer_dev, mididev); mididev->timer_dev = -1; if (source_dev < 0 || source_dev >= num_mididevs) return; if (midi_devs[source_dev]->timer_dev < 0) return; if ((err = oss_timer_attach_client (midi_devs[source_dev]->timer_dev, mididev)) < 0) { cmn_err (CE_CONT, "Cannot attach timer %d to MIDI dev %d, err=%d\n", midi_devs[source_dev]->timer_dev, mididev->dev, err); return; } mididev->timer_dev = midi_devs[source_dev]->timer_dev; mididev->timer_driver = midi_devs[source_dev]->timer_driver; } int oss_midi_output_intr_handler (int dev, int callback_flag) { mididev_t *mididev; oss_native_word flags; unsigned char *buf; int len; midi_packet_header_t *hdr; MDB (cmn_err (CE_CONT, "oss_midi_output_intr_handler()\n")); if (dev < 0 || dev >= num_mididevs) cmn_err (CE_PANIC, "oss_midi_output_intr: Bad device %d\n", dev); mididev = midi_devs[dev]; if (callback_flag) { mididev->out_timeout = 0; } if (!(mididev->open_mode & OPEN_WRITE)) /* Device may have been closed */ { return 0; } MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); while ((len = midi_queue_find_buffer (mididev->out_queue, &buf, &hdr)) >= 0) /* TODO: Check time */ { int i, n = 0; if (!callback_flag) /* Called from write() handler */ if (hdr != NULL && (hdr->options & MIDI_OPT_BUSY)) { /* * This buffer is already playing */ MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return 0; } MDB (cmn_err (CE_CONT, "Bytes in buffer=%d\n", len)); #if 0 if (buf != NULL) MDB (cmn_err (CE_CONT, "First=%02x\n", buf[0])); #endif if (hdr == NULL) { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return 0; } hdr->options |= MIDI_OPT_BUSY; if (mididev->timer_dev >= 0) if (hdr->options & MIDI_OPT_TIMED) { if (mididev->d->flush_output) mididev->d->flush_output (mididev->dev); MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); /* TODO: Does releasing the mutex here cause any race conditions? */ if (oss_timer_wait_time (mididev->timer_dev, mididev->dev, mididev->latency, hdr->time, oss_midi_callback, hdr->options) <= 0) { /* * Not the right time yet. Timer is armed * and the callback function will be called * at the right time. We don't remove the event * so it can be retried later. */ return 0; } MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); } switch (hdr->event_type) { case MIDI_EV_TEMPO: mididev->tempo = hdr->parm; setup_tempo (mididev); break; case MIDI_EV_START: oss_timer_start (mididev->timer_dev); if (mididev->d->timer_setup) mididev->d->timer_setup (mididev->dev); break; case MIDI_EV_WRITE: break; } if (mididev->d->outputc == NULL || (len > 1 && mididev->d->bulk_write != NULL)) { n = mididev->d->bulk_write (mididev->dev, buf, len); if (n <= 0) /* Error */ { MDB (cmn_err (CE_NOTE, "MIDI device %d - output stalled (%d).\n", mididev->dev, n)); n = 0; } } else { for (i = 0; i < len; i++) { MDB (cmn_err (CE_CONT, "Out %d %02x\n", mididev->dev, buf[i])); if (!mididev->d->outputc (mididev->dev, buf[i])) { MDB (cmn_err (CE_NOTE, "MIDI device %d - output stalled (%d).\n", mididev->dev, n)); break; /* device full */ } n++; } } /* Remove the bytes sent out */ if (n > 0 || len == 0) { MDB (cmn_err (CE_CONT, "Remove %d/%d\n", n, len)); midi_queue_remove_chars (mididev->out_queue, n); oss_wakeup (mididev->out_wq, &mididev->mutex, &flags, POLLOUT); } if (n == len) { if (mididev->d->flush_output) mididev->d->flush_output (mididev->dev); } if (n < len) { /* Not everything consumed - arm a timeout */ MDB (cmn_err (CE_CONT, "Not all consumed %d/%d\n", n, len)); if (mididev->out_timeout == 0) /* Not armed yet */ { oss_native_word d = dev; mididev->out_timeout = timeout (oss_midi_callback, (void *) d, 1); } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return 0; /* Not all buffered bytes were sent */ } MDB (cmn_err (CE_CONT, "Everything consumed\n")); } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return 1; /* Everything was done */ } int oss_midi_output_intr (int dev) { MDB (cmn_err (CE_CONT, "oss_midi_output_intr\n")); return oss_midi_output_intr_handler (dev, 0); } static void oss_midi_callback (void *arg) { int dev; dev = (oss_native_word) arg; MDB (cmn_err (CE_CONT, "MIDI timer callback\n")); oss_midi_output_intr_handler (dev, 1); } void oss_midi_set_defaults (mididev_t * mididev) { mididev->prech_timeout = 0; /* Infinite wait */ mididev->event_input = oss_midi_input_event; mididev->tempo = 120; mididev->timebase = OSS_HZ; } static void put_output (mididev_t * mididev, unsigned char *buf, int len); int oss_midi_open (int dev, int no_worries, struct fileinfo *file, int recursive, int open_flags, int *newdev) { /* * oss_midi_open opens a MIDI client (/dev/midi##) and binds it directly to * the MIDI port with the same number (dev). */ oss_native_word flags; oss_midi_client_t *client; int ok, err = OSS_EBUSY; int mode = file->mode & O_ACCMODE; char *cmd; if (dev < 0 || dev >= num_mididevs) return OSS_ENXIO; /* * Don't allow read only access on playback only devices. * However R/W access should be allowed so that the application can be * informed about (possible) status changes. */ if ((mode == OPEN_READ) && !(midi_devs[dev]->flags & MFLAG_INPUT)) { return OSS_EACCES; } /* * Input only devices cannot do output so don't allow it. */ if ((mode & OPEN_WRITE) && !(midi_devs[dev]->flags & MFLAG_OUTPUT)) { return OSS_EACCES; } if (!midi_devs[dev]->enabled) return OSS_ENXIO; if (midi_devs[dev]->unloaded) return OSS_ENXIO; client = oss_midi_clients[dev]; if (client == NULL) return OSS_ENXIO; MUTEX_ENTER_IRQDISABLE (midi_mutex, flags); ok = 1; if (client->open_mode != 0) /* Client busy */ ok = 0; if (midi_devs[dev]->open_mode != 0) /* Device busy */ ok = 0; if (ok) /* Still OK */ { client->mididev = midi_devs[dev]; if (client->mididev == NULL) { cmn_err(CE_WARN, "client->mididev == NULL\n"); MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); return OSS_EIO; } client->open_mode = mode; midi_devs[dev]->open_mode = mode; if ((cmd = GET_PROCESS_NAME (file)) != NULL) strncpy (client->mididev->cmd, cmd, 15); client->mididev->pid = GET_PROCESS_PID (file); /* Release locks before calling device open method */ MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); if ((err = midi_devs[dev]->d->open (dev, mode, oss_midi_input_byte, oss_midi_input_buf, oss_midi_output_intr)) < 0) ok = 0; /* Reacquire locks */ MUTEX_ENTER_IRQDISABLE (midi_mutex, flags); if (!ok) { client->open_mode = 0; midi_devs[dev]->open_mode = 0; } } if (!ok) { MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); return err; } oss_midi_set_defaults (client->mididev); client->mididev->is_timing_master = 0; client->num = dev; MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); if (mode & OPEN_READ) { char name[16]; sprintf (name, "read%d", dev); client->mididev->in_queue = midi_queue_alloc (client->mididev->osdev, name); if (client->mididev->in_queue == NULL) { cmn_err (CE_WARN, "Failed to allocate MIDI input queue\n"); midi_devs[dev]->open_mode = 0; client->open_mode = 0; client->mididev = NULL; return OSS_ENOMEM; } } if (mode & OPEN_WRITE) { char name[16]; int i; unsigned char tmpbuf[4]; sprintf (name, "write%d", dev); client->mididev->out_queue = midi_queue_alloc (client->mididev->osdev, name); /* if (dev==0)midi_queue_debugging(client->mididev->out_queue); */ if (client->mididev->out_queue == NULL) { cmn_err (CE_WARN, "Failed to allocate MIDI output queue\n"); midi_devs[dev]->open_mode = 0; client->open_mode = 0; client->mididev = NULL; return OSS_ENOMEM; } /* * Reset all MIDI controllers to their default values */ if (!(client->mididev->flags & MFLAG_QUIET)) for (i = 0; i < 16; i++) { tmpbuf[0] = 0xb0 | i; /* Control change */ tmpbuf[1] = 121; /* Reset all controllers */ tmpbuf[2] = 127; /* Reset vol/exp/pan too */ put_output (client->mididev, tmpbuf, 3); } if (client->mididev->d->flush_output) client->mididev->d->flush_output (client->mididev->dev); } if (client->mididev->d->init_device) client->mididev->d->init_device (dev); return 0; } int oss_vmidi_open (int dev, int dev_type, struct fileinfo *file, int recursive, int open_flags, int *newdev); static void sync_output (mididev_t * mididev) { int n = 0; unsigned int status; oss_native_word flags; if (mididev->is_killed) { midi_queue_removeall (mididev->out_queue); return; } if (mididev->timer_dev < 0 || !oss_timer_is_running (mididev->timer_dev)) { midi_queue_removeall (mididev->out_queue); return; } MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); if (mididev->d->flush_output) mididev->d->flush_output (mididev->dev); while (!midi_queue_isempty (mididev->out_queue)) { int tmout; if (n++ > 100) { cmn_err (CE_NOTE, "MIDI output didn't get drained (sync).\n"); MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return; } tmout = oss_timer_get_timeout (mididev->timer_dev, mididev->dev); if (oss_sleep (mididev->out_wq, &mididev->mutex, tmout, &flags, &status)) n = 0; /* Not timeout */ if (status & WK_SIGNAL) { mididev->is_killed = 1; MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return; } } n = 0; if (mididev->d->wait_output) while (mididev->d->wait_output (mididev->dev)) { MDB (cmn_err (CE_CONT, "Wait output\n")); if (n++ > 10) { cmn_err (CE_NOTE, "MIDI output doesn't get emptied (sync).\n"); MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return; } oss_sleep (mididev->out_wq, &mididev->mutex, OSS_HZ, &flags, &status); if (status & WK_SIGNAL) { mididev->is_killed = 1; MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return; } } /* * Give the device few moments of extra time to flush all buffers. */ oss_sleep (mididev->out_wq, &mididev->mutex, OSS_HZ / 2, &flags, &status); MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); } static void put_output (mididev_t * mididev, unsigned char *buf, int len) { int i, n; int tmout = 1000; /* Spend here at most 1000 msec */ if (mididev->d->outputc == NULL || (len > 1 && mididev->d->bulk_write != NULL)) { int p = 0; while (p < len && tmout-- > 0) { n = mididev->d->bulk_write (mididev->dev, buf + p, len - p); if (n < 0) /* Error */ { return; } if (n < (len - p)) /* Not all consumed yet */ oss_udelay (1000); p += n; } return; } if (mididev->d->outputc) for (i = 0; i < len; i++) { while (!mididev->d->outputc (mididev->dev, buf[i]) && tmout-- > 0) { oss_udelay (1000); } } } void oss_midi_release (int dev, struct fileinfo *file) { oss_native_word flags; oss_midi_client_t *client; int midi_dev; mididev_t *mididev; int mode; MUTEX_ENTER_IRQDISABLE (midi_mutex, flags); mode = file->mode & O_ACCMODE; client = oss_midi_clients[dev]; if (client->mididev == NULL) /* Not bound to a port device */ { MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); return; } mididev = client->mididev; midi_dev = mididev->dev; untimeout (mididev->mtc_timeout_id); client->open_mode = 0; if (mididev->open_mode & OPEN_WRITE) /* Needs to sync */ { MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); sync_output (mididev); if (mididev->is_timing_master) oss_timer_stop (mididev->timer_dev); mididev->is_killed = 0; /* Too young to die */ /* Shut up possible hanging notes (if any) */ if (!(mididev->flags & MFLAG_QUIET)) { unsigned char tmpbuf[4]; int i; /* Sending one active sensing message should stop most devices */ tmpbuf[0] = 0xfe; put_output (mididev, tmpbuf, 1); #if 1 /* * To make sure we will also send a "all notes off" message to * all MIDI channels. */ for (i = 0; i < 16; i++) { tmpbuf[0] = 0xb0 | i; /* Control change */ tmpbuf[1] = 123; /* All notes off */ tmpbuf[2] = 0; put_output (mididev, tmpbuf, 3); } #endif } sync_output (mididev); MUTEX_ENTER_IRQDISABLE (midi_mutex, flags); } MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); mididev->d->ioctl (midi_dev, SNDCTL_SETSONG, (ioctl_arg) ""); mididev->d->close (midi_dev, mode); MUTEX_ENTER_IRQDISABLE (midi_mutex, flags); mididev->open_mode &= ~mode; MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); if (mididev->timer_dev >= 0) oss_timer_detach_client (mididev->timer_dev, mididev); mididev->timer_dev = -1; mididev->timer_driver = 0; if (mididev->in_queue != NULL) midi_queue_free (mididev->in_queue); mididev->in_queue = NULL; mididev->pid = -1; mididev->cmd[0] = 0; if (mididev->out_queue != NULL) midi_queue_free (mididev->out_queue); mididev->out_queue = NULL; client->mididev = NULL; if (mididev->out_timeout != 0) untimeout (mididev->out_timeout); mididev->out_timeout = 0; } int oss_midi_write (int dev, struct fileinfo *file, uio_t * buf, int count) { oss_midi_client_t *client = oss_midi_clients[dev]; mididev_t *mididev; int ret; unsigned int status; int c = 0, l, loops; oss_native_word flags; midi_packet_header_t hdr; int save_header = 0; int tmout = OSS_HZ; unsigned char *targetbuf; MDB (cmn_err (CE_CONT, "MIDI write %d\n", count)); if (client->mididev == NULL) if ((ret = midi_mapper_autobind (dev, client->open_mode)) < 0) return ret; mididev = client->mididev; if (mididev == NULL) return OSS_EBUSY; MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); /* * Since the application called write again we can ignore possible * earlier received kill signals. */ mididev->is_killed = 0; memset (&hdr, 0, sizeof (hdr)); if (mididev->working_mode != MIDI_MODE_TRADITIONAL) { if (count < sizeof (midi_packet_header_t)) { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); cmn_err (CE_WARN, "Too short MIDI write (no header)\n"); return OSS_EINVAL; } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); if (uiomove (&hdr, sizeof (midi_packet_header_t), UIO_WRITE, buf) != 0) { cmn_err (CE_WARN, "uiomove (header) failed\n"); return OSS_EFAULT; } if (hdr.magic != MIDI_HDR_MAGIC) { cmn_err (CE_WARN, "Bad MIDI write packet header (%04x)\n", hdr.magic); return OSS_EINVAL; } count -= sizeof (midi_packet_header_t); c += sizeof (midi_packet_header_t); /* Force save of the header if no other data was written */ if (count <= 0) save_header = 1; MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); } loops = 0; while (save_header || count > 0) { MDB (cmn_err (CE_CONT, "Write bytes left %d\n", count)); l = count; save_header = 0; if (loops++ > 10) { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); cmn_err (CE_CONT, "MIDI Output buffer %d doesn't drain %d/%d\n", mididev->dev, c, count); return OSS_EIO; } if (l > MIDI_PAYLOAD_SIZE) l = MIDI_PAYLOAD_SIZE; if ((ret = midi_queue_alloc_record (mididev->out_queue, &targetbuf, l, &hdr)) < 0) { if (ret == OSS_ENOSPC) /* Buffers full */ { MDB (cmn_err (CE_CONT, "*** Buffers full ***\n")); tmout = oss_timer_get_timeout (mididev->timer_dev, mididev->dev); if (!oss_sleep (mididev->out_wq, &mididev->mutex, tmout, &flags, &status)) { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); if (c > 0) return c; return OSS_EIO; /* Timeout - why */ } else loops = 0; /* Restart loop limiter */ if (status & WK_SIGNAL) { mididev->is_killed = 1; MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return OSS_EINTR; } if (loops > 95) cmn_err (CE_NOTE, "MIDI Output buffers full\n"); continue; /* Try again later */ } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return ret; } if (l > ret) l = ret; MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); /* cmn_err(CE_CONT, "Copy %08x (%d)\n", targetbuf, l); */ loops = 0; if (l > 0) { MDB (cmn_err (CE_CONT, "Store %d bytes\n", l)); if (uiomove (targetbuf, l, UIO_WRITE, buf) != 0) { cmn_err (CE_WARN, "uiomove (write) failed\n"); return OSS_EFAULT; } count -= l; c += l; } MDB (cmn_err (CE_CONT, "From oss_midi_write\n")); oss_midi_output_intr_handler (mididev->dev, 0); MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); /* midi_queue_trace(mididev->out_queue); */ MDB (cmn_err (CE_CONT, "MIDI write done %d\n\n", c)); return c; } static int do_read (mididev_t * mididev, uio_t * buf, unsigned char *data, int c, int max_bytes, midi_packet_header_t * hdr) { int l = 0; if (mididev->working_mode != MIDI_MODE_TRADITIONAL) { if (max_bytes <= sizeof (*hdr)) { cmn_err (CE_WARN, "MIDI read too small for packet header\n"); return OSS_E2BIG; } hdr->magic = MIDI_HDR_MAGIC; if (uiomove (hdr, sizeof (*hdr), UIO_READ, buf) != 0) { cmn_err (CE_WARN, "uiomove (read) failed\n"); return OSS_EFAULT; } l += sizeof (*hdr); } if (l + c > max_bytes) c = max_bytes - l; if (c <= 0) return OSS_E2BIG; if (uiomove (data, c, UIO_READ, buf) != 0) { cmn_err (CE_WARN, "uiomove (read) failed\n"); return OSS_EFAULT; } midi_queue_remove_chars (mididev->in_queue, c); l += c; return l; } int oss_midi_read (int dev, struct fileinfo *file, uio_t * buf, int count) { oss_midi_client_t *client = oss_midi_clients[dev]; mididev_t *mididev; oss_native_word flags; unsigned char *data; unsigned int status; int c = 0; midi_packet_header_t *hdr; int err; if (client->mididev == NULL) if ((err = midi_mapper_autobind (dev, client->open_mode)) < 0) return err; mididev = client->mididev; if (mididev == NULL) return OSS_EBUSY; MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); if ((c = midi_queue_find_buffer (mididev->in_queue, &data, &hdr)) < 0) { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return c; } if (c > 0 || (mididev->working_mode != MIDI_MODE_TRADITIONAL && c >= 0 && hdr != NULL)) { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return do_read (mididev, buf, data, c, count, hdr); } if (mididev->prech_timeout < 0) /* Non blocking mode */ { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return OSS_EWOULDBLOCK; } if (!oss_sleep (mididev->in_wq, &mididev->mutex, mididev->prech_timeout, &flags, &status)) { /* Timeout */ MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return 0; } if (status & WK_SIGNAL) { mididev->is_killed = 1; MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return OSS_EINTR; } if ((c = midi_queue_get (mididev->in_queue, &data, count, &hdr)) < 0 || (c == 0 && hdr == NULL)) { MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); return c; } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); if (c > 0) return do_read (mididev, buf, data, c, count, hdr); return c; } static int setup_working_mode (mididev_t * mididev) { int timer_dev; int err; if (mididev->timer_dev < 0) /* No timer yet */ { /* * Setup a timer */ if (mididev->timer_driver < 0 || mididev->timer_driver >= oss_num_timer_drivers) { cmn_err (CE_WARN, "Invalid MIDI timer %d selected\n", mididev->timer_driver); return OSS_ENXIO; } if ((timer_dev = oss_timer_create_instance (mididev->timer_driver, mididev)) < 0) return timer_dev; mididev->is_timing_master = 1; if ((err = oss_timer_attach_client (timer_dev, mididev)) < 0) { oss_timer_detach_client (timer_dev, mididev); return err; } mididev->timer_dev = timer_dev; if (mididev->d->timer_setup) mididev->d->timer_setup (mididev->dev); } else timer_dev = mididev->timer_dev; if (timer_dev < 0 || timer_dev >= oss_num_timers || oss_timer_devs[timer_dev] == NULL) { cmn_err (CE_WARN, "Failed to allocate a timer instance\n"); return OSS_ENXIO; } return 0; } static int setup_timebase (mididev_t * mididev) { if (mididev->timer_dev < 0) /* No timer yet */ return 0; return oss_timer_set_timebase (mididev->timer_dev, mididev->timebase); } static int setup_tempo (mididev_t * mididev) { if (mididev->timer_dev < 0) /* No timer yet */ return 0; return oss_timer_set_tempo (mididev->timer_dev, mididev->tempo); } int oss_midi_ioctl (int dev, struct fileinfo *file, unsigned int cmd, ioctl_arg arg) { oss_midi_client_t *client = oss_midi_clients[dev]; mididev_t *mididev; int err, val; oss_native_word d; if (client->mididev == NULL) if ((err = midi_mapper_autobind (dev, client->open_mode)) < 0) return err; mididev = client->mididev; if (mididev == NULL) return OSS_EBUSY; dev = mididev->dev; switch (cmd) { case SNDCTL_MIDI_PRETIME: val = *arg; if (val < -1) return OSS_EINVAL; if (val != -1) val = (OSS_HZ * val) / 100; mididev->prech_timeout = val; return 0; break; case SNDCTL_MIDI_MTCINPUT: val = *arg; switch (val) { case 0: case -1: break; case 24: mididev->mtc_codetype = 0; break; case 25: mididev->mtc_codetype = 1; break; case 29: mididev->mtc_codetype = 2; break; case 30: mididev->mtc_codetype = 3; break; break; default: return OSS_EINVAL; } mididev->mtc_timebase = val; mididev->mtc_t0 = GET_JIFFIES (); mididev->mtc_current = 0; mididev->mtc_prev_t = -1; mididev->mtc_phase = 0; d = dev; if (mididev->mtc_timebase != -1) mididev->mtc_timeout_id = timeout (mtc_generator, (void *) d, OSS_HZ / 100); return *arg = val; break; case SNDCTL_MIDI_SETMODE: val = *arg; if (val != MIDI_MODE_TRADITIONAL && val != MIDI_MODE_TIMED && val != MIDI_MODE_TIMED_ABS) return OSS_EINVAL; mididev->working_mode = val; *arg = val; if ((err = setup_working_mode (mididev)) < 0) return err; if ((err = setup_tempo (mididev)) < 0) return err; if ((err = setup_timebase (mididev)) < 0) return err; return 0; break; case SNDCTL_MIDI_TIMEBASE: val = *arg; if (val < 1 || val > 1000) return OSS_EINVAL; mididev->timebase = val; return setup_timebase (mididev); break; case SNDCTL_MIDI_TEMPO: val = *arg; if (val < 1 || val > 200) return OSS_EINVAL; mididev->tempo = val; return setup_tempo (mididev); break; default: return mididev->d->ioctl (mididev->dev, cmd, arg); } } #ifdef ALLOW_SELECT int oss_midi_chpoll (int dev, struct fileinfo *file, oss_poll_event_t * ev) { short events = ev->events; oss_midi_client_t *client; mididev_t *mididev; oss_native_word flags; int err; if (dev < 0 || dev >= oss_num_midi_clients) return OSS_ENXIO; client = oss_midi_clients[dev]; if (client->mididev == NULL) if ((err = midi_mapper_autobind (dev, client->open_mode)) < 0) return err; mididev = client->mididev; if (mididev == NULL) return OSS_EIO; if ((events & (POLLOUT | POLLWRNORM)) && (mididev->open_mode & OPEN_WRITE)) { MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); if (mididev->out_queue == NULL || midi_queue_spaceleft (mididev->out_queue) < 10) { /* No space yet */ oss_register_poll (mididev->out_wq, &mididev->mutex, &flags, ev); } else { ev->revents |= (POLLOUT | POLLWRNORM) & events; } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); } if ((events & (POLLIN | POLLRDNORM)) && (mididev->open_mode & OPEN_READ)) { MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags); if (mididev->in_queue == NULL || midi_queue_isempty (mididev->in_queue)) { /* No space yet */ oss_register_poll (mididev->in_wq, &mididev->mutex, &flags, ev); } else { ev->revents |= (POLLIN | POLLRDNORM) & events; } MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags); } return 0; } #endif #ifdef VDEV_SUPPORT static oss_cdev_drv_t vmidi_cdev_drv = { oss_vmidi_open, oss_midi_release, oss_midi_read, oss_midi_write, oss_midi_ioctl, #ifdef ALLOW_SELECT oss_midi_chpoll #else NULL #endif }; #endif static oss_cdev_drv_t midi_cdev_drv = { oss_midi_open, oss_midi_release, oss_midi_read, oss_midi_write, oss_midi_ioctl, #ifdef ALLOW_SELECT oss_midi_chpoll #else NULL #endif }; int oss_vmidi_open (int dev, int dev_type, struct fileinfo *file, int recursive, int open_flags, int *newdev) { /* * oss_vmidi_open is another version of oss_midi_open. Instead of binding * the client directly with the device port this routine creates just a * client (device file). The binding operation is left to be done later * using SNDCTL_MIDI_BIND. If no binding is made then the first * read/write/ioctl/etc operation will automatically bind the client * with the first available (lowest number) MIDI port. * * oss_vmidi_open() drives the /dev/midi device file. */ oss_native_word flags; oss_midi_client_t *client = NULL; int d; int mode = file->mode & O_ACCMODE; MUTEX_ENTER_IRQDISABLE (midi_mutex, flags); /* * Find the first midi client number that is free. Start from the client * numbers after the last existing MIDI port. */ for (dev = num_mididevs; dev < oss_num_midi_clients; dev++) { if (oss_midi_clients[dev] != NULL && oss_midi_clients[dev]->open_mode == 0) { client = oss_midi_clients[dev]; break; } } if (client == NULL) { /* * No earlier allocated clients were free so create a new entry. * Also create an anonymous character device. */ MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); client = PMALLOC (osdev, sizeof (*client)); MUTEX_ENTER_IRQDISABLE (midi_mutex, flags); if (oss_num_midi_clients >= MAX_MIDI_CLIENTS) { cmn_err (CE_WARN, "Too many MIDI clients\n"); MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); return OSS_ENXIO; } if (client == NULL) { cmn_err (CE_WARN, "Out of memory\n"); MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); return OSS_ENOMEM; } memset (client, 0, sizeof (*client)); oss_midi_clients[dev = oss_num_midi_clients++] = client; #ifdef CONFIG_OSS_MIDI MDB (cmn_err (CE_CONT, "Installing /dev/midi%02d\n", dev)); oss_install_chrdev (osscore_osdev, NULL, OSS_DEV_MIDI, dev, &midi_cdev_drv, 0); #endif } client->open_mode = mode; client->num = dev; client->mididev = NULL; /* Not bound yet */ MUTEX_EXIT_IRQRESTORE (midi_mutex, flags); if ((d = oss_find_minor (OSS_DEV_MIDI, dev)) < 0) { oss_midi_release (dev, file); return d; } *newdev = d; return dev; } void oss_midi_init (oss_device_t * osdev) { static int already_done = 0; if (already_done) return; already_done = 1; if (sizeof (midi_packet_header_t) != 32) { cmn_err (CE_WARN, "sizeof(midi_packet_header_t) != 32 (%d)\n", sizeof (midi_packet_header_t)); cmn_err (CE_CONT, "MIDI subsystem not activated\n"); return; } osscore_osdev = osdev; MUTEX_INIT (osdev, midi_mutex, MH_FRAMEW); midi_mapper_init (osdev); oss_init_timers (osdev); attach_oss_default_timer (osdev); } void oss_midi_uninit (void) { int dev; midi_mapper_uninit (); detach_oss_default_timer (); oss_uninit_timers (); for (dev = 0; dev < num_mididevs; dev++) { oss_remove_wait_queue (midi_devs[dev]->out_wq); oss_remove_wait_queue (midi_devs[dev]->in_wq); MUTEX_CLEANUP (midi_devs[dev]->mutex); } MUTEX_CLEANUP (midi_mutex); } void install_vmidi (oss_device_t * osdev) { #ifdef CONFIG_OSS_MIDI #ifdef VDEV_SUPPORT oss_install_chrdev (osdev, "midi", OSS_DEV_VMIDI, 0, &vmidi_cdev_drv, CHDEV_VIRTUAL); #endif #endif } int oss_install_mididev (int version, char *id, char *name, midi_driver_t * d, int driver_size, unsigned int flags, void *devc, oss_device_t * osdev) { int curr_midi_dev; mididev_t *op; oss_midi_client_t *client; int i; int old = 0; /* * Old style drivers are always input&output even they don't report that. * Fix the permissions automatically. */ if (!(flags & (MFLAG_INPUT | MFLAG_OUTPUT))) flags |= MFLAG_INPUT | MFLAG_OUTPUT; if (driver_size > sizeof (midi_driver_t)) driver_size = sizeof (midi_driver_t); if (midi_devs == NULL) { midi_devs = PMALLOC (osdev, sizeof (mididev_p) * MAX_MIDI_DEV); } if (midi_devs == NULL) { cmn_err (CE_WARN, "oss_install_mididev: Out of memory\n"); return OSS_ENOMEM; } for (i = 0; i < num_mididevs; i++) if (midi_devs[i]->unloaded && midi_devs[i]->os_id == oss_get_osid (osdev)) { old = 1; op = midi_devs[i]; curr_midi_dev = i; MDB (cmn_err (CE_CONT, "Reincarnation of MIDI device %d\n", i)); break; } if (!old) { if (num_mididevs >= MAX_MIDI_DEV - 1) { cmn_err (CE_WARN, "Too many MIDI devices in the system\n"); return OSS_EIO; } op = midi_devs[num_mididevs] = PMALLOC (osdev, sizeof (mididev_t)); if (op == NULL) { cmn_err (CE_WARN, "oss_install_mididev: Failed to allocate memory\n"); return OSS_ENOMEM; } memset (op, 0, sizeof (*op)); curr_midi_dev = num_mididevs++; } op->devc = devc; op->dev = op->real_dev = curr_midi_dev; op->pid = -1; op->osdev = osdev; op->os_id = oss_get_osid (osdev); op->latency = -1; /* Not known */ if (!old) { MUTEX_INIT (op->osdev, op->mutex, MH_FRAMEW + 1); if ((op->out_wq = oss_create_wait_queue (op->osdev, "midi_out")) == NULL) cmn_err (CE_PANIC, "Cannot create MIDI output wait queue\n"); if ((op->in_wq = oss_create_wait_queue (op->osdev, "midi_in")) == NULL) cmn_err (CE_PANIC, "Cannot create MIDI input wait queue\n"); op->d = PMALLOC (osdev, sizeof (midi_driver_t)); memset (op->d, 0, sizeof (op->d)); memcpy (op->d, d, driver_size); sprintf (op->handle, "%s-md%02d", osdev->handle, ++osdev->num_mididevs); op->port_number = osdev->num_mididevs; } op->flags = flags; op->enabled = 1; op->unloaded = 0; op->mtc_timebase = -1; op->card_number = osdev->cardnum; op->timer_driver = 0; /* Point to the default timer */ op->timer_dev = -1; /* No timer instance created yet */ strncpy (op->name, name, sizeof (op->name) - 1); op->name[sizeof (op->name) - 1] = 0; op->caps = 0; if (flags & MFLAG_INPUT) op->caps |= MIDI_CAP_INPUT; if (flags & MFLAG_OUTPUT) op->caps |= MIDI_CAP_OUTPUT; if (oss_midi_clients[curr_midi_dev] == NULL) { client = PMALLOC (osdev, sizeof (*client)); if (client == NULL) cmn_err (CE_PANIC, "OSS install MIDI: Out of memory.\n"); memset (client, 0, sizeof (*client)); oss_midi_clients[curr_midi_dev] = client; oss_num_midi_clients = num_mididevs; } #ifdef CONFIG_OSS_MIDI /* * Create the device node. */ { char name[32]; #ifdef NEW_DEVICE_NAMING # ifdef USE_DEVICE_SUBDIRS sprintf (name, "oss/%s/mid%d", osdev->nick, osdev->num_mididevs - 1); # else sprintf (name, "%s_mid%d", osdev->nick, osdev->num_mididevs - 1); # endif #else sprintf (name, "midi%02d", curr_midi_dev); #endif oss_install_chrdev (osdev, name, OSS_DEV_MIDI, curr_midi_dev, &midi_cdev_drv, 0); sprintf (op->devnode, "/dev/%s", name); } #endif return curr_midi_dev; }