diff options
Diffstat (limited to 'sys/netatm/uni/sscop_subr.c')
-rw-r--r-- | sys/netatm/uni/sscop_subr.c | 972 |
1 files changed, 972 insertions, 0 deletions
diff --git a/sys/netatm/uni/sscop_subr.c b/sys/netatm/uni/sscop_subr.c new file mode 100644 index 0000000..2d01229 --- /dev/null +++ b/sys/netatm/uni/sscop_subr.c @@ -0,0 +1,972 @@ +/* + * + * =================================== + * 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: sscop_subr.c,v 1.7 1998/08/26 23:29:19 mks Exp $ + * + */ + +/* + * ATM Forum UNI Support + * --------------------- + * + * SSCOP - Subroutines + * + */ + +#ifndef lint +static char *RCSid = "@(#) $Id: sscop_subr.c,v 1.7 1998/08/26 23:29:19 mks Exp $"; +#endif + +#include <netatm/kern_include.h> + +#include <netatm/uni/uni.h> +#include <netatm/uni/sscop.h> +#include <netatm/uni/sscop_misc.h> +#include <netatm/uni/sscop_pdu.h> +#include <netatm/uni/sscop_var.h> + + +/* + * Local functions + */ +static int sscop_proc_xmit __P((struct sscop *)); + + +/* + * Get Next Element from STAT PDU + * + * Arguments: + * m pointer to current buffer in STAT PDU + * pelem pointer to location to store element value + * + * Returns: + * addr pointer to updated current buffer in STAT PDU + * + */ +KBuffer * +sscop_stat_getelem(m, pelem) + KBuffer *m; + sscop_seq *pelem; +{ + caddr_t cp; + + /* + * Get to start of element + * + * Note that we always ensure that the current buffer has + * at least one byte of the next element. + */ + KB_DATASTART(m, cp, caddr_t); + + /* + * See how much of element is in this buffer + */ + if (KB_LEN(m) >= sizeof(sscop_seq)) { + /* + * Get element from this buffer + */ + if ((int)cp & (sizeof(sscop_seq) - 1)) + KM_COPY(cp, (caddr_t)pelem, sizeof(sscop_seq)); + else + *pelem = *(sscop_seq *)cp; + + /* + * Update buffer controls + */ + KB_HEADADJ(m, -sizeof(sscop_seq)); + } else { + /* + * Get element split between two buffers + */ + int i, j; + + /* + * Copy what's in this buffer + */ + i = KB_LEN(m); + KM_COPY(cp, (caddr_t)pelem, i); + KB_LEN(m) = 0; + + /* + * Now get to next buffer + */ + while (m && (KB_LEN(m) == 0)) + m = KB_NEXT(m); + + /* + * And copy remainder of element + */ + j = sizeof(sscop_seq) - i; + KB_DATASTART(m, cp, caddr_t); + KM_COPY(cp, (caddr_t)pelem + i, j); + + /* + * Update buffer controls + */ + KB_HEADADJ(m, -j); + } + + /* + * Put element (sequence number) into host order + */ + NTOHL(*pelem); + + /* + * Get pointers set for next call + */ + while (m && (KB_LEN(m) == 0)) + m = KB_NEXT(m); + + return (m); +} + + +/* + * Locate SD PDU on Pending Ack Queue + * + * Arguments: + * sop pointer to sscop connection block + * seq sequence number of PDU to locate + * + * Returns: + * addr pointer to located PDU header + * 0 SD PDU sequence number not found + * + */ +struct pdu_hdr * +sscop_pack_locate(sop, seq) + struct sscop *sop; + sscop_seq seq; +{ + struct pdu_hdr *php; + + /* + * Loop thru queue until we either find the PDU or the queue's + * sequence numbers are greater than the PDU's sequence number, + * indicating that the PDU is not on the queue. + */ + for (php = sop->so_pack_hd; php; php = php->ph_pack_lk) { + if (php->ph_ns == seq) + break; + + if (SEQ_GT(php->ph_ns, seq, sop->so_ack)) { + php = NULL; + break; + } + } + + return (php); +} + + +/* + * Free Acknowledged SD PDU + * + * Arguments: + * sop pointer to sscop connection block + * seq sequence number of PDU to free + * + * Returns: + * none + * + */ +void +sscop_pack_free(sop, seq) + struct sscop *sop; + sscop_seq seq; +{ + struct pdu_hdr *php, *prev; + + /* + * Unlink PDU from pending ack queue + * + * First, check for an empty queue + */ + php = sop->so_pack_hd; + if (php == NULL) + return; + + /* + * Now check for PDU at head of queue + */ + if (php->ph_ns == seq) { + if ((sop->so_pack_hd = php->ph_pack_lk) == NULL) + sop->so_pack_tl = NULL; + goto found; + } + + /* + * Otherwise, loop thru queue until we either find the PDU or + * the queue's sequence numbers are greater than the PDU's + * sequence number, indicating that the PDU is not on the queue. + */ + prev = php; + php = php->ph_pack_lk; + while (php) { + if (php->ph_ns == seq) { + if ((prev->ph_pack_lk = php->ph_pack_lk) == NULL) + sop->so_pack_tl = prev; + goto found; + } + + if (SEQ_GT(php->ph_ns, seq, sop->so_ack)) + return; + + prev = php; + php = php->ph_pack_lk; + } + + return; + +found: + /* + * We've got the ack'ed PDU - unlink it from retransmit queue + */ + sscop_rexmit_unlink(sop, php); + + /* + * Free PDU buffers + */ + KB_FREEALL(php->ph_buf); + + return; +} + + +/* + * Insert SD PDU into Retransmit Queue + * + * Arguments: + * sop pointer to sscop connection block + * php pointer to SD PDU header + * + * Returns: + * none + * + */ +void +sscop_rexmit_insert(sop, php) + struct sscop *sop; + struct pdu_hdr *php; +{ + struct pdu_hdr *curr, *next; + sscop_seq seq = php->ph_ns; + + /* + * Check for an empty queue + */ + if ((curr = sop->so_rexmit_hd) == NULL) { + php->ph_rexmit_lk = NULL; + sop->so_rexmit_hd = php; + sop->so_rexmit_tl = php; + return; + } + + /* + * Now see if PDU belongs at head of queue + */ + if (SEQ_LT(seq, curr->ph_ns, sop->so_ack)) { + php->ph_rexmit_lk = curr; + sop->so_rexmit_hd = php; + return; + } + + /* + * Otherwise, loop thru the queue until we find the + * proper insertion point for the PDU + */ + while (next = curr->ph_rexmit_lk) { + if (SEQ_LT(seq, next->ph_ns, sop->so_ack)) { + php->ph_rexmit_lk = next; + curr->ph_rexmit_lk = php; + return; + } + curr = next; + } + + /* + * Insert PDU at end of queue + */ + php->ph_rexmit_lk = NULL; + curr->ph_rexmit_lk = php; + sop->so_rexmit_tl = php; + + return; +} + + +/* + * Unlink SD PDU from Retransmit Queue + * + * Arguments: + * sop pointer to sscop connection block + * php pointer to PDU header to unlink + * + * Returns: + * none + * + */ +void +sscop_rexmit_unlink(sop, php) + struct sscop *sop; + struct pdu_hdr *php; +{ + struct pdu_hdr *curr; + + /* + * See if PDU is on retransmit queue + */ + if ((php->ph_rexmit_lk == NULL) && (sop->so_rexmit_tl != php)) + return; + + /* + * It's here somewhere, so first check for the PDU at the + * head of the queue + */ + if (php == sop->so_rexmit_hd) { + if ((sop->so_rexmit_hd = php->ph_rexmit_lk) == NULL) + sop->so_rexmit_tl = NULL; + php->ph_rexmit_lk = NULL; + return; + } + + /* + * Otherwise, loop thru the queue until we find the PDU + */ + for (curr = sop->so_rexmit_hd; curr; curr = curr->ph_rexmit_lk) { + if (curr->ph_rexmit_lk == php) + break; + } + if (curr) { + if ((curr->ph_rexmit_lk = php->ph_rexmit_lk) == NULL) + sop->so_rexmit_tl = curr; + } else { + log(LOG_ERR, + "sscop_rexmit_unlink: Not found - sop=0x%x, php=0x%x\n", + (int)sop, (int)php); +#ifdef DIAGNOSTIC + panic("sscop_rexmit_unlink: Not found"); +#endif + } + php->ph_rexmit_lk = NULL; + + return; +} + + +/* + * Drain Transmission Queues + * + * Arguments: + * sop pointer to sscop connection block + * + * Returns: + * none + * + */ +void +sscop_xmit_drain(sop) + struct sscop *sop; +{ + KBuffer *m; + struct pdu_hdr *php; + + /* + * Free transmission queue buffers + */ + while (m = sop->so_xmit_hd) { + sop->so_xmit_hd = KB_QNEXT(m); + KB_FREEALL(m); + } + sop->so_xmit_tl = NULL; + + /* + * Free retransmission queue + * + * All retranmission buffers are also on the pending ack + * queue (but not the converse), so we just clear the queue + * pointers here and do all the real work below. + */ + sop->so_rexmit_hd = NULL; + sop->so_rexmit_tl = NULL; + + /* + * Free pending ack queue buffers + */ + while (php = sop->so_pack_hd) { + sop->so_pack_hd = php->ph_pack_lk; + KB_FREEALL(php->ph_buf); + } + sop->so_pack_tl = NULL; + + /* + * Clear service required flag + */ + sop->so_flags &= ~SOF_XMITSRVC; + + return; +} + + +/* + * Insert SD PDU into Receive Queue + * + * Arguments: + * sop pointer to sscop connection block + * php pointer to SD PDU header + * + * Returns: + * 0 PDU successfully inserted into queue + * 1 duplicate sequence number PDU on queue, PDU not inserted + * + */ +int +sscop_recv_insert(sop, php) + struct sscop *sop; + struct pdu_hdr *php; +{ + struct pdu_hdr *curr, *next; + sscop_seq seq = php->ph_ns; + + /* + * Check for an empty queue + */ + if ((curr = sop->so_recv_hd) == NULL) { + php->ph_recv_lk = NULL; + sop->so_recv_hd = php; + sop->so_recv_tl = php; + return (0); + } + + /* + * Now see if PDU belongs at head of queue + */ + if (SEQ_LT(seq, curr->ph_ns, sop->so_rcvnext)) { + php->ph_recv_lk = curr; + sop->so_recv_hd = php; + return (0); + } + + /* + * Otherwise, loop thru the queue until we find the + * proper insertion point for the PDU. We also check + * to make sure there isn't a PDU already on the queue + * with a matching sequence number. + */ + while (next = curr->ph_recv_lk) { + if (SEQ_LT(seq, next->ph_ns, sop->so_rcvnext)) { + if (seq == curr->ph_ns) + return (1); + php->ph_recv_lk = next; + curr->ph_recv_lk = php; + return (0); + } + curr = next; + } + + /* + * Insert PDU at end of queue + */ + if (seq == curr->ph_ns) + return (1); + php->ph_recv_lk = NULL; + curr->ph_recv_lk = php; + sop->so_recv_tl = php; + + return (0); +} + + +/* + * Drain Receiver Queues + * + * Arguments: + * sop pointer to sscop connection block + * + * Returns: + * none + * + */ +void +sscop_rcvr_drain(sop) + struct sscop *sop; +{ + struct pdu_hdr *php; + + /* + * Free receive queue buffers + */ + while (php = sop->so_recv_hd) { + sop->so_recv_hd = php->ph_recv_lk; + KB_FREEALL(php->ph_buf); + } + sop->so_recv_tl = NULL; + + return; +} + + +/* + * Service connection's transmit queues + * + * Arguments: + * sop pointer to sscop connection block + * + * Returns: + * none + * + */ +void +sscop_service_xmit(sop) + struct sscop *sop; +{ + KBuffer *m, *n; + struct pdu_hdr *php; + int err = 0, pollsent = 0; + + /* + * Initially assume we need service + */ + sop->so_flags |= SOF_XMITSRVC; + + /* + * Loop until done with queues + * + * (Congestion control will be added later) + */ + while (1) { + if (php = sop->so_rexmit_hd) { + + /* + * Send SD PDU from retransmit queue + * + * First, get a copy of the PDU to send + */ + m = php->ph_buf; + if (KB_LEN(m) == 0) + m = KB_NEXT(m); + KB_COPY(m, 0, KB_COPYALL, n, KB_F_NOWAIT); + if (n == NULL) { + err = 1; + break; + } + + /* + * Now pass it down the stack + */ + STACK_CALL(CPCS_UNITDATA_INV, sop->so_lower, + sop->so_tokl, sop->so_connvc, (int)n, 0, err); + if (err) { + KB_FREEALL(n); + break; + } + + /* + * PDU is on its way, so remove it from + * the retransmit queue + */ + if (sop->so_rexmit_tl == php) { + sop->so_rexmit_hd = NULL; + sop->so_rexmit_tl = NULL; + } else { + sop->so_rexmit_hd = php->ph_rexmit_lk; + } + php->ph_rexmit_lk = NULL; + + /* + * Update PDU's poll sequence + */ + php->ph_nps = sop->so_pollsend; + + } else if (sop->so_xmit_hd) { + + /* + * Newly arrived data waiting to be sent. + * See if transmit window allows us to send it. + */ + if (SEQ_LT(sop->so_send, sop->so_sendmax, sop->so_ack)){ + /* + * OK, send SD PDU from transmission queue + */ + err = sscop_proc_xmit(sop); + if (err) + break; + } else { + /* + * Can't send now, so leave idle phase. + */ + if (sop->so_timer[SSCOP_T_IDLE] != 0) { + sop->so_timer[SSCOP_T_IDLE] = 0; + sop->so_timer[SSCOP_T_NORESP] = + sop->so_parm.sp_timeresp; + err = 1; + } + break; + } + + } else { + + /* + * We're finished, so clear service required flag + */ + sop->so_flags &= ~SOF_XMITSRVC; + break; + } + + /* + * We've sent another SD PDU + */ + sop->so_polldata++; + + /* + * Transition into active (polling) phase + */ + if (sop->so_timer[SSCOP_T_POLL] != 0) { + if (sop->so_flags & SOF_KEEPALIVE) { + /* + * Leaving transient phase + */ + sop->so_flags &= ~SOF_KEEPALIVE; + sop->so_timer[SSCOP_T_POLL] = + sop->so_parm.sp_timepoll; + } + } else { + /* + * Leaving idle phase + */ + sop->so_timer[SSCOP_T_IDLE] = 0; + sop->so_timer[SSCOP_T_NORESP] = + sop->so_parm.sp_timeresp; + sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll; + } + + /* + * Let's see if we need to send a POLL yet + */ + if (sop->so_polldata < sop->so_parm.sp_maxpd) + continue; + + /* + * Yup, send another poll out + */ + SEQ_INCR(sop->so_pollsend, 1); + (void) sscop_send_poll(sop); + pollsent++; + + /* + * Reset data counter for this poll cycle + */ + sop->so_polldata = 0; + + /* + * Restart polling timer in active phase + */ + sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll; + } + + /* + * If we need/want to send a poll, but haven't sent any yet + * on this servicing, send one now + */ + if (err && (pollsent == 0)) { + /* + * Send poll + */ + SEQ_INCR(sop->so_pollsend, 1); + (void) sscop_send_poll(sop); + + /* + * Reset data counter for this poll cycle + */ + sop->so_polldata = 0; + + /* + * Restart polling timer in active phase + */ + sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll; + sop->so_flags &= ~SOF_KEEPALIVE; + } + + return; +} + + +/* + * Process Transmission Queue PDU + * + * For the first entry on the transmission queue: add a PDU header and + * trailer, send a copy of the PDU down the stack and move the PDU from + * the transmission queue to the pending ack queue. + * + * Arguments: + * sop pointer to sscop connection block + * + * Returns: + * 0 head of transmission queue successfully processed + * else processing error, tranmission queue unchanged + * + */ +static int +sscop_proc_xmit(sop) + struct sscop *sop; +{ + KBuffer *m, *ml, *n; + struct pdu_hdr *php; + sscop_seq seq; + int len = 0, err; + int pad, trlen, space; + u_char *cp; + + /* + * Get first buffer chain on queue + */ + if ((m = sop->so_xmit_hd) == NULL) + return (0); + + /* + * Count data and get to last buffer in chain + */ + for (ml = m; ; ml = KB_NEXT(ml)) { + len += KB_LEN(ml); + if (KB_NEXT(ml) == NULL) + break; + } + + /* + * Verify data length + */ + if (len > sop->so_parm.sp_maxinfo) { + sscop_abort(sop, "sscop: maximum data size exceeded\n"); + return (1); + } + + /* + * Get space for PDU header + */ + KB_HEADROOM(m, space); + if (space < sizeof(struct pdu_hdr)) { + /* + * Allocate & link buffer for header + */ + KB_ALLOC(n, sizeof(struct pdu_hdr), KB_F_NOWAIT, KB_T_HEADER); + if (n == NULL) + return (1); + + KB_LEN(n) = 0; + KB_HEADSET(n, sizeof(struct pdu_hdr)); + KB_LINKHEAD(n, m); + KB_QNEXT(n) = KB_QNEXT(m); + KB_QNEXT(m) = NULL; + sop->so_xmit_hd = n; + if (sop->so_xmit_tl == m) + sop->so_xmit_tl = n; + m = n; + } + + /* + * Figure out how much padding we'll need + */ + pad = ((len + (PDU_PAD_ALIGN - 1)) & ~(PDU_PAD_ALIGN - 1)) - len; + trlen = pad + sizeof(struct sd_pdu); + + /* + * Now get space for PDU trailer and padding + */ + KB_TAILROOM(ml, space); + if (space < trlen) { + /* + * Allocate & link buffer for pad and trailer + */ + KB_ALLOC(n, trlen, KB_F_NOWAIT, KB_T_HEADER); + if (n == NULL) + return (1); + + KB_LEN(n) = 0; + KB_LINK(n, ml); + ml = n; + } + + /* + * Build the PDU trailer + * + * Since we can't be sure of alignment in the buffers, we + * have to move this a byte at a time and we have to be + * careful with host byte order issues. + */ + KB_DATASTART(ml, cp, u_char *); + cp += KB_LEN(ml) + pad; + *cp++ = (pad << PT_PAD_SHIFT) | PT_SD; + seq = sop->so_send; + *(cp + 2) = (u_char)(seq & 0xff); + seq >>= 8; + *(cp + 1) = (u_char)(seq & 0xff); + seq >>= 8; + *(cp) = (u_char)(seq & 0xff); + KB_LEN(ml) += trlen; + + /* + * Get a copy of the SD PDU to send + */ + if (KB_LEN(m) == 0) + n = KB_NEXT(m); + else + n = m; + KB_COPY(n, 0, KB_COPYALL, n, KB_F_NOWAIT); + if (n == NULL) { + KB_LEN(ml) -= trlen; + return (1); + } + + /* + * Now pass copy down the stack + */ + STACK_CALL(CPCS_UNITDATA_INV, sop->so_lower, sop->so_tokl, + sop->so_connvc, (int)n, 0, err); + if (err) { + KB_FREEALL(n); + KB_LEN(ml) -= trlen; + return (1); + } + + /* + * PDU is on its way, so remove buffer from + * the transmission queue + */ + if (sop->so_xmit_tl == m) { + sop->so_xmit_hd = NULL; + sop->so_xmit_tl = NULL; + } else { + sop->so_xmit_hd = KB_QNEXT(m); + } + KB_QNEXT(m) = NULL; + + /* + * Build PDU header + * + * We can at least assume/require that the start of + * the user data is aligned. Also note that we don't + * include this header in the buffer len/offset fields. + */ + KB_DATASTART(m, php, struct pdu_hdr *); + php--; + php->ph_ns = sop->so_send; + php->ph_nps = sop->so_pollsend; + php->ph_buf = m; + php->ph_rexmit_lk = NULL; + php->ph_pack_lk = NULL; + + /* + * Put PDU onto the pending ack queue + */ + if (sop->so_pack_hd == NULL) + sop->so_pack_hd = php; + else + sop->so_pack_tl->ph_pack_lk = php; + sop->so_pack_tl = php; + + /* + * Finally, bump send sequence number + */ + SEQ_INCR(sop->so_send, 1); + + return (0); +} + + +/* + * Detect Retransmitted PDUs + * + * Arguments: + * sop pointer to sscop connection block + * nsq connection sequence value (N(SQ)) from received PDU + * + * Returns: + * 0 received PDU was NOT retransmitted + * 1 received PDU was retransmitted + * + */ +int +sscop_is_rexmit(sop, nsq) + struct sscop *sop; + u_char nsq; +{ + + /* + * For Q.SAAL1, N(SQ) doesn't exist + */ + if (sop->so_vers == SSCOP_VERS_QSAAL) + return (0); + + /* + * If we've already received the N(SQ) value, + * then this PDU has been retransmitted + */ + if (nsq == sop->so_rcvconn) + return (1); + + /* + * New PDU, save its N(SQ) + */ + sop->so_rcvconn = nsq; + + return (0); +} + + +/* + * Start connection poll timer + * + * Arguments: + * sop pointer to sscop connection block + * + * Returns: + * none + * + */ +void +sscop_set_poll(sop) + struct sscop *sop; +{ + + /* + * Decide which polling timer value to set + */ + if ((sop->so_xmit_hd != NULL) || SEQ_NEQ(sop->so_send, sop->so_ack)) { + /* + * Data outstanding, poll frequently + */ + sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll; + sop->so_flags &= ~SOF_KEEPALIVE; + } else { + /* + * No data outstanding, just poll occassionally + */ + sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timekeep; + sop->so_flags |= SOF_KEEPALIVE; + } + + return; +} + |