diff --git a/sys/dev/sound/midi/timer.c b/sys/dev/sound/midi/timer.c new file mode 100644 index 000000000000..969153ee1976 --- /dev/null +++ b/sys/dev/sound/midi/timer.c @@ -0,0 +1,564 @@ +/* + * This is the timer engine of /dev/music for FreeBSD. + * + * (C) 2002 Seigo Tanimura + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#include +#include + +#define TMR2TICKS(scp, tmr_val) \ + ((((tmr_val) * (scp)->tempo * (scp)->timebase) + (30 * hz)) / (60 * hz)) +#define CURTICKS(scp) \ + ((scp)->ticks_offset + (scp)->ticks_cur - (scp)->ticks_base) + +struct systmr_timer_softc { + int running; + + u_long ticks_offset; + u_long ticks_base; + u_long ticks_cur; + + int tempo; + int timebase; + + u_long nexteventtime; + u_long preveventtime; + + struct callout timer; +}; + +static timeout_t systmr_timer; +static void systmr_reset(timerdev_info *tmd); +static u_long systmr_time(void); + +static tmr_open_t systmr_open; +static tmr_close_t systmr_close; +static tmr_event_t systmr_event; +static tmr_gettime_t systmr_gettime; +static tmr_ioctl_t systmr_ioctl; +static tmr_armtimer_t systmr_armtimer; + +static timerdev_info systmr_timerdev = { + "System clock", + 0, + 0, + systmr_open, + systmr_close, + systmr_event, + systmr_gettime, + systmr_ioctl, + systmr_armtimer, +}; + +static TAILQ_HEAD(,_timerdev_info) timer_info; +static struct mtx timerinfo_mtx; +static int timerinfo_mtx_init; +static int ntimer; + + +/* Install a system timer. */ +int +timerdev_install(void) +{ + int ret; + timerdev_info *tmd; + struct systmr_timer_softc *scp; + + SEQ_DEBUG(printf("timerdev_install: install a new timer.\n")); + + ret = 0; + tmd = NULL; + scp = NULL; + + scp = malloc(sizeof(*scp), M_DEVBUF, M_WAITOK | M_ZERO); + if (scp == NULL) { + ret = ENOMEM; + goto fail; + } + + tmd = create_timerdev_info_unit(&systmr_timerdev); + if (tmd == NULL) { + ret = ENOMEM; + goto fail; + } + + tmd->softc = scp; + callout_init(&scp->timer, 0); + + mtx_unlock(&tmd->mtx); + + SEQ_DEBUG(printf("timerdev_install: installed a new timer, unit %d.\n", tmd->unit)); + + return (0); + +fail: + if (scp != NULL) + free(scp, M_DEVBUF); + if (tmd != NULL) { + TAILQ_REMOVE(&timer_info, tmd, tmd_link); + free(tmd, M_DEVBUF); + } + + SEQ_DEBUG(printf("timerdev_install: installation failed.\n")); + + return (ret); +} + +/* Create a new timer device info structure. */ +timerdev_info * +create_timerdev_info_unit(timerdev_info *tmdinf) +{ + int unit; + timerdev_info *tmd, *tmdnew; + + /* XXX */ + if (!timerinfo_mtx_init) { + timerinfo_mtx_init = 1; + mtx_init(&timerinfo_mtx, "tmrinf", MTX_DEF); + TAILQ_INIT(&timer_info); + } + + /* As malloc(9) might block, allocate timerdev_info now. */ + tmdnew = malloc(sizeof(timerdev_info), M_DEVBUF, M_WAITOK | M_ZERO); + if (tmdnew == NULL) + return NULL; + bcopy(tmdinf, tmdnew, sizeof(timerdev_info)); + mtx_init(&tmdnew->mtx, "tmrmtx", MTX_DEF); + + mtx_lock(&timerinfo_mtx); + + ntimer++; + + for (unit = 0 ; ; unit++) { + TAILQ_FOREACH(tmd, &timer_info, tmd_link) { + if (tmd->unit == unit) + break; + } + if (tmd == NULL) + break; + } + + tmdnew->unit = unit; + mtx_lock(&tmdnew->mtx); + tmd = TAILQ_FIRST(&timer_info); + while (tmd != NULL) { + if (tmd->prio < tmdnew->prio) + break; + tmd = TAILQ_NEXT(tmd, tmd_link); + } + if (tmd != NULL) + TAILQ_INSERT_BEFORE(tmd, tmdnew, tmd_link); + else + TAILQ_INSERT_TAIL(&timer_info, tmdnew, tmd_link); + + mtx_unlock(&timerinfo_mtx); + + return (tmdnew); +} + +/* + * a small utility function which, given a unit number, returns + * a pointer to the associated timerdev_info struct. + */ +timerdev_info * +get_timerdev_info_unit(int unit) +{ + timerdev_info *tmd; + + /* XXX */ + if (!timerinfo_mtx_init) { + timerinfo_mtx_init = 1; + mtx_init(&timerinfo_mtx, "tmrinf", MTX_DEF); + TAILQ_INIT(&timer_info); + } + + mtx_lock(&timerinfo_mtx); + TAILQ_FOREACH(tmd, &timer_info, tmd_link) { + mtx_lock(&tmd->mtx); + if (tmd->unit == unit && tmd->seq == NULL) + break; + mtx_unlock(&tmd->mtx); + } + mtx_unlock(&timerinfo_mtx); + + return tmd; +} + +/* + * a small utility function which returns a pointer + * to the best preferred timerdev_info struct with + * no sequencer. + */ +timerdev_info * +get_timerdev_info(void) +{ + timerdev_info *tmd; + + /* XXX */ + if (!timerinfo_mtx_init) { + timerinfo_mtx_init = 1; + mtx_init(&timerinfo_mtx, "tmrinf", MTX_DEF); + TAILQ_INIT(&timer_info); + } + + mtx_lock(&timerinfo_mtx); + TAILQ_FOREACH(tmd, &timer_info, tmd_link) { + mtx_lock(&tmd->mtx); + if (tmd->seq == NULL) + break; + mtx_unlock(&tmd->mtx); + } + mtx_unlock(&timerinfo_mtx); + + return tmd; +} + + +/* ARGSUSED */ +static void +systmr_timer(void *d) +{ + timerdev_info *tmd; + struct systmr_timer_softc *scp; + void *seq; + + tmd = (timerdev_info *)d; + scp = (struct systmr_timer_softc *)tmd->softc; + seq = NULL; + + mtx_lock(&tmd->mtx); + + if (tmd->opened) { + callout_reset(&scp->timer, 1, systmr_timer, tmd); + + if (scp->running) { + scp->ticks_cur = TMR2TICKS(scp, systmr_time()); + + if (CURTICKS(scp) >= scp->nexteventtime) { + SEQ_DEBUG(printf("systmr_timer: CURTICKS %lu, call the sequencer.\n", CURTICKS(scp))); + scp->nexteventtime = ULONG_MAX; + seq = tmd->seq; + } + } + } + + mtx_unlock(&tmd->mtx); + + if (seq != NULL) + seq_timer(seq); +} + +static void +systmr_reset(timerdev_info *tmd) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + mtx_assert(&tmd->mtx, MA_OWNED); + + SEQ_DEBUG(printf("systmr_reset: unit %d.\n", tmd->unit)); + + scp->ticks_offset = 0; + scp->ticks_base = scp->ticks_cur = TMR2TICKS(scp, systmr_time()); + + scp->nexteventtime = ULONG_MAX; + scp->preveventtime = 0; +} + +static u_long +systmr_time(void) +{ + struct timeval timecopy; + + getmicrotime(&timecopy); + return timecopy.tv_usec / (1000000 / hz) + (u_long) timecopy.tv_sec * hz; +} + + +/* ARGSUSED */ +static int +systmr_open(timerdev_info *tmd, int oflags, int devtype, struct thread *td) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_open: unit %d.\n", tmd->unit)); + + mtx_lock(&tmd->mtx); + + if (tmd->opened) { + mtx_unlock(&tmd->mtx); + return (EBUSY); + } + + systmr_reset(tmd); + scp->tempo = 60; + scp->timebase = hz; + tmd->opened = 1; + + callout_reset(&scp->timer, 1, systmr_timer, tmd); + + mtx_unlock(&tmd->mtx); + + return (0); +} + +static int +systmr_close(timerdev_info *tmd, int fflag, int devtype, struct thread *td) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_close: unit %d.\n", tmd->unit)); + + mtx_lock(&tmd->mtx); + + tmd->opened = 0; + scp->running = 0; + + callout_stop(&scp->timer); + + mtx_unlock(&tmd->mtx); + + return (0); +} + +static int +systmr_event(timerdev_info *tmd, u_char *ev) +{ + struct systmr_timer_softc *scp; + u_char cmd; + u_long parm, t; + int ret; + void * seq; + + scp = (struct systmr_timer_softc *)tmd->softc; + cmd = ev[1]; + parm = *(int *)&ev[4]; + ret = MORE; + + SEQ_DEBUG(printf("systmr_event: unit %d, cmd %s, parm %lu.\n", + tmd->unit, midi_cmdname(cmd, cmdtab_timer), parm)); + + mtx_lock(&tmd->mtx); + + switch (cmd) { + case TMR_WAIT_REL: + parm += scp->preveventtime; + /* FALLTHRU */ + case TMR_WAIT_ABS: + if (parm > 0) { + if (parm <= CURTICKS(scp)) + break; + t = parm; + scp->nexteventtime = scp->preveventtime = t; + ret = TIMERARMED; + break; + } + break; + + case TMR_START: + systmr_reset(tmd); + scp->running = 1; + break; + + case TMR_STOP: + scp->running = 0; + break; + + case TMR_CONTINUE: + scp->running = 1; + break; + + case TMR_TEMPO: + if (parm > 0) { + RANGE(parm, 8, 360); + scp->ticks_offset += scp->ticks_cur + - scp->ticks_base; + scp->ticks_base = scp->ticks_cur; + scp->tempo = parm; + } + break; + + case TMR_ECHO: + seq = tmd->seq; + mtx_unlock(&tmd->mtx); + seq_copytoinput(seq, ev, 8); + mtx_lock(&tmd->mtx); + break; + } + + mtx_unlock(&tmd->mtx); + + SEQ_DEBUG(printf("systmr_event: timer %s.\n", + ret == TIMERARMED ? "armed" : "not armed")); + + return (ret); +} + +static int +systmr_gettime(timerdev_info *tmd, u_long *t) +{ + struct systmr_timer_softc *scp; + int ret; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_gettime: unit %d.\n", tmd->unit)); + + mtx_lock(&tmd->mtx); + + if (!tmd->opened || t == NULL) { + ret = EINVAL; + goto fail; + } + + *t = CURTICKS(scp); + SEQ_DEBUG(printf("systmr_gettime: ticks %lu.\n", *t)); + +fail: + mtx_unlock(&tmd->mtx); + + return (0); +} + +static int +systmr_ioctl(timerdev_info *tmd, u_long cmd, caddr_t data, int fflag, struct thread *td) +{ + struct systmr_timer_softc *scp; + int ret, val; + + scp = (struct systmr_timer_softc *)tmd->softc; + ret = 0; + + SEQ_DEBUG(printf("systmr_ioctl: unit %d, cmd %s.\n", + tmd->unit, midi_cmdname(cmd, cmdtab_seqioctl))); + + switch (cmd) { + case SNDCTL_TMR_SOURCE: + *(int *)data = TMR_INTERNAL; + break; + + case SNDCTL_TMR_START: + mtx_lock(&tmd->mtx); + systmr_reset(tmd); + scp->running = 1; + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_TMR_STOP: + mtx_lock(&tmd->mtx); + scp->running = 0; + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_TMR_CONTINUE: + mtx_lock(&tmd->mtx); + scp->running = 1; + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_TMR_TIMEBASE: + val = *(int *)data; + mtx_lock(&tmd->mtx); + if (val > 0) { + RANGE(val, 1, 1000); + scp->timebase = val; + } + *(int *)data = scp->timebase; + mtx_unlock(&tmd->mtx); + SEQ_DEBUG(printf("systmr_ioctl: timebase %d.\n", *(int *)data)); + break; + + case SNDCTL_TMR_TEMPO: + val = *(int *)data; + mtx_lock(&tmd->mtx); + if (val > 0) { + RANGE(val, 8, 360); + scp->ticks_offset += scp->ticks_cur + - scp->ticks_base; + scp->ticks_base = scp->ticks_cur; + scp->tempo = val; + } + *(int *)data = scp->tempo; + SEQ_DEBUG(printf("systmr_ioctl: tempo %d.\n", *(int *)data)); + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_SEQ_CTRLRATE: + val = *(int *)data; + if (val > 0) + ret = EINVAL; + else { + mtx_lock(&tmd->mtx); + *(int *)data = ((scp->tempo * scp->timebase) + 30) / 60; + mtx_unlock(&tmd->mtx); + SEQ_DEBUG(printf("systmr_ioctl: ctrlrate %d.\n", *(int *)data)); + } + break; + + case SNDCTL_TMR_METRONOME: + /* NOP. */ + break; + + case SNDCTL_TMR_SELECT: + /* NOP. */ + break; + + default: + ret = EINVAL; + } + + return (ret); +} + +static int +systmr_armtimer(timerdev_info *tmd, u_long t) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_armtimer: unit %d, t %lu.\n", tmd->unit, t)); + + mtx_lock(&tmd->mtx); + + if (t < 0) + t = CURTICKS(scp) + 1; + else if (t > CURTICKS(scp)) + scp->nexteventtime = scp->preveventtime = t; + + mtx_unlock(&tmd->mtx); + + return (0); +} diff --git a/sys/dev/sound/midi/timer.h b/sys/dev/sound/midi/timer.h new file mode 100644 index 000000000000..a615ab64cdc9 --- /dev/null +++ b/sys/dev/sound/midi/timer.h @@ -0,0 +1,80 @@ +/* + * Include file for a midi timer. + * + * Copyright by Seigo Tanimura 2002. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +typedef struct _timerdev_info timerdev_info; + +typedef int (tmr_open_t)(timerdev_info *tmd, int oflags, int devtype, struct thread *td); +typedef int (tmr_close_t)(timerdev_info *tmd, int fflag, int devtype, struct thread *td); +typedef int (tmr_event_t)(timerdev_info *tmd, u_char *ev); +typedef int (tmr_gettime_t)(timerdev_info *tmd, u_long *t); +typedef int (tmr_ioctl_t)(timerdev_info *tmd, u_long cmd, caddr_t data, int fflag, struct thread *td); +typedef int (tmr_armtimer_t)(timerdev_info *tmd, u_long t); + +struct _timerdev_info { + /* + * the first part of the descriptor is filled up from a + * template. + */ + char name[32]; + + int caps; + int prio; + + tmr_open_t *open; + tmr_close_t *close; + tmr_event_t *event; + tmr_gettime_t *gettime; + tmr_ioctl_t *ioctl; + tmr_armtimer_t *armtimer; + + + int unit; + void *softc; + void *seq; + + /* The tailq entry of the next timer device. */ + TAILQ_ENTRY(_timerdev_info) tmd_link; + + int opened; + + struct mtx mtx; +}; + +#ifdef _KERNEL +int timerdev_install(void); +timerdev_info *create_timerdev_info_unit(timerdev_info *tmdinf); +timerdev_info *get_timerdev_info_unit(int unit); +timerdev_info *get_timerdev_info(void); +#endif /* _KERNEL */ + +#endif /* _TIMER_H_ */