root/usr/src/uts/common/io/softmac/softmac_pkt.c
/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/strsubr.h>
#include <inet/led.h>
#include <sys/softmac_impl.h>

mblk_t *
softmac_m_tx(void *arg, mblk_t *mp)
{
        queue_t *wq = ((softmac_t *)arg)->smac_lower->sl_wq;

        /*
         * Optimize for the most common case.
         */
        if (mp->b_next == NULL) {
                if (!SOFTMAC_CANPUTNEXT(wq))
                        return (mp);

                mp->b_flag |= MSGNOLOOP;
                putnext(wq, mp);
                return (NULL);
        }

        while (mp != NULL) {
                mblk_t *next = mp->b_next;

                if (!SOFTMAC_CANPUTNEXT(wq))
                        break;
                mp->b_next = NULL;
                mp->b_flag |= MSGNOLOOP;
                putnext(wq, mp);
                mp = next;
        }
        return (mp);
}

void
softmac_rput_process_data(softmac_lower_t *slp, mblk_t *mp)
{
        /*
         * When packets arrive, the softmac might not be fully started.
         */
        ASSERT((slp->sl_softmac != NULL));
        ASSERT((mp->b_next == NULL) && (mp->b_prev == NULL));

        if (DB_REF(mp) > 1) {
                mblk_t *tmp;
                uint32_t start, stuff, end, value, flags;

                if ((tmp = copymsg(mp)) == NULL) {
                        cmn_err(CE_WARN, "softmac_rput_process_data: "
                            "copymsg failed");
                        goto failed;
                }
                mac_hcksum_get(mp, &start, &stuff, &end, &value, &flags);
                mac_hcksum_set(tmp, start, stuff, end, value, flags);
                freemsg(mp);
                mp = tmp;
        }

        mac_rx(slp->sl_softmac->smac_mh, NULL, mp);
        return;

failed:
        freemsg(mp);
}

#define ACKTIMEOUT      (10 * hz)

static int
dlpi_get_errno(t_uscalar_t error, t_uscalar_t unix_errno)
{
        return (error == DL_SYSERR ? unix_errno : EINVAL);
}

int
softmac_output(softmac_lower_t *slp, mblk_t *mp, t_uscalar_t dl_prim,
    t_uscalar_t ack, mblk_t **mpp)
{
        union DL_primitives     *dlp;
        mac_perim_handle_t      mph;
        int                     err = 0;

        mac_perim_enter_by_mh(slp->sl_softmac->smac_mh, &mph);

        ASSERT(!slp->sl_pending_ioctl);
        ASSERT(slp->sl_pending_prim == DL_PRIM_INVAL);

        /*
         * Record the pending DLPI primitive.
         */
        mutex_enter(&slp->sl_mutex);
        slp->sl_pending_prim = dl_prim;
        mutex_exit(&slp->sl_mutex);

        putnext(slp->sl_wq, mp);

        mutex_enter(&slp->sl_mutex);
        while (slp->sl_pending_prim != DL_PRIM_INVAL) {
                if (cv_reltimedwait(&slp->sl_cv, &slp->sl_mutex, ACKTIMEOUT,
                    TR_CLOCK_TICK) == -1)
                        break;
        }

        mp = slp->sl_ack_mp;
        slp->sl_ack_mp = NULL;

        /*
         * If we timed out, sl_ack_mp will still be NULL, but sl_pending_prim
         * won't be set to DL_PRIM_INVAL.
         */
        ASSERT(mp != NULL || slp->sl_pending_prim != DL_PRIM_INVAL);

        slp->sl_pending_prim = DL_PRIM_INVAL;
        mutex_exit(&slp->sl_mutex);

        if (mp != NULL) {
                dlp = (union DL_primitives *)mp->b_rptr;

                if (dlp->dl_primitive == DL_ERROR_ACK) {
                        err = dlpi_get_errno(dlp->error_ack.dl_errno,
                            dlp->error_ack.dl_unix_errno);
                } else {
                        ASSERT(dlp->dl_primitive == ack);
                }
        } else {
                err = ENOMSG;
        }

        if (mpp != NULL)
                *mpp = mp;
        else
                freemsg(mp);

        mac_perim_exit(mph);
        return (err);
}

void
softmac_ioctl_tx(softmac_lower_t *slp, mblk_t *mp, mblk_t **mpp)
{
        mac_perim_handle_t      mph;

        mac_perim_enter_by_mh(slp->sl_softmac->smac_mh, &mph);

        /*
         * Record that ioctl processing is currently in progress.
         */
        mutex_enter(&slp->sl_mutex);
        slp->sl_pending_ioctl = B_TRUE;
        mutex_exit(&slp->sl_mutex);

        putnext(slp->sl_wq, mp);

        mutex_enter(&slp->sl_mutex);
        while (slp->sl_pending_ioctl)
                cv_wait(&slp->sl_cv, &slp->sl_mutex);
        mp = slp->sl_ack_mp;
        slp->sl_ack_mp = NULL;
        mutex_exit(&slp->sl_mutex);

        ASSERT(mpp != NULL && mp != NULL);
        *mpp = mp;

        mac_perim_exit(mph);
}

int
softmac_mexchange_error_ack(mblk_t **mpp, t_uscalar_t error_primitive,
        t_uscalar_t error, t_uscalar_t unix_errno)
{
        union DL_primitives *dlp;

        if ((*mpp = mexchange(NULL, *mpp, sizeof (dl_error_ack_t), M_PCPROTO,
            DL_ERROR_ACK)) == NULL)
                return (ENOMEM);

        dlp = (union DL_primitives *)(*mpp)->b_rptr;
        dlp->error_ack.dl_error_primitive = error_primitive;
        dlp->error_ack.dl_errno = error;
        dlp->error_ack.dl_unix_errno = unix_errno;

        return (0);
}

int
softmac_proto_tx(softmac_lower_t *slp, mblk_t *mp, mblk_t **mpp)
{
        int err = 0;
        t_uscalar_t dl_prim;

        dl_prim = ((union DL_primitives *)mp->b_rptr)->dl_primitive;

        ASSERT(slp->sl_softmac != NULL);

        switch (dl_prim) {
        case DL_ENABMULTI_REQ:
        case DL_DISABMULTI_REQ:
        case DL_SET_PHYS_ADDR_REQ:
        case DL_UNBIND_REQ:
        case DL_UDQOS_REQ:
        case DL_PROMISCON_REQ:
        case DL_PROMISCOFF_REQ:
                err = softmac_output(slp, mp, dl_prim, DL_OK_ACK, mpp);
                break;
        case DL_BIND_REQ:
                err = softmac_output(slp, mp, dl_prim, DL_BIND_ACK, mpp);
                break;
        case DL_NOTIFY_REQ:
                err = softmac_output(slp, mp, dl_prim, DL_NOTIFY_ACK, mpp);
                break;
        case DL_CONTROL_REQ:
                err = softmac_output(slp, mp, dl_prim, DL_CONTROL_ACK, mpp);
                break;
        case DL_CAPABILITY_REQ:
                err = softmac_output(slp, mp, dl_prim, DL_CAPABILITY_ACK, mpp);
                break;
        default:
                if (mpp != NULL) {
                        *mpp = mp;
                        err = softmac_mexchange_error_ack(mpp, dl_prim,
                            DL_UNSUPPORTED, 0);
                }
                break;
        }
        return (err);
}