summaryrefslogtreecommitdiffstats
path: root/sys/netatm/atm_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netatm/atm_device.c')
-rw-r--r--sys/netatm/atm_device.c883
1 files changed, 883 insertions, 0 deletions
diff --git a/sys/netatm/atm_device.c b/sys/netatm/atm_device.c
new file mode 100644
index 0000000..7f26695
--- /dev/null
+++ b/sys/netatm/atm_device.c
@@ -0,0 +1,883 @@
+/*
+ *
+ * ===================================
+ * HARP | Host ATM Research Platform
+ * ===================================
+ *
+ *
+ * This Host ATM Research Platform ("HARP") file (the "Software") is
+ * made available by Network Computing Services, Inc. ("NetworkCS")
+ * "AS IS". NetworkCS does not provide maintenance, improvements or
+ * support of any kind.
+ *
+ * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
+ * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
+ * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
+ * In no event shall NetworkCS be responsible for any damages, including
+ * but not limited to consequential damages, arising from or relating to
+ * any use of the Software or related support.
+ *
+ * Copyright 1994-1998 Network Computing Services, Inc.
+ *
+ * Copies of this Software may be made, however, the above copyright
+ * notice must be reproduced on all copies.
+ *
+ * @(#) $Id: atm_device.c,v 1.7 1998/03/24 20:42:39 mks Exp $
+ *
+ */
+
+/*
+ * Core ATM Services
+ * -----------------
+ *
+ * ATM device support functions
+ *
+ */
+
+#ifndef lint
+static char *RCSid = "@(#) $Id: atm_device.c,v 1.7 1998/03/24 20:42:39 mks Exp $";
+#endif
+
+#include <netatm/kern_include.h>
+
+
+/*
+ * Private structures for managing allocated kernel memory resources
+ *
+ * For each allocation of kernel memory, one Mem_ent will be used.
+ * The Mem_ent structures will be allocated in blocks inside of a
+ * Mem_blk structure.
+ */
+#define MEM_NMEMENT 10 /* How many Mem_ent's in a Mem_blk */
+
+struct mem_ent {
+ void *me_kaddr; /* Allocated memory address */
+ u_int me_ksize; /* Allocated memory length */
+ void *me_uaddr; /* Memory address returned to caller */
+ u_int me_flags; /* Flags (see below) */
+};
+typedef struct mem_ent Mem_ent;
+
+/*
+ * Memory entry flags
+ */
+#define MEF_NONCACHE 1 /* Memory is noncacheable */
+
+
+struct mem_blk {
+ struct mem_blk *mb_next; /* Next block in chain */
+ Mem_ent mb_mement[MEM_NMEMENT]; /* Allocated memory entries */
+};
+typedef struct mem_blk Mem_blk;
+
+static Mem_blk *atm_mem_head = NULL;
+
+static struct t_atm_cause atm_dev_cause = {
+ T_ATM_ITU_CODING,
+ T_ATM_LOC_USER,
+ T_ATM_CAUSE_VPCI_VCI_ASSIGNMENT_FAILURE,
+ {0, 0, 0, 0}
+};
+
+
+/*
+ * ATM Device Stack Instantiation
+ *
+ * Called at splnet.
+ *
+ * Arguments
+ * ssp pointer to array of stack definition pointers
+ * for connection
+ * ssp[0] points to upper layer's stack definition
+ * ssp[1] points to this layer's stack definition
+ * ssp[2] points to lower layer's stack definition
+ * cvcp pointer to connection vcc for this stack
+ *
+ * Returns
+ * 0 instantiation successful
+ * err instantiation failed - reason indicated
+ *
+ */
+int
+atm_dev_inst(ssp, cvcp)
+ struct stack_defn **ssp;
+ Atm_connvc *cvcp;
+{
+ Cmn_unit *cup = (Cmn_unit *)cvcp->cvc_attr.nif->nif_pif;
+ Cmn_vcc *cvp;
+ int err;
+
+ /*
+ * Check to see if device has been initialized
+ */
+ if ((cup->cu_flags & CUF_INITED) == 0)
+ return ( EIO );
+
+ /*
+ * Validate lower SAP
+ */
+ /*
+ * Device driver is the lowest layer - no need to validate
+ */
+
+ /*
+ * Validate PVC vpi.vci
+ */
+ if (cvcp->cvc_attr.called.addr.address_format == T_ATM_PVC_ADDR) {
+ /*
+ * Look through existing circuits - return error if found
+ */
+ Atm_addr_pvc *pp;
+
+ pp = (Atm_addr_pvc *)cvcp->cvc_attr.called.addr.address;
+ if (atm_dev_vcc_find(cup, ATM_PVC_GET_VPI(pp),
+ ATM_PVC_GET_VCI(pp), 0))
+ return ( EADDRINUSE );
+ }
+
+ /*
+ * Validate our SAP type
+ */
+ switch ((*(ssp+1))->sd_sap) {
+ case SAP_CPCS_AAL3_4:
+ case SAP_CPCS_AAL5:
+ case SAP_ATM:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ /*
+ * Allocate a VCC control block
+ */
+ if ( ( cvp = (Cmn_vcc *)atm_allocate(cup->cu_vcc_pool) ) == NULL )
+ return ( ENOMEM );
+
+ cvp->cv_state = CVS_INST;
+ cvp->cv_toku = (*ssp)->sd_toku;
+ cvp->cv_upper = (*ssp)->sd_upper;
+ cvp->cv_connvc = cvcp;
+
+ /*
+ * Let device have a look at the connection request
+ */
+ err = (*cup->cu_instvcc)(cup, cvp);
+ if (err) {
+ atm_free((caddr_t)cvp);
+ return (err);
+ }
+
+ /*
+ * Looks good so far, so link in device VCC
+ */
+ LINK2TAIL ( cvp, Cmn_vcc, cup->cu_vcc, cv_next );
+
+ /*
+ * Save my token
+ */
+ (*++ssp)->sd_toku = cvp;
+
+ /*
+ * Pass instantiation down the stack
+ */
+ /*
+ * No need - we're the lowest point.
+ */
+ /* err = (*(ssp + 1))->sd_inst(ssp, cvcp); */
+
+ /*
+ * Save the lower layer's interface info
+ */
+ /*
+ * No need - we're the lowest point
+ */
+ /* cvp->cv_lower = (*++ssp)->sd_lower; */
+ /* cvp->cv_tok1 = (*ssp)->sd_toku; */
+
+ return (0);
+}
+
+
+/*
+ * ATM Device Stack Command Handler
+ *
+ * Arguments
+ * cmd stack command code
+ * tok session token (Cmn_vcc)
+ * arg1 command specific argument
+ * arg2 command specific argument
+ *
+ * Returns
+ * none
+ *
+ */
+/*ARGSUSED*/
+void
+atm_dev_lower(cmd, tok, arg1, arg2)
+ int cmd;
+ void *tok;
+ int arg1;
+ int arg2;
+{
+ Cmn_vcc *cvp = (Cmn_vcc *)tok;
+ Atm_connvc *cvcp = cvp->cv_connvc;
+ Cmn_unit *cup = (Cmn_unit *)cvcp->cvc_attr.nif->nif_pif;
+ struct vccb *vcp;
+ u_int state;
+ int s;
+
+ switch ( cmd ) {
+
+ case CPCS_INIT:
+ /*
+ * Sanity check
+ */
+ if ( cvp->cv_state != CVS_INST ) {
+ log ( LOG_ERR,
+ "atm_dev_lower: INIT: tok=0x%x, state=%d\n",
+ (int)tok, cvp->cv_state );
+ break;
+ }
+
+ vcp = cvp->cv_connvc->cvc_vcc;
+
+ /*
+ * Validate SVC vpi.vci
+ */
+ if ( vcp->vc_type & VCC_SVC ) {
+
+ if (atm_dev_vcc_find(cup, vcp->vc_vpi, vcp->vc_vci,
+ vcp->vc_type & (VCC_IN | VCC_OUT))
+ != cvp){
+ log ( LOG_ERR,
+ "atm_dev_lower: dup SVC (%d,%d) tok=0x%x\n",
+ vcp->vc_vpi, vcp->vc_vci, (int)tok );
+ atm_cm_abort(cvp->cv_connvc, &atm_dev_cause);
+ break;
+ }
+ }
+
+ /*
+ * Tell the device to open the VCC
+ */
+ cvp->cv_state = CVS_INITED;
+ s = splimp();
+ if ((*cup->cu_openvcc)(cup, cvp)) {
+ atm_cm_abort(cvp->cv_connvc, &atm_dev_cause);
+ (void) splx(s);
+ break;
+ }
+ (void) splx(s);
+ break;
+
+ case CPCS_TERM: {
+ KBuffer *m, *prev, *next;
+ int *ip;
+
+ s = splimp();
+
+ /*
+ * Disconnect the VCC - ignore return code
+ */
+ if ((cvp->cv_state == CVS_INITED) ||
+ (cvp->cv_state == CVS_ACTIVE)) {
+ (void) (*cup->cu_closevcc)(cup, cvp);
+ }
+ cvp->cv_state = CVS_TERM;
+
+ /*
+ * Remove from interface list
+ */
+ UNLINK ( cvp, Cmn_vcc, cup->cu_vcc, cv_next );
+
+ /*
+ * Free any buffers from this VCC on the ATM interrupt queue
+ */
+ prev = NULL;
+ for (m = atm_intrq.ifq_head; m; m = next) {
+ next = KB_QNEXT(m);
+
+ /*
+ * See if this entry is for the terminating VCC
+ */
+ KB_DATASTART(m, ip, int *);
+ ip++;
+ if (*ip == (int)cvp) {
+ /*
+ * Yep, so dequeue the entry
+ */
+ if (prev == NULL)
+ atm_intrq.ifq_head = next;
+ else
+ KB_QNEXT(prev) = next;
+
+ if (next == NULL)
+ atm_intrq.ifq_tail = prev;
+
+ atm_intrq.ifq_len--;
+
+ /*
+ * Free the unwanted buffers
+ */
+ KB_FREEALL(m);
+ } else {
+ prev = m;
+ }
+ }
+ (void) splx(s);
+
+ /*
+ * Free VCC resources
+ */
+ (void) atm_free((caddr_t)cvp);
+ break;
+ }
+
+ case CPCS_UNITDATA_INV:
+
+ /*
+ * Sanity check
+ *
+ * Use temp state variable since we dont want to lock out
+ * interrupts, but initial VC activation interrupt may
+ * happen here, changing state somewhere in the middle.
+ */
+ state = cvp->cv_state;
+ if ((state != CVS_ACTIVE) &&
+ (state != CVS_INITED)) {
+ log ( LOG_ERR,
+ "atm_dev_lower: UNITDATA: tok=0x%x, state=%d\n",
+ (int)tok, state );
+ KB_FREEALL((KBuffer *)arg1);
+ break;
+ }
+
+ /*
+ * Hand the data off to the device
+ */
+ (*cup->cu_output)(cup, cvp, (KBuffer *)arg1);
+
+ break;
+
+ case CPCS_UABORT_INV:
+ log ( LOG_ERR,
+ "atm_dev_lower: unimplemented stack cmd 0x%x, tok=0x%x\n",
+ cmd, (int)tok );
+ break;
+
+ default:
+ log ( LOG_ERR,
+ "atm_dev_lower: unknown stack cmd 0x%x, tok=0x%x\n",
+ cmd, (int)tok );
+
+ }
+
+ return;
+}
+
+
+
+/*
+ * Allocate kernel memory block
+ *
+ * This function will allocate a kernel memory block of the type specified
+ * in the flags parameter. The returned address will point to a memory
+ * block of the requested size and alignment. The memory block will also
+ * be zeroed. The alloc/free functions will manage/mask both the OS-specific
+ * kernel memory management requirements and the bookkeeping required to
+ * deal with data alignment issues.
+ *
+ * This function should not be called from interrupt level.
+ *
+ * Arguments:
+ * size size of memory block to allocate
+ * align data alignment requirement
+ * flags allocation flags (ATM_DEV_*)
+ *
+ * Returns:
+ * uaddr pointer to aligned memory block
+ * NULL unable to allocate memory
+ *
+ */
+void *
+atm_dev_alloc(size, align, flags)
+ u_int size;
+ u_int align;
+ u_int flags;
+{
+ Mem_blk *mbp;
+ Mem_ent *mep;
+ u_int kalign, ksize;
+ int s, i;
+
+ s = splimp();
+
+ /*
+ * Find a free Mem_ent
+ */
+ mep = NULL;
+ for (mbp = atm_mem_head; mbp && mep == NULL; mbp = mbp->mb_next) {
+ for (i = 0; i < MEM_NMEMENT; i++) {
+ if (mbp->mb_mement[i].me_uaddr == NULL) {
+ mep = &mbp->mb_mement[i];
+ break;
+ }
+ }
+ }
+
+ /*
+ * If there are no free Mem_ent's, then allocate a new Mem_blk
+ * and link it into the chain
+ */
+ if (mep == NULL) {
+ mbp = (Mem_blk *) KM_ALLOC(sizeof(Mem_blk), M_DEVBUF, M_NOWAIT);
+ if (mbp == NULL) {
+ log(LOG_ERR, "atm_dev_alloc: Mem_blk failure\n");
+ (void) splx(s);
+ return (NULL);
+ }
+ KM_ZERO(mbp, sizeof(Mem_blk));
+
+ mbp->mb_next = atm_mem_head;
+ atm_mem_head = mbp;
+ mep = mbp->mb_mement;
+ }
+
+ /*
+ * Now we need to get the kernel's allocation alignment minimum
+ *
+ * This is obviously very OS-specific stuff
+ */
+#ifdef sun
+ if (flags & ATM_DEV_NONCACHE) {
+ /* Byte-aligned */
+ kalign = sizeof(long);
+ } else {
+ /* Doubleword-aligned */
+ kalign = sizeof(double);
+ }
+#elif (defined(BSD) && (BSD >= 199103))
+ kalign = MINALLOCSIZE;
+#else
+ #error Unsupported/unconfigured OS
+#endif
+
+ /*
+ * Figure out how much memory we must allocate to satify the
+ * user's size and alignment needs
+ */
+ if (align <= kalign)
+ ksize = size;
+ else
+ ksize = size + align - kalign;
+
+ /*
+ * Finally, go get the memory
+ */
+ if (flags & ATM_DEV_NONCACHE) {
+#ifdef sun
+ mep->me_kaddr = IOPBALLOC(ksize);
+#elif defined(__i386__)
+ mep->me_kaddr = KM_ALLOC(ksize, M_DEVBUF, M_NOWAIT);
+#else
+ #error Unsupported/unconfigured OS
+#endif
+ } else {
+ mep->me_kaddr = KM_ALLOC(ksize, M_DEVBUF, M_NOWAIT);
+ }
+
+ if (mep->me_kaddr == NULL) {
+ log(LOG_ERR, "atm_dev_alloc: %skernel memory unavailable\n",
+ (flags & ATM_DEV_NONCACHE) ? "non-cacheable " : "");
+ (void) splx(s);
+ return (NULL);
+ }
+
+ /*
+ * Calculate correct alignment address to pass back to user
+ */
+ mep->me_uaddr = (void *) roundup((u_int)mep->me_kaddr, align);
+ mep->me_ksize = ksize;
+ mep->me_flags = flags;
+
+ /*
+ * Clear memory for user
+ */
+ KM_ZERO(mep->me_uaddr, size);
+
+ ATM_DEBUG4("atm_dev_alloc: size=%d, align=%d, flags=%d, uaddr=0x%x\n",
+ size, align, flags, (int)mep->me_uaddr);
+
+ (void) splx(s);
+
+ return (mep->me_uaddr);
+}
+
+
+/*
+ * Free kernel memory block
+ *
+ * This function will free a kernel memory block previously allocated by
+ * the atm_dev_alloc function.
+ *
+ * This function should not be called from interrupt level.
+ *
+ * Arguments:
+ * uaddr pointer to allocated aligned memory block
+ *
+ * Returns:
+ * none
+ *
+ */
+void
+atm_dev_free(uaddr)
+ void *uaddr;
+{
+ Mem_blk *mbp;
+ Mem_ent *mep;
+ int s, i;
+
+ ATM_DEBUG1("atm_dev_free: uaddr=0x%x\n", (int)uaddr);
+
+ s = splimp();
+
+ /*
+ * Protect ourselves...
+ */
+ if (uaddr == NULL)
+ panic("atm_dev_free: trying to free null address");
+
+ /*
+ * Find our associated entry
+ */
+ mep = NULL;
+ for (mbp = atm_mem_head; mbp && mep == NULL; mbp = mbp->mb_next) {
+ for (i = 0; i < MEM_NMEMENT; i++) {
+ if (mbp->mb_mement[i].me_uaddr == uaddr) {
+ mep = &mbp->mb_mement[i];
+ break;
+ }
+ }
+ }
+
+ /*
+ * If we didn't find our entry, then unceremoniously let the caller
+ * know they screwed up (it certainly couldn't be a bug here...)
+ */
+ if (mep == NULL)
+ panic("atm_dev_free: trying to free unknown address");
+
+ /*
+ * Give the memory space back to the kernel
+ */
+ if (mep->me_flags & ATM_DEV_NONCACHE) {
+#ifdef sun
+ IOPBFREE(mep->me_kaddr, mep->me_ksize);
+#elif defined(__i386__)
+ KM_FREE(mep->me_kaddr, mep->me_ksize, M_DEVBUF);
+#else
+ #error Unsupported/unconfigured OS
+#endif
+ } else {
+ KM_FREE(mep->me_kaddr, mep->me_ksize, M_DEVBUF);
+ }
+
+ /*
+ * Free our entry
+ */
+ mep->me_uaddr = NULL;
+
+ (void) splx(s);
+
+ return;
+}
+
+
+#ifdef sun4m
+
+typedef int (*func_t)();
+
+/*
+ * Map an address into DVMA space
+ *
+ * This function will take a kernel virtual address and map it to
+ * a DMA virtual address which can be used during SBus DMA cycles.
+ *
+ * Arguments:
+ * addr kernel virtual address
+ * len length of DVMA space requested
+ * flags allocation flags (ATM_DEV_*)
+ *
+ * Returns:
+ * a DVMA address
+ * NULL unable to map into DMA space
+ *
+ */
+void *
+atm_dma_map(addr, len, flags)
+ caddr_t addr;
+ int len;
+ int flags;
+{
+ if (flags & ATM_DEV_NONCACHE)
+ /*
+ * Non-cacheable memory is already DMA'able
+ */
+ return ((void *)addr);
+ else
+ return ((void *)mb_nbmapalloc(bigsbusmap, addr, len,
+ MDR_BIGSBUS|MB_CANTWAIT, (func_t)NULL, (caddr_t)NULL));
+}
+
+
+/*
+ * Free a DVMA map address
+ *
+ * This function will free DVMA map resources (addresses) previously
+ * allocated with atm_dma_map().
+ *
+ * Arguments:
+ * addr DMA virtual address
+ * flags allocation flags (ATM_DEV_*)
+ *
+ * Returns:
+ * none
+ *
+ */
+void
+atm_dma_free(addr, flags)
+ caddr_t addr;
+ int flags;
+{
+ if ((flags & ATM_DEV_NONCACHE) == 0)
+ mb_mapfree(bigsbusmap, (int)&addr);
+
+ return;
+}
+#endif /* sun4m */
+
+
+/*
+ * Compress buffer chain
+ *
+ * This function will compress a supplied buffer chain into a minimum number
+ * of kernel buffers. Typically, this function will be used because the
+ * number of buffers in an output buffer chain is too large for a device's
+ * DMA capabilities. This should only be called as a last resort, since
+ * all the data copying will surely kill any hopes of decent performance.
+ *
+ * Arguments:
+ * m pointer to source buffer chain
+ *
+ * Returns:
+ * n pointer to compressed buffer chain
+ *
+ */
+KBuffer *
+atm_dev_compress(m)
+ KBuffer *m;
+{
+ KBuffer *n, *n0, **np;
+ int len, space;
+ caddr_t src, dst;
+
+ n = n0 = NULL;
+ np = &n0;
+ dst = NULL;
+ space = 0;
+
+ /*
+ * Copy each source buffer into compressed chain
+ */
+ while (m) {
+
+ if (space == 0) {
+
+ /*
+ * Allocate another buffer for compressed chain
+ */
+ KB_ALLOCEXT(n, ATM_DEV_CMPR_LG, KB_F_NOWAIT, KB_T_DATA);
+ if (n) {
+ space = ATM_DEV_CMPR_LG;
+ } else {
+ KB_ALLOC(n, ATM_DEV_CMPR_SM, KB_F_NOWAIT,
+ KB_T_DATA);
+ if (n) {
+ space = ATM_DEV_CMPR_SM;
+ } else {
+ /*
+ * Unable to get any new buffers, so
+ * just return the partially compressed
+ * chain and hope...
+ */
+ *np = m;
+ break;
+ }
+ }
+
+ KB_HEADSET(n, 0);
+ KB_LEN(n) = 0;
+ KB_BFRSTART(n, dst, caddr_t);
+
+ *np = n;
+ np = &KB_NEXT(n);
+ }
+
+ /*
+ * Copy what we can from source buffer
+ */
+ len = MIN(space, KB_LEN(m));
+ KB_DATASTART(m, src, caddr_t);
+ KM_COPY(src, dst, len);
+
+ /*
+ * Adjust for copied data
+ */
+ dst += len;
+ space -= len;
+
+ KB_HEADADJ(m, -len);
+ KB_TAILADJ(n, len);
+
+ /*
+ * If we've exhausted our current source buffer, free it
+ * and move to the next one
+ */
+ if (KB_LEN(m) == 0) {
+ KB_FREEONE(m, m);
+ }
+ }
+
+ return (n0);
+}
+
+
+/*
+ * Locate VCC entry
+ *
+ * This function will return the VCC entry for a specified interface and
+ * VPI/VCI value.
+ *
+ * Arguments:
+ * cup pointer to interface unit structure
+ * vpi VPI value
+ * vci VCI value
+ * type VCC type
+ *
+ * Returns:
+ * vcp pointer to located VCC entry matching
+ * NULL no VCC found
+ *
+ */
+Cmn_vcc *
+atm_dev_vcc_find(cup, vpi, vci, type)
+ Cmn_unit *cup;
+ u_int vpi;
+ u_int vci;
+ u_int type;
+{
+ Cmn_vcc *cvp;
+ int s = splnet();
+
+ /*
+ * Go find VCC
+ *
+ * (Probably should stick in a hash table some time)
+ */
+ for (cvp = cup->cu_vcc; cvp; cvp = cvp->cv_next) {
+ struct vccb *vcp;
+
+ vcp = cvp->cv_connvc->cvc_vcc;
+ if ((vcp->vc_vci == vci) && (vcp->vc_vpi == vpi) &&
+ ((vcp->vc_type & type) == type))
+ break;
+ }
+
+ (void) splx(s);
+ return (cvp);
+}
+
+
+#ifdef notdef
+/*
+ * Module unloading notification
+ *
+ * This function must be called just prior to unloading the module from
+ * memory. All allocated memory will be freed here and anything else that
+ * needs cleaning up.
+ *
+ * Arguments:
+ * none
+ *
+ * Returns:
+ * none
+ *
+ */
+void
+atm_unload()
+{
+ Mem_blk *mbp;
+ Mem_ent *mep;
+ int s, i;
+
+ s = splimp();
+
+ /*
+ * Free up all of our memory management storage
+ */
+ while (mbp = atm_mem_head) {
+
+ /*
+ * Make sure users have freed up all of their memory
+ */
+ for (i = 0; i < MEM_NMEMENT; i++) {
+ if (mbp->mb_mement[i].me_uaddr != NULL) {
+ panic("atm_unload: unfreed memory");
+ }
+ }
+
+ atm_mem_head = mbp->mb_next;
+
+ /*
+ * Hand this block back to the kernel
+ */
+ KM_FREE((caddr_t) mbp, sizeof(Mem_blk), M_DEVBUF);
+ }
+
+ (void) splx(s);
+
+ return;
+}
+#endif /* notdef */
+
+
+/*
+ * Print a PDU
+ *
+ * Arguments:
+ * cup pointer to device unit
+ * cvp pointer to VCC control block
+ * m pointer to pdu buffer chain
+ * msg pointer to message string
+ *
+ * Returns:
+ * none
+ *
+ */
+void
+atm_dev_pdu_print(cup, cvp, m, msg)
+ Cmn_unit *cup;
+ Cmn_vcc *cvp;
+ KBuffer *m;
+ char *msg;
+{
+ char buf[128];
+
+ sprintf(buf, "%s vcc=(%d,%d)", msg,
+ cvp->cv_connvc->cvc_vcc->vc_vpi,
+ cvp->cv_connvc->cvc_vcc->vc_vci);
+
+ atm_pdu_print(m, buf);
+}
+
OpenPOWER on IntegriCloud