/* * Copyright (c) 1999 The Santa Cruz Operation, Inc.. All Rights Reserved. * * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF THE * SANTA CRUZ OPERATION INC. * * The copyright notice above does not evidence any actual or intended * publication of such source code. */ #ident "@(#)unixsrc:usr/src/i386at/ihvkit/ddi8_sample/ddi8_sample.c /main/hdk_nj/2" /* * DDI 8 Sample Driver for CMOS RAM * * Supports both an uncached read/write model and an ioctl model * for access to CMOS RAM data. * * A device like this wouldn't really need multiple channels, * but this driver uses two channels as an example of dealing * with multiple channels. The second channel causes a 5 msec * delay before each CMOS RAM access. */ #define _DDI 8 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For CMOSREAD/CMOSWRITE ioctls */ /* * Miscellaneous manifest constants. */ #define SAMP_MAXCHAN 1 /* Max supported channel number */ #define SAMP_DEVSIZE 0x40 /* Size, in bytes, of device */ #define SAMP_MINWRITE 0x0E /* First writable byte */ #define SAMP_NPORTS 2 /* * Need to import driver name from Space.c, since administrator * may change it. */ extern const char samp_modname[]; /* * Driver-internal structures. * These are allocated as needed per instance. * There is no global state. */ typedef struct { uint_t chan_flags; /* ... */ } samp_channel_t; /* Flag values for chan_flags. */ #define CHAN_OPEN (1 << 0) typedef struct { samp_channel_t chandata[SAMP_MAXCHAN + 1]; uint_t open_channel_count; /* # open channels */ uint_t in_progress; /* # I/Os in progress */ int base_port; /* First I/O port */ int cfg_state; /* Current configuration state */ lock_t *mutex; /* Mutex lock */ sv_t *sv; /* Synchronization variable */ /* .. */ } samp_instance_t; /* Specific ports. */ #define ADDR_PORT(inst) ((inst)->base_port) #define DATA_PORT(inst) ((inst)->base_port + 1) /* Configuration states for cfg_state. */ #define ACTIVE 0 #define SUSPENDED 1 #define REMOVED 2 /* Lock information structure. */ STATIC LKINFO_DECL(samp_lkinfo, "Sample driver mutex lock", 0); /* Buffer Control Block for all instances. */ static bcb_t *samp_bcb; /* * Prototype declarations for forward references. */ static int samp_config(cfg_func_t func, void *idata, rm_key_t key); static int samp_open(void *idata, channel_t *channelp, int oflags, cred_t *crp, queue_t *q); static int samp_close(void *idata, channel_t channel, int oflags, cred_t *crp, queue_t *q); static int samp_devinfo(void *idata, channel_t channel, di_parm_t parm, void *valp); static void samp_biostart(void *idata, channel_t channel, buf_t *bp); static int samp_ioctl(void *idata, channel_t channel, int cmd, void *arg, int oflags, cred_t *crp, int *rvalp); static int samp_get_config(rm_key_t key, samp_instance_t *samp_idata); static int samp_start_io(samp_instance_t *samp_idata); static void samp_end_io(samp_instance_t *samp_idata); static uchar_t samp_read_cmos(samp_instance_t *samp_idata, channel_t channel, uchar_t addr); static void samp_write_cmos(samp_instance_t *samp_idata, channel_t channel, uchar_t addr, uchar_t val); static void samp_free_instance(samp_instance_t *samp_idata); static void samp_free_globals(void); /* * Driver information structures, for drv_attach(). */ static const drvops_t samp_ops = { samp_config, samp_open, samp_close, samp_devinfo, samp_biostart, samp_ioctl, NULL, /* drvctl */ NULL /* mmap */ }; static const drvinfo_t samp_drvinfo = { &samp_ops, samp_modname, D_MP|D_HOT|D_RANDOM, /* MP-safe, full hot-plug, random access */ NULL, /* Not a STREAMS driver */ SAMP_MAXCHAN /* Must match $maxchan in Node file */ }; /* * _load() entry point is where it all starts. * This, and _unload(), should be the only global symbols. */ int _load(void) { int err; /* * This is a good place for one-time global allocations * and initializations. However, these should be rare, * as most state is per-instance. Preferably, any global * data should be read-only once initialized. * * For this driver, we need to set up a breakup control * block to govern subsequent I/O. */ samp_bcb = bcb_alloc(KM_SLEEP); samp_bcb->bcb_addrtypes = BA_UIO; samp_bcb->bcb_granularity = 1; samp_bcb->bcb_physreqp = physreq_alloc(KM_SLEEP); if (!bcb_prep(samp_bcb, KM_SLEEP)) { /* * The constraints we asked for can't be satisfied. * This shouldn't happen, but just in case... */ samp_free_globals(); return ENOSYS; } /* * Tell the system it can now use us as a driver, * by registering our driver info with drv_attach(). * * Everything else happens once the system starts telling * us about device instances, in samp_config(). This may * happen even before returning from drv_attach(). */ if ((err = drv_attach(&samp_drvinfo)) != 0) samp_free_globals(); return err; } /* * _unload() entry point is called prior to unloading. * This will not be called if any channels are open. */ int _unload(void) { /* * Detach from the driver subsystem. This will have the * side effect of removing all of our driver instances * (see samp_config()). Until the return from drv_detach(), * other entry points may be called, so don't free global * resources yet. */ drv_detach(&samp_drvinfo); /* * Free global allocations. * per channel and per instance data should have already * been deallocated as a result of close and CFG_REMOVE calls */ samp_free_globals(); return 0; } /* * config() entry point is used to tell the driver about new instances, * changes in instance state, and instances to be removed. * * It can also be used to verify that a candidate device is in fact * a device supported by the driver. This sample driver, however, * having no hardware, does not implement CFG_VERIFY. */ static int samp_config(cfg_func_t func, void *idata, rm_key_t key) { samp_instance_t *samp_idata = idata; ASSERT(getpl() == plbase); switch (func) { case CFG_ADD: /* Add a new instance. */ /* Allocate and initialize a new instance structure. */ samp_idata = kmem_zalloc(sizeof (samp_instance_t), KM_SLEEP); /* * As a side effect of the kmem_zalloc, the following * initializations happen implicitly: * * samp_idata->cfg_state = ACTIVE; * samp_idata->open_channel_count = 0; * samp_idata->in_progress = 0; */ /* Get some useful info from the resmgr key. */ if (samp_get_config(key, samp_idata) != 0) { kmem_free(samp_idata, sizeof (samp_instance_t)); return EINVAL; } /* Allocate synchronization objects for this instance. */ samp_idata->mutex = LOCK_ALLOC(1, pltimeout, &samp_lkinfo, KM_SLEEP); samp_idata->sv = SV_ALLOC(KM_SLEEP); /* Pass back idata pointer for subsequent entries. */ *(void **)idata = samp_idata; return 0; case CFG_SUSPEND: /* Suspend activity for an instance. */ ASSERT(samp_idata->cfg_state == ACTIVE); (void) LOCK(samp_idata->mutex, pltimeout); samp_idata->cfg_state = SUSPENDED; /* Wait for I/O in progress to complete. */ while (samp_idata->in_progress) { SV_WAIT(samp_idata->sv, primed, samp_idata->mutex); (void) LOCK(samp_idata->mutex, pltimeout); } UNLOCK(samp_idata->mutex, plbase); return 0; case CFG_MODIFY: /* Modify parameters of a suspended instance. */ ASSERT(samp_idata->cfg_state == SUSPENDED); if (samp_get_config(key, samp_idata) != 0) return EINVAL; return 0; case CFG_RESUME: /* Resume activity on a suspended instance. */ ASSERT(samp_idata->cfg_state == SUSPENDED); (void) LOCK(samp_idata->mutex, pltimeout); samp_idata->cfg_state = ACTIVE; UNLOCK(samp_idata->mutex, plbase); SV_BROADCAST(samp_idata->sv, 0); return 0; case CFG_REMOVE: /* Remove an instance (suspended or not). */ (void) LOCK(samp_idata->mutex, pltimeout); samp_idata->cfg_state = REMOVED; if (samp_idata->open_channel_count != 0) { SV_BROADCAST(samp_idata->sv, 0); UNLOCK(samp_idata->mutex, plbase); } else { UNLOCK(samp_idata->mutex, plbase); samp_free_instance(samp_idata); } return 0; } /* Default case: operation not supported */ return EOPNOTSUPP; } /* * samp_get_config() -- internal routine to get config data * * Gets attribute values of interest from the resmgr key and stores * them in the instance data. */ static int samp_get_config(rm_key_t key, samp_instance_t *samp_idata) { cm_args_t cma; cm_range_t ioa; int err; /* Set up argument structure for a resmgr query. */ cma.cm_key = key; cma.cm_param = CM_IOADDR; cma.cm_val = &ioa; cma.cm_vallen = sizeof ioa; cma.cm_n = 0; /* Get the CM_IOADDR value. */ cm_begin_trans(key, RM_READ); err = cm_getval(&cma); cm_end_trans(key); /* Save the starting I/O address as base_port. */ samp_idata->base_port = ioa.startaddr; /* Make sure enough ports are specified. */ if (!err && ioa.endaddr - ioa.startaddr + 1 < SAMP_NPORTS) err = EINVAL; return err; } /* * open() entry point is called when an application opens a channel. */ /* ARGSUSED */ static int samp_open(void *idata, channel_t *channelp, int oflags, cred_t *crp, queue_t *q) { samp_instance_t *samp_idata = idata; channel_t channel = *channelp; samp_channel_t *samp_chan = &samp_idata->chandata[channel]; int err; ASSERT(channel <= SAMP_MAXCHAN); /* * Most drivers don't do open redirection, so they just use * (*channelp) as is. If this driver did open redirection, * it would pick an appropriate channel number, change *channelp * accordingly, and proceed to open that channel. */ /* * Most drivers don't need to check for privilege, but this one * controls a critical system resource. Writing to this device * requires privilege. */ if ((oflags & FWRITE) && (err = drv_priv(crp))) return err; /* * Keep track of the number of open channels for this instance. * Only bump the count on the first open of a channel. */ ASSERT(getpl() == plbase); (void) LOCK(samp_idata->mutex, pltimeout); if (!(samp_chan->chan_flags & CHAN_OPEN)) { /* First open on channel. */ ++samp_idata->open_channel_count; samp_chan->chan_flags |= CHAN_OPEN; } UNLOCK(samp_idata->mutex, plbase); return 0; } /* * close() entry point is called when an application closes a channel, * but only when the last close for that channel occurs. */ /* ARGSUSED */ static int samp_close(void *idata, channel_t channel, int oflags, cred_t *crp, queue_t *q) { samp_instance_t *samp_idata = idata; samp_channel_t *samp_chan = &samp_idata->chandata[channel]; ASSERT(channel <= SAMP_MAXCHAN); /* * Remember that close() will be called once for each * open channel, so this may not be the last close for * the whole instance. */ ASSERT(getpl() == plbase); (void) LOCK(samp_idata->mutex, pltimeout); ASSERT(samp_idata->open_channel_count > 0); if (--samp_idata->open_channel_count == 0) { /* Last close on instance. */ if (samp_idata->cfg_state == REMOVED) { UNLOCK(samp_idata->mutex, plbase); samp_free_instance(samp_idata); return 0; } } samp_chan->chan_flags &= ~CHAN_OPEN; UNLOCK(samp_idata->mutex, plbase); return 0; } /* * devinfo() entry point is called to query device information on an * open channel. */ /* ARGSUSED */ static int samp_devinfo(void *idata, channel_t channel, di_parm_t parm, void *valp) { ASSERT(channel <= SAMP_MAXCHAN); /* * For this driver, all the parameters are global, so ignore * idata and channel. Most other drivers would have at least * per-instance values, possibly per-channel. */ switch (parm) { case DI_SIZE: ((devsize_t *)valp)->blkoff = SAMP_DEVSIZE; ((devsize_t *)valp)->blkno = 0; return 0; case DI_RBCBP: case DI_WBCBP: *(bcb_t **)valp = samp_bcb; return 0; } /* Default case: operation not supported */ return EOPNOTSUPP; } /* * biostart() entry point is called to initiate I/O. * All I/O is guaranteed to be within DI_SIZE bytes. */ static void samp_biostart(void *idata, channel_t channel, buf_t *bp) { samp_instance_t *samp_idata = idata; uio_t *uiop = bp->b_un.b_uio; uchar_t addr, val; int ret; ASSERT(channel <= SAMP_MAXCHAN); ASSERT(bp->b_addrtype == BA_UIO); ASSERT(bp->b_blkno == 0 && bp->b_blkoff < SAMP_DEVSIZE); /* * Make sure it's OK to start I/O. */ ret = samp_start_io(samp_idata); if (ret != 0) { bioerror(bp, ret); biodone(bp); return; } addr = bp->b_blkoff; if (bp->b_flags & B_READ) { while (uiop->uio_resid) { val = samp_read_cmos(samp_idata, channel, addr); ret = ureadc(val, uiop); if (ret) { bioerror(bp, ret); break; } } /* * The first SAMP_MINWRITE bytes of this * device are read only from this driver. */ } else if (addr < SAMP_MINWRITE) { bioerror(bp, ENXIO); } else { while (uiop->uio_resid) { ret = uwritec(uiop); if (ret == -1) { bioerror(bp, EFAULT); break; } val = ret; samp_write_cmos(samp_idata, channel, addr, val); } } biodone(bp); samp_end_io(samp_idata); } /* * samp_start_io() - prepare to start I/O * * This is where we check the current configuration state, * in case we're suspended or removed. */ static int samp_start_io(samp_instance_t *samp_idata) { ASSERT(getpl() == plbase); /* * If the instance is suspended, wait here. More typically, * the buffer would just be queued on a suspend queue, but * BA_UIO buffers must be processed on the original thread. * * If the instance is removed, fail all I/O. */ recheck: (void) LOCK(samp_idata->mutex, pltimeout); switch (samp_idata->cfg_state) { case SUSPENDED: if (!SV_WAIT_SIG(samp_idata->sv, prilo, samp_idata->mutex)) return EINTR; goto recheck; case REMOVED: UNLOCK(samp_idata->mutex, plbase); return EIO; } samp_idata->in_progress++; UNLOCK(samp_idata->mutex, plbase); return 0; } /* * samp_end_io() - complete I/O * * Now that I/O has completed, sync up with config state. */ static void samp_end_io(samp_instance_t *samp_idata) { ASSERT(getpl() == plbase); (void) LOCK(samp_idata->mutex, pltimeout); samp_idata->in_progress--; UNLOCK(samp_idata->mutex, plbase); /* Wake up waiters in CFG_SUSPEND, if any. */ SV_BROADCAST(samp_idata->sv, 0); } /* * ioctl() entry point is called when an application uses ioctl(2) * to perform a driver-specific operation on an open channel. * * This is the only entry point that has user context and thus * can call copyin() et al. */ /* ARGSUSED */ static int samp_ioctl(void *idata, channel_t channel, int cmd, void *arg, int oflags, cred_t *crp, int *rvalp) { samp_instance_t *samp_idata = idata; uchar_t uch[2]; int err; ASSERT(channel <= SAMP_MAXCHAN); switch (cmd) { case CMOSREAD: if (copyin(arg, &uch[0], 1)) return EFAULT; if (uch[0] >= SAMP_DEVSIZE) return ENXIO; /* Make sure it's OK to start I/O. */ if ((err = samp_start_io(samp_idata)) != 0) return err; uch[1] = samp_read_cmos(samp_idata, channel, uch[0]); samp_end_io(samp_idata); if (copyout(&uch[1], (uchar_t *)arg + 1, 1)) return EFAULT; break; case CMOSWRITE: /* Make sure we only allow writing if open for writing. */ if (!(oflags & FWRITE)) return EACCES; if (copyin(arg, uch, 2)) return EFAULT; if (uch[0] < SAMP_MINWRITE || uch[0] >= SAMP_DEVSIZE) return ENXIO; /* Make sure it's OK to start I/O. */ if ((err = samp_start_io(samp_idata)) != 0) return err; samp_write_cmos(samp_idata, channel, uch[0], uch[1]); samp_end_io(samp_idata); break; default: return EINVAL; } return 0; } /* * samp_read_cmos() -- read from a location in the AT CMOS RAM. */ static uchar_t samp_read_cmos(samp_instance_t *samp_idata, channel_t channel, uchar_t addr) { uchar_t val; ASSERT(addr < SAMP_DEVSIZE); /* On channel 1, wait 5 msec before each I/O. */ if (channel == 1) drv_usecwait(5000); ASSERT(getpl() == plbase); (void) LOCK(samp_idata->mutex, pltimeout); outb(ADDR_PORT(samp_idata), addr); val = inb(DATA_PORT(samp_idata)); UNLOCK(samp_idata->mutex, plbase); return val; } /* * samp_write_cmos() -- write into a location in the AT CMOS RAM. */ static void samp_write_cmos(samp_instance_t *samp_idata, channel_t channel, uchar_t addr, uchar_t val) { ASSERT(addr >= SAMP_MINWRITE && addr < SAMP_DEVSIZE); /* On channel 1, wait 5 msec before each I/O. */ if (channel == 1) drv_usecwait(5000); ASSERT(getpl() == plbase); (void) LOCK(samp_idata->mutex, pltimeout); outb(ADDR_PORT(samp_idata), addr); outb(DATA_PORT(samp_idata), val); UNLOCK(samp_idata->mutex, plbase); } /* * samp_free_instance() -- free per-instance data structures */ static void samp_free_instance(samp_instance_t *samp_idata) { SV_DEALLOC(samp_idata->sv); LOCK_DEALLOC(samp_idata->mutex); kmem_free(samp_idata, sizeof (samp_instance_t)); } /* * samp_free_globals() -- free global data structures */ static void samp_free_globals(void) { physreq_free(samp_bcb->bcb_physreqp); bcb_free(samp_bcb); }