Files
Arquivotheca.SunOS-4.1.4/sys/sbusdev/dbri_driver.c
seta75D ff309bfe1c Init
2021-10-11 18:37:13 -03:00

853 lines
20 KiB
C

#ident "@(#) dbri_driver.c 1.1@(#) Copyright (c) 1991-92 Sun Microsystems, Inc."
/*
* AUDIO/ISDN Chip driver - for ATT T5900FC (DBRI) and MMCODEC
*/
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/debug.h>
#include <machine/intreg.h>
#include <machine/psl.h>
#include <machine/mmu.h>
#include <sundev/mbvar.h>
#include <sun/audioio.h>
#include <sbusdev/audiovar.h>
#include <sbusdev/dbri_reg.h>
#include <sbusdev/mmcodec_reg.h>
#if !defined(STANDALONE)
#include <sbusdev/aclone.h>
#endif
#include <sun/isdnio.h>
#include <sun/dbriio.h>
#include <sbusdev/dbrivar.h>
#include <sys/callout.h> /* XXX - to debug timers not being cancelled */
#include "audiodebug.h"
#if defined(SUN4M_35) && defined(IOMMU)
#include <machine/cpu.h
#include <machine/iommu.h
#endif
/* Local declarations */
dbri_dev_t *Dbri_devices = NULL; /* device ctrlr array */
int Ndbri = 0; /* number of devices found up to now */
int Curdbri = 0; /* current dbri attach unit */
int dbri_identify();
int dbri_attach();
addr_t map_regs();
void report_dev();
/* Declare audio ops vector for DBRI support routines */
struct aud_ops Dbri_audops = {
dbri_close,
dbri_ioctl,
dbri_start,
dbri_stop,
dbri_setflag,
dbri_setinfo,
dbri_queuecmd,
dbri_flushcmd,
};
/*
* Declare ops vector for auto configuration.
* It must be named dbri_ops, since the name is constructed from the
* device name when config writes ioconf.c.
*/
struct dev_ops dbri_ops = {
1, /* revision */
dbri_identify, /* identify routine */
dbri_attach, /* attach routine */
};
/*
* Streams declarations
*/
static struct module_info dbri_modinfo = {
0x6175, /* module ID number */
"dbri", /* module name */
0, /* min packet size accepted */
DBRI_MAXPACKET, /* max packet size accepted */
DBRI_HIWATER, /* hi-water mark */
DBRI_LOWATER, /* lo-water mark */
};
/*
* Queue information structure for read queue
*/
static struct qinit dbri_rinit = {
(int(*)())audio_rput, /* put procedure */
(int(*)())audio_rsrv, /* service procedure */
dbri_open, /* called on startup */
(int(*)())audio_close, /* called on finish */
NULL, /* for 3bnet only */
&dbri_modinfo, /* module information structure */
NULL, /* module statistics structure */
};
/*
* Queue information structure for write queue
*/
static struct qinit dbri_winit = {
(int(*)())audio_wput, /* put procedure */
(int(*)())audio_wsrv, /* service procedure */
NULL, /* called on startup */
NULL, /* called on finish */
NULL, /* for 3bnet only */
&dbri_modinfo, /* module information structure */
NULL, /* module statistics structure */
};
char *dbri_modules[] = {
0,
};
struct streamtab dbri_info = {
&dbri_rinit, /* qinit for read side */
&dbri_winit, /* qinit for write side */
NULL, /* mux qinit for read */
NULL, /* mux qinit for write */
dbri_modules, /* list of modules to be pushed */
};
/*
* Called by match_driver() and add_drv_layer() in autoconf.c. Returns
* TRUE if the given string refers to this driver, else FALSE.
*/
static int
dbri_identify(name)
char *name;
{
if (strncmp(name, "SUNW,DBRI", 9) != 0)
return (FALSE);
switch (name[9]) {
case 'a':
case 'b':
case 'c':
(void) printf("DBRI: Driver does not support DBRI version %c\n",
name[9]);
return (FALSE);
case 'd':
(void) printf("DBRI: driver will work but upgrade your hardware\n");
break; /* okay, found one */
case 'e':
case 'f':
break;
default:
(void) printf("DBRI: Warning Unknown DBRI version '%c'.\n",
name[9]);
break;
}
Ndbri++;
return (TRUE);
} /* dbri_identify */
/*ARGSUSED*/
void
dbri_dont_cache(start, size)
caddr_t start;
int size;
{
#ifdef sun4m
#ifdef SUN4M_35
extern struct iommuinfo iommu_info;
if (iommu_info.ccoher == 0) {
size = mmu_ptob(mmu_btopr(size));
/* Round start down to nearest page start */
start = (caddr_t)((int)start & PAGEMASK);
{
caddr_t end;
for (end = start+size; start <= end; start += MMU_PAGESIZE) {
(void) hat_dontcache((addr_t)start);
}
}
}
#endif SUN4M_35
#else sun4m
/* Round up size to full pages */
size = mmu_ptob(mmu_btopr(size));
/* Round start down to nearest page start */
start = (caddr_t)((int)start & PAGEMASK);
{
caddr_t end;
for (end = start+size; start <= end; start += MMU_PAGESIZE) {
(void) vac_dontcache((addr_t)start);
}
}
#endif /* sun4m */
return;
}
char *
dbri_mode_name(mode)
dbri_mode_t mode;
{
int i;
static struct {
char *name;
dbri_mode_t value;
} mode_names[] = {
{"DBRI_MODE_UNKNOWN", DBRI_MODE_UNKNOWN},
{"DBRI_MODE_XPRNT", DBRI_MODE_XPRNT},
{"DBRI_MODE_HDLC", DBRI_MODE_HDLC},
{"DBRI_MODE_HDLC_D", DBRI_MODE_HDLC_D},
{"DBRI_MODE_SER", DBRI_MODE_SER},
{"DBRI_MODE_FIXED", DBRI_MODE_FIXED},
{0, (dbri_mode_t)0}
};
static char buf[64];
for (i = 0; mode_names[i].name != NULL; ++i) {
if (mode_names[i].value == mode)
return (mode_names[i].name);
}
(void) sprintf(buf, "unknown mode(0x%x)", (unsigned int)mode);
return (buf);
}
char *
dbri_channel_name(channel)
int channel;
{
int i;
for (i = 0; i < (int)DBRI_LAST_CHNL; ++i) {
if (Chan_tab[i].chan == (dbri_chnl_t)channel)
return (Chan_tab[i].name);
}
if ((dbri_chnl_t)i == DBRI_NO_CHNL)
return ("DBRI_NO_CHNL");
if ((dbri_chnl_t)i == DBRI_HOST_CHNL)
return ("DBRI_HOST_CHNL");
return ("unknown channel");
}
static int
dbri_setup_mem_map(driver, size)
dbri_dev_t *driver;
int size;
{
caddr_t tvaddr;
int i, nb;
dprintf("dbri_setup_mem_map: allocating %d bytes\n", size);
size += DBRI_MD_ALIGN;
driver->mem_base = new_kmem_zalloc((u_int) size, KMEM_SLEEP);
if (driver->mem_base == NULL) {
(void) printf("dbri_setup_mem_map: out of memory\n");
return (0);
}
dprintf("dbri_setup_mem_map: mem base is 0x%x\n",
(unsigned int)driver->mem_base);
driver->mem_size = size;
dbri_dont_cache(driver->mem_base, driver->mem_size);
#ifdef sun4m
#ifndef lint /* XXX lint barfs on arg 1 */
driver->sb_addr = (caddr_t)mb_nbmapalloc(dvmamap,
driver->mem_base, driver->mem_size,
MDR_BIGSBUS|MB_CANTWAIT, (func_t)0, (caddr_t)0);
#endif /* lint */
if (driver->sb_addr == (caddr_t)0) {
(void) printf("dbri_setup_mem_map: out of dvma memory\n");
kmem_free(driver->mem_base, (u_int) driver->mem_size);
return (0);
}
dprintf("dbri_setup_mem_map: dvma base is 0x%x\n", driver->sb_addr);
driver->addr_offset = (int)(driver->sb_addr - driver->mem_base);
#else /* sun4m */
driver->addr_offset = 0;
#endif /* sun4m */
dprintf("dbri_setup_mem_map: addr_offset is 0x%x\n",
(unsigned int)driver->addr_offset);
/*
* Map is as follows:
* First is the command queue, then the interrupt queues, then the
* command descriptors for each minor device
*/
tvaddr = driver->mem_base;
driver->cmdqp = (dbri_chip_cmdq_t *)tvaddr;
tvaddr += sizeof (dbri_chip_cmdq_t);
driver->intq.intq_bp = (dbri_intq_t *)tvaddr;
tvaddr += DBRI_NUM_INTQS * sizeof (dbri_intq_t);
ASSERT ((sizeof(dbri_cmd_t) % DBRI_MD_ALIGN) == 0);
dprintf("old tvaddr = 0x%x ",tvaddr);
tvaddr = (caddr_t)roundup((u_int)tvaddr, DBRI_MD_ALIGN);
dprintf("new tvaddr = 0x%x \n",tvaddr);
dprintf("dbri_setup_mem_map: starting cmd's at 0x%x\n",
(unsigned int)tvaddr);
for (i = 0; i < Nstreams; ++i) {
aud_stream_t *asp;
struct aud_cmdlist *list;
asp = &driver->ioc[i].as;
nb = Chan_tab[i].numbufs;
if (nb > 0) {
dbri_cmd_t *pool;
int j;
j = nb * sizeof (dbri_cmd_t);
pool = (dbri_cmd_t *)tvaddr;
tvaddr += j;
bzero((caddr_t)pool, (unsigned)j);
list = &asp->cmdlist;
list->memory = (caddr_t)pool;
list->size = j;
for (j = 0; j < nb; ++j, ++pool) {
pool->cmd.next = list->free;
list->free = &pool->cmd;
}
} else {
struct aud_cmdlist *bchan_list;
aud_stream_t *bchan_asp;
int redundant_channel = TRUE;
/*
* kludge for 56kbps and transparent channels
* so that they point back to the already allocated
* cmdlists of the base B channels.
*/
switch (i) {
case DBRI_TE_B1_56_OUT:
case DBRI_TE_B1_TR_OUT:
bchan_asp =
&driver->ioc[(int)DBRI_TE_B1_OUT].as;
break;
case DBRI_TE_B2_56_OUT:
case DBRI_TE_B2_TR_OUT:
bchan_asp =
&driver->ioc[(int)DBRI_TE_B2_OUT].as;
break;
case DBRI_TE_B1_56_IN:
case DBRI_TE_B1_TR_IN:
bchan_asp = &driver->ioc[(int)DBRI_TE_B1_IN].as;
break;
case DBRI_TE_B2_56_IN:
case DBRI_TE_B2_TR_IN:
bchan_asp = &driver->ioc[(int)DBRI_TE_B2_IN].as;
break;
case DBRI_NT_B1_56_OUT:
case DBRI_NT_B1_TR_OUT:
bchan_asp =
&driver->ioc[(int)DBRI_NT_B1_OUT].as;
break;
case DBRI_NT_B2_56_OUT:
case DBRI_NT_B2_TR_OUT:
bchan_asp =
&driver->ioc[(int)DBRI_NT_B2_OUT].as;
break;
case DBRI_NT_B1_56_IN:
case DBRI_NT_B1_TR_IN:
bchan_asp = &driver->ioc[(int)DBRI_NT_B1_IN].as;
break;
case DBRI_NT_B2_56_IN:
case DBRI_NT_B2_TR_IN:
bchan_asp = &driver->ioc[(int)DBRI_NT_B2_IN].as;
break;
default:
/* Do nothing for all other channels */
redundant_channel = FALSE;
break;
}
/*
* If this is a transparent or 56kbps channel then
* point back to the command lists of the base
* B channels. Otherwise just zero out the cmdlist
* pointers.
*/
list = &asp->cmdlist;
if (redundant_channel) {
bchan_list = &bchan_asp->cmdlist;
list->memory = bchan_list->memory;
list->size = bchan_list->size;
list->free = bchan_list->free;
continue;
} else {
list->memory = NULL;
list->size = 0;
}
}
} /* for */
dprintf("dbri_setup_mem_map: ending tvaddr is 0x%x\n",
(unsigned int)tvaddr);
return (1);
} /* dbri_setup_mem_map */
/*
* Attach to the device.
*/
static int
dbri_attach(dev)
struct dev_info *dev;
{
int nb, total_nb;
dbri_dev_t *driver;
int i, tbytes;
extern struct aud_ops Dbri_audops;
extern dbri_chan_tab_t Chan_tab[];
extern void mmcodec_startup_audio();
ATRACEINIT();
ATRACEPRINT("dbri0: debugging version of audio driver\n");
/*
* Each unit has a 'aud_state_t' that contains generic audio
* device state information. Also, each unit has a
* 'dbri_dev_t' that contains device-specific data.
*/
ASSERT(dev != NULL);
ASSERT(Ndbri > 0);
/*
* Sanity check the configuration tables.
*/
for (i = 0; i < (int)DBRI_LAST_CHNL; ++i) {
if (Chan_tab[i].chan != (dbri_chnl_t)i) {
printf("dbri: %s: Chan_tab[%d].chan = %d\n",
((Chan_tab[i].chan)
? "Invalid entry"
: "FYI"),
i, Chan_tab[i].chan);
if (Chan_tab[i].chan)
return (-1);
}
}
for (i = 0; i < (int)ISDN_LAST_PORT; ++i) {
if (Port_tab[i].port != (isdn_port_t)i) {
if ((isdn_port_t)i == ISDN_PRI_MGT)
continue;
printf("dbri: %s: Port_tab[%d].port = %d\n",
((Port_tab[i].port == ISDN_PORT_NONE)
? "Invalid entry"
: "FYI"),
i, Port_tab[i].port);
if (Port_tab[i].port)
return (-1);
}
}
#ifdef sun4c
if (slaveslot(dev->devi_reg->reg_addr) >= 0) {
printf("%s in slave slot will not work.\n",
dev->devi_name);
return (-1);
}
#endif sun4c
dev->devi_unit = Curdbri;
dprintf("dbri_attach: attaching device %d\n", Curdbri);
if (Dbri_devices == NULL) {
Dbri_devices = ((dbri_dev_t *)
new_kmem_zalloc((u_int)
(Ndbri * sizeof (dbri_dev_t)),
KMEM_SLEEP));
}
/*
* Identify the audio device and assign a unit number.
* Get the address of this unit's audio device state structure.
*/
driver = &Dbri_devices[Curdbri];
ASSERT(driver != NULL);
driver->hwi_state.devdata = (void *)driver;
driver->hwi_state.monitor_gain = 0;
driver->hwi_state.output_muted = FALSE;
driver->hwi_state.audio_spl = 0; /* unnecessary for dbri */
driver->dbri_dev = dev;
driver->dbri_version = dev->devi_name[9]; /* XXX DIC */
#if !defined(STANDALONE)
{
int status;
status = aclone_register((caddr_t)&dbri_info,
1 + Curdbri, /* Base for dbri is 1 */
(int)DBRI_MINOR_AUDIO_RW + Nstreams,
(int)DBRI_MINOR_AUDIO_RO + Nstreams,
(int)DBRI_MINOR_AUDIOCTL + Nstreams);
if (status < 0)
printf("dbri: Could not register with aclone!\n");
}
#endif /* STANDALONE */
total_nb = 0;
/* Initialize the play info struct */
for (i = 0; i < Nstreams; ++i) {
aud_stream_t *asp;
asp = &driver->ioc[i].as;
ASSERT(asp != NULL);
asp->v = &driver->hwi_state;
asp->info.minordev = (int)Chan_tab[i].minordev;
asp->record_as = &driver->ioc[(int)Chan_tab[i].in_as].as;
asp->play_as = &driver->ioc[(int)Chan_tab[i].out_as].as;
asp->control_as = &driver->ioc[(int)Chan_tab[i].control_as].as;
asp->info.sample_rate = Chan_tab[i].samplerate;
asp->info.channels = Chan_tab[i].channels;
asp->info.precision = Chan_tab[i].len; /* precision */
asp->info.encoding = AUDIO_ENCODING_ULAW; /* XXX work this */
asp->info.gain = AUDIO_MAX_GAIN;
asp->info.balance = AUDIO_MID_BALANCE;
asp->type = Chan_tab[i].audtype;
AsToDs(asp)->pipe = DBRI_BAD_PIPE;
AsToDs(asp)->recv_eol = FALSE;
AsToDs(asp)->i_info.type = Chan_tab[i].isdntype;
switch (i) { /* adjust special channels */
case DBRI_AUDIO_OUT:
asp->info.port = AUDIO_SPEAKER;
/* XXX - avail_ports needs to be adjusted for Sunergy*/
asp->info.avail_ports =
AUDIO_SPEAKER|AUDIO_HEADPHONE|AUDIO_LINE_OUT;
asp->info.gain = DBRI_DEFAULT_GAIN;
break;
case DBRI_AUDIO_IN:
asp->info.port = AUDIO_MICROPHONE;
/* XXX - avail_ports needs to be adjusted for Sunergy*/
asp->info.avail_ports = AUDIO_MICROPHONE|AUDIO_LINE_IN;
asp->info.gain = DBRI_DEFAULT_GAIN;
break;
case DBRI_AUDIOCTL:
break;
case DBRI_TE_D_IN:
case DBRI_TE_D_OUT:
case DBRI_NT_D_IN:
case DBRI_NT_D_OUT:
asp->info.encoding = AUDIO_ENCODING_NONE;
break;
default:
/*
* Nothing here currently.
* Would like to have additional channels added
* in dbri_conf.c to be sufficiently defined.
*/
break;
} /* switch */
/*
* Initialize virtual chained DMA command block free
* lists. Record channels receive a different number of
* buffers than play chanels.
*/
asp->input_size = Chan_tab[i].input_size;
AsToDs(asp)->i_var.t101 = Default_T101_timer;
AsToDs(asp)->i_var.t102 = Default_T102_timer;
AsToDs(asp)->i_var.t103 = Default_T103_timer;
AsToDs(asp)->i_var.t104 = Default_T104_timer;
AsToDs(asp)->i_var.asmb = Default_asmb;
AsToDs(asp)->i_var.power = Default_power;
nb = Chan_tab[i].numbufs;
total_nb += nb;
} /* for */
tbytes = sizeof (dbri_chip_cmdq_t);
tbytes += DBRI_NUM_INTQS * sizeof (dbri_intq_t);
tbytes += total_nb * sizeof (dbri_cmd_t);
(void) dbri_setup_mem_map(driver, tbytes);
dprintf("dbri_attach: intq = 0x%x cmdq = 0x%x\n",
(unsigned int)driver->intq.intq_bp,
(unsigned int)driver->cmdqp);
dprintf("dbri_attach: serialp = 0x%x\n",
(unsigned int)&driver->ser_sts);
/*
* Init the ops vector and back-pointers to the audio struct
*/
driver->hwi_state.ops = &Dbri_audops;
/*
* Map in the registers for this device.
*/
driver->chip = (dbri_reg_t *)map_regs(dev->devi_reg->reg_addr,
dev->devi_reg->reg_size, dev->devi_reg->reg_bustype);
#if 1 /* XXX */
{
int oldlevel = dev->devi_intr->int_pri;
switch (dev->devi_name[9]) {
case 'a':
panic("dbri: Found version 'a' where it couldn't be.");
break;
case 'c':
#ifdef sun4m
dev->devi_intr->int_pri = 57; /* XXX - C2 Specific */
#endif
break;
default:
break;
}
if (oldlevel != dev->devi_intr->int_pri) {
printf("dbri: Changed int_pri from %d to %d!!!\n",
oldlevel, dev->devi_intr->int_pri);
}
}
#endif /* XXX3 */
/*
* Add the interrupt for this device.
*/
driver->intrlevel = ipltospl(dev->devi_intr->int_pri);
if (Curdbri == 0) {
dprintf("dbri_attach: before addintr dev->pri=0x%x ilev=0x%x\n",
(unsigned int)dev->devi_intr->int_pri,
(unsigned int)driver->intrlevel);
addintr(dev->devi_intr->int_pri, dbri_intr, dev->devi_name,
dev->devi_unit);
adddma(dev->devi_intr->int_pri);
}
report_dev(dev);
dbri_initchip(driver); /* Initialize the chip */
/* Allow users to use this unit */
driver->openinhibit = FALSE;
/* Start up audio functionality */
mmcodec_startup_audio(driver);
Curdbri++; /* Attach succeeded, increment unit number counter. */
return (0);
} /* dbri_attach */
#if defined(DBRI_LOADABLE)
#include <sys/conf.h>
#include <sun/autoconf.h>
#include <sun/vddrv.h>
struct cdevsw dbri_cdevsw = {
/* open, close, read, write, */
0, 0, 0, 0,
/* ioctl, reset, select, mmap, */
0, 0, 0, 0,
/* str, segmap */
&dbri_info, 0,
};
extern char dbri_version[];
/*
* struct vdldrv is different in sun4m and sun4c, -Dsun4m or -Dsun4c is
* required for proper compilation. The new sun4m fields are at the end of
* the structure and are therefore initialized to zeros here.
*/
struct vdldrv Dbri_driver = {
VDMAGIC_DRV, /* Drv_magic */
dbri_version, /* Drv_name */
&dbri_ops, /* Drv_dev_ops */
NULL, /* Drv_bdevsw */
&dbri_cdevsw, /* Drv_cdevsw */
0, /* Drv_blockmajor, 0 means let system choose */
0, /* Drv_charmajor, 0 means let system choose */
};
/*
* dbri_init - Entry point routine, modload/unload support.
*/
int
dbri_init(function_code, vdp, vdi, vds)
unsigned function_code;
struct vddrv *vdp;
addr_t vdi;
struct vdstat *vds;
{
int status;
int dbri_unattach();
#ifdef lint
(void) dbri_init(0, vdp, vdi, vds);
#endif
switch (function_code) {
case VDLOAD:
vdp->vdd_vdtab = (struct vdlinkage*)&Dbri_driver;
dprintf("dbri_init: loading driver\n");
Ndbri = 0;
Curdbri = 0;
Dbri_devices = NULL;
return (0);
case VDUNLOAD:
if (Dbri_devices == NULL)
return (0);
#if !defined(STANDALONE)
/*
* Removing audioclone mappings
*/
aclone_unregister((caddr_t)&dbri_info);
#endif /* STANDALONE */
status = dbri_unattach(vdp);
return (status);
case VDSTAT:
return (0);
}
return (EINVAL);
} /* dbri_init */
/*ARGSUSED*/
static int
dbri_unattach(vdp)
struct vddrv *vdp;
{
dbri_dev_t *driver;
int busy = 0;
int i, dev;
int s;
/*
* Cycle through devices and if any minor device is open then the
* driver cannot be unloaded.
*/
for (dev = 0; dev < Ndbri; ++dev) {
driver = &Dbri_devices[dev];
s = splr(driver->intrlevel);
for (i = 0; i < Nstreams; ++i) {
aud_stream_t *asp;
asp = &driver->ioc[i].as;
if (asp->info.open != 0)
++busy;
}
if (busy) {
(void) splx(s);
return (EBUSY);
}
/* Set DBRI back to initial state */
dbri_initchip(driver);
remintr(driver->dbri_dev->devi_intr->int_pri, dbri_intr);
(void) splx(s);
} /* for */
#if 1 /* XXX */
{
struct callout *p1,
*p2;
extern void mmcodec_watchdog();
char *fn = 0;
/* already spl'ed above clock? */
for (p1 = &calltodo; (p2 = p1->c_next) != 0; p1 = p2) {
if (p2->c_func == (int(*)())dbri_keep_alive) {
fn = "dbri_keep_alive";
} else if (p2->c_func == (int(*)())dbri_nt_timer) {
fn = "dbri_nt_timer";
} else if (p2->c_func == (int(*)())dbri_te_timer) {
fn = "dbri_te_timer";
} else if (p2->c_func == (int(*)())mmcodec_watchdog) {
fn = "mmcodec_watchdog";
} else {
continue;
}
dprintf("dbri: callout queue = %s(0x%x) time=%d.\n",
fn, (unsigned int)p2->c_arg, p2->c_time);
untimeout(p2->c_func, p2->c_arg); /* XXX */
}
}
#endif
/* cycle through devices releasing memory */
for (dev = 0; dev < Ndbri; ++dev) {
driver = &Dbri_devices[dev];
/*
* Deallocate per minor device resources.
*/
dprintf("dbri_init: deallocating memory\n");
#ifdef sun4m
mb_mapfree(dvmamap, &driver->sb_addr);
#endif /* sun4m */
kmem_free(driver->mem_base, (u_int) driver->mem_size);
}
/* Now release main driver structure */
kmem_free((caddr_t)Dbri_devices, (u_int) (Ndbri * sizeof (dbri_dev_t)));
Ndbri = 0;
Dbri_devices = NULL;
return (0);
}
#endif /* DBRI_LOADABLE */