FreeBSD/Linux Kernel Cross Reference
sys/i386/isa/atpic.c
1 /*-
2 * Copyright (c) 2003 John Baldwin <jhb@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the author nor the names of any co-contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 /*
31 * PIC driver for the 8259A Master and Slave PICs in PC/AT machines.
32 */
33
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD: releng/5.3/sys/i386/isa/atpic.c 133017 2004-08-02 15:31:10Z scottl $");
36
37 #include "opt_auto_eoi.h"
38 #include "opt_isa.h"
39
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/bus.h>
43 #include <sys/interrupt.h>
44 #include <sys/kernel.h>
45 #include <sys/lock.h>
46 #include <sys/module.h>
47 #include <sys/mutex.h>
48
49 #include <machine/cpufunc.h>
50 #include <machine/frame.h>
51 #include <machine/intr_machdep.h>
52 #include <machine/md_var.h>
53 #include <machine/resource.h>
54 #include <machine/segments.h>
55
56 #include <dev/ic/i8259.h>
57 #include <i386/isa/icu.h>
58 #ifdef PC98
59 #include <pc98/pc98/pc98.h>
60 #else
61 #include <i386/isa/isa.h>
62 #endif
63 #include <isa/isavar.h>
64
65 #define MASTER 0
66 #define SLAVE 1
67
68 /*
69 * PC-98 machines wire the slave 8259A to pin 7 on the master PIC, and
70 * PC-AT machines wire the slave PIC to pin 2 on the master PIC.
71 */
72 #ifdef PC98
73 #define ICU_SLAVEID 7
74 #else
75 #define ICU_SLAVEID 2
76 #endif
77
78 /*
79 * Determine the base master and slave modes not including auto EOI support.
80 * All machines that FreeBSD supports use 8086 mode.
81 */
82 #ifdef PC98
83 /*
84 * PC-98 machines do not support auto EOI on the second PIC. Also, it
85 * seems that PC-98 machine PICs use buffered mode, and the master PIC
86 * uses special fully nested mode.
87 */
88 #define BASE_MASTER_MODE (ICW4_SFNM | ICW4_BUF | ICW4_MS | ICW4_8086)
89 #define BASE_SLAVE_MODE (ICW4_BUF | ICW4_8086)
90 #else
91 #define BASE_MASTER_MODE ICW4_8086
92 #define BASE_SLAVE_MODE ICW4_8086
93 #endif
94
95 /* Enable automatic EOI if requested. */
96 #ifdef AUTO_EOI_1
97 #define MASTER_MODE (BASE_MASTER_MODE | ICW4_AEOI)
98 #else
99 #define MASTER_MODE BASE_MASTER_MODE
100 #endif
101 #ifdef AUTO_EOI_2
102 #define SLAVE_MODE (BASE_SLAVE_MODE | ICW4_AEOI)
103 #else
104 #define SLAVE_MODE BASE_SLAVE_MODE
105 #endif
106
107 #define IRQ_MASK(irq) (1 << (irq))
108 #define IMEN_MASK(ai) (IRQ_MASK((ai)->at_irq))
109
110 #define NUM_ISA_IRQS 16
111
112 static void atpic_init(void *dummy);
113
114 unsigned int imen; /* XXX */
115 #ifndef PC98
116 static int using_elcr;
117 #endif
118
119 inthand_t
120 IDTVEC(atpic_intr0), IDTVEC(atpic_intr1), IDTVEC(atpic_intr2),
121 IDTVEC(atpic_intr3), IDTVEC(atpic_intr4), IDTVEC(atpic_intr5),
122 IDTVEC(atpic_intr6), IDTVEC(atpic_intr7), IDTVEC(atpic_intr8),
123 IDTVEC(atpic_intr9), IDTVEC(atpic_intr10), IDTVEC(atpic_intr11),
124 IDTVEC(atpic_intr12), IDTVEC(atpic_intr13), IDTVEC(atpic_intr14),
125 IDTVEC(atpic_intr15);
126
127 #define IRQ(ap, ai) ((ap)->at_irqbase + (ai)->at_irq)
128
129 #define ATPIC(io, base, eoi, imenptr) \
130 { { atpic_enable_source, atpic_disable_source, (eoi), \
131 atpic_enable_intr, atpic_vector, atpic_source_pending, NULL, \
132 atpic_resume, atpic_config_intr }, (io), (base), \
133 IDT_IO_INTS + (base), (imenptr) }
134
135 #define INTSRC(irq) \
136 { { &atpics[(irq) / 8].at_pic }, IDTVEC(atpic_intr ## irq ), \
137 (irq) % 8 }
138
139 struct atpic {
140 struct pic at_pic;
141 int at_ioaddr;
142 int at_irqbase;
143 uint8_t at_intbase;
144 uint8_t *at_imen;
145 };
146
147 struct atpic_intsrc {
148 struct intsrc at_intsrc;
149 inthand_t *at_intr;
150 int at_irq; /* Relative to PIC base. */
151 enum intr_trigger at_trigger;
152 u_long at_count;
153 u_long at_straycount;
154 };
155
156 static void atpic_enable_source(struct intsrc *isrc);
157 static void atpic_disable_source(struct intsrc *isrc, int eoi);
158 static void atpic_eoi_master(struct intsrc *isrc);
159 static void atpic_eoi_slave(struct intsrc *isrc);
160 static void atpic_enable_intr(struct intsrc *isrc);
161 static int atpic_vector(struct intsrc *isrc);
162 static void atpic_resume(struct intsrc *isrc);
163 static int atpic_source_pending(struct intsrc *isrc);
164 static int atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
165 enum intr_polarity pol);
166 static void i8259_init(struct atpic *pic, int slave);
167
168 static struct atpic atpics[] = {
169 ATPIC(IO_ICU1, 0, atpic_eoi_master, (uint8_t *)&imen),
170 ATPIC(IO_ICU2, 8, atpic_eoi_slave, ((uint8_t *)&imen) + 1)
171 };
172
173 static struct atpic_intsrc atintrs[] = {
174 INTSRC(0),
175 INTSRC(1),
176 INTSRC(2),
177 INTSRC(3),
178 INTSRC(4),
179 INTSRC(5),
180 INTSRC(6),
181 INTSRC(7),
182 INTSRC(8),
183 INTSRC(9),
184 INTSRC(10),
185 INTSRC(11),
186 INTSRC(12),
187 INTSRC(13),
188 INTSRC(14),
189 INTSRC(15),
190 };
191
192 CTASSERT(sizeof(atintrs) / sizeof(atintrs[0]) == NUM_ISA_IRQS);
193
194 static __inline void
195 _atpic_eoi_master(struct intsrc *isrc)
196 {
197
198 KASSERT(isrc->is_pic == &atpics[MASTER].at_pic,
199 ("%s: mismatched pic", __func__));
200 #ifndef AUTO_EOI_1
201 outb(atpics[MASTER].at_ioaddr, OCW2_EOI);
202 #endif
203 }
204
205 /*
206 * The data sheet says no auto-EOI on slave, but it sometimes works.
207 * So, if AUTO_EOI_2 is enabled, we use it.
208 */
209 static __inline void
210 _atpic_eoi_slave(struct intsrc *isrc)
211 {
212
213 KASSERT(isrc->is_pic == &atpics[SLAVE].at_pic,
214 ("%s: mismatched pic", __func__));
215 #ifndef AUTO_EOI_2
216 outb(atpics[SLAVE].at_ioaddr, OCW2_EOI);
217 #ifndef AUTO_EOI_1
218 outb(atpics[MASTER].at_ioaddr, OCW2_EOI);
219 #endif
220 #endif
221 }
222
223 static void
224 atpic_enable_source(struct intsrc *isrc)
225 {
226 struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
227 struct atpic *ap = (struct atpic *)isrc->is_pic;
228
229 mtx_lock_spin(&icu_lock);
230 if (*ap->at_imen & IMEN_MASK(ai)) {
231 *ap->at_imen &= ~IMEN_MASK(ai);
232 outb(ap->at_ioaddr + ICU_IMR_OFFSET, *ap->at_imen);
233 }
234 mtx_unlock_spin(&icu_lock);
235 }
236
237 static void
238 atpic_disable_source(struct intsrc *isrc, int eoi)
239 {
240 struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
241 struct atpic *ap = (struct atpic *)isrc->is_pic;
242
243 mtx_lock_spin(&icu_lock);
244 if (ai->at_trigger != INTR_TRIGGER_EDGE) {
245 *ap->at_imen |= IMEN_MASK(ai);
246 outb(ap->at_ioaddr + ICU_IMR_OFFSET, *ap->at_imen);
247 }
248
249 /*
250 * Take care to call these functions directly instead of through
251 * a function pointer. All of the referenced variables should
252 * still be hot in the cache.
253 */
254 if (eoi == PIC_EOI) {
255 if (isrc->is_pic == &atpics[MASTER].at_pic)
256 _atpic_eoi_master(isrc);
257 else
258 _atpic_eoi_slave(isrc);
259 }
260
261 mtx_unlock_spin(&icu_lock);
262 }
263
264 static void
265 atpic_eoi_master(struct intsrc *isrc)
266 {
267 #ifndef AUTO_EOI_1
268 mtx_lock_spin(&icu_lock);
269 _atpic_eoi_master(isrc);
270 mtx_unlock_spin(&icu_lock);
271 #endif
272 }
273
274 static void
275 atpic_eoi_slave(struct intsrc *isrc)
276 {
277 #ifndef AUTO_EOI_2
278 mtx_lock_spin(&icu_lock);
279 _atpic_eoi_slave(isrc);
280 mtx_unlock_spin(&icu_lock);
281 #endif
282 }
283
284 static void
285 atpic_enable_intr(struct intsrc *isrc)
286 {
287 }
288
289 static int
290 atpic_vector(struct intsrc *isrc)
291 {
292 struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
293 struct atpic *ap = (struct atpic *)isrc->is_pic;
294
295 return (IRQ(ap, ai));
296 }
297
298 static int
299 atpic_source_pending(struct intsrc *isrc)
300 {
301 struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
302 struct atpic *ap = (struct atpic *)isrc->is_pic;
303
304 return (inb(ap->at_ioaddr) & IMEN_MASK(ai));
305 }
306
307 static void
308 atpic_resume(struct intsrc *isrc)
309 {
310 struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
311 struct atpic *ap = (struct atpic *)isrc->is_pic;
312
313 if (ai->at_irq == 0) {
314 i8259_init(ap, ap == &atpics[SLAVE]);
315 #ifndef PC98
316 if (ap == &atpics[SLAVE] && using_elcr)
317 elcr_resume();
318 #endif
319 }
320 }
321
322 static int
323 atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig,
324 enum intr_polarity pol)
325 {
326 struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc;
327 u_int vector;
328
329 /* Map conforming values to edge/hi and sanity check the values. */
330 if (trig == INTR_TRIGGER_CONFORM)
331 trig = INTR_TRIGGER_EDGE;
332 if (pol == INTR_POLARITY_CONFORM)
333 pol = INTR_POLARITY_HIGH;
334 vector = atpic_vector(isrc);
335 if ((trig == INTR_TRIGGER_EDGE && pol == INTR_POLARITY_LOW) ||
336 (trig == INTR_TRIGGER_LEVEL && pol == INTR_POLARITY_HIGH)) {
337 printf(
338 "atpic: Mismatched config for IRQ%u: trigger %s, polarity %s\n",
339 vector, trig == INTR_TRIGGER_EDGE ? "edge" : "level",
340 pol == INTR_POLARITY_HIGH ? "high" : "low");
341 return (EINVAL);
342 }
343
344 /* If there is no change, just return. */
345 if (ai->at_trigger == trig)
346 return (0);
347
348 #ifdef PC98
349 if ((vector == 0 || vector == 1 || vector == 7 || vector == 8) &&
350 trig == INTR_TRIGGER_LEVEL) {
351 if (bootverbose)
352 printf(
353 "atpic: Ignoring invalid level/low configuration for IRQ%u\n",
354 vector);
355 return (EINVAL);
356 }
357 return (ENXIO);
358 #else
359 /*
360 * Certain IRQs can never be level/lo, so don't try to set them
361 * that way if asked. At least some ELCR registers ignore setting
362 * these bits as well.
363 */
364 if ((vector == 0 || vector == 1 || vector == 2 || vector == 13) &&
365 trig == INTR_TRIGGER_LEVEL) {
366 if (bootverbose)
367 printf(
368 "atpic: Ignoring invalid level/low configuration for IRQ%u\n",
369 vector);
370 return (EINVAL);
371 }
372 if (!using_elcr) {
373 if (bootverbose)
374 printf("atpic: No ELCR to configure IRQ%u as %s\n",
375 vector, trig == INTR_TRIGGER_EDGE ? "edge/high" :
376 "level/low");
377 return (ENXIO);
378 }
379 if (bootverbose)
380 printf("atpic: Programming IRQ%u as %s\n", vector,
381 trig == INTR_TRIGGER_EDGE ? "edge/high" : "level/low");
382 mtx_lock_spin(&icu_lock);
383 elcr_write_trigger(atpic_vector(isrc), trig);
384 ai->at_trigger = trig;
385 mtx_unlock_spin(&icu_lock);
386 return (0);
387 #endif /* PC98 */
388 }
389
390 static void
391 i8259_init(struct atpic *pic, int slave)
392 {
393 int imr_addr;
394
395 /* Reset the PIC and program with next four bytes. */
396 mtx_lock_spin(&icu_lock);
397 #ifdef DEV_MCA
398 /* MCA uses level triggered interrupts. */
399 if (MCA_system)
400 outb(pic->at_ioaddr, ICW1_RESET | ICW1_IC4 | ICW1_LTIM);
401 else
402 #endif
403 outb(pic->at_ioaddr, ICW1_RESET | ICW1_IC4);
404 imr_addr = pic->at_ioaddr + ICU_IMR_OFFSET;
405
406 /* Start vector. */
407 outb(imr_addr, pic->at_intbase);
408
409 /*
410 * Setup slave links. For the master pic, indicate what line
411 * the slave is configured on. For the slave indicate
412 * which line on the master we are connected to.
413 */
414 if (slave)
415 outb(imr_addr, ICU_SLAVEID);
416 else
417 outb(imr_addr, IRQ_MASK(ICU_SLAVEID));
418
419 /* Set mode. */
420 if (slave)
421 outb(imr_addr, SLAVE_MODE);
422 else
423 outb(imr_addr, MASTER_MODE);
424
425 /* Set interrupt enable mask. */
426 outb(imr_addr, *pic->at_imen);
427
428 /* Reset is finished, default to IRR on read. */
429 outb(pic->at_ioaddr, OCW3_SEL | OCW3_RR);
430
431 #ifndef PC98
432 /* OCW2_L1 sets priority order to 3-7, 0-2 (com2 first). */
433 if (!slave)
434 outb(pic->at_ioaddr, OCW2_R | OCW2_SL | OCW2_L1);
435 #endif
436 mtx_unlock_spin(&icu_lock);
437 }
438
439 void
440 atpic_startup(void)
441 {
442 struct atpic_intsrc *ai;
443 int i;
444
445 /* Start off with all interrupts disabled. */
446 imen = 0xffff;
447 i8259_init(&atpics[MASTER], 0);
448 i8259_init(&atpics[SLAVE], 1);
449 atpic_enable_source((struct intsrc *)&atintrs[ICU_SLAVEID]);
450
451 /* Install low-level interrupt handlers for all of our IRQs. */
452 for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) {
453 if (i == ICU_SLAVEID)
454 continue;
455 ai->at_intsrc.is_count = &ai->at_count;
456 ai->at_intsrc.is_straycount = &ai->at_straycount;
457 setidt(((struct atpic *)ai->at_intsrc.is_pic)->at_intbase +
458 ai->at_irq, ai->at_intr, SDT_SYS386IGT, SEL_KPL,
459 GSEL(GCODE_SEL, SEL_KPL));
460 }
461
462 #ifdef DEV_MCA
463 /* For MCA systems, all interrupts are level triggered. */
464 if (MCA_system)
465 for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
466 ai->at_trigger = INTR_TRIGGER_LEVEL;
467 else
468 #endif
469
470 #ifdef PC98
471 for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
472 switch (i) {
473 case 0:
474 case 1:
475 case 7:
476 case 8:
477 ai->at_trigger = INTR_TRIGGER_EDGE;
478 break;
479 default:
480 ai->at_trigger = INTR_TRIGGER_LEVEL;
481 break;
482 }
483 #else
484 /*
485 * Look for an ELCR. If we find one, update the trigger modes.
486 * If we don't find one, assume that IRQs 0, 1, 2, and 13 are
487 * edge triggered and that everything else is level triggered.
488 * We only use the trigger information to reprogram the ELCR if
489 * we have one and as an optimization to avoid masking edge
490 * triggered interrupts. For the case that we don't have an ELCR,
491 * it doesn't hurt to mask an edge triggered interrupt, so we
492 * assume level trigger for any interrupt that we aren't sure is
493 * edge triggered.
494 */
495 if (elcr_probe() == 0) {
496 using_elcr = 1;
497 for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
498 ai->at_trigger = elcr_read_trigger(i);
499 } else {
500 for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++)
501 switch (i) {
502 case 0:
503 case 1:
504 case 2:
505 case 8:
506 case 13:
507 ai->at_trigger = INTR_TRIGGER_EDGE;
508 break;
509 default:
510 ai->at_trigger = INTR_TRIGGER_LEVEL;
511 break;
512 }
513 }
514 #endif /* PC98 */
515 }
516
517 static void
518 atpic_init(void *dummy __unused)
519 {
520 struct atpic_intsrc *ai;
521 int i;
522
523 /* Loop through all interrupt sources and add them. */
524 for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) {
525 if (i == ICU_SLAVEID)
526 continue;
527 intr_register_source(&ai->at_intsrc);
528 }
529 }
530 SYSINIT(atpic_init, SI_SUB_INTR, SI_ORDER_SECOND + 1, atpic_init, NULL)
531
532 void
533 atpic_handle_intr(struct intrframe iframe)
534 {
535 struct intsrc *isrc;
536
537 KASSERT((u_int)iframe.if_vec < NUM_ISA_IRQS,
538 ("unknown int %d\n", iframe.if_vec));
539 isrc = &atintrs[iframe.if_vec].at_intsrc;
540
541 /*
542 * If we don't have an ithread, see if this is a spurious
543 * interrupt.
544 */
545 if (isrc->is_ithread == NULL &&
546 (iframe.if_vec == 7 || iframe.if_vec == 15)) {
547 int port, isr;
548
549 /*
550 * Read the ISR register to see if IRQ 7/15 is really
551 * pending. Reset read register back to IRR when done.
552 */
553 port = ((struct atpic *)isrc->is_pic)->at_ioaddr;
554 mtx_lock_spin(&icu_lock);
555 outb(port, OCW3_SEL | OCW3_RR | OCW3_RIS);
556 isr = inb(port);
557 outb(port, OCW3_SEL | OCW3_RR);
558 mtx_unlock_spin(&icu_lock);
559 if ((isr & IRQ_MASK(7)) == 0)
560 return;
561 }
562 intr_execute_handlers(isrc, &iframe);
563 }
564
565 #ifdef DEV_ISA
566 /*
567 * Bus attachment for the ISA PIC.
568 */
569 static struct isa_pnp_id atpic_ids[] = {
570 { 0x0000d041 /* PNP0000 */, "AT interrupt controller" },
571 { 0 }
572 };
573
574 static int
575 atpic_probe(device_t dev)
576 {
577 int result;
578
579 result = ISA_PNP_PROBE(device_get_parent(dev), dev, atpic_ids);
580 if (result <= 0)
581 device_quiet(dev);
582 return (result);
583 }
584
585 /*
586 * We might be granted IRQ 2, as this is typically consumed by chaining
587 * between the two PIC components. If we're using the APIC, however,
588 * this may not be the case, and as such we should free the resource.
589 * (XXX untested)
590 *
591 * The generic ISA attachment code will handle allocating any other resources
592 * that we don't explicitly claim here.
593 */
594 static int
595 atpic_attach(device_t dev)
596 {
597 struct resource *res;
598 int rid;
599
600 /* Try to allocate our IRQ and then free it. */
601 rid = 0;
602 res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, 0);
603 if (res != NULL)
604 bus_release_resource(dev, SYS_RES_IRQ, rid, res);
605 return (0);
606 }
607
608 static device_method_t atpic_methods[] = {
609 /* Device interface */
610 DEVMETHOD(device_probe, atpic_probe),
611 DEVMETHOD(device_attach, atpic_attach),
612 DEVMETHOD(device_detach, bus_generic_detach),
613 DEVMETHOD(device_shutdown, bus_generic_shutdown),
614 DEVMETHOD(device_suspend, bus_generic_suspend),
615 DEVMETHOD(device_resume, bus_generic_resume),
616 { 0, 0 }
617 };
618
619 static driver_t atpic_driver = {
620 "atpic",
621 atpic_methods,
622 1, /* no softc */
623 };
624
625 static devclass_t atpic_devclass;
626
627 DRIVER_MODULE(atpic, isa, atpic_driver, atpic_devclass, 0, 0);
628 #ifndef PC98
629 DRIVER_MODULE(atpic, acpi, atpic_driver, atpic_devclass, 0, 0);
630 #endif
631
632 /*
633 * Return a bitmap of the current interrupt requests. This is 8259-specific
634 * and is only suitable for use at probe time.
635 */
636 intrmask_t
637 isa_irq_pending(void)
638 {
639 u_char irr1;
640 u_char irr2;
641
642 irr1 = inb(IO_ICU1);
643 irr2 = inb(IO_ICU2);
644 return ((irr2 << 8) | irr1);
645 }
646 #endif /* DEV_ISA */
Cache object: dd0b9d42fe9a9f4bb33e618ff4cb683d
|