/*
 * snd/sound.c
 * 
 * Main sound driver for FreeBSD. This file provides the main
 * entry points for probe/attach and all i/o demultiplexing, including
 * default routines for generic devices.
 * 
 * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it)
 * 
 * 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.
 *
 *
 * For each card type a template "snddev_info" structure contains
 * all the relevant parameters, both for configuration and runtime.
 *
 * In this file we build tables of pointers to the descriptors for
 * the various supported cards. The generic probe routine scans
 * the table(s) looking for a matching entry, then invokes the
 * board-specific probe routine. If successful, a pointer to the
 * correct snddev_info is stored in snddev_last_probed, for subsequent
 * use in the attach routine. The generic attach routine copies
 * the template to a permanent descriptor (pcm_info[unit] and
 * friends), initializes all generic parameters, and calls the
 * board-specific attach routine.
 *
 * On device calls, the generic routines do the checks on unit and
 * device parameters, then call the board-specific routines if
 * available, or try to perform the task using the default code.
 *
 */

#include "opt_devfs.h"

#include <i386/isa/snd/sound.h>
#ifdef DEVFS
#include <sys/devfsext.h>
#endif /* DEVFS */


#if NPCM > 0	/* from "pcm.h" via disgusting #include in snd/sound.h */

extern struct isa_driver pcmdriver ;
#define SNDSTAT_BUF_SIZE        4000
static char status_buf[SNDSTAT_BUF_SIZE] ;
static int status_len = 0 ;
static void init_status(snddev_info *d);

static d_open_t sndopen;
static d_close_t sndclose;
static d_ioctl_t sndioctl;
static d_read_t sndread;
static d_write_t sndwrite;
static d_mmap_t sndmmap;

#define CDEV_MAJOR 30
static struct cdevsw snd_cdevsw = {
	sndopen, sndclose, sndread, sndwrite,
	sndioctl, nostop, noreset, nodevtotty,
	sndselect, sndmmap, nostrategy, "snd",
	NULL, -1,
};

/*
 * descriptors for active devices.
 *
 */
snddev_info pcm_info[NPCM_MAX] ;
snddev_info midi_info[NPCM_MAX] ;
snddev_info synth_info[NPCM_MAX] ;

u_long nsnd = NPCM ;	/* total number of sound devices */

/*
 * Hooks for APM support, but code not operational yet.
 */

#include "apm.h"
#include <i386/include/apm_bios.h>
#if NAPM > 0

static int
sound_suspend(void *arg)
{
	/*
	 * I think i can safely do nothing here and
	 * reserve all the work for wakeup time
	 */
	printf("Called APM sound suspend hook for unit %d\n", (int)arg);
	return 0 ;
}

static int
sound_resume(void *arg)
{
    snddev_info *d = NULL ;

    d = &pcm_info[(int)arg] ;
	/*
	 * reinitialize card registers.
	 * Flush buffers and reinitialize DMA channels.
	 * If a write was pending, pretend it is done
	 * (and issue any wakeup we need).
	 * If a read is pending, restart it.
	 */
    if (d->bd_id == MD_YM0020) {
	DDB(printf("setting up yamaha registers\n"));
	outb(0x370, 6 /* dma config */ ) ;
	if (FULL_DUPLEX(d))
	    outb(0x371, 0xa9 ); /* use both dma chans */
	else
	    outb(0x371, 0x8b ); /* use low dma chan */
    }
	printf("Called APM sound resume hook for unit %d\n", (int)arg);
	return 0 ;
}

static void
init_sound_apm(int unit)
{
	struct apmhook *ap;

        ap = malloc(sizeof *ap, M_DEVBUF, M_NOWAIT);
        bzero(ap, sizeof *ap);

	ap->ah_fun = sound_resume;
	ap->ah_arg = (void *)unit;
	ap->ah_name = "pcm resume handler";
	ap->ah_order = APM_MID_ORDER;
	apm_hook_establish(APM_HOOK_RESUME, ap);

        ap = malloc(sizeof *ap, M_DEVBUF, M_NOWAIT);
        bzero(ap, sizeof *ap);

	ap->ah_fun = sound_suspend;
	ap->ah_arg = (void *)unit;
	ap->ah_name = "pcm suspend handler";
	ap->ah_order = APM_MID_ORDER;
	apm_hook_establish(APM_HOOK_SUSPEND, ap);
}
#endif /* NAPM */

/*
 * the probe routine can only return an int to the upper layer. Hence,
 * it leaves the pointer to the last successfully
 * probed device descriptor in snddev_last_probed
 */
snddev_info *snddev_last_probed = NULL ;

static snddev_info *
generic_snd_probe(struct isa_device * dev, snddev_info **p[], char *s);

/*
 * here are the lists of known cards. Similar cards (e.g. all
 * sb clones, all mss clones, ... are in the same array.
 * All lists of devices of the same type (eg. all pcm, all midi...)
 * are in the same array.
 * Each probe for a device type gets the pointer to the main array
 * and then scans the sublists.
 *
 * XXX should use DATA_SET to create a linker set for sb_devs and other
 * such structures.
 */

extern snddev_info sb_op_desc;
extern snddev_info mss_op_desc;

static snddev_info *sb_devs[] = {	/* all SB clones	 */
    &sb_op_desc,
    NULL,
} ;

static snddev_info *mss_devs[] = {	/* all MSS clones	*/
    &mss_op_desc,
    NULL,
} ;

static snddev_info **pcm_devslist[] = {	/* all pcm devices	*/
    mss_devs,
    sb_devs,
    NULL
} ;


int
pcmprobe(struct isa_device * dev)
{
    bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) );
    return generic_snd_probe(dev, pcm_devslist, "pcm") ? 1 : 0 ;
}

static snddev_info **midi_devslist[] = {/* all midi devices	*/
    NULL
} ;

int
midiprobe(struct isa_device * dev)
{
    bzero(&midi_info[dev->id_unit], sizeof(midi_info[dev->id_unit]) );
    return 0 ;
    return generic_snd_probe(dev, midi_devslist, "midi") ? 1 : 0 ;
}

int
synthprobe(struct isa_device * dev)
{
    bzero(&synth_info[dev->id_unit], sizeof(synth_info[dev->id_unit]) );
    return 0 ;
}

/*
 * this is the generic attach routine
 */

int
pcmattach(struct isa_device * dev)
{
    snddev_info *d = NULL ;
    struct isa_device *dvp;
    int stat = 0;
    dev_t isadev;
    void *cookie;

    dev->id_ointr = pcmintr;

    if ( (dev->id_unit >= NPCM_MAX) ||		/* too many devs	*/
	 (snddev_last_probed == NULL) ||	/* last probe failed	*/
	 (snddev_last_probed->attach==NULL) )	/* no attach routine	*/
	return 0 ; /* fail */

    /*
     * default initialization: copy generic parameters for the routine,
     * initialize from the isa_device structure, and allocate memory.
     * If everything succeeds, then call the attach routine for
     * further initialization.
     */
    pcm_info[dev->id_unit] = *snddev_last_probed ;
    d = &pcm_info[dev->id_unit] ;

    d->io_base = dev->id_iobase ;
    d->irq = ffs(dev->id_irq) - 1 ;
    d->dbuf_out.chan = dev->id_drq ;
    if (dev->id_flags != -1 && dev->id_flags & DV_F_DUAL_DMA &&
	    (dev->id_flags & DV_F_DRQ_MASK) != 4 ) /* enable dma2 */
	d->dbuf_in.chan = dev->id_flags & DV_F_DRQ_MASK ;
    else
	d->dbuf_in.chan = d->dbuf_out.chan ;
#if 1 /* does this cause trouble with PnP cards ? */
    if (d->bd_id == 0)
	d->bd_id = (dev->id_flags & DV_F_DEV_MASK) >> DV_F_DEV_SHIFT ;
#endif
    d->status_ptr = 0;

    /*
     * Allocates memory and initializes the dma structs properly. We
     * use independent buffers for each channel.  For the time being,
     * this is done independently of the dma setting. In future
     * revisions, if we see that we have a single dma, we might decide
     * to use a single buffer to save memory.
     */
    alloc_dbuf( &(d->dbuf_out), d->bufsize );
    alloc_dbuf( &(d->dbuf_in), d->bufsize );

    isa_dma_acquire(d->dbuf_out.chan);
    if (FULL_DUPLEX(d))
	isa_dma_acquire(d->dbuf_in.chan);
    /*
     * initialize standard parameters for the device. This can be
     * overridden by device-specific configurations but better do
     * here the generic things.
     */

    d->play_speed = d->rec_speed = 8000 ;
    d->play_blocksize = d->rec_blocksize = 2048 ;
    d->play_fmt = d->rec_fmt = AFMT_MU_LAW ;

    isadev = makedev(CDEV_MAJOR, 0);
    cdevsw_add(&isadev, &snd_cdevsw, NULL);

#ifdef DEVFS
#ifndef GID_GAMES
#define GID_SND UID_ROOT
#else
#define GID_SND GID_GAMES /* i am not really sure this is a good one. */
#endif
#define UID_SND UID_ROOT
#define PERM_SND 0660
    /*
     * XXX remember to store the returned tokens if you want to
     * be able to remove the device later
     *
     * Make links to first successfully probed unit.
     * Attempts by later devices to make these links will fail.
     */
    cookie = devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_DSP,
	DV_CHR, UID_SND, GID_SND, PERM_SND, "dsp%r", dev->id_unit);
    if (cookie) devfs_link(cookie, "dsp");

    cookie = devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_DSP16,
	DV_CHR, UID_SND, GID_SND, PERM_SND, "dspW%r", dev->id_unit);
    if (cookie) devfs_link(cookie, "dspW");

    cookie = devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_AUDIO,
	DV_CHR, UID_SND, GID_SND, PERM_SND, "audio%r", dev->id_unit);
    if (cookie) devfs_link(cookie, "audio");

    cookie = devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_CTL,
	DV_CHR, UID_SND, GID_SND, PERM_SND, "mixer%r", dev->id_unit);
    if (cookie) devfs_link(cookie, "mixer");

    cookie = devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_STATUS,
	DV_CHR, UID_SND, GID_SND, PERM_SND, "sndstat%r", dev->id_unit);
    if (cookie) devfs_link(cookie, "sndstat");

#if 0 /* these two are still unsupported... */
    cookie = devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_MIDIN,
	DV_CHR, UID_SND, GID_SND, PERM_SND, "midi%r", dev->id_unit);
    if (cookie) devfs_link(cookie, "midi");

    cookie = devfs_add_devswf(&snd_cdevsw, (dev->id_unit << 4) | SND_DEV_SYNTH,
	DV_CHR, UID_SND, GID_SND, PERM_SND, "sequencer%r", dev->id_unit);
    if (cookie) devfs_link(cookie, "sequencer");
#endif
#endif /* DEVFS */

    /*
     * should try and find a suitable value for id_id, otherwise
     * the interrupt is not registered and dispatched properly.
     * This is important for PnP devices, where "dev" is built on
     * the fly and many field are not initialized.
     */
    if (dev->id_driver == NULL) {
	dev->id_driver = &pcmdriver ;
	dvp=find_isadev(isa_devtab_tty, &pcmdriver, 0);
	if (dvp)
	    dev->id_id = dvp->id_id;
    }

    d->magic = MAGIC(dev->id_unit); /* debugging... */
    /*
     * and finally, call the device attach routine
     * XXX I should probably use d->attach(dev)
     */
    stat = snddev_last_probed->attach(dev);
#if 0
    /*
     * XXX hooks for synt support. Try probe and attach...
     */
    if (d->synth_base && opl3_probe(dev) ) {
	opl3_attach(dev);
    }
#endif
    snddev_last_probed = NULL ;

#if NAPM > 0
    init_sound_apm(dev->id_unit);
#endif
    return stat ;
}

int midiattach(struct isa_device * dev) { return 0 ; }
int synthattach(struct isa_device * dev) { return 0 ; }

struct isa_driver pcmdriver = { pcmprobe, pcmattach, "pcm" } ;

struct isa_driver mididriver = { midiprobe, midiattach, "midi" } ;
struct isa_driver synthdriver = { synthprobe, synthattach, "synth" } ;

void
pcmintr(int unit)
{
    DEB(printf("__/\\/ pcmintr -- unit %d\n", unit));
    pcm_info[unit].interrupts++;
    if (pcm_info[unit].isr)
	pcm_info[unit].isr(unit);
#if 0 /* these do not exist at the moment. */
    if (midi_info[unit].isr)
	midi_info[unit].isr(unit);
    if (synth_info[unit].isr)
	synth_info[unit].isr(unit);
#endif
}

static snddev_info *
generic_snd_probe(struct isa_device * dev, snddev_info **p[], char *s)
{
    snddev_info **q ;
    struct isa_device saved_dev ;

    snddev_last_probed = NULL ;

    saved_dev = *dev ; /* the probe routine might alter parameters */

    /*
     * XXX todo: should try to match flags with device type.
     */
    for ( ; p[0] != NULL ; p++ )
	for ( q = *p ; q[0] ; q++ )
	    if (q[0]->probe && q[0]->probe(dev))
		return (snddev_last_probed = q[0]) ;
	    else
		*dev = saved_dev ;

    return NULL ;
}


/*
 * a small utility function which, given a device number, returns
 * a pointer to the associated snddev_info struct, and sets the unit
 * number.
 */
static snddev_info *
get_snddev_info(dev_t dev, int *unit)
{
    int u;
    snddev_info *d = NULL ;

    dev = minor(dev);
    u = dev >> 4 ;
    if (unit)
	*unit = u ;

    if (u >= NPCM_MAX ||
	( pcm_info[u].io_base == 0 && (dev & 0x0f) != SND_DEV_STATUS)) {
	int i;
	for (i = 0 ; i < NPCM_MAX ; i++)
	    if (pcm_info[i].io_base)
		break ;
	if (i != NPCM_MAX) 
	    printf("pcm%d: unit not configured, perhaps you want pcm%d ?\n",
		 u, i);
	else
	    printf("no pcm units configured\b");
	return NULL ;
    }
    switch(dev & 0x0f) {
    case SND_DEV_CTL :	/* /dev/mixer handled by pcm */
    case SND_DEV_STATUS : /* /dev/sndstat handled by pcm */
    case SND_DEV_SNDPROC :	/* /dev/sndproc handled by pcm */
    case SND_DEV_DSP :
    case SND_DEV_DSP16 :
    case SND_DEV_AUDIO :
    case SND_DEV_SEQ : /* XXX when enabled... */
	d = & pcm_info[u] ;
	break ;
    case SND_DEV_SEQ2 :
    case SND_DEV_MIDIN:
    default:
	printf("unsupported subdevice %d\n", dev & 0xf);
	return NULL ;
    }
    return d ;
}

/*
 * here are the switches for the main functions. The switches do
 * all necessary checks on the device number to make sure
 * that the device is configured. They also provide some default
 * functionalities so that device-specific drivers have to deal
 * only with special cases.
 */

static int
sndopen(dev_t i_dev, int flags, int mode, struct proc * p)
{
    int dev, unit ;
    snddev_info *d;

    dev = minor(i_dev);
    d = get_snddev_info(dev, &unit);

    DEB(printf("open snd%d subdev %d flags 0x%08x mode 0x%08x\n",
	unit, dev & 0xf, flags, mode));

    if (d == NULL)
	return (ENXIO) ;

    switch(dev & 0x0f) {
    case SND_DEV_SEQ:	/* sequencer. Hack... */
#if 0 /* XXX hook for opl3 support */
	if (d->synth_base)
	    return opl3_open(i_dev, flags, mode, p);
	else
#endif
	    return ENXIO ;

    case SND_DEV_CTL : /* mixer ... */
	return 0 ;	/* always succeed */

    case SND_DEV_STATUS : /* implemented right here */
	init_status(&pcm_info[unit]);
	d->status_ptr = 0 ;
	return 0 ;

    default:
	if (d->open == NULL) {
	    printf("open: unit %d not configured, perhaps you want unit %d ?\n",
		unit, unit+1 );
	    return (ENXIO) ;
	} else
	    return d->open(i_dev, flags, mode, p);
    }
    return ENXIO ;
}

static int
sndclose(dev_t i_dev, int flags, int mode, struct proc * p)
{
    int dev, unit ;
    snddev_info *d;

    dev = minor(i_dev);
    d = get_snddev_info(dev, &unit);

    DEB(printf("close snd%d subdev %d\n", unit, dev & 0xf));

    if (d == NULL)
	return (ENXIO) ;

    switch(dev & 0xf) { /* only those for which close makes sense */
    case SND_DEV_SEQ:
#if 0	/* XXX hook for opl3 support */
	if (d->synth_base)
		return opl3_close(i_dev, flags, mode, p);
	else
#endif
		return ENXIO ;

    case SND_DEV_AUDIO :
    case SND_DEV_DSP :
    case SND_DEV_DSP16 :
	if (d->close)
	    return d->close(i_dev, flags, mode, p);
    }
    return 0 ;
}

static int
sndread(dev_t i_dev, struct uio * buf, int flag)
{
    int ret, dev, unit;
    snddev_info *d ;
    u_long s;

    dev = minor(i_dev);

    d = get_snddev_info(dev, &unit);
    DEB(printf("read snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag));

    if (d == NULL)
	return ENXIO ;

    if ( (dev & 0x0f) == SND_DEV_STATUS ) {
	int l, c;
	u_char *p;

	l = buf->uio_resid;
	s=spltty();
	c = status_len - d->status_ptr ;
	if (c < 0) /* should not happen! */
	    c = 0 ;
	if (c < l)
	    l = c ;
	p = status_buf + d->status_ptr ;
	d->status_ptr += l ;
	splx(s);
	return uiomove(p, l, buf) ;
    }

    if (d->read)	/* device-specific read */
	return d->read(i_dev, buf, flag);

    /*
     * the generic read routine. device-specific stuff should only
     * be in the dma-handling procedures.
     */
    s = spltty();
    if ( d->flags & SND_F_READING ) {
        /* another reader is in, deny request */
        splx(s);
	DDB(printf("read denied, another reader is in\n"));
	/*
	 * sleep for a while to avoid killing the machine.
	 */
	tsleep( (void *)s, PZERO, "sndar", hz ) ;
        return EBUSY ;
    }
    if ( ! FULL_DUPLEX(d) ) {           /* half duplex */
        if ( d->flags & SND_F_WRITING ) {
            /* another writer is in, deny request */
            splx(s);
	    DDB(printf("read denied, half duplex and a writer is in\n"));
	    tsleep( (void *)s, PZERO, "sndaw", hz ) ;
            return EBUSY ;
        }
        while ( d->dbuf_out.dl ) {
	    /*
	     * we have a pending dma operation, post a read request
	     * and wait for the write to complete.
	     */
            d->flags |= SND_F_READING ;
            DEB(printf("sndread: sleeping waiting for write to end\n"));
            ret = tsleep( (caddr_t)&(d->dbuf_out),
                 PRIBIO | PCATCH , "sndrdw", hz ) ;
            if (ret == ERESTART || ret == EINTR) {
                d->flags &= ~SND_F_READING ;
                splx(s);
                return EINTR ;
            }
        }
    }
    d->flags |= SND_F_READING ;
    splx(s);
    
    return dsp_read_body(d, buf);
}

static int
sndwrite(dev_t i_dev, struct uio * buf, int flag)
{
    int ret, dev, unit;
    snddev_info *d;
    u_long s;

    dev = minor(i_dev);
    d = get_snddev_info(dev, &unit);

    DEB(printf("write snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag));

    if (d == NULL)
	return (ENXIO) ;

    switch( dev & 0x0f) {	/* only writeable devices */
    case SND_DEV_MIDIN:	/* XXX is this writable ? */
    case SND_DEV_SEQ :
    case SND_DEV_SEQ2 :
    case SND_DEV_DSP :
    case SND_DEV_DSP16 :
    case SND_DEV_AUDIO :
	break ;
    default:
	return EPERM ; /* for non-writeable devices ; */
    }
    if (d->write)
	return d->write(i_dev, buf, flag);

    /*
     * Otherwise, use the generic write routine. device-specific
     * stuff should only be in the dma-handling procedures.
     */

    s = spltty();
    if ( d->flags & SND_F_WRITING ) {
        /* another writer is in, deny request */
        splx(s);
	DDB(printf("write denied, another writer is in\n"));
	tsleep( (void *)s, PZERO , "sndaw", hz ) ;
        return EBUSY ;
    }
    if ( ! FULL_DUPLEX(d) ) {           /* half duplex */
        if ( d->flags & SND_F_READING ) {
            /* another reader is in, deny request */
            splx(s);
	    DDB(printf("write denied, half duplex and a reader is in\n"));
	    tsleep( (void *)s, PZERO, "sndar", hz ) ;
            return EBUSY ;
        }
        while ( d->dbuf_in.dl ) {
	    /*
	     * we have a pending read dma. Post a write request
	     * and wait for the read to complete (in fact I could
	     * abort the read dma...
	     */
            d->flags |= SND_F_WRITING ;
            DEB(printf("sndwrite: sleeping waiting for read to end\n"));
            ret = tsleep( (caddr_t)&(d->dbuf_out),
                 PRIBIO | PCATCH , "sndwr", hz ) ;
            if (ret == ERESTART || ret == EINTR) {
                d->flags &= ~SND_F_WRITING ;
                splx(s);
                return EINTR ;
            }
        }
    }
    d->flags |= SND_F_WRITING ;
    splx(s);
    
    return dsp_write_body(d, buf);
}

/*
 * generic sound ioctl. Functions of the default driver can be
 * overridden by the device-specific ioctl call.
 * If a device-specific call returns ENOSYS (Function not implemented),
 * the default driver is called. Otherwise, the returned value
 * is passed up.
 *
 * The default handler, for many parameters, sets the value in the
 * descriptor, sets SND_F_INIT, and calls the callback function with
 * reason INIT. If successful, the callback returns 1 and the caller
 * can update the parameter.
 */

static int
sndioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc * p)
{
    int ret = ENOSYS, dev, unit ;
    snddev_info *d;
    u_long s;

    dev = minor(i_dev);
    d = get_snddev_info(dev, &unit);

    if (d == NULL)
	return (ENXIO) ;

    if ( (dev & 0x0f) == SND_DEV_SEQ ) {
	/* sequencer. Hack... */
#if 0
	if (d->synth_base)
	    return opl3_ioctl(i_dev, cmd, arg, mode, p) ;
	else
#endif
	    return ENXIO ;
    }
    if (d->ioctl)
	ret = d->ioctl(dev, cmd, arg, mode, p);
    if (ret != ENOSYS)
	return ret ;

    /*
     * pass control to the default ioctl handler. Set ret to 0 now.
     */
    ret = 0 ;

    /*
     * The linux ioctl interface for the sound driver has a thousand
     * different calls, and it is unpractical to put the names in
     * the switch().  So we have some tests before for common routines,
     * such as the ones related to the mixer.  But we really ought
     * to redesign the interface!
     *
     * Reading from the mixer just requires to look at the cached
     * copy in d->mix_levels[dev], so this routine should cover
     * practically all needs for mixer reading.
     */
    if ( (cmd & MIXER_READ(0)) == MIXER_READ(0) && (cmd & 0xff) < 32 ) {
	int dev = cmd & 0x1f ;
	if ( d->mix_devs & (1<<dev) ) { /* supported */
	    *(int *)arg = d->mix_levels[dev];
	    return 0 ;
	} else
	    return EINVAL ;
    }

    /*
     * all routines are called with int. blocked. Make sure that
     * ints are re-enabled when calling slow or blocking functions!
     */
    s = spltty();
    switch(cmd) {

    /*
     * we start with the new ioctl interface.
     */
    case AIONWRITE :	/* how many bytes can write ? */
	if (d->dbuf_out.dl)
	    dsp_wr_dmaupdate(&(d->dbuf_out));
	*(int *)arg = d->dbuf_out.fl;
	break;

    case AIOSSIZE :     /* set the current blocksize */
	{
	    struct snd_size *p = (struct snd_size *)arg;
	    if (p->play_size <= 1 && p->rec_size <= 1) { /* means no blocks */
		d->flags &= ~SND_F_HAS_SIZE ;
	    } else {
		RANGE (p->play_size, 40, d->dbuf_out.bufsize /4);
		d->play_blocksize = p->play_size  & ~3 ;
		RANGE (p->rec_size, 40, d->dbuf_in.bufsize /4);
		d->rec_blocksize = p->rec_size  & ~3 ;
		d->flags |= SND_F_HAS_SIZE ;
	    }
	}
	splx(s);
	ask_init(d);
	/* FALLTHROUGH */
    case AIOGSIZE :	/* get the current blocksize */
	{
	    struct snd_size *p = (struct snd_size *)arg;
	    p->play_size = d->play_blocksize ;
	    p->rec_size = d->rec_blocksize ;
	}
	break ;

    case AIOSFMT :
	{
	    snd_chan_param *p = (snd_chan_param *)arg;
	    d->play_speed = p->play_rate;
	    d->rec_speed = p->play_rate; /* XXX one speed allowed */
	    if (p->play_format & AFMT_STEREO)
		d->flags |= SND_F_STEREO ;
	    else
		d->flags &= ~SND_F_STEREO ;
	    d->play_fmt = p->play_format & ~AFMT_STEREO ;
	    d->rec_fmt = p->rec_format & ~AFMT_STEREO ;
	}
	splx(s);
	if (!ask_init(d))
	    break ; /* could not reinit */
	/* FALLTHROUGH */

    case AIOGFMT :
	{
	    snd_chan_param *p = (snd_chan_param *)arg;
	    p->play_rate = d->play_speed;
	    p->rec_rate = d->rec_speed;
	    p->play_format = d->play_fmt;
	    p->rec_format = d->rec_fmt;
	    if (d->flags & SND_F_STEREO) {
		p->play_format |= AFMT_STEREO ;
		p->rec_format |= AFMT_STEREO ;
	    }
	}
	break;

    case AIOGCAP :     /* get capabilities */
	/* this should really be implemented by the driver */
	{
	    snd_capabilities *p = (snd_capabilities *)arg;
	    p->rate_min = 5000;
	    p->rate_max = 48000; /* default */
	    p->bufsize = d->bufsize;
	    p->formats = d->audio_fmt; /* default */
	    p->mixers = 1 ; /* default: one mixer */
	    p->inputs = d->mix_devs ;
	    p->left = p->right = 255 ;
	}
	break ;

    case AIOSTOP:
	if (*(int *)arg == AIOSYNC_PLAY) /* play */
	    *(int *)arg = dsp_wrabort(d, 1 /* restart */);
	else if (*(int *)arg == AIOSYNC_CAPTURE)
	    *(int *)arg = dsp_rdabort(d, 1 /* restart */);
	else {
	    splx(s);
	    printf("AIOSTOP: bad channel 0x%x\n", *(int *)arg);
	    *(int *)arg = 0 ;
	}
	break ;

    case AIOSYNC:
	printf("AIOSYNC chan 0x%03lx pos %d unimplemented\n",
	    ((snd_sync_parm *)arg)->chan,
	    ((snd_sync_parm *)arg)->pos);
	break;
    /*
     * here follow the standard ioctls (filio.h etc.)
     */
    case FIONREAD : /* get # bytes to read */
	if ( d->dbuf_in.dl )
	    dsp_rd_dmaupdate(&(d->dbuf_in));
	*(int *)arg = d->dbuf_in.rl;
	break;

    case FIOASYNC: /*set/clear async i/o */
	DEB( printf("FIOASYNC\n") ; )
	break;

    case SNDCTL_DSP_NONBLOCK :
    case FIONBIO : /* set/clear non-blocking i/o */
	if ( *(int *)arg == 0 )
	    d->flags &= ~SND_F_NBIO ;
	else
	    d->flags |= SND_F_NBIO ;
	break ;

    /*
     * Finally, here is the linux-compatible ioctl interface
     */
#define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int)
    case THE_REAL_SNDCTL_DSP_GETBLKSIZE:
    case SNDCTL_DSP_GETBLKSIZE:
	*(int *) arg = d->play_blocksize ;
	break ;

    case SNDCTL_DSP_SETBLKSIZE :
	{
	    int t = *(int *)arg;
	    if (t <= 1) { /* means no blocks */
		d->flags &= ~SND_F_HAS_SIZE ;
	    } else {
		RANGE (t, 40, d->dbuf_out.bufsize /4);
		d->play_blocksize =
		d->rec_blocksize = t  & ~3 ; /* align to multiple of 4 */
		d->flags |= SND_F_HAS_SIZE ;
	    }
	}
	splx(s);
	ask_init(d);
	break ;
    case SNDCTL_DSP_RESET:
	DEB(printf("dsp reset\n"));
	dsp_wrabort(d, 1 /* restart */);
	dsp_rdabort(d, 1 /* restart */);
	break ;

    case SNDCTL_DSP_SYNC:
	DEB(printf("dsp sync\n"));
	splx(s);
	snd_sync(d, 1, d->dbuf_out.bufsize - 4); /* DMA does not start with <4 bytes */
	break ;

    case SNDCTL_DSP_SPEED:
	d->play_speed = d->rec_speed = *(int *)arg ;
	splx(s);
	if (ask_init(d))
	    *(int *)arg = d->play_speed ;
	break ;

    case SNDCTL_DSP_STEREO:
	if ( *(int *)arg == 0 )
	    d->flags &= ~SND_F_STEREO ; /* mono */
	else if ( *(int *)arg == 1 )
	    d->flags |= SND_F_STEREO ; /* stereo */
	else {
	    printf("dsp stereo: %d is invalid, assuming 1\n", *(int *)arg );
	    d->flags |= SND_F_STEREO ; /* stereo */
	}
	splx(s);
	if (ask_init(d))
	    *(int *)arg = (d->flags & SND_F_STEREO) ? 1 : 0 ;
	break ;

    case SOUND_PCM_WRITE_CHANNELS:
	if ( *(int *)arg == 1)
	    d->flags &= ~SND_F_STEREO ; /* mono */
	else if ( *(int *)arg == 2)
	    d->flags |= SND_F_STEREO ; /* stereo */
	else {
	    ret = EINVAL ;
	    break ;
	}
	splx(s);
	if (ask_init(d))
	    *(int *)arg = (d->flags & SND_F_STEREO) ? 2 : 1 ;
	break ;

    case SOUND_PCM_READ_RATE:
	*(int *)arg = d->play_speed;
	break ;

    case SOUND_PCM_READ_CHANNELS:
	*(int *)arg = (d->flags & SND_F_STEREO) ? 2 : 1;
	break ;

    case SNDCTL_DSP_GETFMTS:	/* returns a mask of supported fmts */
	*(int *)arg = (int)d->audio_fmt ;
	break ;

    case SNDCTL_DSP_SETFMT:	/* sets _one_ format */
	/*
	 * when some card (SB16) is opened RDONLY or WRONLY,
	 * only one of the fields is set, the other becomes 0.
	 * This makes it possible to select DMA channels at runtime.
	 */
	if (d->play_fmt)
	    d->play_fmt = *(int *)arg ;
	if (d->rec_fmt)
	    d->rec_fmt = *(int *)arg ;
	splx(s);
	if (ask_init(d))
	    *(int *)arg = d->play_fmt ;
	break ;

    case SNDCTL_DSP_SUBDIVIDE:
	/* XXX watch out, this is RW! */
	DEB(printf("SNDCTL_DSP_SUBDIVIDE yet unimplemented\n");)
	break;

    case SNDCTL_DSP_SETFRAGMENT:
	/* XXX watch out, this is RW! */
	DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg));
	{
	    int bytes, count;
	    bytes = *(int *)arg & 0xffff ;
	    count = ( *(int *)arg >> 16) & 0xffff ;
	    if (bytes > 15)
		bytes = 15 ;
	    bytes = 1 << bytes ;
	    if (bytes <= 1) { /* means no blocks */
		d->flags &= ~SND_F_HAS_SIZE ;
	    } else {
		RANGE (bytes, 40, d->dbuf_out.bufsize /4);
		d->play_blocksize =
		d->rec_blocksize = bytes  & ~3 ; /* align to multiple of 4 */
		d->flags |= SND_F_HAS_SIZE ;
	    }
	    splx(s);
	    ask_init(d);
#if 0
	    /* XXX todo: set the buffer size to the # of fragments */
	    count = d->dbuf_in.bufsize / d->play_blocksize ;
	    bytes = ffs(d->play_blocksize) - 1;
	    /*
	     * don't change arg, since it's fake anyways and some
	     * programs might fail if we do.
	     */
	    *(int *)arg = (count << 16) | bytes ;
#endif
	}
	break ;

    case SNDCTL_DSP_GETISPACE:
	/* return space available in the input queue */
	{
	    audio_buf_info *a = (audio_buf_info *)arg;
	    snd_dbuf *b = &(d->dbuf_in);
	    if (b->dl)
		dsp_rd_dmaupdate( b );
	    a->bytes = d->dbuf_in.fl ;
	    a->fragments = 1 ;
	    a->fragstotal = b->bufsize / d->rec_blocksize ;
	    a->fragsize = d->rec_blocksize ;
	}
	break ;

    case SNDCTL_DSP_GETOSPACE:
	/* return space available in the output queue */
	{
	    audio_buf_info *a = (audio_buf_info *)arg;
	    snd_dbuf *b = &(d->dbuf_out);
	    if (b->dl)
		dsp_wr_dmaupdate( b );
	    a->bytes = d->dbuf_out.fl ;
	    a->fragments = 1 ;
	    a->fragstotal = b->bufsize / d->play_blocksize ;
	    a->fragsize = d->play_blocksize ;
	}
	break ;

    case SNDCTL_DSP_GETIPTR:
	{
	    count_info *a = (count_info *)arg;
	    snd_dbuf *b = &(d->dbuf_in);
	    if (b->dl)
		dsp_rd_dmaupdate( b );
	    a->bytes = b->total;
	    a->blocks = (b->total - b->prev_total +
		    d->rec_blocksize -1 ) / d->rec_blocksize ;
	    a->ptr = b->fp ; /* XXX not sure... */
	    b->prev_total = b->total ;
	}
	break;

    case SNDCTL_DSP_GETOPTR:
	{
	    count_info *a = (count_info *)arg;
	    snd_dbuf *b = &(d->dbuf_out);
	    if (b->dl)
		dsp_wr_dmaupdate( b );
	    a->bytes = b->total;
	    a->blocks = (b->total - b->prev_total
		    /* +d->play_blocksize -1*/ ) / d->play_blocksize ;
	    a->ptr = b->rp ; /* XXX not sure... */
	    b->prev_total = b->total ;
	}
	break;

    case SNDCTL_DSP_GETCAPS :
	*(int *) arg = 0x0 ; /* revision */
	if (FULL_DUPLEX(d))
		*(int *) arg |= DSP_CAP_DUPLEX ;
	*(int *) arg |= DSP_CAP_REALTIME ;
	break ;

    case SOUND_PCM_READ_BITS:
	if (d->play_fmt == AFMT_S16_LE)
	    *(int *) arg = 16 ;
	else
	    *(int *) arg = 8 ;
	break ;

    /*
     * mixer calls
     */

    case SOUND_MIXER_READ_DEVMASK :
    case SOUND_MIXER_READ_CAPS :
    case SOUND_MIXER_READ_STEREODEVS :
	*(int *)arg = d->mix_devs;
	break ;

    case SOUND_MIXER_READ_RECMASK :
	*(int *)arg = d->mix_rec_devs;
	break ;

    case SOUND_MIXER_READ_RECSRC :
	*(int *)arg = d->mix_recsrc ;
	break;

    default:
	DEB(printf("default ioctl snd%d subdev %d fn 0x%08x fail\n",
	    unit, dev & 0xf, cmd));
	ret = EINVAL;
	break ;
    }
    splx(s);
    return ret ;
}

/*
 * we use the name 'select', but the new "poll" interface this is
 * really sndpoll. Second arg for poll is not "rw" but "events"
 */
int
sndselect(dev_t i_dev, int rw, struct proc * p)
{
    int dev, unit, c = 1 /* default: success */ ;
    snddev_info *d ;
    u_long flags;

    dev = minor(i_dev);
    d = get_snddev_info(dev, &unit);
    DEB(printf("sndselect dev 0x%04x rw 0x%08x\n",i_dev, rw));
    if (d == NULL ) /* should not happen! */
	return (ENXIO) ;
    if (d->select == NULL)
        return ( (rw & (POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM)) | POLLHUP);
    else if (d->select != sndselect )
	return d->select(i_dev, rw, p);
    else {
	/* handle it here with the generic code */
	/*
	 * if the user selected a block size, then we want to use the
	 * device as a block device, and select will return ready when
	 * we have a full block.
	 * In all other cases, select will return when 1 byte is ready.
	 */
	int lim = 1;

	int revents = 0 ;
	if (rw & (POLLOUT | POLLWRNORM) ) {
	    if ( d->flags & SND_F_HAS_SIZE )
		lim = d->play_blocksize ;
	    /* XXX fix the test here for half duplex devices */
	    if (1 /* write is compatible with current mode */) {
		flags = spltty();
		if (d->dbuf_out.dl)
		    dsp_wr_dmaupdate(&(d->dbuf_out));
		c = d->dbuf_out.fl ;
		if (c < lim) /* no space available */
		    selrecord(p, & (d->wsel));
		else
		    revents |= rw & (POLLOUT | POLLWRNORM);
		splx(flags);
	    }
        }
        if (rw & (POLLIN | POLLRDNORM)) {
	    if ( d->flags & SND_F_HAS_SIZE )
		lim = d->rec_blocksize ;
	    /* XXX fix the test here */
	    if (1 /* read is compatible with current mode */) {
		flags = spltty();
		if ( d->dbuf_in.dl == 0 ) /* dma idle, restart it */
		    dsp_rdintr(d);
		else
		    dsp_rd_dmaupdate(&(d->dbuf_in));
		c = d->dbuf_in.rl ;
		if (c < lim) /* no data available */
		    selrecord(p, & (d->rsel));
		else
		    revents |= rw & (POLLIN | POLLRDNORM);
		splx(flags);
	    }
	    DEB(printf("sndselect on read: %d >= %d flags 0x%08x\n",
		c, lim, d->flags));
	    return c < lim ? 0 : 1 ;
	}
	return revents;
    }
    return ENXIO ; /* notreached */
}

/*
 * The mmap interface allows access to the play and read buffer,
 * plus the device descriptor.
 * The various blocks are accessible at the following offsets:
 *
 * 0x00000000 ( 0   ) : write buffer ;
 * 0x01000000 (16 MB) : read buffer ;
 * 0x02000000 (32 MB) : device descriptor (dangerous!)
 *
 * WARNING: the mmap routines assume memory areas are aligned. This
 * is true (probably) for the dma buffers, but likely false for the
 * device descriptor. As a consequence, we do not know where it is
 * located in the requested area.
 */
#include <sys/mman.h>
#include <vm/vm.h>      
#include <vm/vm_kern.h> 
#include <vm/vm_param.h>
#include <vm/pmap.h>    
#include <vm/vm_extern.h>

static int
sndmmap(dev_t dev, vm_offset_t offset, int nprot)
{
    snddev_info *d = get_snddev_info(dev, NULL);

    DEB(printf("sndmmap d 0x%p dev 0x%04x ofs 0x%08x nprot 0x%08x\n",
	d, dev, offset, nprot));
    
    if (d == NULL || nprot & PROT_EXEC)
	return -1 ; /* forbidden */

    if (offset >= d->dbuf_out.bufsize && (nprot & PROT_WRITE) )
	return -1 ; /* can only write to the first block */

    if (offset < d->dbuf_out.bufsize)
	return i386_btop(vtophys(d->dbuf_out.buf + offset));
    offset -= 1 << 24;
    if ( (offset >= 0) && (offset < d->dbuf_in.bufsize))
	return i386_btop(vtophys(d->dbuf_in.buf + offset));
    offset -= 1 << 24;
    if ( (offset >= 0) && (offset < 0x2000)) {
	return i386_btop(vtophys( ((int)d & ~0xfff) + offset));
    }
    return -1 ;
}


/*
 * ask_init sets the init flag in the device descriptor, and
 * possibly calls the appropriate callback routine, returning 1
 * if the callback was successful. This enables ioctls handler for
 * rw parameters to read back the updated value.
 * Since the init callback can be slow, ask_init() should be called
 * with interrupts enabled.
 */

int
ask_init(snddev_info *d)
{
    u_long s;

    if ( d->callback == NULL )
	return 0 ;
    s = spltty();
    if ( d->flags & SND_F_PENDING_IO ||
	 d->dbuf_out.dl || d->dbuf_in.dl ) {
	/* cannot do it now, record the request and return */
	d->flags |= SND_F_INIT ;
	splx(s);
	return 0 ;
    } else {
	splx(s);
	d->callback(d, SND_CB_INIT );
	return 1;
    }
}

/*
 * these are the functions for the soundstat device. We copy parameters
 * from the device info structure to static variables, and from there
 * back to the structure when done.
 */

static void
init_status(snddev_info *d)
{
    /*
     * Write the status information to the status_buf and update
     * status_len. There is a limit of SNDSTAT_BUF_SIZE bytes for the data.
     */

    int             i;

    if (status_len != 0) /* only do init once */
	return ;
    sprintf(status_buf,
	"FreeBSD Audio Driver (981002) "  __DATE__ " " __TIME__ "\n"
	"Installed devices:\n");

    for (i = 0; i < NPCM_MAX; i++) {
        if (pcm_info[i].open)
            sprintf(status_buf + strlen(status_buf),
		"pcm%d: <%s> at 0x%x irq %d dma %d:%d\n",
		i, pcm_info[i].name, pcm_info[i].io_base,
		pcm_info[i].irq,
		pcm_info[i].dbuf_out.chan, pcm_info[i].dbuf_in.chan);
        if (midi_info[i].open)
            sprintf(status_buf + strlen(status_buf),
		"midi%d: <%s> at 0x%x irq %d dma %d:%d\n",
		i, midi_info[i].name, midi_info[i].io_base,
		midi_info[i].irq,
		midi_info[i].dbuf_out.chan, midi_info[i].dbuf_in.chan);
        if (pcm_info[i].synth_base) {
	    char *s = "???";
	    switch (pcm_info[i].synth_type) {
	    case 2 : s = "OPL2"; break;
	    case 3 : s = "OPL3"; break;
	    case 4 : s = "OPL4"; break;
	    }

            sprintf(status_buf + strlen(status_buf),
		"sequencer%d: <%s> at 0x%x (not functional)\n",
		i, s, pcm_info[i].synth_base);
	}
    }
    status_len = strlen(status_buf) ;
}

/*
 * finally, some "libraries"
 */

/*
 * isa_dmastatus1() is a wrapper for isa_dmastatus(), which
 * might return -1 or -2 in some cases (errors). Since for the
 * user code it is more comfortable not to check for these cases,
 * negative values are mapped back to 0 (which is reasonable).
 */

int
isa_dmastatus1(int channel)
{
    int r = isa_dmastatus(channel);
    if (r<0) r = 0;
    return r;
}

/*
 * snd_conflict scans already-attached boards to see if
 * the current address is conflicting with one of the already
 * assigned ones. Returns 1 if a conflict is detected.
 */
int
snd_conflict(int io_base)
{
    int i;
    for (i=0; i< NPCM_MAX ; i++) {
        if ( (io_base == pcm_info[i].io_base  ) ||
             (io_base == pcm_info[i].alt_base ) ||
             (io_base == pcm_info[i].conf_base) ||
             (io_base == pcm_info[i].mix_base ) ||
             (io_base == pcm_info[i].midi_base) ||
             (io_base == pcm_info[i].synth_base) ) {
            BVDDB(printf("device at 0x%x already attached as unit %d\n",
                io_base, i);)
            return 1 ;
        }
    }
    return 0;
}

void
snd_set_blocksize(snddev_info *d)
{
    int tmp ;
    /*
     * compute the sample size, and possibly
     * set the blocksize so as to guarantee approx 1/4s
     * between callbacks.
     */
    tmp = 1 ;
    if (d->flags & SND_F_STEREO) tmp += tmp;
    if (d->play_fmt & (AFMT_S16_LE|AFMT_U16_LE)) tmp += tmp;
    d->dbuf_out.sample_size = tmp ;
    tmp = tmp * d->play_speed;
    if ( (d->flags & SND_F_HAS_SIZE) == 0) {
	d->play_blocksize = (tmp / 4) & ~3; /* 0.25s, aligned to 4 */
	RANGE (d->play_blocksize, 1024, (d->bufsize / 4) & ~3);
    }

    tmp = 1 ;
    if (d->flags & SND_F_STEREO) tmp += tmp;
    if (d->rec_fmt & (AFMT_S16_LE|AFMT_U16_LE)) tmp += tmp;
    tmp = tmp * d->rec_speed;
    d->dbuf_in.sample_size = tmp ;
    if ( (d->flags & SND_F_HAS_SIZE) == 0) {
	d->rec_blocksize = (tmp / 4) & ~3; /* 0.25s, aligned to 4 */
	RANGE (d->rec_blocksize, 1024, (d->bufsize / 4) & ~3);
    }
}

/*
 * The various mixers use a variety of bitmasks etc. The Voxware
 * driver had a very nice technique to describe a mixer and interface
 * to it. A table defines, for each channel, which register, bits,
 * offset, polarity to use. This procedure creates the new value
 * using the table and the old value.
 */

void
change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval)
{
    u_char mask;
    int shift;

    DEB(printf("ch_bits dev %d ch %d val %d old 0x%02x "
	"r %d p %d bit %d off %d\n",
	dev, chn, newval, *regval,
	(*t)[dev][chn].regno, (*t)[dev][chn].polarity,
	(*t)[dev][chn].nbits, (*t)[dev][chn].bitoffs ) );

    if ( (*t)[dev][chn].polarity == 1)	/* reverse */
	newval = 100 - newval ;

    mask = (1 << (*t)[dev][chn].nbits) - 1;
    newval = (int) ((newval * mask) + 50) / 100; /* Scale it */
    shift = (*t)[dev][chn].bitoffs /*- (*t)[dev][LEFT_CHN].nbits + 1*/;

    *regval &= ~(mask << shift);        /* Filter out the previous value */
    *regval |= (newval & mask) << shift;        /* Set the new value */
}


/*
 * code for translating between U8 and ULAW. Needed to support
 * /dev/audio on the SoundBlaster. Actually, we would also need
 * ulaw -> 16 bits (for the soundblaster as well, when used in
 * full-duplex)
 */

#if 1
void
translate_bytes (u_char *table, u_char *buff, int n)
{
    u_long   i;

    if (n <= 0)
	return;
  
    for (i = 0; i < n; ++i)
	buff[i] = table[buff[i]];
}
#else
/* inline */
void
translate_bytes (const void *table, void *buff, int n)
{     
    if (n > 0) { 
	__asm__ (  "   cld\n"
		   "1: lodsb\n"
		   "   xlatb\n"
		   "   stosb\n"
		   "   loop 1b\n":
	    : "b" (table), "c" (n), "D" (buff), "S" (buff)
	    : "bx", "cx", "di", "si", "ax");
    }
}   

#endif

#endif	/* NPCM > 0 */
