/* * * =================================== * 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. * * @(#) $FreeBSD$ * */ /* * Core ATM Services * ----------------- * * Miscellaneous ATM subroutines * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef lint __RCSID("@(#) $FreeBSD$"); #endif /* * Global variables */ struct atm_pif *atm_interface_head = NULL; struct atm_ncm *atm_netconv_head = NULL; Atm_endpoint *atm_endpoints[ENDPT_MAX+1] = {NULL}; struct sp_info *atm_pool_head = NULL; struct stackq_entry *atm_stackq_head = NULL, *atm_stackq_tail; struct atm_sock_stat atm_sock_stat = { { 0 } }; int atm_init = 0; int atm_debug = 0; int atm_dev_print = 0; int atm_print_data = 0; int atm_version = ATM_VERSION; struct timeval atm_debugtime = {0, 0}; uma_zone_t atm_attributes_zone; /* * Local functions */ static void atm_compact(struct atm_time *); static KTimeout_ret atm_timexp(void *); /* * Local variables */ static struct atm_time *atm_timeq = NULL; static struct atm_time atm_compactimer = {0, 0}; static uma_zone_t atm_stackq_zone; /* * Initialize ATM kernel * * Performs any initialization required before things really get underway. * Called from ATM domain initialization or from first registration function * which gets called. * * Arguments: * none * * Returns: * none * */ void atm_initialize() { /* * Never called from interrupts, so no locking needed */ if (atm_init) return; atm_init = 1; atm_intrq.ifq_maxlen = ATM_INTRQ_MAX; mtx_init(&atm_intrq.ifq_mtx, "atm_inq", NULL, MTX_DEF); atmintrq_present = 1; atm_attributes_zone = uma_zcreate("atm attributes", sizeof(Atm_attributes), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); if (atm_attributes_zone == NULL) panic("atm_initialize: unable to create attributes zone"); uma_zone_set_max(atm_attributes_zone, 100); atm_stackq_zone = uma_zcreate("atm stackq", sizeof(struct stackq_entry), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); if (atm_stackq_zone == NULL) panic("atm_initialize: unable to create stackq zone"); uma_zone_set_max(atm_stackq_zone, 10); register_netisr(NETISR_ATM, atm_intr); /* * Initialize subsystems */ atm_sock_init(); atm_cm_init(); atm_aal5_init(); /* * Prime the timer */ (void) timeout(atm_timexp, (void *)0, hz/ATM_HZ); /* * Start the compaction timer */ atm_timeout(&atm_compactimer, SPOOL_COMPACT, atm_compact); } /* * Allocate a Control Block * * Gets a new control block allocated from the specified storage pool, * acquiring memory for new pool chunks if required. The returned control * block's contents will be cleared. * * Arguments: * sip pointer to sp_info for storage pool * * Returns: * addr pointer to allocated control block * 0 allocation failed * */ void * atm_allocate(sip) struct sp_info *sip; { void *bp; struct sp_chunk *scp; struct sp_link *slp; int s = splnet(); /* * Count calls */ sip->si_allocs++; /* * Are there any free in the pool? */ if (sip->si_free) { /* * Find first chunk with a free block */ for (scp = sip->si_poolh; scp; scp = scp->sc_next) { if (scp->sc_freeh != NULL) break; } } else { /* * No free blocks - have to allocate a new * chunk (but put a limit to this) */ struct sp_link *slp_next; int i; /* * First time for this pool?? */ if (sip->si_chunksiz == 0) { size_t n; /* * Initialize pool information */ n = sizeof(struct sp_chunk) + sip->si_blkcnt * (sip->si_blksiz + sizeof(struct sp_link)); sip->si_chunksiz = roundup(n, SPOOL_ROUNDUP); /* * Place pool on kernel chain */ LINK2TAIL(sip, struct sp_info, atm_pool_head, si_next); } if (sip->si_chunks >= sip->si_maxallow) { sip->si_fails++; (void) splx(s); return (NULL); } scp = malloc(sip->si_chunksiz, M_DEVBUF, M_NOWAIT | M_ZERO); if (scp == NULL) { sip->si_fails++; (void) splx(s); return (NULL); } scp->sc_info = sip; scp->sc_magic = SPOOL_MAGIC; /* * Divy up chunk into free blocks */ slp = (struct sp_link *)(scp + 1); scp->sc_freeh = slp; for (i = sip->si_blkcnt; i > 1; i--) { slp_next = (struct sp_link *)((caddr_t)(slp + 1) + sip->si_blksiz); slp->sl_u.slu_next = slp_next; slp = slp_next; } slp->sl_u.slu_next = NULL; scp->sc_freet = slp; /* * Add new chunk to end of pool */ if (sip->si_poolh) sip->si_poolt->sc_next = scp; else sip->si_poolh = scp; sip->si_poolt = scp; sip->si_chunks++; sip->si_total += sip->si_blkcnt; sip->si_free += sip->si_blkcnt; if (sip->si_chunks > sip->si_maxused) sip->si_maxused = sip->si_chunks; } /* * Allocate the first free block in chunk */ slp = scp->sc_freeh; scp->sc_freeh = slp->sl_u.slu_next; scp->sc_used++; sip->si_free--; bp = (slp + 1); /* * Save link back to pool chunk */ slp->sl_u.slu_chunk = scp; /* * Clear out block */ bzero(bp, sip->si_blksiz); (void) splx(s); return (bp); } /* * Free a Control Block * * Returns a previously allocated control block back to the owners * storage pool. * * Arguments: * bp pointer to block to be freed * * Returns: * none * */ void atm_free(bp) void *bp; { struct sp_info *sip; struct sp_chunk *scp; struct sp_link *slp; int s = splnet(); /* * Get containing chunk and pool info */ slp = (struct sp_link *)bp; slp--; scp = slp->sl_u.slu_chunk; if (scp->sc_magic != SPOOL_MAGIC) panic("atm_free: chunk magic missing"); sip = scp->sc_info; /* * Add block to free chain */ if (scp->sc_freeh) { scp->sc_freet->sl_u.slu_next = slp; scp->sc_freet = slp; } else scp->sc_freeh = scp->sc_freet = slp; slp->sl_u.slu_next = NULL; sip->si_free++; scp->sc_used--; (void) splx(s); return; } /* * Storage Pool Compaction * * Called periodically in order to perform compaction of the * storage pools. Each pool will be checked to see if any chunks * can be freed, taking some care to avoid freeing too many chunks * in order to avoid memory thrashing. * * Called at splnet. * * Arguments: * tip pointer to timer control block (atm_compactimer) * * Returns: * none * */ static void atm_compact(tip) struct atm_time *tip; { struct sp_info *sip; struct sp_chunk *scp; int i; struct sp_chunk *scp_prev; /* * Check out all storage pools */ for (sip = atm_pool_head; sip; sip = sip->si_next) { /* * Always keep a minimum number of chunks around */ if (sip->si_chunks <= SPOOL_MIN_CHUNK) continue; /* * Maximum chunks to free at one time will leave * pool with at least 50% utilization, but never * go below minimum chunk count. */ i = ((sip->si_free * 2) - sip->si_total) / sip->si_blkcnt; i = MIN(i, sip->si_chunks - SPOOL_MIN_CHUNK); /* * Look for chunks to free */ scp_prev = NULL; for (scp = sip->si_poolh; scp && i > 0; ) { if (scp->sc_used == 0) { /* * Found a chunk to free, so do it */ if (scp_prev) { scp_prev->sc_next = scp->sc_next; if (sip->si_poolt == scp) sip->si_poolt = scp_prev; } else sip->si_poolh = scp->sc_next; free((caddr_t)scp, M_DEVBUF); /* * Update pool controls */ sip->si_chunks--; sip->si_total -= sip->si_blkcnt; sip->si_free -= sip->si_blkcnt; i--; if (scp_prev) scp = scp_prev->sc_next; else scp = sip->si_poolh; } else { scp_prev = scp; scp = scp->sc_next; } } } /* * Restart the compaction timer */ atm_timeout(&atm_compactimer, SPOOL_COMPACT, atm_compact); return; } /* * Release a Storage Pool * * Frees all dynamic storage acquired for a storage pool. * This function is normally called just prior to a module's unloading. * * Arguments: * sip pointer to sp_info for storage pool * * Returns: * none * */ void atm_release_pool(sip) struct sp_info *sip; { struct sp_chunk *scp, *scp_next; int s = splnet(); /* * Free each chunk in pool */ for (scp = sip->si_poolh; scp; scp = scp_next) { /* * Check for memory leaks */ if (scp->sc_used) panic("atm_release_pool: unfreed blocks"); scp_next = scp->sc_next; free((caddr_t)scp, M_DEVBUF); } /* * Update pool controls */ sip->si_poolh = NULL; sip->si_chunks = 0; sip->si_total = 0; sip->si_free = 0; /* * Unlink pool from active chain */ sip->si_chunksiz = 0; UNLINK(sip, struct sp_info, atm_pool_head, si_next); (void) splx(s); return; } /* * Handle timer tick expiration * * Decrement tick count in first block on timer queue. If there * are blocks with expired timers, call their timeout function. * This function is called ATM_HZ times per second. * * Arguments: * arg argument passed on timeout() call * * Returns: * none * */ static KTimeout_ret atm_timexp(arg) void *arg; { struct atm_time *tip; int s = splimp(); /* * Decrement tick count */ if (((tip = atm_timeq) == NULL) || (--tip->ti_ticks > 0)) { goto restart; } /* * Stack queue should have been drained */ #ifdef DIAGNOSTIC if (atm_stackq_head != NULL) panic("atm_timexp: stack queue not empty"); #endif /* * Dispatch expired timers */ while (((tip = atm_timeq) != NULL) && (tip->ti_ticks == 0)) { void (*func)(struct atm_time *); /* * Remove expired block from queue */ atm_timeq = tip->ti_next; tip->ti_flag &= ~TIF_QUEUED; /* * Call timeout handler (with network interrupts locked out) */ func = tip->ti_func; (void) splx(s); s = splnet(); (*func)(tip); (void) splx(s); s = splimp(); /* * Drain any deferred calls */ STACK_DRAIN(); } restart: /* * Restart the timer */ (void) splx(s); (void) timeout(atm_timexp, (void *)0, hz/ATM_HZ); return; } /* * Schedule a control block timeout * * Place the supplied timer control block on the timer queue. The * function (func) will be called in 't' timer ticks with the * control block address as its only argument. There are ATM_HZ * timer ticks per second. The ticks value stored in each block is * a delta of the number of ticks from the previous block in the queue. * Thus, for each tick interval, only the first block in the queue * needs to have its tick value decremented. * * Arguments: * tip pointer to timer control block * t number of timer ticks until expiration * func pointer to function to call at expiration * * Returns: * none * */ void atm_timeout(tip, t, func) struct atm_time *tip; int t; void (*func)(struct atm_time *); { struct atm_time *tip1, *tip2; int s; /* * Check for double queueing error */ if (tip->ti_flag & TIF_QUEUED) panic("atm_timeout: double queueing"); /* * Make sure we delay at least a little bit */ if (t <= 0) t = 1; /* * Find out where we belong on the queue */ s = splimp(); for (tip1 = NULL, tip2 = atm_timeq; tip2 && (tip2->ti_ticks <= t); tip1 = tip2, tip2 = tip1->ti_next) { t -= tip2->ti_ticks; } /* * Place ourselves on queue and update timer deltas */ if (tip1 == NULL) atm_timeq = tip; else tip1->ti_next = tip; tip->ti_next = tip2; if (tip2) tip2->ti_ticks -= t; /* * Setup timer block */ tip->ti_flag |= TIF_QUEUED; tip->ti_ticks = t; tip->ti_func = func; (void) splx(s); return; } /* * Cancel a timeout * * Remove the supplied timer control block from the timer queue. * * Arguments: * tip pointer to timer control block * * Returns: * 0 control block successfully dequeued * 1 control block not on timer queue * */ int atm_untimeout(tip) struct atm_time *tip; { struct atm_time *tip1, *tip2; int s; /* * Is control block queued? */ if ((tip->ti_flag & TIF_QUEUED) == 0) return(1); /* * Find control block on the queue */ s = splimp(); for (tip1 = NULL, tip2 = atm_timeq; tip2 && (tip2 != tip); tip1 = tip2, tip2 = tip1->ti_next) { } if (tip2 == NULL) { (void) splx(s); return (1); } /* * Remove block from queue and update timer deltas */ tip2 = tip->ti_next; if (tip1 == NULL) atm_timeq = tip2; else tip1->ti_next = tip2; if (tip2) tip2->ti_ticks += tip->ti_ticks; /* * Reset timer block */ tip->ti_flag &= ~TIF_QUEUED; (void) splx(s); return (0); } /* * Queue a Stack Call * * Queues a stack call which must be deferred to the global stack queue. * The call parameters are stored in entries which are allocated from the * stack queue storage pool. * * Arguments: * cmd stack command * func destination function * token destination layer's token * cvp pointer to connection vcc * arg1 command argument * arg2 command argument * * Returns: * 0 call queued * errno call not queued - reason indicated * */ int atm_stack_enq(cmd, func, token, cvp, arg1, arg2) int cmd; void (*func)(int, void *, int, int); void *token; Atm_connvc *cvp; int arg1; int arg2; { struct stackq_entry *sqp; int s = splnet(); /* * Get a new queue entry for this call */ sqp = uma_zalloc(atm_stackq_zone, M_ZERO); if (sqp == NULL) { (void) splx(s); return (ENOMEM); } /* * Fill in new entry */ sqp->sq_next = NULL; sqp->sq_cmd = cmd; sqp->sq_func = func; sqp->sq_token = token; sqp->sq_arg1 = arg1; sqp->sq_arg2 = arg2; sqp->sq_connvc = cvp; /* * Put new entry at end of queue */ if (atm_stackq_head == NULL) atm_stackq_head = sqp; else atm_stackq_tail->sq_next = sqp; atm_stackq_tail = sqp; (void) splx(s); return (0); } /* * Drain the Stack Queue * * Dequeues and processes entries from the global stack queue. * * Arguments: * none * * Returns: * none * */ void atm_stack_drain() { struct stackq_entry *sqp, *qprev, *qnext; int s = splnet(); int cnt; /* * Loop thru entire queue until queue is empty * (but panic rather loop forever) */ do { cnt = 0; qprev = NULL; for (sqp = atm_stackq_head; sqp; ) { /* * Got an eligible entry, do STACK_CALL stuff */ if (sqp->sq_cmd & STKCMD_UP) { if (sqp->sq_connvc->cvc_downcnt) { /* * Cant process now, skip it */ qprev = sqp; sqp = sqp->sq_next; continue; } /* * OK, dispatch the call */ sqp->sq_connvc->cvc_upcnt++; (*sqp->sq_func)(sqp->sq_cmd, sqp->sq_token, sqp->sq_arg1, sqp->sq_arg2); sqp->sq_connvc->cvc_upcnt--; } else { if (sqp->sq_connvc->cvc_upcnt) { /* * Cant process now, skip it */ qprev = sqp; sqp = sqp->sq_next; continue; } /* * OK, dispatch the call */ sqp->sq_connvc->cvc_downcnt++; (*sqp->sq_func)(sqp->sq_cmd, sqp->sq_token, sqp->sq_arg1, sqp->sq_arg2); sqp->sq_connvc->cvc_downcnt--; } /* * Dequeue processed entry and free it */ cnt++; qnext = sqp->sq_next; if (qprev) qprev->sq_next = qnext; else atm_stackq_head = qnext; if (qnext == NULL) atm_stackq_tail = qprev; uma_zfree(atm_stackq_zone, sqp); sqp = qnext; } } while (cnt > 0); /* * Make sure entire queue was drained */ if (atm_stackq_head != NULL) panic("atm_stack_drain: Queue not emptied"); (void) splx(s); } /* * Process Interrupt Queue * * Processes entries on the ATM interrupt queue. This queue is used by * device interface drivers in order to schedule events from the driver's * lower (interrupt) half to the driver's stack services. * * The interrupt routines must store the stack processing function to call * and a token (typically a driver/stack control block) at the front of the * queued buffer. We assume that the function pointer and token values are * both contained (and properly aligned) in the first buffer of the chain. * * Arguments: * none * * Returns: * none * */ void atm_intr() { KBuffer *m; caddr_t cp; atm_intr_func_t func; void *token; int s; for (; ; ) { /* * Get next buffer from queue */ s = splimp(); IF_DEQUEUE(&atm_intrq, m); (void) splx(s); if (m == NULL) break; /* * Get function to call and token value */ KB_DATASTART(m, cp, caddr_t); func = *(atm_intr_func_t *)cp; cp += sizeof(func); token = *(void **)cp; KB_HEADADJ(m, -(sizeof(func) + sizeof(token))); if (KB_LEN(m) == 0) { KBuffer *m1; KB_UNLINKHEAD(m, m1); m = m1; } /* * Call processing function */ (*func)(token, m); /* * Drain any deferred calls */ STACK_DRAIN(); } } /* * Print a pdu buffer chain * * Arguments: * m pointer to pdu buffer chain * msg pointer to message header string * * Returns: * none * */ void atm_pdu_print(m, msg) KBuffer *m; char *msg; { caddr_t cp; int i; char c = ' '; printf("%s:", msg); while (m) { KB_DATASTART(m, cp, caddr_t); printf("%cbfr=%p data=%p len=%d: ", c, m, cp, KB_LEN(m)); c = '\t'; if (atm_print_data) { for (i = 0; i < KB_LEN(m); i++) { printf("%2x ", (u_char)*cp++); } printf("\n"); } else { printf("\n"); } m = KB_NEXT(m); } }