#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;
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);
if ((err = softmac_capab_send(slp, B_FALSE)) != 0)
return (err);
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) {
if (softmac->smac_capab_flags & MAC_CAPAB_HCKSUM)
size += sizeof (dl_capability_sub_t) +
sizeof (dl_capab_hcksum_t);
if (size == 0)
return (0);
}
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;
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)) {
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;
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;
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;
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);
}
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);
}