root/usr/src/uts/common/io/softmac/softmac_capab.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2022 Garrett D'Amore
 */

#include <sys/types.h>
#include <sys/mac.h>
#include <sys/softmac_impl.h>

typedef struct softmac_capab_ops {
        int     (*sc_hcksum_ack)(void *, t_uscalar_t);
        int     (*sc_zcopy_ack)(void *, t_uscalar_t);
} softmac_capab_ops_t;

static int      dl_capab(ldi_handle_t, mblk_t **);
static int      softmac_fill_hcksum_ack(void *, t_uscalar_t);
static int      softmac_fill_zcopy_ack(void *, t_uscalar_t);
static int      softmac_adv_hcksum_ack(void *, t_uscalar_t);
static int      softmac_adv_zcopy_ack(void *, t_uscalar_t);
static int      softmac_enable_hcksum_ack(void *, t_uscalar_t);
static int      softmac_capab_send(softmac_lower_t *, boolean_t);
static int      i_capab_ack(mblk_t *, queue_t *, softmac_capab_ops_t *, void *);
static int      i_capab_id_ack(mblk_t *, dl_capability_sub_t *, queue_t *,
    softmac_capab_ops_t *, void *);
static int      i_capab_sub_ack(mblk_t *, dl_capability_sub_t *, queue_t *,
    softmac_capab_ops_t *, void *);
static int      i_capab_hcksum_ack(dl_capab_hcksum_t *, queue_t *,
    softmac_capab_ops_t *, void *);
static int      i_capab_zcopy_ack(dl_capab_zerocopy_t *, queue_t *,
    softmac_capab_ops_t *, void *);
static int      i_capab_hcksum_verify(dl_capab_hcksum_t *, queue_t *);
static int      i_capab_zcopy_verify(dl_capab_zerocopy_t *, queue_t *);

static softmac_capab_ops_t softmac_fill_capab_ops =
{
        softmac_fill_hcksum_ack,
        softmac_fill_zcopy_ack,
};

static softmac_capab_ops_t softmac_adv_capab_ops =
{
        softmac_adv_hcksum_ack,
        softmac_adv_zcopy_ack,
};

static softmac_capab_ops_t softmac_enable_capab_ops =
{
        softmac_enable_hcksum_ack,
        NULL,
};

int
softmac_fill_capab(ldi_handle_t lh, softmac_t *softmac)
{
        mblk_t                  *mp = NULL;
        union DL_primitives     *prim;
        int                     err = 0;

        if ((err = dl_capab(lh, &mp)) != 0)
                goto exit;

        prim = (union DL_primitives *)mp->b_rptr;
        if (prim->dl_primitive == DL_ERROR_ACK) {
                err = -1;
                goto exit;
        }

        err = i_capab_ack(mp, NULL, &softmac_fill_capab_ops, softmac);

exit:
        freemsg(mp);
        return (err);
}

static int
dl_capab(ldi_handle_t lh, mblk_t **mpp)
{
        dl_capability_req_t     *capb;
        union DL_primitives     *dl_prim;
        mblk_t                  *mp;
        int                     err;

        if ((mp = allocb(sizeof (dl_capability_req_t), BPRI_MED)) == NULL)
                return (ENOMEM);
        mp->b_datap->db_type = M_PROTO;

        capb = (dl_capability_req_t *)mp->b_wptr;
        mp->b_wptr += sizeof (dl_capability_req_t);
        bzero(mp->b_rptr, sizeof (dl_capability_req_t));
        capb->dl_primitive = DL_CAPABILITY_REQ;

        (void) ldi_putmsg(lh, mp);
        if ((err = ldi_getmsg(lh, &mp, (timestruc_t *)NULL)) != 0)
                return (err);

        dl_prim = (union DL_primitives *)mp->b_rptr;
        switch (dl_prim->dl_primitive) {
        case DL_CAPABILITY_ACK:
                if (MBLKL(mp) < DL_CAPABILITY_ACK_SIZE) {
                        printf("dl_capability: DL_CAPABILITY_ACK "
                            "protocol err\n");
                        break;
                }
                *mpp = mp;
                return (0);

        case DL_ERROR_ACK:
                if (MBLKL(mp) < DL_ERROR_ACK_SIZE) {
                        printf("dl_capability: DL_ERROR_ACK protocol err\n");
                        break;
                }
                if (((dl_error_ack_t *)dl_prim)->dl_error_primitive !=
                    DL_CAPABILITY_REQ) {
                        printf("dl_capability: DL_ERROR_ACK rtnd prim %u\n",
                            ((dl_error_ack_t *)dl_prim)->dl_error_primitive);
                        break;
                }

                *mpp = mp;
                return (0);

        default:
                printf("dl_capability: bad ACK header %u\n",
                    dl_prim->dl_primitive);
                break;
        }

        freemsg(mp);
        return (-1);
}

static int
softmac_fill_hcksum_ack(void *arg, t_uscalar_t flags)
{
        softmac_t       *softmac = (softmac_t *)arg;

        /*
         * There are two types of acks we process here:
         * 1. acks in reply to a (first form) generic capability req
         *    (no ENABLE flag set)
         * 2. acks in reply to a ENABLE capability req.
         *    (ENABLE flag set)
         * Only the first type should be expected here.
         */

        if (flags & HCKSUM_ENABLE) {
                cmn_err(CE_WARN, "softmac_fill_hcksum_ack: unexpected "
                    "HCKSUM_ENABLE flag in hardware checksum capability");
        } else if (flags & (HCKSUM_INET_PARTIAL | HCKSUM_INET_FULL_V4 |
            HCKSUM_INET_FULL_V6 | HCKSUM_IPHDRCKSUM)) {
                softmac->smac_capab_flags |= MAC_CAPAB_HCKSUM;
                softmac->smac_hcksum_txflags = flags;
        }
        return (0);
}

static int
softmac_fill_zcopy_ack(void *arg, t_uscalar_t flags)
{
        softmac_t       *softmac = (softmac_t *)arg;

        ASSERT(flags == DL_CAPAB_VMSAFE_MEM);
        softmac->smac_capab_flags &= (~MAC_CAPAB_NO_ZCOPY);
        return (0);
}

int
softmac_capab_enable(softmac_lower_t *slp)
{
        softmac_t       *softmac = slp->sl_softmac;
        int             err;

        if (softmac->smac_no_capability_req)
                return (0);

        /*
         * Send DL_CAPABILITY_REQ to get capability advertisement.
         */
        if ((err = softmac_capab_send(slp, B_FALSE)) != 0)
                return (err);

        /*
         * Send DL_CAPABILITY_REQ to enable specific capabilities.
         */
        if ((err = softmac_capab_send(slp, B_TRUE)) != 0)
                return (err);

        return (0);
}

static int
softmac_capab_send(softmac_lower_t *slp, boolean_t enable)
{
        softmac_t               *softmac;
        dl_capability_req_t     *capb;
        dl_capability_sub_t     *subcapb;
        mblk_t                  *reqmp, *ackmp;
        int                     err;
        size_t                  size = 0;

        softmac = slp->sl_softmac;

        if (enable) {
                /* No need to enable DL_CAPAB_ZEROCOPY */
                if (softmac->smac_capab_flags & MAC_CAPAB_HCKSUM)
                        size += sizeof (dl_capability_sub_t) +
                            sizeof (dl_capab_hcksum_t);

                if (size == 0)
                        return (0);
        }

        /*
         * Create DL_CAPABILITY_REQ message and send it down
         */
        reqmp = allocb(sizeof (dl_capability_req_t) + size, BPRI_MED);
        if (reqmp == NULL)
                return (ENOMEM);

        bzero(reqmp->b_rptr, sizeof (dl_capability_req_t) + size);

        DB_TYPE(reqmp) = M_PROTO;
        reqmp->b_wptr = reqmp->b_rptr + sizeof (dl_capability_req_t) + size;

        capb = (dl_capability_req_t *)reqmp->b_rptr;
        capb->dl_primitive = DL_CAPABILITY_REQ;

        if (!enable)
                goto output;

        capb->dl_sub_offset = sizeof (dl_capability_req_t);

        if (softmac->smac_capab_flags & MAC_CAPAB_HCKSUM) {
                dl_capab_hcksum_t *hck_subcapp;

                size = sizeof (dl_capability_sub_t) +
                    sizeof (dl_capab_hcksum_t);
                capb->dl_sub_length += size;

                subcapb = (dl_capability_sub_t *)(capb + 1);
                subcapb->dl_cap = DL_CAPAB_HCKSUM;
                subcapb->dl_length = sizeof (dl_capab_hcksum_t);
                hck_subcapp = (dl_capab_hcksum_t *)(subcapb + 1);
                hck_subcapp->hcksum_version = HCKSUM_VERSION_1;
                hck_subcapp->hcksum_txflags =
                    softmac->smac_hcksum_txflags | HCKSUM_ENABLE;
        }

output:
        err = softmac_proto_tx(slp, reqmp, &ackmp);
        if (err == 0) {
                if (enable) {
                        err = i_capab_ack(ackmp, NULL,
                            &softmac_enable_capab_ops, softmac);
                } else {
                        err = i_capab_ack(ackmp, NULL,
                            &softmac_adv_capab_ops, softmac);
                }
        }
        freemsg(ackmp);

        return (err);
}

static int
softmac_adv_hcksum_ack(void *arg, t_uscalar_t flags)
{
        softmac_t       *softmac = (softmac_t *)arg;

        /*
         * There are two types of acks we process here:
         * 1. acks in reply to a (first form) generic capability req
         *    (no ENABLE flag set)
         * 2. acks in reply to a ENABLE capability req.
         *    (ENABLE flag set)
         * Only the first type should be expected here.
         */

        if (flags & HCKSUM_ENABLE) {
                cmn_err(CE_WARN, "softmac_adv_hcksum_ack: unexpected "
                    "HCKSUM_ENABLE flag in hardware checksum capability");
                return (-1);
        } else if (flags & (HCKSUM_INET_PARTIAL | HCKSUM_INET_FULL_V4 |
            HCKSUM_INET_FULL_V6 | HCKSUM_IPHDRCKSUM)) {
                /*
                 * The acknowledgement should be the same as we got when
                 * the softmac is created.
                 */
                if (!(softmac->smac_capab_flags & MAC_CAPAB_HCKSUM)) {
                        ASSERT(B_FALSE);
                        return (-1);
                }
                if (softmac->smac_hcksum_txflags != flags) {
                        ASSERT(B_FALSE);
                        return (-1);
                }
        }

        return (0);
}

static int
softmac_adv_zcopy_ack(void *arg, t_uscalar_t flags)
{
        softmac_t       *softmac = (softmac_t *)arg;

        /*
         * The acknowledgement should be the same as we got when
         * the softmac is created.
         */
        ASSERT(flags == DL_CAPAB_VMSAFE_MEM);
        if (softmac->smac_capab_flags & MAC_CAPAB_NO_ZCOPY) {
                ASSERT(B_FALSE);
                return (-1);
        }

        return (0);
}

static int
softmac_enable_hcksum_ack(void *arg, t_uscalar_t flags)
{
        softmac_t       *softmac = (softmac_t *)arg;

        /*
         * There are two types of acks we process here:
         * 1. acks in reply to a (first form) generic capability req
         *    (no ENABLE flag set)
         * 2. acks in reply to a ENABLE capability req.
         *    (ENABLE flag set)
         * Only the second type should be expected here.
         */

        if (flags & HCKSUM_ENABLE) {
                if ((flags & ~HCKSUM_ENABLE) != softmac->smac_hcksum_txflags) {
                        cmn_err(CE_WARN, "softmac_enable_hcksum_ack: unexpected"
                            " hardware capability flag value 0x%x", flags);
                        return (-1);
                }
        } else {
                cmn_err(CE_WARN, "softmac_enable_hcksum_ack: "
                    "hardware checksum flag HCKSUM_ENABLE is not set");
                return (-1);
        }

        return (0);
}

static int
i_capab_ack(mblk_t *mp, queue_t *q, softmac_capab_ops_t *op, void *arg)
{
        union DL_primitives     *prim;
        dl_capability_ack_t     *cap;
        dl_capability_sub_t     *sub, *end;
        int                     err = 0;

        prim = (union DL_primitives *)mp->b_rptr;
        ASSERT(prim->dl_primitive == DL_CAPABILITY_ACK);

        cap = (dl_capability_ack_t *)prim;
        if (cap->dl_sub_length == 0)
                goto exit;

        /* Is dl_sub_length correct? */
        if ((sizeof (*cap) + cap->dl_sub_length) > MBLKL(mp)) {
                err = EINVAL;
                goto exit;
        }

        sub = (dl_capability_sub_t *)((caddr_t)cap + cap->dl_sub_offset);
        end = (dl_capability_sub_t *)((caddr_t)cap + cap->dl_sub_length
            - sizeof (*sub));
        for (; (sub <= end) && (err == 0); ) {
                switch (sub->dl_cap) {
                case DL_CAPAB_ID_WRAPPER:
                        err = i_capab_id_ack(mp, sub, q, op, arg);
                        break;
                default:
                        err = i_capab_sub_ack(mp, sub, q, op, arg);
                        break;
                }
                sub = (dl_capability_sub_t *)((caddr_t)sub + sizeof (*sub)
                    + sub->dl_length);
        }

exit:
        return (err);
}

static int
i_capab_id_ack(mblk_t *mp, dl_capability_sub_t *outers,
    queue_t *q, softmac_capab_ops_t *op, void *arg)
{
        dl_capab_id_t           *capab_id;
        dl_capability_sub_t     *inners;
        caddr_t                 capend;
        int                     err = EINVAL;

        ASSERT(outers->dl_cap == DL_CAPAB_ID_WRAPPER);

        capend = (caddr_t)(outers + 1) + outers->dl_length;
        if (capend > (caddr_t)mp->b_wptr) {
                cmn_err(CE_WARN, "i_capab_id_ack: malformed "
                    "sub-capability too long");
                return (err);
        }

        capab_id = (dl_capab_id_t *)(outers + 1);

        if (outers->dl_length < sizeof (*capab_id) ||
            (inners = &capab_id->id_subcap,
            inners->dl_length > (outers->dl_length - sizeof (*inners)))) {
                cmn_err(CE_WARN, "i_capab_id_ack: malformed "
                    "encapsulated capab type %d too long",
                    inners->dl_cap);
                return (err);
        }

        if ((q != NULL) && (!dlcapabcheckqid(&capab_id->id_mid, q))) {
                cmn_err(CE_WARN, "i_capab_id_ack: pass-thru module(s) "
                    "detected, discarding capab type %d", inners->dl_cap);
                return (err);
        }

        /* Process the encapsulated sub-capability */
        return (i_capab_sub_ack(mp, inners, q, op, arg));
}

static int
i_capab_sub_ack(mblk_t *mp, dl_capability_sub_t *sub, queue_t *q,
    softmac_capab_ops_t *op, void *arg)
{
        caddr_t                 capend;
        dl_capab_hcksum_t       *hcksum;
        dl_capab_zerocopy_t     *zcopy;
        int                     err = 0;

        capend = (caddr_t)(sub + 1) + sub->dl_length;
        if (capend > (caddr_t)mp->b_wptr) {
                cmn_err(CE_WARN, "i_capab_sub_ack: "
                    "malformed sub-capability too long");
                return (EINVAL);
        }

        switch (sub->dl_cap) {
        case DL_CAPAB_HCKSUM:
                hcksum = (dl_capab_hcksum_t *)(sub + 1);
                err = i_capab_hcksum_ack(hcksum, q, op, arg);
                break;

        case DL_CAPAB_ZEROCOPY:
                zcopy = (dl_capab_zerocopy_t *)(sub + 1);
                err = i_capab_zcopy_ack(zcopy, q, op, arg);
                break;

        default:
                cmn_err(CE_WARN, "i_capab_sub_ack: unknown capab type %d",
                    sub->dl_cap);
                err = EINVAL;
        }

        return (err);
}

static int
i_capab_hcksum_ack(dl_capab_hcksum_t *hcksum, queue_t *q,
    softmac_capab_ops_t *op, void *arg)
{
        t_uscalar_t             flags;
        int                     err = 0;

        if ((err = i_capab_hcksum_verify(hcksum, q)) != 0)
                return (err);

        flags = hcksum->hcksum_txflags;

        if (!(flags & (HCKSUM_INET_PARTIAL | HCKSUM_INET_FULL_V4 |
            HCKSUM_INET_FULL_V6 | HCKSUM_IPHDRCKSUM | HCKSUM_ENABLE))) {
                cmn_err(CE_WARN, "i_capab_hcksum_ack: invalid "
                    "hardware checksum capability flags 0x%x", flags);
                return (EINVAL);
        }

        if (op->sc_hcksum_ack)
                return (op->sc_hcksum_ack(arg, flags));
        else {
                cmn_err(CE_WARN, "i_capab_hcksum_ack: unexpected hardware "
                    "checksum acknowledgement");
                return (EINVAL);
        }
}

static int
i_capab_zcopy_ack(dl_capab_zerocopy_t *zcopy, queue_t *q,
    softmac_capab_ops_t *op, void *arg)
{
        t_uscalar_t             flags;
        int                     err = 0;

        if ((err = i_capab_zcopy_verify(zcopy, q)) != 0)
                return (err);

        flags = zcopy->zerocopy_flags;
        if (!(flags & DL_CAPAB_VMSAFE_MEM)) {
                cmn_err(CE_WARN, "i_capab_zcopy_ack: invalid zcopy capability "
                    "flags 0x%x", flags);
                return (EINVAL);
        }
        if (op->sc_zcopy_ack)
                return (op->sc_zcopy_ack(arg, flags));
        else {
                cmn_err(CE_WARN, "i_capab_zcopy_ack: unexpected zcopy "
                    "acknowledgement");
                return (EINVAL);
        }
}

static int
i_capab_hcksum_verify(dl_capab_hcksum_t *hcksum, queue_t *q)
{
        if (hcksum->hcksum_version != HCKSUM_VERSION_1) {
                cmn_err(CE_WARN, "i_capab_hcksum_verify: "
                    "unsupported hardware checksum capability (version %d, "
                    "expected %d)", hcksum->hcksum_version, HCKSUM_VERSION_1);
                return (-1);
        }

        if ((q != NULL) && !dlcapabcheckqid(&hcksum->hcksum_mid, q)) {
                cmn_err(CE_WARN, "i_capab_hcksum_verify: unexpected pass-thru "
                    "module detected; hardware checksum capability discarded");
                return (-1);
        }
        return (0);
}

static int
i_capab_zcopy_verify(dl_capab_zerocopy_t *zcopy, queue_t *q)
{
        if (zcopy->zerocopy_version != ZEROCOPY_VERSION_1) {
                cmn_err(CE_WARN, "i_capab_zcopy_verify: unsupported zcopy "
                    "capability (version %d, expected %d)",
                    zcopy->zerocopy_version, ZEROCOPY_VERSION_1);
                return (-1);
        }

        if ((q != NULL) && !dlcapabcheckqid(&zcopy->zerocopy_mid, q)) {
                cmn_err(CE_WARN, "i_capab_zcopy_verify: unexpected pass-thru "
                    "module detected; zcopy checksum capability discarded");
                return (-1);
        }
        return (0);
}