FreeBSD/Linux Kernel Cross Reference
sys/netatm/atm_subr.c
1 /*
2 *
3 * ===================================
4 * HARP | Host ATM Research Platform
5 * ===================================
6 *
7 *
8 * This Host ATM Research Platform ("HARP") file (the "Software") is
9 * made available by Network Computing Services, Inc. ("NetworkCS")
10 * "AS IS". NetworkCS does not provide maintenance, improvements or
11 * support of any kind.
12 *
13 * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
14 * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
15 * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
16 * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
17 * In no event shall NetworkCS be responsible for any damages, including
18 * but not limited to consequential damages, arising from or relating to
19 * any use of the Software or related support.
20 *
21 * Copyright 1994-1998 Network Computing Services, Inc.
22 *
23 * Copies of this Software may be made, however, the above copyright
24 * notice must be reproduced on all copies.
25 *
26 * @(#) $FreeBSD$
27 *
28 */
29
30 /*
31 * Core ATM Services
32 * -----------------
33 *
34 * Miscellaneous ATM subroutines
35 *
36 */
37
38 #include <sys/param.h>
39 #include <sys/sysctl.h>
40 #include <netatm/kern_include.h>
41 #include <net/intrq.h>
42
43 #ifndef lint
44 __RCSID("@(#) $FreeBSD$");
45 #endif
46
47
48 /*
49 * Global variables
50 */
51 struct atm_pif *atm_interface_head = NULL;
52 struct atm_ncm *atm_netconv_head = NULL;
53 Atm_endpoint *atm_endpoints[ENDPT_MAX+1] = {NULL};
54 struct sp_info *atm_pool_head = NULL;
55 struct stackq_entry *atm_stackq_head = NULL, *atm_stackq_tail;
56 #ifdef sgi
57 int atm_intr_index;
58 #endif
59 struct atm_sock_stat atm_sock_stat = { { 0 } };
60 int atm_init = 0;
61 int atm_version = ATM_VERSION;
62 struct timeval atm_debugtime = {0, 0};
63 const int atmintrq_present = 1;
64
65 struct sp_info atm_attributes_pool = {
66 "atm attributes pool", /* si_name */
67 sizeof(Atm_attributes), /* si_blksiz */
68 10, /* si_blkcnt */
69 100 /* si_maxallow */
70 };
71
72 /*
73 * net.harp.atm.atm_debug
74 */
75 int atm_debug;
76 SYSCTL_INT(_net_harp_atm, OID_AUTO, atm_debug, CTLFLAG_RW,
77 &atm_debug, 0, "HARP ATM layer debugging flag");
78
79 /*
80 * net.harp.atm.atm_dev_print
81 */
82 int atm_dev_print;
83 SYSCTL_INT(_net_harp_atm, OID_AUTO, atm_dev_print, CTLFLAG_RW,
84 &atm_dev_print, 0, "display ATM CPCS headers");
85
86 /*
87 * net.harp.atm.atm_print_data
88 */
89 int atm_print_data;
90 SYSCTL_INT(_net_harp_atm, OID_AUTO, atm_print_data, CTLFLAG_RW,
91 &atm_print_data, 0, "display ATM CPCS payloads");
92
93 /*
94 * Local functions
95 */
96 static void atm_compact __P((struct atm_time *));
97 static KTimeout_ret atm_timexp __P((void *));
98
99 /*
100 * Local variables
101 */
102 static struct atm_time *atm_timeq = NULL;
103 static struct atm_time atm_compactimer = {0, 0};
104
105 static struct sp_info atm_stackq_pool = {
106 "Service stack queue pool", /* si_name */
107 sizeof(struct stackq_entry), /* si_blksiz */
108 10, /* si_blkcnt */
109 10 /* si_maxallow */
110 };
111
112
113 /*
114 * Initialize ATM kernel
115 *
116 * Performs any initialization required before things really get underway.
117 * Called from ATM domain initialization or from first registration function
118 * which gets called.
119 *
120 * Arguments:
121 * none
122 *
123 * Returns:
124 * none
125 *
126 */
127 void
128 atm_initialize()
129 {
130 /*
131 * Never called from interrupts, so no locking needed
132 */
133 if (atm_init)
134 return;
135 atm_init = 1;
136
137 #ifndef __FreeBSD__
138 /*
139 * Add ATM protocol family
140 */
141 (void) protocol_family(&atmdomain, NULL, NULL);
142 #endif
143
144 atm_intrq.ifq_maxlen = ATM_INTRQ_MAX;
145 #ifdef sgi
146 atm_intr_index = register_isr(atm_intr);
147 #endif
148 #ifdef __FreeBSD__
149 register_netisr(NETISR_ATM, atm_intr);
150 #endif
151
152 /*
153 * Initialize subsystems
154 */
155 atm_aal5_init();
156
157 /*
158 * Prime the timer
159 */
160 (void) timeout(atm_timexp, (void *)0, hz/ATM_HZ);
161
162 /*
163 * Start the compaction timer
164 */
165 atm_timeout(&atm_compactimer, SPOOL_COMPACT, atm_compact);
166 }
167
168
169 /*
170 * Allocate a Control Block
171 *
172 * Gets a new control block allocated from the specified storage pool,
173 * acquiring memory for new pool chunks if required. The returned control
174 * block's contents will be cleared.
175 *
176 * Arguments:
177 * sip pointer to sp_info for storage pool
178 *
179 * Returns:
180 * addr pointer to allocated control block
181 * 0 allocation failed
182 *
183 */
184 void *
185 atm_allocate(sip)
186 struct sp_info *sip;
187 {
188 void *bp;
189 struct sp_chunk *scp;
190 struct sp_link *slp;
191 int s = splnet();
192
193 /*
194 * Count calls
195 */
196 sip->si_allocs++;
197
198 /*
199 * Are there any free in the pool?
200 */
201 if (sip->si_free) {
202
203 /*
204 * Find first chunk with a free block
205 */
206 for (scp = sip->si_poolh; scp; scp = scp->sc_next) {
207 if (scp->sc_freeh != NULL)
208 break;
209 }
210
211 } else {
212
213 /*
214 * No free blocks - have to allocate a new
215 * chunk (but put a limit to this)
216 */
217 struct sp_link *slp_next;
218 int i;
219
220 /*
221 * First time for this pool??
222 */
223 if (sip->si_chunksiz == 0) {
224 size_t n;
225
226 /*
227 * Initialize pool information
228 */
229 n = sizeof(struct sp_chunk) +
230 sip->si_blkcnt *
231 (sip->si_blksiz + sizeof(struct sp_link));
232 sip->si_chunksiz = roundup(n, SPOOL_ROUNDUP);
233
234 /*
235 * Place pool on kernel chain
236 */
237 LINK2TAIL(sip, struct sp_info, atm_pool_head, si_next);
238 }
239
240 if (sip->si_chunks >= sip->si_maxallow) {
241 sip->si_fails++;
242 (void) splx(s);
243 return (NULL);
244 }
245
246 scp = (struct sp_chunk *)
247 KM_ALLOC(sip->si_chunksiz, M_DEVBUF, M_NOWAIT);
248 if (scp == NULL) {
249 sip->si_fails++;
250 (void) splx(s);
251 return (NULL);
252 }
253 scp->sc_next = NULL;
254 scp->sc_info = sip;
255 scp->sc_magic = SPOOL_MAGIC;
256 scp->sc_used = 0;
257
258 /*
259 * Divy up chunk into free blocks
260 */
261 slp = (struct sp_link *)(scp + 1);
262 scp->sc_freeh = slp;
263
264 for (i = sip->si_blkcnt; i > 1; i--) {
265 slp_next = (struct sp_link *)((caddr_t)(slp + 1) +
266 sip->si_blksiz);
267 slp->sl_u.slu_next = slp_next;
268 slp = slp_next;
269 }
270 slp->sl_u.slu_next = NULL;
271 scp->sc_freet = slp;
272
273 /*
274 * Add new chunk to end of pool
275 */
276 if (sip->si_poolh)
277 sip->si_poolt->sc_next = scp;
278 else
279 sip->si_poolh = scp;
280 sip->si_poolt = scp;
281
282 sip->si_chunks++;
283 sip->si_total += sip->si_blkcnt;
284 sip->si_free += sip->si_blkcnt;
285 if (sip->si_chunks > sip->si_maxused)
286 sip->si_maxused = sip->si_chunks;
287 }
288
289 /*
290 * Allocate the first free block in chunk
291 */
292 slp = scp->sc_freeh;
293 scp->sc_freeh = slp->sl_u.slu_next;
294 scp->sc_used++;
295 sip->si_free--;
296 bp = (slp + 1);
297
298 /*
299 * Save link back to pool chunk
300 */
301 slp->sl_u.slu_chunk = scp;
302
303 /*
304 * Clear out block
305 */
306 KM_ZERO(bp, sip->si_blksiz);
307
308 (void) splx(s);
309 return (bp);
310 }
311
312
313 /*
314 * Free a Control Block
315 *
316 * Returns a previously allocated control block back to the owners
317 * storage pool.
318 *
319 * Arguments:
320 * bp pointer to block to be freed
321 *
322 * Returns:
323 * none
324 *
325 */
326 void
327 atm_free(bp)
328 void *bp;
329 {
330 struct sp_info *sip;
331 struct sp_chunk *scp;
332 struct sp_link *slp;
333 int s = splnet();
334
335 /*
336 * Get containing chunk and pool info
337 */
338 slp = (struct sp_link *)bp;
339 slp--;
340 scp = slp->sl_u.slu_chunk;
341 if (scp->sc_magic != SPOOL_MAGIC)
342 panic("atm_free: chunk magic missing");
343 sip = scp->sc_info;
344
345 /*
346 * Add block to free chain
347 */
348 if (scp->sc_freeh) {
349 scp->sc_freet->sl_u.slu_next = slp;
350 scp->sc_freet = slp;
351 } else
352 scp->sc_freeh = scp->sc_freet = slp;
353 slp->sl_u.slu_next = NULL;
354 sip->si_free++;
355 scp->sc_used--;
356
357 (void) splx(s);
358 return;
359 }
360
361
362 /*
363 * Storage Pool Compaction
364 *
365 * Called periodically in order to perform compaction of the
366 * storage pools. Each pool will be checked to see if any chunks
367 * can be freed, taking some care to avoid freeing too many chunks
368 * in order to avoid memory thrashing.
369 *
370 * Called at splnet.
371 *
372 * Arguments:
373 * tip pointer to timer control block (atm_compactimer)
374 *
375 * Returns:
376 * none
377 *
378 */
379 static void
380 atm_compact(tip)
381 struct atm_time *tip;
382 {
383 struct sp_info *sip;
384 struct sp_chunk *scp;
385 int i;
386 struct sp_chunk *scp_prev;
387
388 /*
389 * Check out all storage pools
390 */
391 for (sip = atm_pool_head; sip; sip = sip->si_next) {
392
393 /*
394 * Always keep a minimum number of chunks around
395 */
396 if (sip->si_chunks <= SPOOL_MIN_CHUNK)
397 continue;
398
399 /*
400 * Maximum chunks to free at one time will leave
401 * pool with at least 50% utilization, but never
402 * go below minimum chunk count.
403 */
404 i = ((sip->si_free * 2) - sip->si_total) / sip->si_blkcnt;
405 i = MIN(i, sip->si_chunks - SPOOL_MIN_CHUNK);
406
407 /*
408 * Look for chunks to free
409 */
410 scp_prev = NULL;
411 for (scp = sip->si_poolh; scp && i > 0; ) {
412
413 if (scp->sc_used == 0) {
414
415 /*
416 * Found a chunk to free, so do it
417 */
418 if (scp_prev) {
419 scp_prev->sc_next = scp->sc_next;
420 if (sip->si_poolt == scp)
421 sip->si_poolt = scp_prev;
422 } else
423 sip->si_poolh = scp->sc_next;
424
425 KM_FREE((caddr_t)scp, sip->si_chunksiz,
426 M_DEVBUF);
427
428 /*
429 * Update pool controls
430 */
431 sip->si_chunks--;
432 sip->si_total -= sip->si_blkcnt;
433 sip->si_free -= sip->si_blkcnt;
434 i--;
435 if (scp_prev)
436 scp = scp_prev->sc_next;
437 else
438 scp = sip->si_poolh;
439 } else {
440 scp_prev = scp;
441 scp = scp->sc_next;
442 }
443 }
444 }
445
446 /*
447 * Restart the compaction timer
448 */
449 atm_timeout(&atm_compactimer, SPOOL_COMPACT, atm_compact);
450
451 return;
452 }
453
454
455 /*
456 * Release a Storage Pool
457 *
458 * Frees all dynamic storage acquired for a storage pool.
459 * This function is normally called just prior to a module's unloading.
460 *
461 * Arguments:
462 * sip pointer to sp_info for storage pool
463 *
464 * Returns:
465 * none
466 *
467 */
468 void
469 atm_release_pool(sip)
470 struct sp_info *sip;
471 {
472 struct sp_chunk *scp, *scp_next;
473 int s = splnet();
474
475 /*
476 * Free each chunk in pool
477 */
478 for (scp = sip->si_poolh; scp; scp = scp_next) {
479
480 /*
481 * Check for memory leaks
482 */
483 if (scp->sc_used)
484 panic("atm_release_pool: unfreed blocks");
485
486 scp_next = scp->sc_next;
487
488 KM_FREE((caddr_t)scp, sip->si_chunksiz, M_DEVBUF);
489 }
490
491 /*
492 * Update pool controls
493 */
494 sip->si_poolh = NULL;
495 sip->si_chunks = 0;
496 sip->si_total = 0;
497 sip->si_free = 0;
498
499 /*
500 * Unlink pool from active chain
501 */
502 sip->si_chunksiz = 0;
503 UNLINK(sip, struct sp_info, atm_pool_head, si_next);
504
505 (void) splx(s);
506 return;
507 }
508
509
510 /*
511 * Handle timer tick expiration
512 *
513 * Decrement tick count in first block on timer queue. If there
514 * are blocks with expired timers, call their timeout function.
515 * This function is called ATM_HZ times per second.
516 *
517 * Arguments:
518 * arg argument passed on timeout() call
519 *
520 * Returns:
521 * none
522 *
523 */
524 static KTimeout_ret
525 atm_timexp(arg)
526 void *arg;
527 {
528 struct atm_time *tip;
529 int s = splimp();
530
531
532 /*
533 * Decrement tick count
534 */
535 if (((tip = atm_timeq) == NULL) || (--tip->ti_ticks > 0)) {
536 goto restart;
537 }
538
539 /*
540 * Stack queue should have been drained
541 */
542 #ifdef DIAGNOSTIC
543 if (atm_stackq_head != NULL)
544 panic("atm_timexp: stack queue not empty");
545 #endif
546
547 /*
548 * Dispatch expired timers
549 */
550 while (((tip = atm_timeq) != NULL) && (tip->ti_ticks == 0)) {
551 void (*func)__P((struct atm_time *));
552
553 /*
554 * Remove expired block from queue
555 */
556 atm_timeq = tip->ti_next;
557 tip->ti_flag &= ~TIF_QUEUED;
558
559 /*
560 * Call timeout handler (with network interrupts locked out)
561 */
562 func = tip->ti_func;
563 (void) splx(s);
564 s = splnet();
565 (*func)(tip);
566 (void) splx(s);
567 s = splimp();
568
569 /*
570 * Drain any deferred calls
571 */
572 STACK_DRAIN();
573 }
574
575 restart:
576 /*
577 * Restart the timer
578 */
579 (void) splx(s);
580 (void) timeout(atm_timexp, (void *)0, hz/ATM_HZ);
581
582 return;
583 }
584
585
586 /*
587 * Schedule a control block timeout
588 *
589 * Place the supplied timer control block on the timer queue. The
590 * function (func) will be called in 't' timer ticks with the
591 * control block address as its only argument. There are ATM_HZ
592 * timer ticks per second. The ticks value stored in each block is
593 * a delta of the number of ticks from the previous block in the queue.
594 * Thus, for each tick interval, only the first block in the queue
595 * needs to have its tick value decremented.
596 *
597 * Arguments:
598 * tip pointer to timer control block
599 * t number of timer ticks until expiration
600 * func pointer to function to call at expiration
601 *
602 * Returns:
603 * none
604 *
605 */
606 void
607 atm_timeout(tip, t, func)
608 struct atm_time *tip;
609 int t;
610 void (*func)__P((struct atm_time *));
611 {
612 struct atm_time *tip1, *tip2;
613 int s;
614
615
616 /*
617 * Check for double queueing error
618 */
619 if (tip->ti_flag & TIF_QUEUED)
620 panic("atm_timeout: double queueing");
621
622 /*
623 * Make sure we delay at least a little bit
624 */
625 if (t <= 0)
626 t = 1;
627
628 /*
629 * Find out where we belong on the queue
630 */
631 s = splimp();
632 for (tip1 = NULL, tip2 = atm_timeq; tip2 && (tip2->ti_ticks <= t);
633 tip1 = tip2, tip2 = tip1->ti_next) {
634 t -= tip2->ti_ticks;
635 }
636
637 /*
638 * Place ourselves on queue and update timer deltas
639 */
640 if (tip1 == NULL)
641 atm_timeq = tip;
642 else
643 tip1->ti_next = tip;
644 tip->ti_next = tip2;
645
646 if (tip2)
647 tip2->ti_ticks -= t;
648
649 /*
650 * Setup timer block
651 */
652 tip->ti_flag |= TIF_QUEUED;
653 tip->ti_ticks = t;
654 tip->ti_func = func;
655
656 (void) splx(s);
657 return;
658 }
659
660
661 /*
662 * Cancel a timeout
663 *
664 * Remove the supplied timer control block from the timer queue.
665 *
666 * Arguments:
667 * tip pointer to timer control block
668 *
669 * Returns:
670 * 0 control block successfully dequeued
671 * 1 control block not on timer queue
672 *
673 */
674 int
675 atm_untimeout(tip)
676 struct atm_time *tip;
677 {
678 struct atm_time *tip1, *tip2;
679 int s;
680
681 /*
682 * Is control block queued?
683 */
684 if ((tip->ti_flag & TIF_QUEUED) == 0)
685 return(1);
686
687 /*
688 * Find control block on the queue
689 */
690 s = splimp();
691 for (tip1 = NULL, tip2 = atm_timeq; tip2 && (tip2 != tip);
692 tip1 = tip2, tip2 = tip1->ti_next) {
693 }
694
695 if (tip2 == NULL) {
696 (void) splx(s);
697 return (1);
698 }
699
700 /*
701 * Remove block from queue and update timer deltas
702 */
703 tip2 = tip->ti_next;
704 if (tip1 == NULL)
705 atm_timeq = tip2;
706 else
707 tip1->ti_next = tip2;
708
709 if (tip2)
710 tip2->ti_ticks += tip->ti_ticks;
711
712 /*
713 * Reset timer block
714 */
715 tip->ti_flag &= ~TIF_QUEUED;
716
717 (void) splx(s);
718 return (0);
719 }
720
721
722 /*
723 * Queue a Stack Call
724 *
725 * Queues a stack call which must be deferred to the global stack queue.
726 * The call parameters are stored in entries which are allocated from the
727 * stack queue storage pool.
728 *
729 * Arguments:
730 * cmd stack command
731 * func destination function
732 * token destination layer's token
733 * cvp pointer to connection vcc
734 * arg1 command argument
735 * arg2 command argument
736 *
737 * Returns:
738 * 0 call queued
739 * errno call not queued - reason indicated
740 *
741 */
742 int
743 atm_stack_enq(cmd, func, token, cvp, arg1, arg2)
744 int cmd;
745 void (*func)__P((int, void *, int, int));
746 void *token;
747 Atm_connvc *cvp;
748 int arg1;
749 int arg2;
750 {
751 struct stackq_entry *sqp;
752 int s = splnet();
753
754 /*
755 * Get a new queue entry for this call
756 */
757 sqp = (struct stackq_entry *)atm_allocate(&atm_stackq_pool);
758 if (sqp == NULL) {
759 (void) splx(s);
760 return (ENOMEM);
761 }
762
763 /*
764 * Fill in new entry
765 */
766 sqp->sq_next = NULL;
767 sqp->sq_cmd = cmd;
768 sqp->sq_func = func;
769 sqp->sq_token = token;
770 sqp->sq_arg1 = arg1;
771 sqp->sq_arg2 = arg2;
772 sqp->sq_connvc = cvp;
773
774 /*
775 * Put new entry at end of queue
776 */
777 if (atm_stackq_head == NULL)
778 atm_stackq_head = sqp;
779 else
780 atm_stackq_tail->sq_next = sqp;
781 atm_stackq_tail = sqp;
782
783 (void) splx(s);
784 return (0);
785 }
786
787
788 /*
789 * Drain the Stack Queue
790 *
791 * Dequeues and processes entries from the global stack queue.
792 *
793 * Arguments:
794 * none
795 *
796 * Returns:
797 * none
798 *
799 */
800 void
801 atm_stack_drain()
802 {
803 struct stackq_entry *sqp, *qprev, *qnext;
804 int s = splnet();
805 int cnt;
806
807 /*
808 * Loop thru entire queue until queue is empty
809 * (but panic rather loop forever)
810 */
811 do {
812 cnt = 0;
813 qprev = NULL;
814 for (sqp = atm_stackq_head; sqp; ) {
815
816 /*
817 * Got an eligible entry, do STACK_CALL stuff
818 */
819 if (sqp->sq_cmd & STKCMD_UP) {
820 if (sqp->sq_connvc->cvc_downcnt) {
821
822 /*
823 * Cant process now, skip it
824 */
825 qprev = sqp;
826 sqp = sqp->sq_next;
827 continue;
828 }
829
830 /*
831 * OK, dispatch the call
832 */
833 sqp->sq_connvc->cvc_upcnt++;
834 (*sqp->sq_func)(sqp->sq_cmd,
835 sqp->sq_token,
836 sqp->sq_arg1,
837 sqp->sq_arg2);
838 sqp->sq_connvc->cvc_upcnt--;
839 } else {
840 if (sqp->sq_connvc->cvc_upcnt) {
841
842 /*
843 * Cant process now, skip it
844 */
845 qprev = sqp;
846 sqp = sqp->sq_next;
847 continue;
848 }
849
850 /*
851 * OK, dispatch the call
852 */
853 sqp->sq_connvc->cvc_downcnt++;
854 (*sqp->sq_func)(sqp->sq_cmd,
855 sqp->sq_token,
856 sqp->sq_arg1,
857 sqp->sq_arg2);
858 sqp->sq_connvc->cvc_downcnt--;
859 }
860
861 /*
862 * Dequeue processed entry and free it
863 */
864 cnt++;
865 qnext = sqp->sq_next;
866 if (qprev)
867 qprev->sq_next = qnext;
868 else
869 atm_stackq_head = qnext;
870 if (qnext == NULL)
871 atm_stackq_tail = qprev;
872 atm_free((caddr_t)sqp);
873 sqp = qnext;
874 }
875 } while (cnt > 0);
876
877 /*
878 * Make sure entire queue was drained
879 */
880 if (atm_stackq_head != NULL)
881 panic("atm_stack_drain: Queue not emptied");
882
883 (void) splx(s);
884 }
885
886
887 /*
888 * Process Interrupt Queue
889 *
890 * Processes entries on the ATM interrupt queue. This queue is used by
891 * device interface drivers in order to schedule events from the driver's
892 * lower (interrupt) half to the driver's stack services.
893 *
894 * The interrupt routines must store the stack processing function to call
895 * and a token (typically a driver/stack control block) at the front of the
896 * queued buffer. We assume that the function pointer and token values are
897 * both contained (and properly aligned) in the first buffer of the chain.
898 *
899 * Arguments:
900 * none
901 *
902 * Returns:
903 * none
904 *
905 */
906 void
907 atm_intr()
908 {
909 KBuffer *m;
910 caddr_t cp;
911 atm_intr_func_t func;
912 void *token;
913 int s;
914
915 for (; ; ) {
916 /*
917 * Get next buffer from queue
918 */
919 s = splimp();
920 IF_DEQUEUE(&atm_intrq, m);
921 (void) splx(s);
922 if (m == NULL)
923 break;
924
925 /*
926 * Get function to call and token value
927 */
928 KB_DATASTART(m, cp, caddr_t);
929 func = *(atm_intr_func_t *)cp;
930 cp += sizeof(func);
931 token = *(void **)cp;
932 KB_HEADADJ(m, -(sizeof(func) + sizeof(token)));
933 if (KB_LEN(m) == 0) {
934 KBuffer *m1;
935 KB_UNLINKHEAD(m, m1);
936 m = m1;
937 }
938
939 /*
940 * Call processing function
941 */
942 (*func)(token, m);
943
944 /*
945 * Drain any deferred calls
946 */
947 STACK_DRAIN();
948 }
949 }
950
951
952 /*
953 * Print a pdu buffer chain
954 *
955 * Arguments:
956 * m pointer to pdu buffer chain
957 * msg pointer to message header string
958 *
959 * Returns:
960 * none
961 *
962 */
963 void
964 atm_pdu_print(const KBuffer *m, const char *msg)
965 {
966 const u_char *cp;
967 int i;
968 char c = ' ';
969
970 printf("%s:", msg);
971 while (m) {
972 KB_DATASTART(m, cp, const u_char *);
973 printf("%cbfr=%p data=%p len=%d: ",
974 c, m, cp, KB_LEN(m));
975 c = '\t';
976 if (atm_print_data) {
977 for (i = 0; i < KB_LEN(m); i++) {
978 printf("%2x ", *cp++);
979 }
980 printf("<end_bfr>\n");
981 } else {
982 printf("\n");
983 }
984 m = KB_NEXT(m);
985 }
986 }
Cache object: 4f98b3ba294139b14ce8461738122a07
|