things to note when developing driver: * Keep a console open (xterm -C ) The errors from cmn_err() goes here... * cc -c -D_KERNEL -xarch=sparcv9 .c * ld -r driver.o a.o b.o -o * add_drv -call modload(1M) -updates /etc/name_to_major (Note: /etc/minor_perm defines default permission for minor nodes) Custom defined drivers can never specify the major numbers to the system. * adb -k $ for struct with int, int, flat Notes: * The kernel is preemptible, interruptible * The kernel is memory resident * The OBP probing std complies to IEEE 1275 standard. * drvconfig(1M) configures existing devices in /devices. The entries in /devices are created by ddi_create_minor_node() ??? * See man -s 9e intro; man -s 9f intro; man -s 9s intro; * Block driver does **not** have read, write, ioctl, mmap, segmap, entry points defined. * DDI/DKI- Device driver interface; Device kernel interface These routines are marked: DDI only -- (processor dependent) hardware spec routines; DDI/DKI -- Stable interface (hw independent) interface routines; DKI only -- may not be supported in future. * In addition to DDI/DKI interfaces, sun has defined large set of routines which is called "SunDDI" * Look at soldc.sun.com --> click on driver development for more info examples of driver source code, etc. * Check at www.inklingresearch.com/~mfernest/courses/SI375 for code examples in this course SI375, Intro to Device drivers. * There is mmap system call as well as mmap kernel call. The kernel does the real memory mapping ???? * Previously for manipulating /dev links multiple programs like disks, tape, ports etc were used. Now only devfsadm(1M) replaces all of them. * Use "boot -a" to have the operator ask for options during boot. You can specify boot device, dir paths for loading device modules, /etc/system file pathname etc during boot. You can also edit /etc/system to specify moddir: /platform/SUNW,Ultra-60/kernel /platform/sun4u/kernel /kernel /usr/kernel The modules range from platform specific to platform independent modules. * Hardware devices could be either memory mapped, programmed I/O or DMA. For DMA physical address is being used. Sun uses DVMA (direct virtual memory access) where virtual address is used. * Devices request service by interrupting CPU. The interrupt is async to currently running process (LWP)(probably not caused by the current process) so should not reference the current process/lwp. * SPARC has 0-15 interrupt levels. Cur Interrupt level is part of CPU PSL (processor status long word) which is higher priority?? 0 is highest ??? High level interrupts are never preempted. But lowlevel interrupts could be preempted... examples....?? Higher numeric value is higher priority. Software intr has lowest priority. * The device interrupts are of 2 types: polling, vectored interrupts. In case of polling--- all devices at a certain priority (say 4) will be polled after the interrupt occurs. what ever ready, xxintr for that device is executed. In other case-- vectored interrupt-- after interrupt, device has to place the interrupt vector on the Bus. Then the kernel jump table is indexed by interrupt vector to get interrupt handlers and execute it. SBus Devices * SBus devices are "self identifying". What does it mean? 1) It has PROM which has PROM code (at addr 0) executed during boot (It is FCode) and 2) has attributes list. The (2)nd part makes it self identifying. VME devices are not self identifying. For such devices .conf file says which addr, offset to access the registers to identify the device. PCI Devices * PCI bus has 3 addr spaces: config, Memory and I/O * config space has deviceID and VendorID both 16 bit regs specified; specifies size and location of memory space. *** Important Point ***** It is this capability which gives self identifying capability ??? (apart from being able to run PROM code ??? ) SCSI Bus * Messsage based protocol. * Devices are not self identifying. xxx.conf files are needed to describe the name and bus location of devices. e.g. name="st" class="scsi" target=0 lun=0; ..... name="st" class="scsi" target=6 lun=0; Note: This is used during probe. Note: The class="scsi" or "pci" or "sbus" is well understood by kernel. There is only a specific set of well understood buses by OS. Note: More entries for target=7 to target=15 may be added only if wide scsi tapes are present in the system. Note: Each entry in xxx.conf is a rule. Where is the defn of this "rule" ? man driver.conf(4) For each rule in xxx.conf, one or more devinfo nodes are created by the kernel. During probe, if the node is created out of xxx.conf file, corresponding ddi_ routine to check if it is self identifiable device would return false. The properties defined here could be retrieved by a ddi_ routine. In the above example, for each scsi controller (of class="scsi"), 6 devinfo children would be created, for which "target" and lun properties would be created and initialized. Note that it is left to the probe to see if those devices exist or not. The probe may not find these devices, then they are not attached. Another example from man page: Example 1 name="ACME,simple" class="vme" reg=0x7d,0x400000,0x110600; Single children gets created under all vme bus controller node parents. Example 2 name="ACME,example" parent="pseudo" instance=0 debug-level=1; name="ACME,example" parent="pseudo" instance=1; whizzy-mode="on"; debug-level=3; Under "pseudo" parent devinfo node, two instances of the device gets created with appropriate properties. When a property is out of the rule, it is applicable to all rules. * Most SCSI controllers allow multiple outstanding requests (often 64 or 128). So driver need not reorder requests for optimum disk access patterns, it can just send them all to SCSI controller. * SCSI protocol involves host sending a cmd to dev; receives immediate ack of cmd; receives real data block later. PROBE process * For each devinfo node, the probe(9E) routine may be called. 1) This provides opportunity to make sure the h/w is indeed present for the devinfo node. (sometimes e.g. for tape drives, there may be a devinfo node, but the tape may not be present) 2) provides opportunity to probe a new child device (of PCI Bus node) given only a parent node. Devinfo node for this device may not exist, but devinfo node for parent node exists. DMA * For DMA addr size is imp. e.g. 24 or 32 bit for VME bus devices. Depending on this different system addr space will get used. * The driver has to find out allocation from DVMA space. * There is an I/O cache between DMA device and main memory. This allows for increased throughput. This takes care of updating Virtual address cache, etc as required. ???? Read more on this???? ddi_dma_sync(9F) keeps caches consistent with memory. * fsync(fd) syncs that file with disk. Uses internally ddi_dma_sync(dma_handle, ...) if applicable. * Steps required to setup a DMA transfer: 1) Use ddi_dma_lim_sparc to inform system of possible DMA limits of the device. 2) If you have kernel addr, use ddi_dma_addr_setup(); or a buffer, use ddi_dma_buf_setup(); Or create a req structure using ddi_dma_req_t. 3) Use ddi_dma_setup() to setup mapping. 4) Anytime, you can use ddi_dma_htoc() to get to a cookie. This cookie contains the addr and size to give the device for DMA. 5) Setup device for DMA with the cookie info. and start transfer. 6) After transfer, use ddi_dma_free() * Example DMA tx: xxstart(xs) { ddi_dma_buf_setup(dip, bp, flags, xstart, xs, ..., &xs->dmahandle); ddi_dma_htoc(xs->dmahandle, 0, &cookie); xs->reg->dma_addr = cookie.dmac_address; xs->reg->block_number = bp->b_un.b_blkno; xs->reg->transfer_size = cookie.dmac_size; xs->reg->direction = direction; xs->reg->csr = START_DMA | INTERRUPT_ENABLE; } * If you do large DMA transfer, you may have to use DMA windows. See ddi_dma_curwin(), ddi_dma_movwin(), etc: xxstart(xs) { .... result = ddi_dma_buf_setup(...); xs->partial = (result == DDI_DMA_PARTIAL_MAP ); /* Rember it is partial */ ..... } Device hierarchy: * The leaf node driver, to map the dev memory to kernel memory may ask the parent (controller node) to do this. (that in turn may ask it's parent e.g. Sbus controller or even root nexus node to do the same). Debug * Edit /etc/system to set moddebug to turn on debugging: Use modctl.h for values: e.g. set moddebug=0x80000000 * http://playground.sun.com/1275/bindings * Driver Developer Site 1.0 Answer Book (docs.sun.com) Writing FCode 3.X programs * * probe routine is written only for devices which are not self identifying types. * xxidentify() and xxattach() are mandatory routines required to be provided. * probe routine is optional. * Identify just identifies that this node is for this driver. xidentify(){ if strcmp(ddi_get_name(dip), "mydevice") ==0) return DDI_IDENTIFIED; else not ; } * Probe routine : /* Instance specific state structure */ struct xxstate { dev_info_t *dip; int instance; dev_t dev; volatile struct device_reg *reg; .... struct buf *blist; /* Pending transfers */ /* mutexes, conditional variables, cookies, flags, etc */ } xxprobe(dev_info_t *dip) { struct xxstate *xs; /* This happens for xxx.conf created nodes. */ /* PROM recognized returns success .conf recognized return failure */ if (ddi_dev_is_sid(dip) == DDI_SUCCESS ) return DDI_PROBE_DONTCARE; xs = ddi_get_soft_state(statep, ddi_get_instance(dip)); /* Some devices depending on which Bus it is present is capable * of generating specified highlevel interrupt. * Only if it is capable, then set an interrupt handler. */ if (ddi_intr_hilevel(dip, inumber) == 0) ddi_add_intr(dip, inumber, ...,xxintr, ...., xs .. ); flags |= DIDINTR; /* In case of error to remember to rm interrupt later */ /* Initialize mutex, and some conditional variables */ flags |= DIDLOCKS; /* Now map the register set. The register sets available is provided * by reg property. sbus, vme has reg property defined. (more than 1 reg * property may exist???) * Map those reg properties to kernel state structure. * (If the dev don't have reg property, do the parent bus dev reg * property get used? ) */ ddi_map_regs(dip, reg_set_number, (caddr_t *) &xs->reg, (offset_t)0, sizeof(struct device_reg)); /* Kernel did this for us!! how simple it is to map regs!! */ /* To verify the existence of a device, peek at a status register. * Sometimes, a pci bus reg contents indicates the existence of a device. * If the peek fails, probably the reg is provided by the device. * If the peek contents indicates no device, then probably reg is there * at the controller-- which indicates that the device is not there. */ if (ddi_peekc(dip, &xs->reg->csr, (char*)NULL) != DDI_SUCCESS) goto out; if (ddi_pokec(dip, &xs->reg->csr, (char)RESET) != DDI_SUCCESS) goto out; device_state = xs->reg->csr; if (device_state & OK) return DDI_PROBE_SUCCESS; else failure; } xxattach(dip, cmd) { /* Do all usual stuff */ struct xxstate *xs = ddi_get_instance(dip); /* allocate transfer list header */ xs->blist = getrbuf(KM_SLEEP); For all minor nodes, ddi_create_minor_node(dip, "xx", S_IFCHR, instance, DDI_NT_BLOCK, isclonable); flags = DIDINTR|DIDMAP|DIDNODES; etc. ddi_set_driver_private(dip, (caddr_t) flags); /* can get this flags later */ ddi_report_dev(dip); /* Prints to stdout about attached dev */ return DDI_SUCCESS; } xxdetach(dip,cmd) /* could be nodev */ { /* intuitive */ } xxprop_op() /* By default ddi_prop_op() */ { /* used to return the property being queried */ /* Could be used by devinfo driver to get a property from this driver */ But how to get a list of properties defined by this driver ????? /* Attach could use ddi_prop_create() to create prop by instance basis * or even device minor node basis. */ } xxgetinfo(dip, cmd, void *arg, void **resultp) { /* Used to return dip or inst num given dev */ /* cmd specifies to return dip or inst */ /* Could be used by any kernel module to get instance of * the device given dev. * Could be used by other kernel modules to get to a dip * given dev */ } xxopen(dev_t *devp, flags, otyp, cread_t *cr) { otyp is either OTYP_CHR or OTYP_BLK or OTYP_LYR (what is layered open?) Layered open is because of a devdrv calling another devdriver open, so the last close can be kept track only by the driver. It is optional to support layered open. For other types, kernel calls close only for the last close; however other drivers may call close multiple times, so it is required to keep track of open/close counters. If device does not exist (or no instance found), should return ENXIO; /* On some conditions, the kernel will try to reattach the device for * ENXIO return value and may retry this open */ Should remember in instance specific state struct if it is exclusive open or not; if exclusive open but already open, etc return EAGAIN; Remember to increment internal instance specific open counter; } xxclose() { /* Intuitive */ } /* Used by kernel modules to print error message on this device */ xxprint(dev, str) { cmn_err(CE_WARN, "xx%d: %s", MYDEVTOINST(dev), str); } Buffer header structure: * Used by block and chr drivers. Note that block drivers should have mounted file systems by which read/write happens through its provided strategy routine. * struct buf{ int b_flags; B_BUSY, B_DONE, B_KERNBUF, B_PAGEIO, B_PHYS, B_READ, B_WANTED, B_WRITE, B_ERROR; cdddr_t b_addr; /* in a union */ /* Kernel virt addr, if specified */ _b_blkno; block# on device struct page *b_pages; /* page list for PAGEIO. Otherwise DVMA may be * used through b_addr */ void *b_private; /* priv opaque drv ptr */ } xxstrategy(struct buf *bp) { could have been called from either block or character device. Called from physio from write/read entry points in chr driver or from write/read entry points in file system driver. (that calls physio which calls strategy) Calls bp_mapin if required: to get kernel virtaddr map for the pages. If kernel is not going to look at the buf content later, or if the transfer is directly to user buffer (by DVMA), then bp_mapin to set b_addr is not required. Enqueues the write request to a list; If the queue was empty calls xxstart(xs); In case of error calls biodone(bp); /* usually biodone called in physio*/ } * Character driver unique routines: xxread() xxwrite() xxioctl() xxmmap() xxsegmap() xxchpoll() (or nochpoll() can be used ) xxminphys() -- to find out max block size for transfer for physio() * Almost all special devices or character devices (like framebuffer etc) * The read/write requires user info. This is supplied from uio structure: uio_seg_t uio_segflg; UIO_USERSPACE or UIO_SYSSPACE iovec_t *uio_iov; short uio_fmode; FREAD and/or FWRITE int uio_resid; /* No of bytes to transfer. On return set to no not txed */ int uio_offset; /* Offset into the file */ etc. * SunOS uses always iovectors even for read (like readv) * uio gives everything about the read: offset, bytes, user/kernel addr to which read/write has to be made; file mode as opened, etc. static int xxread(dev, struct uio *uiop, cred_t *credp) { Just check if the device is there by calling, ddi_get_soft_state() etc.; if not return ENXIO; return physio(xxstrategy, (struct buf*)NULL, dev, B_READ, xxminphys, uiop); /* If you dont use physio, make sure to decrement uiop->uio_resid */ Note that physio also locks down the user buffers as required. (There is no other routine to lock down the user pages */ } xxwrite(...) { similar to xxread other than B_WRITE } * Algorithm for physio(): physio(xxstrategy, bp, dev, rw_flag, minphys, uiop) { If bp == NULL allocate one; iovloop : set up buffer for I/O ; --> what setup?? Initializing buf for READ/WRITE Intialize buf len. while (more data){ bp->b_flags = B_BUSY | B_PHYS | rw_flag; more buffer setup; (*minphys)(bp); /* Break down the transfer unit if required */ Lock down and **fault in** user pages; (*strategy)(bp); biowait(bp); unlock user pages update uio structure (uio_resid, uio_offset, iov_len, iov_base) } bp->b_flags &= ~(B_BUSY|B_WANTED|B_BPHYS); If more remaining go to iovloop: } xxinphys(struct buf *bp) { bp->b_bcount = min(bp->b_count, XXMINVAL); minphys(bp); /* Makes sure bp->b_bcount is less than system defined limit*/ } /* Note that the following entry point xxioctl is not available * for streams driver. */ xxioctl(dev, cmd, arg, mode, credp, rvalp) { Note: rvalp is for non-negative int return of the system call. if instance not found return ENXIO; switch (cmd) XX_GET_STATUS : (for ioctl(fd, XX_GET_STATUS, &myarg) ) ddi_copyout(xs->reg->csr, (caddr_t)arg, sizeof(u_char), mode); mode = FKIOCTL means arg is in kernel space, otherwise userspace of current kernel thread. For kernel space ddi_copyout is like bcopy or it is like copyout. *rvalp = 0; /* 0 return for ioctl syscall */ return 0; default: return ENOTTY; } * The cmds for ioctl is unique for that device. typically like 'd'<<8 | 1 etc It is very important that they should not conflict with 'S' << 8 which is used for stream ioctl cmds like I_STR, I_PUSH etc. The ioctl system call would not know whether you mean ioctl(fd, I_PUSH, ..) or ioctl(fd, IOGETATTR, ..); * mmap(2) system call supported only for regular file and shared mem. to map a region to a user virt addr. In doing that it has to setup pagetables for the user process so that the virtual addr range maps to device segment. (1) If the file is reg file, then read/write goes to the file system buffers related to those blocks. (2) If the block driver of regfile also support devmap, then that would probably be called. (3) Otherwise, Each ptr ref in that region may result in making a call to write???? Or is it just that mmap would fail saying that it is not supported for this device ??? /* this supported only on character device */ xxsegmap(dev, off, struct as *asp, caddr_t *addrp, off_t len, unsigned prot, unsigned maxprot, unsigned flags, credp) { Check for prot etc. return ddi_segmap(dev, off, asp, addrp, len, prot, maxprot, flags, cred); } mmap(2) system call { Call xxsegmap(){ either specifies its own segmap or call/specify ddi_segmap(){ For each page in the addr range call xxmmap(dev, off_t page_no, int prot){ return physical page number for this page; Use hat_getkpfnum(&xs->reg->csr+off); } Setup phys->virt page translation entries as required for this page } } } xxmap(dev, off_t page, int prot) { return physical page no for this page offset; use hat_getkpfnum() routine; } /* Only char devices may be polled. Streams devices don't need this entry pt. * Non-streams char drivers could choose to support this function. */ xxchpoll() { /* Intuitive */ Set in revents for all satisfied conditions. Onlything, a pollhead struct is allocated and returned if no events have occured-- there is a discussion of pollwakeup() routine being called from interrupt routine. } KERNEL routines --------------- * Process signalling * Drivers can choose to send signal to the accessing process: void *proc_ref(void); /* Return handle to accessing process */ void proc_uref(void *pref); /* Remove resources allocated */ int proc_signal(void *pref, int sig); /* Send sig to process */ Questions: - The system call is executed by one LWP. The other LWP's could be executing other user contexts and other system calls. In that case, (1) you send SIGINT (default action: terminate). How does this get handled? synchronously? After sending the signal, do you continue system call ? Ans: Note that you will send the signal either from top half or bottom half(intr routine) of the driver. This discussion is only for the top half. For bottom half, obviously the signal delivery is asynchronous. If the signal is SIGKILL, then the termination is synchronous. If the signal action is DEFAULT and def-action is to terminate, then also it is synchronous--process gets terminated. If the signal has been captured, then the signal delivery is async, so it is queued and the system call proceeds normally. (e.g. I may send SIG_USR1, a harmless signal, I dont want the cur system call to fail, also the sig handler should execute in user context after the cur system call returns. or I may send SIGPIPE which is captured, but still I want to return the current system call -1 gracefully). (2) you send SIGKILL. synchronous. If other kernel threads are waiting in calls like cv_wait_sig(), they get terminated; if there are any threads in cv_wait(), they could not be interrupted!! Does this mean, the termination of other LWP's possible only when they reach a routine like cv_wait_sig()???? Otherwise, is there a general routine to mask out all signals in device driver code (to mask out even SIGKILL) ? As part of termination, close is called on all open fd's. Interrupt Routine Remember ddi_add_intr(dip, inumber, ...,xxintr, ...., xs .. ); This makes sure for both PCI and SBUS devices, the polled vector mechanism is used. On receiving the intr inumber, the DDI infrastructure looks at things (may be reg property???) and comes to know what device instance made this interrupt. Then it calls xxintr passing the xs. Once we have xs, what more you want!! Instance specific structure!! static int xxintr(caddr_t arg) /* arg is xs -- struct xxstate *xs; */ { /* Do job for this interrupt */ /* Suppose this intr generated after a full buffer transfer... */ bp = remove_request(xs->blist); bp -> b_resid = 0; /* Important!!! decrement b_resid appropriately !! */ /* If it is char non-streams driver supporting chpoll, send pollwakeup, * as the chpoll routine waits for this. */ pollwakeup(&xs->pollhead, POLLIN); xxstart(); may be trigger transfer for more in future; /* map out the kernel virt addr, if this driver maps in the pages */ if (bp->b_flags & B_PAGEIO ) bp_mapout(bp); biodone(bp); ---> probably this will wake up the physio() which may be waiting on biowait() after calling xxstrategy(). /* If the device is also a char device, then somebody may be waiting * on this interrupt from xxread() routine. In that case cv_signal() * to wake them up. See xxread() example that follows. */ cv_signal(&xs->intrcv); If I am the one responsible for handling intr, return DDI_INTR_CLAIMED; else return unclaimed; } xxread(dev_t dev, struct uio *uiop, cred_t *credp) { instance = XXINST(dev); xs = ddi_get_soft_state(statep, instance); if (xs == NULL) return ENXIO; mutex_enter(&xs->mutex); /* Use this lock before examining anything */ Loop on xs->busycv, if applicable: while (xs->busy) if (cv_wait_sig(&xs->busycv, &xs->mutex) == 0) goto intr; /* Return EINTR if it gets interrupted by signal. Probably if you * have started reading something, don't return EINTR */ xs->busy = TRUE; xxstart(xs, uiop); cv_wait(&xs->intrcv, &xs->mutex); ..... ..... xs->busy = FALSE; cv_broadcast(&xs->busycv); /* Tell every body it is nolonger busy */ mutex_exit(&xs); return EINTR; } /* Simple xxintr() example for char driver */ xxintr(xs) { mutex_enter(&xs->mutex); cv_signal(&xs->intrcv); mutex_exit(&xs->mutex); return DDI_INTR_CLAIMED; } * If interrupt handling would take long time interacting with the device, software interrupt should be invoked to handle the device. xxattach() should take care of this: xxattach(dip, cmd) { .... if (ddi_intr_hilevel(dip, inumber)) /* If the inumber is hilevel intr in this machine */ { /* Add high level intr */ ddi_add_intr(dip,inumber, ... xxhighintr, ...); ddi_add_soft_intr(dip, DDI_SOFTINT_HI,.., &xs->id, ..., xxlowintr, ...); /* Note s/w intrs should be remembered by id's. and would be used * by highlevel intr to trigger them explicitly */ } else ddi_add_intr(dip, inumber, ..., xintr, ... ); .... } xxhighintr(xs) { xs->reg->csr = INTERRUPT_SERVICED; garbage = xs->reg->csr; /* Force flushout data by dummy read */ ddi_trigger_softintr(xs->id); return DDI_INTR_CLAIMED; } * Data transfer routines: copyin(), copyout(), bcopy(), int uiomove(caddr_t cp, size_t n, uio_rw rw, uio_t *uio) { flag in uio struct says if it is UIO_USERSPACE or UIO_SYSSPACE A block copy is done accordingly. convenient function. } * Time related routines: void delay(clock_t ticks); int timeout(void (*fn) (), caddr_t arg, clock_t ticks); int untimeout(int id); Schedules fn to be called after the number of clock ticks. delay() calls timeout. Global variable hz specifies the number of clockticks per second. drv_usecwait(); drv_usectohz(); drv_hztousec(); Sync routines: * biowait() --> physio() calls this to wait for the transfer to complete. * biodone() --> called from bottom half of driver to wakeup thread who called biowait(); * kmem_alloc, kmem_free, kmem_zalloc * Driver support routines: ddi_dev_nintrs() -- No of intr specifications for the device ddi_dev_nregs() -- No of regs specs for the device ddi_dev_regsize(dip, regno, resultp) -- size in bytes of the given register cred_t *ddi_get_cred(); -- Get current login credential. Done by looking into current lwp kernel thread. int drv_priv(cred_t *cr); --- Am I root user ? * Less frequently used driver support fns: ddi_slaveonly() -- device can't do DMA ddi_dev_is_sid() -- Is device self identifying? (non xxx.conf devices) ddi_prop_create() -- creates property on dev instance or even on a minor device basis. DDI Support routines: ddi_soft_state_init() -- convenience function (instead of kmem_alloc ) STREAMS: * cc -c -D_KERNEL -xarch=sparcv9 .c * ld -r strdriver.o a.o b.o -o * cp strdriver /kernel/strmod * fd = open any character device; ioctl(fd, I_PUSH, "strdriver"); * what class of devices support streams pushing? all char drvrs??? * To list the stream module use I_LIST ioctl on a stream fd, with struct str_list arg. * Each stream module has read and write queue. Each queue has list of messages (and optional priority band messages) To access priority band messages, you should use strqget, strqset. * There are all kinds of messages for read, ioctl, delay, send break char, send signal, copyin, copyout, hangup, flush, ack, nack, etc. * The unknown commands should be sent downstream. If driver does not understand it, it should send NACK. * From SVR4 onwards, the old ioctl commands are sent as stream messages down stream. So it is important that no device uses ioctl commands 'S' << 8 | x commands. ioctl(fd, cmd, ...) ---> ioctl(fd, I_STR, &strioctl); strioctl.ic_cmd = cmd; ic_timout = INFTIM; ic_len = TRANSPARENT; The stream identifies the cmd is oldstyle cmd by looking at TRANSPARENT; * For stream synchronization, if you are manipulating with queue structs, you may have to call, freezestr, unfreezestr, (freeze whole stream) or qprocson and qprocsoff (just disable scheduling read, srv procedures); * Links: Michael.Ernest@inklingresearch.com docs.sun.com:80/ab2/coll.45.13 http://soldc.sun.com/develop/support/driver http://www.inklingresearch.com/~mfernest/courses/SI375