As described in Chapter 9, STREAMS Drivers, and as seen in the previous data structures, there are four entry points:
dev_ops - identify(9E), attach(9E), getinfo(9E)).
cb_ops - open(9E), close(9E), read(9E), write(9E), ioctl(9E).
Now look at a real example taken from the Solaris 7 system. The driver pts(7D) is the pseudo terminal slave driver.
/* * Slave Stream Pseudo Terminal Module */ #include <sys/types.h> #include <sys/param.h> #include <sys/stream.h> #include <sys/stropts.h> #include <sys/stat.h> #include <sys/errno.h> #include <sys/debug.h> #include <sys/cmn_err.h> #include <sys/modctl.h> #include <sys/conf.h> #include <sys/ddi.h> #include <sys/sunddi.h> static int ptsopen (queue_t*, dev_t*, int, int, cred_tstatic int ptsclose (queue_t*, int, cred_t*); static int ptswput (queue_t*, mblk_t*); static int ptsrsrv (queue_t*); static int ptswsrv (queue_t*); static int pts_devinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,void **result); static struct module_info pts_info = { 0xface, "pts", 0, 512, 512, 128 }; static struct qinit ptsrint = { NULL, ptsrsrv, ptsopen, ptsclose, NULL, &pts_info, NULL }; static struct qinit ptswint = { ptswput, ptswsrv, NULL, NULL, NULL, &pts_info, NULL }; static struct streamtab ptsinfo = { &ptsrint, &ptswint, NULL, NULL }; static int pts_identify(dev_info_t *devi); static int pts_attach(dev_info_t *devi, ddi_attach_cmd_t cmd); static int pts_detach(dev_info_t *devi, ddi_detach_cmd_t cmd); static dev_info_t *pts_dip; /* private copy of devinfo ptr */ extern kmutex_t pt_lock; extern pt_cnt; static struct cb_ops cb_pts_ops = { nulldev, /* cb_open */ nulldev, /* cb_close */ nodev, /* cb_strategy */ nodev, /* cb_print */ nodev, /* cb_dump */ nodev, /* cb_read */ nodev, /* cb_write */ nodev, /* cb_ioctl */ nodev, /* cb_devmap */ nodev, /* cb_mmap */ nodev, /* cb_segmap */ nochpoll, /* cb_chpoll */ ddi_prop_op, /* cb_prop_op */ &ptsinfo, /* cb_stream */ D_MP /* cb_flag */ }; static struct dev_ops pts_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ pts_devinfo, /* devo_getinfo */ pts_identify, /* devo_identify */ nulldev, /* devo_probe */ pts_attach, /* devo_attach */ pts_detach, /* devo_detach */ nodev, /* devo_reset */ &cb_pts_ops, /* devo_cb_ops */ (struct bus_ops*) NULL /* devo_bus_ops */ }; /* * Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module: a pseudo driver */ "Slave Stream Pseudo Terminal driver'pts'", &pts_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; int _init(void) { return (mod_install(&modlinkage)); } int _fini(void) { return (mod_remove(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } static int pts_identify(dev_info_t *devi) { if (strcmp(ddi_get_name(devi), "pts") == 0) return (DDI_IDENTIFIED); else return (DDI_NOT_IDENTIFIED); } static int pts_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { int i; char name[5]; if (cmd != DDI_ATTACH) return (DDI_FAILURE); for (i = 0; i < pt_cnt; i++) { (void) sprintf(name, "%d", i); if (ddi_create_minor_node(devi, name, S_IFCHR, i, NULL, 0) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (DDI_FAILURE); } } return (DDI_SUCCESS); } static int pts_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { ddi_remove_minor_node(devi, NULL); return (DDI_SUCCESS); } static int pts_devinfo (dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { int error; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if (pts_dip == NULL) { error = DDI_FAILURE; } else { *result = (void *) pts_dip; error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *) 0; error = DDI_SUCCESS; break; default: error = DDI_FAILURE; } return (error); } /* the open, close, wput, rsrv, and wsrv routines are presented * here solely for the sake of showing how they interact with the * configuration data structures and routines. Therefore, the * bulk of their code is not included. */ static int ptsopen(rqp, devp, oflag, sflag, credp) queue_t *rqp; /* pointer to the read side queue */ dev_t *devp; /* pointer to stream tail's dev */ int oflag; /* the user open(2) supplied flags */ int sflag; /* open state flag */ cred_t *credp; /* credentials */ { qprocson(rqp); return (0); } static int ptsclose(rqp, flag, credp) queue_t *rqp; int flag; cred_t *credp; { qprocsoff(rqp); return (0); } static int ptswput(qp, mp) queue_t *qp; mblk_t *mp; { return (0); } static int ptsrsrv(qp) queue_t *qp; { return (0); } static int ptswsrv(qp) queue_t *qp; { return (0); }
Here are the structures if you are working with a module instead of a driver. Notice that a modlstrmod(9S) is used in modlinkage(9S) and fmodsw(9S) points to streamtab(9S) instead of going through dev_ops(9S).
extern struct streamtab pteminfo; static struct fmodsw fsw = { "ptem", &pteminfo, D_NEW | D_MP }; /* * Module linkage information for the kernel. */ extern struct mod_ops mod_strmodops; static struct modlstrmod modlstrmod = { &mod_strmodops, "pty hardware emulator", &fsw }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modlstrmod, NULL };
Here are some compile, assemble, and link lines for an example driver with two C modules and an assembly language module.
cc -D_KERNEL -c example_one.c cc -D_KERNEL -c example_two.c as -P -D_ASM -D_KERNEL -I. -o example_asm.o example_asm.s ld -r -o example example_one.o example_two.o example_asm.o |
See Writing Device Drivers for more information on the sequence of installing and loading device drivers. The procedure is to copy your driver or module to /kernel/drv or /kernel/strmod respectively. For drivers run add_drv(1M).
Next, see the code that enables a driver to determine if it is running as a regular driver, a module, or a cloneable driver. The open routine returns sflag, which is checked.
if (sflag == MODOPEN) /* then the module is being pushed */ else if (sflag == CLONEOPEN) /* then its being opened as a clonable driver */ else /* its being opened as a regular driver */