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