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 * ATM device support functions
35 *
36 */
37
38 #include <netatm/kern_include.h>
39
40 #ifndef lint
41 __RCSID("@(#) $FreeBSD$");
42 #endif
43
44
45 /*
46 * Private structures for managing allocated kernel memory resources
47 *
48 * For each allocation of kernel memory, one Mem_ent will be used.
49 * The Mem_ent structures will be allocated in blocks inside of a
50 * Mem_blk structure.
51 */
52 #define MEM_NMEMENT 10 /* How many Mem_ent's in a Mem_blk */
53
54 struct mem_ent {
55 void *me_kaddr; /* Allocated memory address */
56 u_int me_ksize; /* Allocated memory length */
57 void *me_uaddr; /* Memory address returned to caller */
58 u_int me_flags; /* Flags (see below) */
59 };
60 typedef struct mem_ent Mem_ent;
61
62 /*
63 * Memory entry flags
64 */
65 #define MEF_NONCACHE 1 /* Memory is noncacheable */
66
67
68 struct mem_blk {
69 struct mem_blk *mb_next; /* Next block in chain */
70 Mem_ent mb_mement[MEM_NMEMENT]; /* Allocated memory entries */
71 };
72 typedef struct mem_blk Mem_blk;
73
74 static Mem_blk *atm_mem_head = NULL;
75
76 static struct t_atm_cause atm_dev_cause = {
77 T_ATM_ITU_CODING,
78 T_ATM_LOC_USER,
79 T_ATM_CAUSE_VPCI_VCI_ASSIGNMENT_FAILURE,
80 {0, 0, 0, 0}
81 };
82
83
84 /*
85 * ATM Device Stack Instantiation
86 *
87 * Called at splnet.
88 *
89 * Arguments
90 * ssp pointer to array of stack definition pointers
91 * for connection
92 * ssp[0] points to upper layer's stack definition
93 * ssp[1] points to this layer's stack definition
94 * ssp[2] points to lower layer's stack definition
95 * cvcp pointer to connection vcc for this stack
96 *
97 * Returns
98 * 0 instantiation successful
99 * err instantiation failed - reason indicated
100 *
101 */
102 int
103 atm_dev_inst(ssp, cvcp)
104 struct stack_defn **ssp;
105 Atm_connvc *cvcp;
106 {
107 Cmn_unit *cup = (Cmn_unit *)cvcp->cvc_attr.nif->nif_pif;
108 Cmn_vcc *cvp;
109 int err;
110
111 /*
112 * Check to see if device has been initialized
113 */
114 if ((cup->cu_flags & CUF_INITED) == 0)
115 return ( EIO );
116
117 /*
118 * Validate lower SAP
119 */
120 /*
121 * Device driver is the lowest layer - no need to validate
122 */
123
124 /*
125 * Validate PVC vpi.vci
126 */
127 if (cvcp->cvc_attr.called.addr.address_format == T_ATM_PVC_ADDR) {
128 /*
129 * Look through existing circuits - return error if found
130 */
131 Atm_addr_pvc *pp;
132
133 pp = (Atm_addr_pvc *)cvcp->cvc_attr.called.addr.address;
134 if (atm_dev_vcc_find(cup, ATM_PVC_GET_VPI(pp),
135 ATM_PVC_GET_VCI(pp), 0))
136 return ( EADDRINUSE );
137 }
138
139 /*
140 * Validate our SAP type
141 */
142 switch ((*(ssp+1))->sd_sap) {
143 case SAP_CPCS_AAL3_4:
144 case SAP_CPCS_AAL5:
145 case SAP_ATM:
146 break;
147 default:
148 return (EINVAL);
149 }
150
151 /*
152 * Allocate a VCC control block
153 */
154 if ( ( cvp = (Cmn_vcc *)atm_allocate(cup->cu_vcc_pool) ) == NULL )
155 return ( ENOMEM );
156
157 cvp->cv_state = CVS_INST;
158 cvp->cv_toku = (*ssp)->sd_toku;
159 cvp->cv_upper = (*ssp)->sd_upper;
160 cvp->cv_connvc = cvcp;
161
162 /*
163 * Let device have a look at the connection request
164 */
165 err = (*cup->cu_instvcc)(cup, cvp);
166 if (err) {
167 atm_free((caddr_t)cvp);
168 return (err);
169 }
170
171 /*
172 * Looks good so far, so link in device VCC
173 */
174 LINK2TAIL ( cvp, Cmn_vcc, cup->cu_vcc, cv_next );
175
176 /*
177 * Save my token
178 */
179 (*++ssp)->sd_toku = cvp;
180
181 /*
182 * Pass instantiation down the stack
183 */
184 /*
185 * No need - we're the lowest point.
186 */
187 /* err = (*(ssp + 1))->sd_inst(ssp, cvcp); */
188
189 /*
190 * Save the lower layer's interface info
191 */
192 /*
193 * No need - we're the lowest point
194 */
195 /* cvp->cv_lower = (*++ssp)->sd_lower; */
196 /* cvp->cv_tok1 = (*ssp)->sd_toku; */
197
198 return (0);
199 }
200
201
202 /*
203 * ATM Device Stack Command Handler
204 *
205 * Arguments
206 * cmd stack command code
207 * tok session token (Cmn_vcc)
208 * arg1 command specific argument
209 * arg2 command specific argument
210 *
211 * Returns
212 * none
213 *
214 */
215 /*ARGSUSED*/
216 void
217 atm_dev_lower(cmd, tok, arg1, arg2)
218 int cmd;
219 void *tok;
220 int arg1;
221 int arg2;
222 {
223 Cmn_vcc *cvp = (Cmn_vcc *)tok;
224 Atm_connvc *cvcp = cvp->cv_connvc;
225 Cmn_unit *cup = (Cmn_unit *)cvcp->cvc_attr.nif->nif_pif;
226 struct vccb *vcp;
227 u_int state;
228 int s;
229
230 switch ( cmd ) {
231
232 case CPCS_INIT:
233 /*
234 * Sanity check
235 */
236 if ( cvp->cv_state != CVS_INST ) {
237 log ( LOG_ERR,
238 "atm_dev_lower: INIT: tok=%p, state=%d\n",
239 tok, cvp->cv_state );
240 break;
241 }
242
243 vcp = cvp->cv_connvc->cvc_vcc;
244
245 /*
246 * Validate SVC vpi.vci
247 */
248 if ( vcp->vc_type & VCC_SVC ) {
249
250 if (atm_dev_vcc_find(cup, vcp->vc_vpi, vcp->vc_vci,
251 vcp->vc_type & (VCC_IN | VCC_OUT))
252 != cvp){
253 log ( LOG_ERR,
254 "atm_dev_lower: dup SVC (%d,%d) tok=%p\n",
255 vcp->vc_vpi, vcp->vc_vci, tok );
256 atm_cm_abort(cvp->cv_connvc, &atm_dev_cause);
257 break;
258 }
259 }
260
261 /*
262 * Tell the device to open the VCC
263 */
264 cvp->cv_state = CVS_INITED;
265 s = splimp();
266 if ((*cup->cu_openvcc)(cup, cvp)) {
267 atm_cm_abort(cvp->cv_connvc, &atm_dev_cause);
268 (void) splx(s);
269 break;
270 }
271 (void) splx(s);
272 break;
273
274 case CPCS_TERM: {
275 KBuffer *m, *prev, *next;
276 int *ip;
277
278 s = splimp();
279
280 /*
281 * Disconnect the VCC - ignore return code
282 */
283 if ((cvp->cv_state == CVS_INITED) ||
284 (cvp->cv_state == CVS_ACTIVE)) {
285 (void) (*cup->cu_closevcc)(cup, cvp);
286 }
287 cvp->cv_state = CVS_TERM;
288
289 /*
290 * Remove from interface list
291 */
292 UNLINK ( cvp, Cmn_vcc, cup->cu_vcc, cv_next );
293
294 /*
295 * Free any buffers from this VCC on the ATM interrupt queue
296 */
297 prev = NULL;
298 for (m = atm_intrq.ifq_head; m; m = next) {
299 next = KB_QNEXT(m);
300
301 /*
302 * See if this entry is for the terminating VCC
303 */
304 KB_DATASTART(m, ip, int *);
305 ip++;
306 if (*ip == (int)cvp) {
307 /*
308 * Yep, so dequeue the entry
309 */
310 if (prev == NULL)
311 atm_intrq.ifq_head = next;
312 else
313 KB_QNEXT(prev) = next;
314
315 if (next == NULL)
316 atm_intrq.ifq_tail = prev;
317
318 atm_intrq.ifq_len--;
319
320 /*
321 * Free the unwanted buffers
322 */
323 KB_FREEALL(m);
324 } else {
325 prev = m;
326 }
327 }
328 (void) splx(s);
329
330 /*
331 * Free VCC resources
332 */
333 (void) atm_free((caddr_t)cvp);
334 break;
335 }
336
337 case CPCS_UNITDATA_INV:
338
339 /*
340 * Sanity check
341 *
342 * Use temp state variable since we dont want to lock out
343 * interrupts, but initial VC activation interrupt may
344 * happen here, changing state somewhere in the middle.
345 */
346 state = cvp->cv_state;
347 if ((state != CVS_ACTIVE) &&
348 (state != CVS_INITED)) {
349 log ( LOG_ERR,
350 "atm_dev_lower: UNITDATA: tok=%p, state=%d\n",
351 tok, state );
352 KB_FREEALL((KBuffer *)arg1);
353 break;
354 }
355
356 /*
357 * Hand the data off to the device
358 */
359 (*cup->cu_output)(cup, cvp, (KBuffer *)arg1);
360
361 break;
362
363 case CPCS_UABORT_INV:
364 log ( LOG_ERR,
365 "atm_dev_lower: unimplemented stack cmd 0x%x, tok=%p\n",
366 cmd, tok );
367 break;
368
369 default:
370 log ( LOG_ERR,
371 "atm_dev_lower: unknown stack cmd 0x%x, tok=%p\n",
372 cmd, tok );
373
374 }
375
376 return;
377 }
378
379
380
381 /*
382 * Allocate kernel memory block
383 *
384 * This function will allocate a kernel memory block of the type specified
385 * in the flags parameter. The returned address will point to a memory
386 * block of the requested size and alignment. The memory block will also
387 * be zeroed. The alloc/free functions will manage/mask both the OS-specific
388 * kernel memory management requirements and the bookkeeping required to
389 * deal with data alignment issues.
390 *
391 * This function should not be called from interrupt level.
392 *
393 * Arguments:
394 * size size of memory block to allocate
395 * align data alignment requirement
396 * flags allocation flags (ATM_DEV_*)
397 *
398 * Returns:
399 * uaddr pointer to aligned memory block
400 * NULL unable to allocate memory
401 *
402 */
403 void *
404 atm_dev_alloc(size, align, flags)
405 u_int size;
406 u_int align;
407 u_int flags;
408 {
409 Mem_blk *mbp;
410 Mem_ent *mep;
411 u_int kalign, ksize;
412 int s, i;
413
414 s = splimp();
415
416 /*
417 * Find a free Mem_ent
418 */
419 mep = NULL;
420 for (mbp = atm_mem_head; mbp && mep == NULL; mbp = mbp->mb_next) {
421 for (i = 0; i < MEM_NMEMENT; i++) {
422 if (mbp->mb_mement[i].me_uaddr == NULL) {
423 mep = &mbp->mb_mement[i];
424 break;
425 }
426 }
427 }
428
429 /*
430 * If there are no free Mem_ent's, then allocate a new Mem_blk
431 * and link it into the chain
432 */
433 if (mep == NULL) {
434 mbp = (Mem_blk *) KM_ALLOC(sizeof(Mem_blk), M_DEVBUF, M_NOWAIT);
435 if (mbp == NULL) {
436 log(LOG_ERR, "atm_dev_alloc: Mem_blk failure\n");
437 (void) splx(s);
438 return (NULL);
439 }
440 KM_ZERO(mbp, sizeof(Mem_blk));
441
442 mbp->mb_next = atm_mem_head;
443 atm_mem_head = mbp;
444 mep = mbp->mb_mement;
445 }
446
447 /*
448 * Now we need to get the kernel's allocation alignment minimum
449 *
450 * This is obviously very OS-specific stuff
451 */
452 #ifdef sun
453 if (flags & ATM_DEV_NONCACHE) {
454 /* Byte-aligned */
455 kalign = sizeof(long);
456 } else {
457 /* Doubleword-aligned */
458 kalign = sizeof(double);
459 }
460 #elif (defined(BSD) && (BSD >= 199103))
461 kalign = MINALLOCSIZE;
462 #else
463 #error Unsupported/unconfigured OS
464 #endif
465
466 /*
467 * Figure out how much memory we must allocate to satify the
468 * user's size and alignment needs
469 */
470 if (align <= kalign)
471 ksize = size;
472 else
473 ksize = size + align - kalign;
474
475 /*
476 * Finally, go get the memory
477 */
478 if (flags & ATM_DEV_NONCACHE) {
479 #ifdef sun
480 mep->me_kaddr = IOPBALLOC(ksize);
481 #elif defined(__i386__)
482 mep->me_kaddr = KM_ALLOC(ksize, M_DEVBUF, M_NOWAIT);
483 #else
484 #error Unsupported/unconfigured OS
485 #endif
486 } else {
487 mep->me_kaddr = KM_ALLOC(ksize, M_DEVBUF, M_NOWAIT);
488 }
489
490 if (mep->me_kaddr == NULL) {
491 log(LOG_ERR, "atm_dev_alloc: %skernel memory unavailable\n",
492 (flags & ATM_DEV_NONCACHE) ? "non-cacheable " : "");
493 (void) splx(s);
494 return (NULL);
495 }
496
497 /*
498 * Calculate correct alignment address to pass back to user
499 */
500 mep->me_uaddr = (void *) roundup((u_int)mep->me_kaddr, align);
501 mep->me_ksize = ksize;
502 mep->me_flags = flags;
503
504 /*
505 * Clear memory for user
506 */
507 KM_ZERO(mep->me_uaddr, size);
508
509 ATM_DEBUG4("atm_dev_alloc: size=%d, align=%d, flags=%d, uaddr=%p\n",
510 size, align, flags, mep->me_uaddr);
511
512 (void) splx(s);
513
514 return (mep->me_uaddr);
515 }
516
517
518 /*
519 * Free kernel memory block
520 *
521 * This function will free a kernel memory block previously allocated by
522 * the atm_dev_alloc function.
523 *
524 * This function should not be called from interrupt level.
525 *
526 * Arguments:
527 * uaddr pointer to allocated aligned memory block
528 *
529 * Returns:
530 * none
531 *
532 */
533 void
534 atm_dev_free(uaddr)
535 void *uaddr;
536 {
537 Mem_blk *mbp;
538 Mem_ent *mep;
539 int s, i;
540
541 ATM_DEBUG1("atm_dev_free: uaddr=%p\n", uaddr);
542
543 s = splimp();
544
545 /*
546 * Protect ourselves...
547 */
548 if (uaddr == NULL)
549 panic("atm_dev_free: trying to free null address");
550
551 /*
552 * Find our associated entry
553 */
554 mep = NULL;
555 for (mbp = atm_mem_head; mbp && mep == NULL; mbp = mbp->mb_next) {
556 for (i = 0; i < MEM_NMEMENT; i++) {
557 if (mbp->mb_mement[i].me_uaddr == uaddr) {
558 mep = &mbp->mb_mement[i];
559 break;
560 }
561 }
562 }
563
564 /*
565 * If we didn't find our entry, then unceremoniously let the caller
566 * know they screwed up (it certainly couldn't be a bug here...)
567 */
568 if (mep == NULL)
569 panic("atm_dev_free: trying to free unknown address");
570
571 /*
572 * Give the memory space back to the kernel
573 */
574 if (mep->me_flags & ATM_DEV_NONCACHE) {
575 #ifdef sun
576 IOPBFREE(mep->me_kaddr, mep->me_ksize);
577 #elif defined(__i386__)
578 KM_FREE(mep->me_kaddr, mep->me_ksize, M_DEVBUF);
579 #else
580 #error Unsupported/unconfigured OS
581 #endif
582 } else {
583 KM_FREE(mep->me_kaddr, mep->me_ksize, M_DEVBUF);
584 }
585
586 /*
587 * Free our entry
588 */
589 mep->me_uaddr = NULL;
590
591 (void) splx(s);
592
593 return;
594 }
595
596
597 #ifdef sun4m
598
599 typedef int (*func_t)();
600
601 /*
602 * Map an address into DVMA space
603 *
604 * This function will take a kernel virtual address and map it to
605 * a DMA virtual address which can be used during SBus DMA cycles.
606 *
607 * Arguments:
608 * addr kernel virtual address
609 * len length of DVMA space requested
610 * flags allocation flags (ATM_DEV_*)
611 *
612 * Returns:
613 * a DVMA address
614 * NULL unable to map into DMA space
615 *
616 */
617 void *
618 atm_dma_map(addr, len, flags)
619 caddr_t addr;
620 int len;
621 int flags;
622 {
623 if (flags & ATM_DEV_NONCACHE)
624 /*
625 * Non-cacheable memory is already DMA'able
626 */
627 return ((void *)addr);
628 else
629 return ((void *)mb_nbmapalloc(bigsbusmap, addr, len,
630 MDR_BIGSBUS|MB_CANTWAIT, (func_t)NULL, (caddr_t)NULL));
631 }
632
633
634 /*
635 * Free a DVMA map address
636 *
637 * This function will free DVMA map resources (addresses) previously
638 * allocated with atm_dma_map().
639 *
640 * Arguments:
641 * addr DMA virtual address
642 * flags allocation flags (ATM_DEV_*)
643 *
644 * Returns:
645 * none
646 *
647 */
648 void
649 atm_dma_free(addr, flags)
650 caddr_t addr;
651 int flags;
652 {
653 if ((flags & ATM_DEV_NONCACHE) == 0)
654 mb_mapfree(bigsbusmap, (int)&addr);
655
656 return;
657 }
658 #endif /* sun4m */
659
660
661 /*
662 * Compress buffer chain
663 *
664 * This function will compress a supplied buffer chain into a minimum number
665 * of kernel buffers. Typically, this function will be used because the
666 * number of buffers in an output buffer chain is too large for a device's
667 * DMA capabilities. This should only be called as a last resort, since
668 * all the data copying will surely kill any hopes of decent performance.
669 *
670 * Arguments:
671 * m pointer to source buffer chain
672 *
673 * Returns:
674 * n pointer to compressed buffer chain
675 *
676 */
677 KBuffer *
678 atm_dev_compress(m)
679 KBuffer *m;
680 {
681 KBuffer *n, *n0, **np;
682 int len, space;
683 caddr_t src, dst;
684
685 n = n0 = NULL;
686 np = &n0;
687 dst = NULL;
688 space = 0;
689
690 /*
691 * Copy each source buffer into compressed chain
692 */
693 while (m) {
694
695 if (space == 0) {
696
697 /*
698 * Allocate another buffer for compressed chain
699 */
700 KB_ALLOCEXT(n, ATM_DEV_CMPR_LG, KB_F_NOWAIT, KB_T_DATA);
701 if (n) {
702 space = ATM_DEV_CMPR_LG;
703 } else {
704 KB_ALLOC(n, ATM_DEV_CMPR_SM, KB_F_NOWAIT,
705 KB_T_DATA);
706 if (n) {
707 space = ATM_DEV_CMPR_SM;
708 } else {
709 /*
710 * Unable to get any new buffers, so
711 * just return the partially compressed
712 * chain and hope...
713 */
714 *np = m;
715 break;
716 }
717 }
718
719 KB_HEADSET(n, 0);
720 KB_LEN(n) = 0;
721 KB_BFRSTART(n, dst, caddr_t);
722
723 *np = n;
724 np = &KB_NEXT(n);
725 }
726
727 /*
728 * Copy what we can from source buffer
729 */
730 len = MIN(space, KB_LEN(m));
731 KB_DATASTART(m, src, caddr_t);
732 KM_COPY(src, dst, len);
733
734 /*
735 * Adjust for copied data
736 */
737 dst += len;
738 space -= len;
739
740 KB_HEADADJ(m, -len);
741 KB_TAILADJ(n, len);
742
743 /*
744 * If we've exhausted our current source buffer, free it
745 * and move to the next one
746 */
747 if (KB_LEN(m) == 0) {
748 KB_FREEONE(m, m);
749 }
750 }
751
752 return (n0);
753 }
754
755
756 /*
757 * Locate VCC entry
758 *
759 * This function will return the VCC entry for a specified interface and
760 * VPI/VCI value.
761 *
762 * Arguments:
763 * cup pointer to interface unit structure
764 * vpi VPI value
765 * vci VCI value
766 * type VCC type
767 *
768 * Returns:
769 * vcp pointer to located VCC entry matching
770 * NULL no VCC found
771 *
772 */
773 Cmn_vcc *
774 atm_dev_vcc_find(cup, vpi, vci, type)
775 Cmn_unit *cup;
776 u_int vpi;
777 u_int vci;
778 u_int type;
779 {
780 Cmn_vcc *cvp;
781 int s = splnet();
782
783 /*
784 * Go find VCC
785 *
786 * (Probably should stick in a hash table some time)
787 */
788 for (cvp = cup->cu_vcc; cvp; cvp = cvp->cv_next) {
789 struct vccb *vcp;
790
791 vcp = cvp->cv_connvc->cvc_vcc;
792 if ((vcp->vc_vci == vci) && (vcp->vc_vpi == vpi) &&
793 ((vcp->vc_type & type) == type))
794 break;
795 }
796
797 (void) splx(s);
798 return (cvp);
799 }
800
801
802 #ifdef notdef
803 /*
804 * Module unloading notification
805 *
806 * This function must be called just prior to unloading the module from
807 * memory. All allocated memory will be freed here and anything else that
808 * needs cleaning up.
809 *
810 * Arguments:
811 * none
812 *
813 * Returns:
814 * none
815 *
816 */
817 void
818 atm_unload()
819 {
820 Mem_blk *mbp;
821 Mem_ent *mep;
822 int s, i;
823
824 s = splimp();
825
826 /*
827 * Free up all of our memory management storage
828 */
829 while (mbp = atm_mem_head) {
830
831 /*
832 * Make sure users have freed up all of their memory
833 */
834 for (i = 0; i < MEM_NMEMENT; i++) {
835 if (mbp->mb_mement[i].me_uaddr != NULL) {
836 panic("atm_unload: unfreed memory");
837 }
838 }
839
840 atm_mem_head = mbp->mb_next;
841
842 /*
843 * Hand this block back to the kernel
844 */
845 KM_FREE((caddr_t) mbp, sizeof(Mem_blk), M_DEVBUF);
846 }
847
848 (void) splx(s);
849
850 return;
851 }
852 #endif /* notdef */
853
854
855 /*
856 * Print a PDU
857 *
858 * Arguments:
859 * cup pointer to device unit
860 * cvp pointer to VCC control block
861 * m pointer to pdu buffer chain
862 * msg pointer to message string
863 *
864 * Returns:
865 * none
866 *
867 */
868 void
869 atm_dev_pdu_print(cup, cvp, m, msg)
870 Cmn_unit *cup;
871 Cmn_vcc *cvp;
872 KBuffer *m;
873 char *msg;
874 {
875 char buf[128];
876
877 snprintf(buf, sizeof(buf), "%s vcc=(%d,%d)", msg,
878 cvp->cv_connvc->cvc_vcc->vc_vpi,
879 cvp->cv_connvc->cvc_vcc->vc_vci);
880
881 atm_pdu_print(m, buf);
882 }
883
Cache object: 0291c703798ad413349d84b3e8450b48
|