/*
 * New Leaf driver open entry point.  We make a vnode and go through specfs
 * in order to obtain open close exclusions guarantees.  Note that we drop
 * OTYP_LYR if it was specified - we are going through specfs and it provides
 * last close semantics (FKLYR is provided to open(9E)).  Also, since
 * spec_open will drive attach via e_ddi_hold_devi_by_dev for a makespecvp
 * vnode with no SDIP_SET on the common snode, the dev_lopen caller no longer
 * needs to call ddi_hold_installed_driver.
 */
int
dev_lopen(dev_t *devp, int flag, int otype, struct cred *cred)
{
	struct vnode	*vp;
	int		error;
	struct vnode	*cvp;

	vp = makespecvp(*devp, (otype == OTYP_BLK) ? VBLK : VCHR);
	error = VOP_OPEN(&vp, flag | FKLYR, cred, NULL);
	if (error == 0) {
		/* Pick up the (possibly) new dev_t value. */
		*devp = vp->v_rdev;

		/*
		 * Place extra hold on the common vnode, which contains the
		 * open count, so that it is not destroyed by the VN_RELE of
		 * the shadow makespecvp vnode below.
		 */
		cvp = STOV(VTOCS(vp));
		VN_HOLD(cvp);
	}

	/* release the shadow makespecvp vnode. */
	VN_RELE(vp);
	return (error);
}
/*
 * Leaf driver close entry point.  We make a vnode and go through specfs in
 * order to obtain open close exclusions guarantees.  Note that we drop
 * OTYP_LYR if it was specified - we are going through specfs and it provides
 * last close semantics (FLKYR is provided to close(9E)).
 */
int
dev_lclose(dev_t dev, int flag, int otype, struct cred *cred)
{
	struct vnode	*vp;
	int		error;
	struct vnode	*cvp;
	char		*funcname;
	ulong_t		offset;

	vp = makespecvp(dev, (otype == OTYP_BLK) ? VBLK : VCHR);
	error = VOP_CLOSE(vp, flag | FKLYR, 1, (offset_t)0, cred, NULL);

	/*
	 * Release the extra dev_lopen hold on the common vnode. We inline a
	 * VN_RELE(cvp) call so that we can detect more dev_lclose calls than
	 * dev_lopen calls without panic. See vn_rele.  If our inline of
	 * vn_rele called VOP_INACTIVE(cvp, CRED(), ...) we would panic on the
	 * "release the makespecvp vnode" VN_RELE(vp) that follows  - so
	 * instead we diagnose this situation.  Note that the driver has
	 * still seen a double close(9E), but that would have occurred with
	 * the old dev_close implementation too.
	 */
	cvp = STOV(VTOCS(vp));
	mutex_enter(&cvp->v_lock);
	switch (cvp->v_count) {
	default:
		cvp->v_count--;
		break;

	case 0:
		VTOS(vp)->s_commonvp = NULL;	/* avoid panic */
		/*FALLTHROUGH*/
	case 1:
		/*
		 * The following message indicates a serious problem in the
		 * identified driver, the driver should be fixed. If obtaining
		 * a panic dump is needed to diagnose the driver problem then
		 * adding "set dev_lclose_ce=3" to /etc/system will cause a
		 * panic when this occurs.
		 */
		funcname = modgetsymname((uintptr_t)caller(), &offset);
		cmn_err(dev_lclose_ce, "dev_lclose: extra close of dev_t 0x%lx "
		    "from %s`%s()", dev, mod_containing_pc(caller()),
		    funcname ? funcname : "unknown...");
		break;
	}
	mutex_exit(&cvp->v_lock);

	/* release the makespecvp vnode. */
	VN_RELE(vp);
	return (error);
}
Beispiel #3
0
/*
 * wput(9E) is symmetric for master and slave sides, so this handles both
 * without splitting the codepath.  (The only exception to this is the
 * processing of zcons ioctls, which is restricted to the master side.)
 *
 * zc_wput() looks at the other side; if there is no process holding that
 * side open, it frees the message.  This prevents processes from hanging
 * if no one is holding open the console.  Otherwise, it putnext's high
 * priority messages, putnext's normal messages if possible, and otherwise
 * enqueues the messages; in the case that something is enqueued, wsrv(9E)
 * will take care of eventually shuttling I/O to the other side.
 */
static void
zc_wput(queue_t *qp, mblk_t *mp)
{
	unsigned char type = mp->b_datap->db_type;
	zc_state_t *zcs;
	struct iocblk *iocbp;
	file_t *slave_filep;
	struct snode *slave_snodep;
	int slave_fd;

	ASSERT(qp->q_ptr);

	DBG1("entering zc_wput, %s side", zc_side(qp));

	/*
	 * Process zcons ioctl messages if qp is the master console's write
	 * queue.
	 */
	zcs = (zc_state_t *)qp->q_ptr;
	if (zcs->zc_master_rdq != NULL && qp == WR(zcs->zc_master_rdq) &&
	    type == M_IOCTL) {
		iocbp = (struct iocblk *)(void *)mp->b_rptr;
		switch (iocbp->ioc_cmd) {
		case ZC_HOLDSLAVE:
			/*
			 * Hold the slave's vnode and increment the refcount
			 * of the snode.  If the vnode is already held, then
			 * indicate success.
			 */
			if (iocbp->ioc_count != TRANSPARENT) {
				miocack(qp, mp, 0, EINVAL);
				return;
			}
			if (zcs->zc_slave_vnode != NULL) {
				miocack(qp, mp, 0, 0);
				return;
			}

			/*
			 * The process that passed the ioctl must be running in
			 * the global zone.
			 */
			if (curzone != global_zone) {
				miocack(qp, mp, 0, EINVAL);
				return;
			}

			/*
			 * The calling process must pass a file descriptor for
			 * the slave device.
			 */
			slave_fd =
			    (int)(intptr_t)*(caddr_t *)(void *)mp->b_cont->
			    b_rptr;
			slave_filep = getf(slave_fd);
			if (slave_filep == NULL) {
				miocack(qp, mp, 0, EINVAL);
				return;
			}
			if (ZC_STATE_TO_SLAVEDEV(zcs) !=
			    slave_filep->f_vnode->v_rdev) {
				releasef(slave_fd);
				miocack(qp, mp, 0, EINVAL);
				return;
			}

			/*
			 * Get a reference to the slave's vnode.  Also bump the
			 * reference count on the associated snode.
			 */
			ASSERT(vn_matchops(slave_filep->f_vnode,
			    spec_getvnodeops()));
			zcs->zc_slave_vnode = slave_filep->f_vnode;
			VN_HOLD(zcs->zc_slave_vnode);
			slave_snodep = VTOCS(zcs->zc_slave_vnode);
			mutex_enter(&slave_snodep->s_lock);
			++slave_snodep->s_count;
			mutex_exit(&slave_snodep->s_lock);
			releasef(slave_fd);
			miocack(qp, mp, 0, 0);
			return;
		case ZC_RELEASESLAVE:
			/*
			 * Release the master's handle on the slave's vnode.
			 * If there isn't a handle for the vnode, then indicate
			 * success.
			 */
			if (iocbp->ioc_count != TRANSPARENT) {
				miocack(qp, mp, 0, EINVAL);
				return;
			}
			if (zcs->zc_slave_vnode == NULL) {
				miocack(qp, mp, 0, 0);
				return;
			}

			/*
			 * The process that passed the ioctl must be running in
			 * the global zone.
			 */
			if (curzone != global_zone) {
				miocack(qp, mp, 0, EINVAL);
				return;
			}

			/*
			 * The process that passed the ioctl must have provided
			 * a file descriptor for the slave device.  Make sure
			 * this is correct.
			 */
			slave_fd =
			    (int)(intptr_t)*(caddr_t *)(void *)mp->b_cont->
			    b_rptr;
			slave_filep = getf(slave_fd);
			if (slave_filep == NULL) {
				miocack(qp, mp, 0, EINVAL);
				return;
			}
			if (zcs->zc_slave_vnode->v_rdev !=
			    slave_filep->f_vnode->v_rdev) {
				releasef(slave_fd);
				miocack(qp, mp, 0, EINVAL);
				return;
			}

			/*
			 * Decrement the snode's reference count and release the
			 * vnode.
			 */
			ASSERT(vn_matchops(slave_filep->f_vnode,
			    spec_getvnodeops()));
			slave_snodep = VTOCS(zcs->zc_slave_vnode);
			mutex_enter(&slave_snodep->s_lock);
			--slave_snodep->s_count;
			mutex_exit(&slave_snodep->s_lock);
			VN_RELE(zcs->zc_slave_vnode);
			zcs->zc_slave_vnode = NULL;
			releasef(slave_fd);
			miocack(qp, mp, 0, 0);
			return;
		default:
			break;
		}
	}

	if (zc_switch(RD(qp)) == NULL) {
		DBG1("wput to %s side (no one listening)", zc_side(qp));
		switch (type) {
		case M_FLUSH:
			handle_mflush(qp, mp);
			break;
		case M_IOCTL:
			miocnak(qp, mp, 0, 0);
			break;
		default:
			freemsg(mp);
			break;
		}
		return;
	}

	if (type >= QPCTL) {
		DBG1("(hipri) wput, %s side", zc_side(qp));
		switch (type) {
		case M_READ:		/* supposedly from ldterm? */
			DBG("zc_wput: tossing M_READ\n");
			freemsg(mp);
			break;
		case M_FLUSH:
			handle_mflush(qp, mp);
			break;
		default:
			/*
			 * Put this to the other side.
			 */
			ASSERT(zc_switch(RD(qp)) != NULL);
			putnext(zc_switch(RD(qp)), mp);
			break;
		}
		DBG1("done (hipri) wput, %s side", zc_side(qp));
		return;
	}

	/*
	 * Only putnext if there isn't already something in the queue.
	 * otherwise things would wind up out of order.
	 */
	if (qp->q_first == NULL && bcanputnext(RD(zc_switch(qp)), mp->b_band)) {
		DBG("wput: putting message to other side\n");
		putnext(RD(zc_switch(qp)), mp);
	} else {
		DBG("wput: putting msg onto queue\n");
		(void) putq(qp, mp);
	}
	DBG1("done wput, %s side", zc_side(qp));
}