freebsd-dev/sys/dev/sound/midi/timer.c
2003-02-19 05:47:46 +00:00

565 lines
11 KiB
C

/*
* 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 <dev/sound/midi/midi.h>
#include <dev/sound/midi/sequencer.h>
#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", NULL, 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", NULL, 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", NULL, 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", NULL, 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);
}