From: ag@yoda.omnicron.com (Keith Gabryelski)
Newsgroups: comp.unix.amiga
Subject: Working audio driver source.
Date: 23 Dec 92 00:39:33 GMT
Organization: Omnicron Data Systems

Here is a working audio driver I wrote about a month ago.  It replaces
the code in /usr/sys/amiga/driver/audio.[ch] and has only one known
bug which is that small samples may have the first part of their
sample cut off.

I was away from home for the last month while this device driver was
running and the sayclock (/usr/amiga/src/cmd/clksnd/sayclock) program
was outputting a stream of data every ten minutes to it.  So I believe
this is a mostly functional audio driver.

It currently supports stream audio data and repeated wave forms (via a
repeat count ioctl) so this code can be used in the X server to
produce console beeps.  I may update the X11R5 code someday to handle
this.

I would also like this audio driver to be used for the console
driver's ^G (beep).  I haven't thought of a good way of interfacing
kernel accesses to the audio driver yet but will send patches when
this has been completed.

I am currently in the midst of moving to Boston and working on a
startup project so you will have to forgive me if I do not reply to
mail reports very quickly.

Merry Christmas.

Pax, Keith


# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# This archive contains:
#	audio.c	audio.h	
#

echo x - audio.c
cat >audio.c <<'@EOF'
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/cmn_err.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/inline.h>
#include <sys/mkdev.h>
#include "memory.h"
#include "amigahr.h"
#include "audio.h"

extern char *panicstr;

static int audioopen(queue_t *, dev_t *, int, int, cred_t *);
static int audioclose(queue_t *);
static int audiowput(struct queue *, mblk_t *);
static int audiowsrv(struct queue *);
static void audioioctl(queue_t *, mblk_t *);
static int audioinit(void);
static int postaudio(struct audio_client *, mblk_t *);
static struct audio_channel *grab_channel(struct audio_client *);
static int fill_buffer(struct audio_buffer *, signed char *, int);
static int allocate_channel_buffers(struct audio_channel *);
static void loadup_dma(struct audio_channel *, struct audio_buffer *);

static struct module_info audiomiinfo =
{
    0, "audio", 0, INFPSZ, 1024, 0,
};

static struct qinit audiorinit =
{
    NULL, NULL, audioopen, audioclose, NULL, &audiomiinfo,
};

static struct module_info audiomoinfo =
{
    42, "audio", 0, INFPSZ, AUDIO_BUFFER_SIZE * 4, AUDIO_BUFFER_SIZE,
};

static struct qinit audiowinit =
{
    audiowput, audiowsrv, audioopen, audioclose, NULL, &audiomoinfo,
};

struct streamtab audioinfo =
{
    &audiorinit, &audiowinit, NULL, NULL,
};

#define blklen(bp)	(bp->b_wptr - bp->b_rptr)

static struct audio_buffer silent_buffer, handfeed_buffer;

/*
 * audio_channel: one for each audio port we have.  The standard
 * amiga has four channels, two dedicated to the left speaker, and
 * two to the right speaker.
 */
static struct audio_channel audio_channel[MAXCHANNELS] =
{
    { NULL, AIEAUD0, DMAAUD0, },
    { NULL, AIEAUD1, DMAAUD1, },
    { NULL, AIEAUD2, DMAAUD2, },
    { NULL, AIEAUD3, DMAAUD3, },
};

static struct audio_client *audio_clients;

static int audioinit(void)
{
    int i;
    static boolean_t initialized;

    if (initialized)
	return 0;

    silent_buffer.data = AllocMem(2, MEMF_CHIP);
    if (!silent_buffer.data)
    {
	printf("audioinit: Couldn't allocate 2 bytes of chip memory\n");
	return 1;
    }
    silent_buffer.data[0] = silent_buffer.data[1] = 0;
    silent_buffer.size = 2;
    silent_buffer.nbytes = 2;
    silent_buffer.period = 1;
    silent_buffer.volume = 0;
    silent_buffer.repeat = 0;

    handfeed_buffer.data = silent_buffer.data;
    handfeed_buffer.size = 2;
    handfeed_buffer.nbytes = 2;
    handfeed_buffer.period = 1;
    handfeed_buffer.volume = 0;
    handfeed_buffer.repeat = 0;

    for (i = 0; i < MAXCHANNELS; ++i)
    {
	/*
	 * Clear and disable channel
	 */
	AMIGA->intena = AINTCLR | audio_channel[i].dmadone;
	AMIGA->intreq = AINTCLR | audio_channel[i].dmadone;
	AMIGA->dmacon =  DMACLR | audio_channel[i].enable;

	audio_channel[i].audio = &AMIGA->audio[i];
	audio_channel[i].audio->buf = silent_buffer.data;
	audio_channel[i].audio->size = 1;
	audio_channel[i].audio->period = 1;
	audio_channel[i].audio->loud = 0;
	audio_channel[i].audio->data = 0;
	if (allocate_channel_buffers(&audio_channel[i]))
	{
	    printf("couldn't allocate channel buffer %d\n", i);

	    /* XXX Need to free previously allocated audio buffers */
	     
	    return 1;
	}

	/* <IMPLEMENT> There should be a timeout here incase */
	/* <IMPLEMENT> the audio hardware is broken */
	while (!(AMIGA->intreqr & audio_channel[i].dmadone))
	    ;

	AMIGA->intreq = AINTCLR | audio_channel[i].dmadone;
    }

    initialized = B_TRUE;

    return 0;
}

static int allocate_channel_buffers(struct audio_channel *ach)
{
    ach->buffer[0].data = AllocMem(AUDIO_BUFFER_SIZE, MEMF_CHIP);
    if (!ach->buffer[0].data)
	return ENOMEM;

    ach->buffer[0].size = AUDIO_BUFFER_SIZE;

    ach->buffer[1].data = AllocMem(AUDIO_BUFFER_SIZE, MEMF_CHIP);
    if (!ach->buffer[1].data)
    {
	FreeMem(ach->buffer[0].data, AUDIO_BUFFER_SIZE);
	return ENOMEM;
    }

    ach->buffer[1].size = AUDIO_BUFFER_SIZE;

    ach->buffer[2].data = AllocMem(AUDIO_BUFFER_SIZE, MEMF_CHIP);
    if (!ach->buffer[2].data)
    {
	FreeMem(ach->buffer[0].data, AUDIO_BUFFER_SIZE);
	FreeMem(ach->buffer[1].data, AUDIO_BUFFER_SIZE);
	return ENOMEM;
    }

    ach->buffer[2].size = AUDIO_BUFFER_SIZE;

    ach->buffer[0].nbytes = ach->buffer[1].nbytes = ach->buffer[2].nbytes = 0;
    ach->playing = ach->loaded = ach->filling = 0;

    return 0;
}

static int audioopen(queue_t *q, dev_t *devp, int flag, int sflag,
		     cred_t *credp)
{
    struct audio_client *acl, *aclp;
    minor_t mindev;
    int s;

    if (q->q_ptr)
	return EBUSY;

    if (sflag == MODOPEN || getminor(*devp))
	return EINVAL;

    acl = kmem_alloc(sizeof *acl, 0);
    if (!acl)
	return ENOMEM;

    s = splaudio();

    if (audioinit())
    {
	splx(s);
	return ENOMEM;
    }

    for (mindev = 1; mindev <= MAXMIN; ++mindev)
    {
	for (aclp = audio_clients; aclp; aclp = aclp->next)
	{
	    if (mindev == aclp->mindev)
		break;
	}

	if (!aclp)
	    break;
    }
    if (mindev > MAXMIN)
    {
	splx(s);
	kmem_free(acl, sizeof *acl);
	return ENXIO;
    }

    acl->flags = 0;
    acl->port = 0;
    acl->volume = BLOUD;
    acl->period = BPER;
    acl->repeat = 0;
    acl->wq = WR(q);
    acl->mindev = mindev;
    *devp = makedevice(getmajor(*devp), mindev);

    q->q_ptr = acl;
    WR(q)->q_ptr = acl;

    acl->next = audio_clients;
    audio_clients = acl;

    splx(s);

    return 0;
}

static int audioclose(queue_t *q)
{
    struct audio_client **aclpp, *acl = (struct audio_client *)q->q_ptr;
    int s = splaudio();

    while (WR(q)->q_first)
    {
	/*
	 * There are still some requests pending;  Wait for drain.
	 */

	acl->flags |= AUD_CCLOSING;
	if (sleep(acl, PCATCH | PZERO))
	    break;
    }

    if (audio_channel[acl->port].client == acl)
	audio_channel[acl->port].client = 0;

    for (aclpp = &audio_clients; *aclpp; aclpp = &(*aclpp)->next)
    {
	if (*aclpp == acl)
	{
	    *aclpp = (*aclpp)->next;
	    break;
	}
    }

    q->q_ptr = 0;
    WR(q)->q_ptr = 0;

    splx(s);

    kmem_free(acl, sizeof *acl);

    return 0;
}

static int audiowput(struct queue *q, mblk_t *mp)
{
    int s = splaudio();

    switch (mp->b_datap->db_type)
    {
    case M_DATA:
	putq(q, mp);
	break;

    case M_IOCTL:
    case M_IOCDATA:
	audioioctl(q, mp);
	break;

    default:
	freemsg(mp);
	break;
    }

    splx(s);

    return 0;
}

static void audioioctl(queue_t *q, mblk_t *mp)
{
    struct iocblk *iocbp = (struct iocblk *)mp->b_rptr;
    struct audio_client *audio_client = (struct audio_client *)q->q_ptr;
    
    if (mp->b_datap->db_type == M_IOCDATA)
    {
	/*
	 * For copyin/copyout failures, just free message.
	 */
	if (((struct copyresp *)mp->b_rptr)->cp_rval)
	{
	    freemsg(mp);
	    return;
	}

	if (!((struct copyresp *)mp->b_rptr)->cp_private)
	{
	    mp->b_datap->db_type = M_IOCACK;
	    freemsg(unlinkb(mp));
	    iocbp->ioc_count = 0;
	    iocbp->ioc_rval = 0;
	    iocbp->ioc_error = 0;
	    putnext(RD(q), mp);
	    return;
	}
    }

    switch (iocbp->ioc_cmd)
    {
    case AUDIO_VOLUME:
	{
	    int *volume;

	    if (mp->b_datap->db_type == M_IOCTL &&
		iocbp->ioc_count == TRANSPARENT)
	    {
		struct copyreq *creq = (struct copyreq *)mp->b_rptr;
		mp->b_datap->db_type = M_COPYIN;
		creq->cq_addr = *(caddr_t *)mp->b_cont->b_rptr;
		mp->b_wptr = mp->b_rptr + sizeof *creq;
		creq->cq_size = sizeof *volume;
		creq->cq_flag = 0;
		creq->cq_private = (mblk_t *)1;
		putnext(RD(q), mp);
		return;
	    }
	    else
	    {
		mblk_t *bp1;
		int i;

		(void) pullupmsg(mp->b_cont, -1);

		if (!mp->b_cont || blklen(mp->b_cont) != sizeof *volume)
		{
		    iocbp->ioc_error = EINVAL;
		    mp->b_datap->db_type = M_IOCNAK;
		    iocbp->ioc_count = 0;
		    putnext(RD(q), mp);
		    break;
		}

		volume = (int *)mp->b_cont->b_rptr;

		mp->b_datap->db_type = M_IOCACK;

		audio_client->volume = *volume;

		bp1 = unlinkb(mp);
		if (bp1)
		    freeb(bp1);
		iocbp->ioc_count = 0;
		putnext(RD(q), mp);

	    }
	    break;
	}

    case AUDIO_PERIOD:
	{
	    int *period;

	    if (mp->b_datap->db_type == M_IOCTL &&
		iocbp->ioc_count == TRANSPARENT)
	    {
		struct copyreq *creq = (struct copyreq *)mp->b_rptr;
		mp->b_datap->db_type = M_COPYIN;
		creq->cq_addr = *(caddr_t *)mp->b_cont->b_rptr;
		mp->b_wptr = mp->b_rptr + sizeof *creq;
		creq->cq_size = sizeof *period;
		creq->cq_flag = 0;
		creq->cq_private = (mblk_t *)1;
		putnext(RD(q), mp);
		return;
	    }
	    else
	    {
		mblk_t *bp1;
		int i;

		(void) pullupmsg(mp->b_cont, -1);

		if (!mp->b_cont || blklen(mp->b_cont) != sizeof *period)
		{
		    iocbp->ioc_error = EINVAL;
		    mp->b_datap->db_type = M_IOCNAK;
		    iocbp->ioc_count = 0;
		    putnext(RD(q), mp);
		    break;
		}

		period = (int *)mp->b_cont->b_rptr;

		mp->b_datap->db_type = M_IOCACK;

		audio_client->period = *period;

		bp1 = unlinkb(mp);
		if (bp1)
		    freeb(bp1);
		iocbp->ioc_count = 0;
		putnext(RD(q), mp);

	    }
	    break;
	}

    case AUDIO_REPEAT:
	{
	    int *repeat;

	    if (mp->b_datap->db_type == M_IOCTL &&
		iocbp->ioc_count == TRANSPARENT)
	    {
		struct copyreq *creq = (struct copyreq *)mp->b_rptr;
		mp->b_datap->db_type = M_COPYIN;
		creq->cq_addr = *(caddr_t *)mp->b_cont->b_rptr;
		mp->b_wptr = mp->b_rptr + sizeof *creq;
		creq->cq_size = sizeof *repeat;
		creq->cq_flag = 0;
		creq->cq_private = (mblk_t *)1;
		putnext(RD(q), mp);
		return;
	    }
	    else
	    {
		mblk_t *bp1;
		int i;

		(void) pullupmsg(mp->b_cont, -1);

		if (!mp->b_cont || blklen(mp->b_cont) != sizeof *repeat)
		{
		    iocbp->ioc_error = EINVAL;
		    mp->b_datap->db_type = M_IOCNAK;
		    iocbp->ioc_count = 0;
		    putnext(RD(q), mp);
		    break;
		}

		repeat = (int *)mp->b_cont->b_rptr;

		mp->b_datap->db_type = M_IOCACK;

		audio_client->repeat = *repeat;

		bp1 = unlinkb(mp);
		if (bp1)
		    freeb(bp1);
		iocbp->ioc_count = 0;
		putnext(RD(q), mp);

	    }
	    break;
	}

    default:
	mp->b_datap->db_type = M_IOCNAK;
	freemsg(unlinkb(mp));
	iocbp->ioc_count = 0;
	iocbp->ioc_rval = 0;
	iocbp->ioc_error = EINVAL;
	putnext(RD(q), mp);
	return;
    }

    mp->b_datap->db_type = M_IOCACK;
    freemsg(unlinkb(mp));
    iocbp->ioc_count = 0;
    iocbp->ioc_rval = 0;
    iocbp->ioc_error = 0;
    putnext(RD(q), mp);
}

static int audiowsrv(queue_t *q)
{
    mblk_t *mp;
    struct audio_client *acl = (struct audio_client *)q->q_ptr;
    int s = splaudio();

    /*
     * Service all requests possible.
     */

    while ((mp = getq(q, mp)))
    {
	if (postaudio(acl, mp))
	{
	    /*
	     * Couldn't service this request so places us on the
	     * audio_clients list and we will be qenabled after
	     * the next write interrupt.
	     */

	    putbq(q, mp);
	    break;
	}

	freemsg(mp);
    }

    splx(s);

    return 0;
}

static int fill_buffer(struct audio_buffer *buffer, signed char *data, int len)
{
    int size = min(len, buffer->size - buffer->nbytes);

    bcopy(data, buffer->data + buffer->nbytes, size);
    buffer->nbytes += size;

    return size;
}

static int postaudio(struct audio_client *acl, mblk_t *mp)
{
    struct audio_channel *ach;

    ach = grab_channel(acl);
    if (!ach)
	return 1;

    if (!ach->filling)
    {
	/*
	 * Find a buffer that we may use for filling
	 */

	ach->filling =
	    !ach->buffer[0].nbytes ? &ach->buffer[0] :
		!ach->buffer[1].nbytes ? &ach->buffer[1] :
		    !ach->buffer[2].nbytes ? &ach->buffer[2] :
			(panic("no fillable audio buffers"),
			 (struct audio_buffer *)0);

	ach->filling->volume = acl->volume;
	ach->filling->period = acl->period;
	ach->filling->repeat = acl->repeat;
    }

    mp->b_rptr +=
	fill_buffer(ach->filling, (signed char *)mp->b_rptr, blklen(mp));

    if (!ach->playing)
    {
	/*
	 * Handfeed first byte and load up the audio hardware to grab
	 * dma registers at next scan line.  This is the only way to
	 * garrantee that we will play the first dma'd audio sound to
	 * a channel.
	 */

	AMIGA->intena = AINTCLR | ach->dmadone; /* Disable interupts */
	AMIGA->dmacon =  DMACLR | ach->enable;  /* Disable DMA for now */
	AMIGA->intreq = AINTCLR | ach->dmadone; /* Clear interupt */

	if (!ach->loaded)
	    ach->loaded = &handfeed_buffer;

	ach->audio->buf = silent_buffer.data;
	ach->audio->size = 1;
	ach->audio->period = 1;
	ach->audio->loud = 0;
	ach->audio->data = 0;

	/*
	 * Busy-wait for hand fed word
	 */

	while (!(AMIGA->intreqr & ach->dmadone))
	    ;

	AMIGA->dmacon =  DMASET | ach->enable;  /* Enable DMA */
	AMIGA->intreq = AINTCLR | ach->dmadone; /* Clear interupt */
	AMIGA->intena = AINTSET | ach->dmadone; /* Enable interupts */
    }

    return mp->b_wptr - mp->b_rptr;
}

static struct audio_channel *grab_channel(struct audio_client *acl)
{
    if (audio_channel[acl->port].client != acl)
    {
	int i;

	for (i = 0; i < MAXCHANNELS; ++i)
	{
	    if (!audio_channel[i].client)
	    {
		/*
		 * This channel is not allocated--Use it.
		 */

		acl->port = i;
		break;
	    }
	}

	if (i >= MAXCHANNELS)
	    return 0;			/* No free channels */

	audio_channel[acl->port].client = acl;
    }

    return &audio_channel[acl->port];
}

static void loadup_dma(struct audio_channel *ach,
		       struct audio_buffer *load_buffer)
{
    ach->audio->buf = load_buffer->data;
    ach->audio->size = load_buffer->nbytes / 2;

    ach->loaded = load_buffer;
}

void audiointr(unsigned int reqbits)
{
    int i;

    if (panicstr)
    {
	/*
	 * The system has paniced; Turn off audio ports.
	 */

	AMIGA->intena = AINTCLR | AIEAUD0 | AIEAUD1 | AIEAUD2 | AIEAUD3;
	AMIGA->dmacon =  DMACLR | DMAAUD0 | DMAAUD1 | DMAAUD2 | DMAAUD3;
	return;
    }

    for (i = 0; i < MAXCHANNELS; ++i)
    {
	/*
	 * If this channel is in use: Check to see if the DMA finished
	 * our last request and post another audio if it has.  If this
	 * queue is empty, remove it from the audio_channel queue,
	 * disable the audio channel and wake up the client if it is
	 * sleeping in close.
	 */

	if (reqbits & (1 << i))
	{
	    struct audio_channel *ach = &audio_channel[i];
	    struct audio_client *acl = ach->client;

	    if (ach->playing && ach->playing != &silent_buffer)
	    {
		ach->audio->period = ach->playing->period;
		ach->audio->loud = ach->playing->volume;
		if (ach->playing->repeat > 0)
		{
		    --ach->playing->repeat;
		    loadup_dma(ach, ach->playing);
		    continue;
		}
		else
		    ach->playing->nbytes = 0;
	    }

	    if (acl)
	    {
		qenable(acl->wq);

		if (!acl->wq->q_first && acl->flags & AUD_CCLOSING)
		{
		    acl->flags &= ~AUD_CCLOSING;
		    wakeup(acl);
		    ach->client = 0;
		}
	    }

	    ach->playing = ach->loaded;

	    if (ach->filling)
	    {
		loadup_dma(ach, ach->filling);
		ach->filling = 0;
		if (acl)
		    qenable(acl->wq);
	    }
	    else
	    {
		if (!ach->playing)
		{
		    struct audio_client *aclp;

		    AMIGA->intena = AINTCLR | ach->dmadone;
		    AMIGA->dmacon =  DMACLR | ach->enable;

		    ach->client = 0;

		    for (aclp = audio_clients; aclp; aclp = aclp->next)
			qenable(aclp->wq);
		}
		else if (ach->playing == &silent_buffer)
		    ach->loaded = 0;
		else
		    loadup_dma(ach, &silent_buffer);
	    }
	}
    }
}

boolean_t play_audio(struct audio_client *client, struct audio_buffer *buffer,
		     boolean_t force)
{
    int s = splaudio();
    
    splx(s);

    return B_TRUE;
}
@EOF

chmod 644 audio.c

echo x - audio.h
cat >audio.h <<'@EOF'
#ifndef _AUDIO_H
#define _AUDIO_H

#define MAXCHANNELS	(4)

#ifdef _KERNEL

#define AUD_CCLOSING	(0x01)		/* client in close */

struct audio_client
{
    int				 flags;
    int				 port;
    unsigned short		 volume;
    unsigned short		 period;
    int				 repeat;
    queue_t			*wq;
    struct audio_client		*next;
    minor_t			 mindev;
};

struct audio_buffer
{
    signed char			*data;	/* chip memory */
    int				 size;	/* size of buffer */
    int				 nbytes; /* number of bytes copied in buffer */
    unsigned short		 volume;
    unsigned short		 period;
    int				 repeat; /* number of interations for wave */
};

struct audio_channel
{
    volatile struct amiga_audio	*audio;
    int				 dmadone; /* AMIGA->intreqr; AUD block done */
    int				 enable; /* AMIGA->dmacon; AUD chan enable */
    struct audio_client		*client; /* Client connected to this channel */
    struct audio_buffer		 buffer[3];
    struct audio_buffer		*playing, *loaded, *filling;
};

#endif /* _KERNEL */

#define AUDIO_BUFFER_SIZE	8192

#define	AUIOC			('U' << 8)
#define AUDIO_VOLUME		(AUIOC | 1)
#define AUDIO_PERIOD		(AUIOC | 2)
#define AUDIO_REPEAT		(AUIOC | 3)


#define BLOUD 63
#define BPER  400

#define	splaudio	spl4

#endif /* _AUDIO_H */
@EOF

chmod 644 audio.h

exit 0