sundlpi.c revision 3a18a1c198c1b0bc47599d4a37028047fc8358cc
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Common Sun DLPI routines.
*/
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/byteorder.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/dlpi.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunldi.h>
#include <sys/cmn_err.h>
void
dlbindack(
queue_t *wq,
mblk_t *mp,
t_scalar_t sap,
const void *addrp,
t_uscalar_t addrlen,
t_uscalar_t maxconind,
t_uscalar_t xidtest)
{
union DL_primitives *dlp;
size_t size;
size = sizeof (dl_bind_ack_t) + addrlen;
if ((mp = mexchange(wq, mp, size, M_PCPROTO, DL_BIND_ACK)) == NULL)
return;
dlp = (union DL_primitives *)mp->b_rptr;
dlp->bind_ack.dl_sap = sap;
dlp->bind_ack.dl_addr_length = addrlen;
dlp->bind_ack.dl_addr_offset = sizeof (dl_bind_ack_t);
dlp->bind_ack.dl_max_conind = maxconind;
dlp->bind_ack.dl_xidtest_flg = xidtest;
if (addrlen != 0)
bcopy(addrp, mp->b_rptr + sizeof (dl_bind_ack_t), addrlen);
qreply(wq, mp);
}
void
dlokack(
queue_t *wq,
mblk_t *mp,
t_uscalar_t correct_primitive)
{
union DL_primitives *dlp;
if ((mp = mexchange(wq, mp, sizeof (dl_ok_ack_t), M_PCPROTO,
DL_OK_ACK)) == NULL)
return;
dlp = (union DL_primitives *)mp->b_rptr;
dlp->ok_ack.dl_correct_primitive = correct_primitive;
qreply(wq, mp);
}
void
dlerrorack(
queue_t *wq,
mblk_t *mp,
t_uscalar_t error_primitive,
t_uscalar_t error,
t_uscalar_t unix_errno)
{
union DL_primitives *dlp;
if ((mp = mexchange(wq, mp, sizeof (dl_error_ack_t), M_PCPROTO,
DL_ERROR_ACK)) == NULL)
return;
dlp = (union DL_primitives *)mp->b_rptr;
dlp->error_ack.dl_error_primitive = error_primitive;
dlp->error_ack.dl_errno = error;
dlp->error_ack.dl_unix_errno = unix_errno;
qreply(wq, mp);
}
void
dluderrorind(
queue_t *wq,
mblk_t *mp,
const void *addrp,
t_uscalar_t addrlen,
t_uscalar_t error,
t_uscalar_t unix_errno)
{
union DL_primitives *dlp;
size_t size;
size = sizeof (dl_uderror_ind_t) + addrlen;
if ((mp = mexchange(wq, mp, size, M_PCPROTO, DL_UDERROR_IND)) == NULL)
return;
dlp = (union DL_primitives *)mp->b_rptr;
dlp->uderror_ind.dl_dest_addr_length = addrlen;
dlp->uderror_ind.dl_dest_addr_offset = sizeof (dl_uderror_ind_t);
dlp->uderror_ind.dl_unix_errno = unix_errno;
dlp->uderror_ind.dl_errno = error;
if (addrlen != 0)
bcopy(addrp, mp->b_rptr + sizeof (dl_uderror_ind_t), addrlen);
qreply(wq, mp);
}
void
dlphysaddrack(
queue_t *wq,
mblk_t *mp,
const void *addrp,
t_uscalar_t len)
{
union DL_primitives *dlp;
size_t size;
size = sizeof (dl_phys_addr_ack_t) + len;
if ((mp = mexchange(wq, mp, size, M_PCPROTO, DL_PHYS_ADDR_ACK)) == NULL)
return;
dlp = (union DL_primitives *)mp->b_rptr;
dlp->physaddr_ack.dl_addr_length = len;
dlp->physaddr_ack.dl_addr_offset = sizeof (dl_phys_addr_ack_t);
if (len != 0)
bcopy(addrp, mp->b_rptr + sizeof (dl_phys_addr_ack_t), len);
qreply(wq, mp);
}
void
dlcapabsetqid(dl_mid_t *idp, const queue_t *q)
{
#ifndef _LP64
idp->mid[0] = (t_uscalar_t)q;
#else
idp->mid[0] = (t_uscalar_t)BMASK_32((uint64_t)q);
idp->mid[1] = (t_uscalar_t)BMASK_32(((uint64_t)q) >> 32);
#endif
}
boolean_t
dlcapabcheckqid(const dl_mid_t *idp, const queue_t *q)
{
#ifndef _LP64
return ((queue_t *)(idp->mid[0]) == q);
#else
return ((queue_t *)
((uint64_t)idp->mid[0] | ((uint64_t)idp->mid[1] << 32)) == q);
#endif
}
void
dlnotifyack(
queue_t *wq,
mblk_t *mp,
uint32_t notifications)
{
union DL_primitives *dlp;
if ((mp = mexchange(wq, mp, sizeof (dl_notify_ack_t), M_PROTO,
DL_NOTIFY_ACK)) == NULL)
return;
dlp = (union DL_primitives *)mp->b_rptr;
dlp->notify_ack.dl_notifications = notifications;
qreply(wq, mp);
}
static int
dl_op(ldi_handle_t lh, mblk_t **mpp, t_uscalar_t expprim, size_t minlen,
dl_error_ack_t *dleap, timestruc_t *tvp)
{
int err;
size_t len;
mblk_t *mp = *mpp;
t_uscalar_t reqprim, ackprim, ackreqprim;
union DL_primitives *dlp;
reqprim = ((union DL_primitives *)mp->b_rptr)->dl_primitive;
(void) ldi_putmsg(lh, mp);
switch (err = ldi_getmsg(lh, &mp, tvp)) {
case 0:
break;
case ETIME:
cmn_err(CE_NOTE, "!dl_op: timed out waiting for %s to %s",
dl_primstr(reqprim), dl_primstr(expprim));
return (ETIME);
default:
cmn_err(CE_NOTE, "!dl_op: ldi_getmsg() for %s failed: %d",
dl_primstr(expprim), err);
return (err);
}
len = MBLKL(mp);
if (len < sizeof (t_uscalar_t)) {
cmn_err(CE_NOTE, "!dl_op: received runt DLPI message");
freemsg(mp);
return (EBADMSG);
}
dlp = (union DL_primitives *)mp->b_rptr;
ackprim = dlp->dl_primitive;
if (ackprim == expprim) {
if (len < minlen)
goto runt;
if (ackprim == DL_OK_ACK) {
if (dlp->ok_ack.dl_correct_primitive != reqprim) {
ackreqprim = dlp->ok_ack.dl_correct_primitive;
goto mixup;
}
}
*mpp = mp;
return (0);
}
if (ackprim == DL_ERROR_ACK) {
if (len < DL_ERROR_ACK_SIZE)
goto runt;
if (dlp->error_ack.dl_error_primitive != reqprim) {
ackreqprim = dlp->error_ack.dl_error_primitive;
goto mixup;
}
/*
* Return a special error code (ENOTSUP) indicating that the
* caller has returned DL_ERROR_ACK. Callers that want more
* details an pass a non-NULL dleap.
*/
if (dleap != NULL)
*dleap = dlp->error_ack;
freemsg(mp);
return (ENOTSUP);
}
cmn_err(CE_NOTE, "!dl_op: expected %s but received %s",
dl_primstr(expprim), dl_primstr(ackprim));
freemsg(mp);
return (EBADMSG);
runt:
cmn_err(CE_NOTE, "!dl_op: received runt %s", dl_primstr(ackprim));
freemsg(mp);
return (EBADMSG);
mixup:
cmn_err(CE_NOTE, "!dl_op: received %s for %s instead of %s",
dl_primstr(ackprim), dl_primstr(ackreqprim), dl_primstr(reqprim));
freemsg(mp);
return (EBADMSG);
}
/*
* Send a DL_ATTACH_REQ for `ppa' over `lh' and wait for the response.
*
* Returns an errno; ENOTSUP indicates a DL_ERROR_ACK response (and the
* caller can get the contents by passing a non-NULL `dleap').
*/
int
dl_attach(ldi_handle_t lh, int ppa, dl_error_ack_t *dleap)
{
mblk_t *mp;
int err;
mp = mexchange(NULL, NULL, DL_ATTACH_REQ_SIZE, M_PROTO, DL_ATTACH_REQ);
if (mp == NULL)
return (ENOMEM);
((dl_attach_req_t *)mp->b_rptr)->dl_ppa = ppa;
err = dl_op(lh, &mp, DL_OK_ACK, DL_OK_ACK_SIZE, dleap, NULL);
if (err == 0)
freemsg(mp);
return (err);
}
/*
* Send a DL_BIND_REQ for `sap' over `lh' and wait for the response.
*
* Returns an errno; ENOTSUP indicates a DL_ERROR_ACK response (and the
* caller can get the contents by passing a non-NULL `dleap').
*/
int
dl_bind(ldi_handle_t lh, uint_t sap, dl_error_ack_t *dleap)
{
dl_bind_req_t *dlbrp;
dl_bind_ack_t *dlbap;
mblk_t *mp;
int err;
mp = mexchange(NULL, NULL, DL_BIND_REQ_SIZE, M_PROTO, DL_BIND_REQ);
if (mp == NULL)
return (ENOMEM);
dlbrp = (dl_bind_req_t *)mp->b_rptr;
dlbrp->dl_sap = sap;
dlbrp->dl_conn_mgmt = 0;
dlbrp->dl_max_conind = 0;
dlbrp->dl_xidtest_flg = 0;
dlbrp->dl_service_mode = DL_CLDLS;
err = dl_op(lh, &mp, DL_BIND_ACK, DL_BIND_ACK_SIZE, dleap, NULL);
if (err == 0) {
dlbap = (dl_bind_ack_t *)mp->b_rptr;
if (dlbap->dl_sap != sap) {
cmn_err(CE_NOTE, "!dl_bind: DL_BIND_ACK: bad sap %u",
dlbap->dl_sap);
err = EPROTO;
}
freemsg(mp);
}
return (err);
}
/*
* Send a DL_PHYS_ADDR_REQ over `lh' and wait for the response. The caller
* must set `*physlenp' to the size of `physaddr' (both of which must be
* non-NULL); upon success they will be updated to contain the actual physical
* address and length.
*
* Returns an errno; ENOTSUP indicates a DL_ERROR_ACK response (and the
* caller can get the contents by passing a non-NULL `dleap').
*/
int
dl_phys_addr(ldi_handle_t lh, uchar_t *physaddr, size_t *physlenp,
dl_error_ack_t *dleap)
{
dl_phys_addr_ack_t *dlpap;
mblk_t *mp;
int err;
t_uscalar_t paddrlen, paddroff;
timestruc_t tv;
mp = mexchange(NULL, NULL, DL_PHYS_ADDR_REQ_SIZE, M_PROTO,
DL_PHYS_ADDR_REQ);
if (mp == NULL)
return (ENOMEM);
((dl_phys_addr_req_t *)mp->b_rptr)->dl_addr_type = DL_CURR_PHYS_ADDR;
/*
* In case some provider doesn't implement or NAK the
* request, just wait for 15 seconds.
*/
tv.tv_sec = 15;
tv.tv_nsec = 0;
err = dl_op(lh, &mp, DL_PHYS_ADDR_ACK, DL_PHYS_ADDR_ACK_SIZE, dleap,
&tv);
if (err == 0) {
dlpap = (dl_phys_addr_ack_t *)mp->b_rptr;
paddrlen = dlpap->dl_addr_length;
paddroff = dlpap->dl_addr_offset;
if (paddroff == 0 || paddrlen == 0 || paddrlen > *physlenp ||
!MBLKIN(mp, paddroff, paddrlen)) {
cmn_err(CE_NOTE, "!dl_phys_addr: DL_PHYS_ADDR_ACK: "
"bad length/offset %d/%d", paddrlen, paddroff);
err = EBADMSG;
} else {
bcopy(mp->b_rptr + paddroff, physaddr, paddrlen);
*physlenp = paddrlen;
}
freemsg(mp);
}
return (err);
}
/*
* Send a DL_INFO_REQ over `lh' and wait for the response. The caller must
* pass a non-NULL `dliap', which upon success will contain the dl_info_ack_t
* from the provider. The caller may optionally get the provider's physical
* address by passing a non-NULL `physaddr' and setting `*physlenp' to its
* size; upon success they will be updated to contain the actual physical
* address and its length.
*
* Returns an errno; ENOTSUP indicates a DL_ERROR_ACK response (and the
* caller can get the contents by passing a non-NULL `dleap').
*/
int
dl_info(ldi_handle_t lh, dl_info_ack_t *dliap, uchar_t *physaddr,
size_t *physlenp, dl_error_ack_t *dleap)
{
mblk_t *mp;
int err;
int addrlen, addroff;
mp = mexchange(NULL, NULL, DL_INFO_REQ_SIZE, M_PCPROTO, DL_INFO_REQ);
if (mp == NULL)
return (ENOMEM);
err = dl_op(lh, &mp, DL_INFO_ACK, DL_INFO_ACK_SIZE, dleap, NULL);
if (err != 0)
return (err);
*dliap = *(dl_info_ack_t *)mp->b_rptr;
if (physaddr != NULL) {
addrlen = dliap->dl_addr_length - ABS(dliap->dl_sap_length);
addroff = dliap->dl_addr_offset;
if (addroff == 0 || addrlen <= 0 || addrlen > *physlenp ||
!MBLKIN(mp, addroff, dliap->dl_addr_length)) {
cmn_err(CE_NOTE, "!dl_info: DL_INFO_ACK: "
"bad length/offset %d/%d", addrlen, addroff);
freemsg(mp);
return (EBADMSG);
}
if (dliap->dl_sap_length > 0)
addroff += dliap->dl_sap_length;
bcopy(mp->b_rptr + addroff, physaddr, addrlen);
*physlenp = addrlen;
}
freemsg(mp);
return (err);
}
/*
* Send a DL_NOTIFY_REQ over `lh' and wait for the response. The caller
* should set `notesp' to the set of notifications they wish to enable;
* upon success it will contain the notifications enabled by the provider.
*
* Returns an errno; ENOTSUP indicates a DL_ERROR_ACK response (and the
* caller can get the contents by passing a non-NULL `dleap').
*/
int
dl_notify(ldi_handle_t lh, uint32_t *notesp, dl_error_ack_t *dleap)
{
mblk_t *mp;
int err;
mp = mexchange(NULL, NULL, DL_NOTIFY_REQ_SIZE, M_PROTO, DL_NOTIFY_REQ);
if (mp == NULL)
return (ENOMEM);
((dl_notify_req_t *)mp->b_rptr)->dl_notifications = *notesp;
err = dl_op(lh, &mp, DL_NOTIFY_ACK, DL_NOTIFY_ACK_SIZE, dleap, NULL);
if (err == 0) {
*notesp = ((dl_notify_ack_t *)mp->b_rptr)->dl_notifications;
freemsg(mp);
}
return (err);
}
const char *
dl_primstr(t_uscalar_t prim)
{
switch (prim) {
case DL_INFO_REQ: return ("DL_INFO_REQ");
case DL_INFO_ACK: return ("DL_INFO_ACK");
case DL_ATTACH_REQ: return ("DL_ATTACH_REQ");
case DL_DETACH_REQ: return ("DL_DETACH_REQ");
case DL_BIND_REQ: return ("DL_BIND_REQ");
case DL_BIND_ACK: return ("DL_BIND_ACK");
case DL_UNBIND_REQ: return ("DL_UNBIND_REQ");
case DL_OK_ACK: return ("DL_OK_ACK");
case DL_ERROR_ACK: return ("DL_ERROR_ACK");
case DL_ENABMULTI_REQ: return ("DL_ENABMULTI_REQ");
case DL_DISABMULTI_REQ: return ("DL_DISABMULTI_REQ");
case DL_PROMISCON_REQ: return ("DL_PROMISCON_REQ");
case DL_PROMISCOFF_REQ: return ("DL_PROMISCOFF_REQ");
case DL_UNITDATA_REQ: return ("DL_UNITDATA_REQ");
case DL_UNITDATA_IND: return ("DL_UNITDATA_IND");
case DL_UDERROR_IND: return ("DL_UDERROR_IND");
case DL_PHYS_ADDR_REQ: return ("DL_PHYS_ADDR_REQ");
case DL_PHYS_ADDR_ACK: return ("DL_PHYS_ADDR_ACK");
case DL_SET_PHYS_ADDR_REQ: return ("DL_SET_PHYS_ADDR_REQ");
case DL_NOTIFY_REQ: return ("DL_NOTIFY_REQ");
case DL_NOTIFY_ACK: return ("DL_NOTIFY_ACK");
case DL_NOTIFY_IND: return ("DL_NOTIFY_IND");
case DL_CAPABILITY_REQ: return ("DL_CAPABILITY_REQ");
case DL_CAPABILITY_ACK: return ("DL_CAPABILITY_ACK");
case DL_CONTROL_REQ: return ("DL_CONTROL_REQ");
case DL_CONTROL_ACK: return ("DL_CONTROL_ACK");
case DL_PASSIVE_REQ: return ("DL_PASSIVE_REQ");
case DL_INTR_MODE_REQ: return ("DL_INTR_MODE_REQ");
case DL_UDQOS_REQ: return ("DL_UDQOS_REQ");
default: return ("<unknown primitive>");
}
}
const char *
dl_errstr(t_uscalar_t err)
{
switch (err) {
case DL_ACCESS: return ("DL_ACCESS");
case DL_BADADDR: return ("DL_BADADDR");
case DL_BADCORR: return ("DL_BADCORR");
case DL_BADDATA: return ("DL_BADDATA");
case DL_BADPPA: return ("DL_BADPPA");
case DL_BADPRIM: return ("DL_BADPRIM");
case DL_BADQOSPARAM: return ("DL_BADQOSPARAM");
case DL_BADQOSTYPE: return ("DL_BADQOSTYPE");
case DL_BADSAP: return ("DL_BADSAP");
case DL_BADTOKEN: return ("DL_BADTOKEN");
case DL_BOUND: return ("DL_BOUND");
case DL_INITFAILED: return ("DL_INITFAILED");
case DL_NOADDR: return ("DL_NOADDR");
case DL_NOTINIT: return ("DL_NOTINIT");
case DL_OUTSTATE: return ("DL_OUTSTATE");
case DL_SYSERR: return ("DL_SYSERR");
case DL_UNSUPPORTED: return ("DL_UNSUPPORTED");
case DL_UNDELIVERABLE: return ("DL_UNDELIVERABLE");
case DL_NOTSUPPORTED: return ("DL_NOTSUPPORTED ");
case DL_TOOMANY: return ("DL_TOOMANY");
case DL_NOTENAB: return ("DL_NOTENAB");
case DL_BUSY: return ("DL_BUSY");
case DL_NOAUTO: return ("DL_NOAUTO");
case DL_NOXIDAUTO: return ("DL_NOXIDAUTO");
case DL_NOTESTAUTO: return ("DL_NOTESTAUTO");
case DL_XIDAUTO: return ("DL_XIDAUTO");
case DL_TESTAUTO: return ("DL_TESTAUTO");
case DL_PENDING: return ("DL_PENDING");
default: return ("<unknown error>");
}
}
const char *
dl_mactypestr(t_uscalar_t mactype)
{
switch (mactype) {
case DL_CSMACD: return ("CSMA/CD");
case DL_TPB: return ("Token Bus");
case DL_TPR: return ("Token Ring");
case DL_METRO: return ("Metro Net");
case DL_ETHER: return ("Ethernet");
case DL_HDLC: return ("HDLC");
case DL_CHAR: return ("Sync Character");
case DL_CTCA: return ("CTCA");
case DL_FDDI: return ("FDDI");
case DL_FRAME: return ("Frame Relay (LAPF)");
case DL_MPFRAME: return ("MP Frame Relay");
case DL_ASYNC: return ("Async Character");
case DL_IPX25: return ("X.25 (Classic IP)");
case DL_LOOP: return ("Software Loopback");
case DL_FC: return ("Fiber Channel");
case DL_ATM: return ("ATM");
case DL_IPATM: return ("ATM (Classic IP)");
case DL_X25: return ("X.25 (LAPB)");
case DL_ISDN: return ("ISDN");
case DL_HIPPI: return ("HIPPI");
case DL_100VG: return ("100BaseVG Ethernet");
case DL_100VGTPR: return ("100BaseVG Token Ring");
case DL_ETH_CSMA: return ("Ethernet/IEEE 802.3");
case DL_100BT: return ("100BaseT");
case DL_IB: return ("Infiniband");
case DL_IPV4: return ("IPv4 Tunnel");
case DL_IPV6: return ("IPv6 Tunnel");
case DL_WIFI: return ("IEEE 802.11");
case DL_IPNET: return ("IPNET");
default: return ("<unknown mactype>");
}
}