/* * 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 #include #include 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); }