/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Standard module for handling DLPI Style 2 attach/detach */ #include #include #include #include #include #include #include #include #include #include #include static struct streamtab drstab; static struct fmodsw fsw = { DRMODNAME, &drstab, D_MP }; /* * Module linkage information for the kernel. */ static struct modlstrmod modlstrmod = { &mod_strmodops, "dr compatibility for DLPI style 2 drivers", &fsw }; static struct modlinkage modlinkage = { MODREV_1, &modlstrmod, 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 dropen(queue_t *, dev_t *, int, int, cred_t *); static int drclose(queue_t *, int, cred_t *); static int drrput(queue_t *, mblk_t *); static int drwput(queue_t *, mblk_t *); static struct module_info drinfo = { 0, DRMODNAME, 0, INFPSZ, 1, 0 }; static struct qinit drrinit = { (int (*)())drrput, NULL, dropen, drclose, NULL, &drinfo }; static struct qinit drwinit = { (int (*)())drwput, NULL, NULL, NULL, NULL, &drinfo }; static struct streamtab drstab = { &drrinit, &drwinit, NULL, NULL }; /* * This module is pushed directly on top of the bottom driver * in a DLPI style-2 stream by stropen(). It intercepts * DL_ATTACH_REQ/DL_DETACH_REQ messages on the write side * and acks on the read side, calls qassociate where needed. * The primary purpose is to workaround a DR race condition * affecting non-DDI compliant DLPI style 2 drivers, which may * cause the system to panic. * * The following action is taken: * Write side (drwput): * attach request: hold driver instance assuming ppa == instance. * This way, the instance cannot be detached while the * driver is processing DL_ATTACH_REQ. * * On a successful hold, store the dip in a ring buffer * to be processed lated by the read side. * If hold fails (most likely ppa != instance), we store * NULL in the ring buffer and read side won't take * any action on ack. * * Read side (drrput): * attach success: if (dip held on write side) associate queue with dip * attach failure: if (dip held on write side) release hold on dip * detach success: associate queue with NULL * detach failure: do nothing * * The module assumes that incoming DL_ATTACH_REQ/DL_DETACH_REQ * messages are ordered (non-concurrent) and the bottom * driver processes them and sends acknowledgements in the same * order. This assumption is reasonable because concurrent * association results in non-deterministic queue behavior. * The module is coded carefully such that unordered messages * do not result in a system panic. * * The module handles multiple outstanding messages queued * in the bottom driver. Messages processed on the write side * but not yet arrived at read side are placed in the ring buffer * dr_dip[], between dr_nfirst and dr_nlast. The write side is * producer and the read side is the consumer. The buffer is full * when dr_nfirst == dr_nlast. * * The current size of the ring buffer is 64 (MAX_DLREQS) per stream. * During normal testing, we have not seen outstanding messages * above 10. */ #define MAX_DLREQS 64 #define INCR(x) {(x)++; if ((x) >= MAX_DLREQS) (x) = 0; } struct drstate { kmutex_t dr_lock; major_t dr_major; int dr_nfirst; int dr_nlast; dev_info_t *dr_dip[MAX_DLREQS]; }; /* ARGSUSED1 */ static int dropen(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *crp) { struct drstate *dsp; if (sflag != MODOPEN) { /* must be a pushed module */ return (EINVAL); } if (secpolicy_net_rawaccess(crp) != 0) { return (EPERM); } if (q->q_ptr != NULL) { return (0); /* already open */ } dsp = kmem_zalloc(sizeof (*dsp), KM_SLEEP); dsp->dr_major = getmajor(*devp); mutex_init(&dsp->dr_lock, NULL, MUTEX_DEFAULT, NULL); q->q_ptr = OTHERQ(q)->q_ptr = dsp; qprocson(q); ddi_assoc_queue_with_devi(q, NULL); return (0); } /* ARGSUSED1 */ static int drclose(queue_t *q, int cflag, cred_t *crp) { struct drstate *dsp = q->q_ptr; ASSERT(dsp); ddi_assoc_queue_with_devi(q, NULL); qprocsoff(q); mutex_destroy(&dsp->dr_lock); kmem_free(dsp, sizeof (*dsp)); q->q_ptr = NULL; return (0); } static int drrput(queue_t *q, mblk_t *mp) { struct drstate *dsp; union DL_primitives *dlp; dev_info_t *dip; switch (DB_TYPE(mp)) { case M_PROTO: case M_PCPROTO: break; default: putnext(q, mp); return (0); } /* make sure size is sufficient for dl_primitive */ if (MBLKL(mp) < sizeof (t_uscalar_t)) { putnext(q, mp); return (0); } dlp = (union DL_primitives *)mp->b_rptr; switch (dlp->dl_primitive) { case DL_OK_ACK: { /* check for proper size, let upper layer deal with error */ if (MBLKL(mp) < DL_OK_ACK_SIZE) { putnext(q, mp); return (0); } dsp = q->q_ptr; switch (dlp->ok_ack.dl_correct_primitive) { case DL_ATTACH_REQ: /* * ddi_assoc_queue_with_devi() will hold dip, * so release after association. * * dip is NULL means we didn't hold dip on read side. * (unlikely, but possible), so we do nothing. */ mutex_enter(&dsp->dr_lock); dip = dsp->dr_dip[dsp->dr_nlast]; dsp->dr_dip[dsp->dr_nlast] = NULL; INCR(dsp->dr_nlast); mutex_exit(&dsp->dr_lock); if (dip) { ddi_assoc_queue_with_devi(q, dip); ddi_release_devi(dip); } break; case DL_DETACH_REQ: ddi_assoc_queue_with_devi(q, NULL); break; default: break; } break; } case DL_ERROR_ACK: if (dlp->error_ack.dl_error_primitive != DL_ATTACH_REQ) break; dsp = q->q_ptr; mutex_enter(&dsp->dr_lock); dip = dsp->dr_dip[dsp->dr_nlast]; dsp->dr_dip[dsp->dr_nlast] = NULL; INCR(dsp->dr_nlast); mutex_exit(&dsp->dr_lock); /* * Release dip on attach failure */ if (dip) { ddi_release_devi(dip); } break; default: break; } putnext(q, mp); return (0); } /* * Detect dl attach, hold the dip to prevent it from detaching */ static int drwput(queue_t *q, mblk_t *mp) { struct drstate *dsp; union DL_primitives *dlp; dev_info_t *dip; switch (DB_TYPE(mp)) { case M_PROTO: case M_PCPROTO: break; default: putnext(q, mp); return (0); } /* make sure size is sufficient for dl_primitive */ if (MBLKL(mp) < sizeof (t_uscalar_t)) { putnext(q, mp); return (0); } dlp = (union DL_primitives *)mp->b_rptr; switch (dlp->dl_primitive) { case DL_ATTACH_REQ: /* * Check for proper size of the message. * * If size is correct, get the ppa and attempt to * hold the device assuming ppa is instance. * * If size is wrong, we can't get the ppa, but * still increment dr_nfirst because the read side * will get a error ack on DL_ATTACH_REQ. */ dip = NULL; dsp = q->q_ptr; if (MBLKL(mp) >= DL_OK_ACK_SIZE) { dip = ddi_hold_devi_by_instance(dsp->dr_major, dlp->attach_req.dl_ppa, E_DDI_HOLD_DEVI_NOATTACH); } mutex_enter(&dsp->dr_lock); dsp->dr_dip[dsp->dr_nfirst] = dip; INCR(dsp->dr_nfirst); /* * Check if ring buffer is full. If so, assert in debug * kernel and produce a warning in non-debug kernel. */ ASSERT(dsp->dr_nfirst != dsp->dr_nlast); if (dsp->dr_nfirst == dsp->dr_nlast) { cmn_err(CE_WARN, "drcompat: internal buffer full"); } mutex_exit(&dsp->dr_lock); break; default: break; } putnext(q, mp); return (0); }