📄 sound.c
字号:
/* * 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 "snd.h" */#define SNDSTAT_BUF_SIZE 4000static 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 30static struct cdevsw snd_cdevsw = { sndopen, sndclose, sndread, sndwrite, sndioctl, nxstop, nxreset, nxdevtotty, sndselect, sndmmap, nxstrategy, "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 > 0static intsound_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 intsound_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 voidinit_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} ;intpcmprobe(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} ;intmidiprobe(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 ;}intsynthprobe(struct isa_device * dev){ bzero(&synth_info[dev->id_unit], sizeof(synth_info[dev->id_unit]) ); return 0 ;}/* * this is the generic attach routine */intpcmattach(struct isa_device * dev){ snddev_info *d = NULL ; struct isa_device *dvp; int stat = 0; dev_t isadev; void *cookie; 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#define GID_SND UID_ROOT /* GID_GAMES */#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%n", 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%n", 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%n", 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%n", 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%n", 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%n", 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%n", 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" } ;voidpcmintr(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 intsndopen(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 ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -