diff options
author | Doug Thompson <dougthompson@xmission.com> | 2009-05-04 14:46:50 -0400 |
---|---|---|
committer | Borislav Petkov <borislav.petkov@amd.com> | 2009-06-10 06:18:51 -0400 |
commit | 93c2df58b5b1a434cca8f60067e0e12d1942b7f1 (patch) | |
tree | 54b28a1bb6eae8cf5cf5d1ad7e98624f86576ced /drivers/edac/amd64_edac.c | |
parent | e2ce7255e84db656853e91536e6023f92ff89f97 (diff) |
amd64_edac: add DRAM address type conversion facilities
Borislav:
- cleanup/fix comments, add BKDG refs
- fix function return value patterns
- cleanup dbg calls
Reviewed-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Signed-off-by: Doug Thompson <dougthompson@xmission.com>
Signed-off-by: Borislav Petkov <borislav.petkov@amd.com>
Diffstat (limited to 'drivers/edac/amd64_edac.c')
-rw-r--r-- | drivers/edac/amd64_edac.c | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index 4716fb561e6e..28f85c9e3afc 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c | |||
@@ -434,4 +434,298 @@ int amd64_get_dram_hole_info(struct mem_ctl_info *mci, u64 *hole_base, | |||
434 | } | 434 | } |
435 | EXPORT_SYMBOL_GPL(amd64_get_dram_hole_info); | 435 | EXPORT_SYMBOL_GPL(amd64_get_dram_hole_info); |
436 | 436 | ||
437 | /* | ||
438 | * Return the DramAddr that the SysAddr given by @sys_addr maps to. It is | ||
439 | * assumed that sys_addr maps to the node given by mci. | ||
440 | * | ||
441 | * The first part of section 3.4.4 (p. 70) shows how the DRAM Base (section | ||
442 | * 3.4.4.1) and DRAM Limit (section 3.4.4.2) registers are used to translate a | ||
443 | * SysAddr to a DramAddr. If the DRAM Hole Address Register (DHAR) is enabled, | ||
444 | * then it is also involved in translating a SysAddr to a DramAddr. Sections | ||
445 | * 3.4.8 and 3.5.8.2 describe the DHAR and how it is used for memory hoisting. | ||
446 | * These parts of the documentation are unclear. I interpret them as follows: | ||
447 | * | ||
448 | * When node n receives a SysAddr, it processes the SysAddr as follows: | ||
449 | * | ||
450 | * 1. It extracts the DRAMBase and DRAMLimit values from the DRAM Base and DRAM | ||
451 | * Limit registers for node n. If the SysAddr is not within the range | ||
452 | * specified by the base and limit values, then node n ignores the Sysaddr | ||
453 | * (since it does not map to node n). Otherwise continue to step 2 below. | ||
454 | * | ||
455 | * 2. If the DramHoleValid bit of the DHAR for node n is clear, the DHAR is | ||
456 | * disabled so skip to step 3 below. Otherwise see if the SysAddr is within | ||
457 | * the range of relocated addresses (starting at 0x100000000) from the DRAM | ||
458 | * hole. If not, skip to step 3 below. Else get the value of the | ||
459 | * DramHoleOffset field from the DHAR. To obtain the DramAddr, subtract the | ||
460 | * offset defined by this value from the SysAddr. | ||
461 | * | ||
462 | * 3. Obtain the base address for node n from the DRAMBase field of the DRAM | ||
463 | * Base register for node n. To obtain the DramAddr, subtract the base | ||
464 | * address from the SysAddr, as shown near the start of section 3.4.4 (p.70). | ||
465 | */ | ||
466 | static u64 sys_addr_to_dram_addr(struct mem_ctl_info *mci, u64 sys_addr) | ||
467 | { | ||
468 | u64 dram_base, hole_base, hole_offset, hole_size, dram_addr; | ||
469 | int ret = 0; | ||
470 | |||
471 | dram_base = get_dram_base(mci); | ||
472 | |||
473 | ret = amd64_get_dram_hole_info(mci, &hole_base, &hole_offset, | ||
474 | &hole_size); | ||
475 | if (!ret) { | ||
476 | if ((sys_addr >= (1ull << 32)) && | ||
477 | (sys_addr < ((1ull << 32) + hole_size))) { | ||
478 | /* use DHAR to translate SysAddr to DramAddr */ | ||
479 | dram_addr = sys_addr - hole_offset; | ||
480 | |||
481 | debugf2("using DHAR to translate SysAddr 0x%lx to " | ||
482 | "DramAddr 0x%lx\n", | ||
483 | (unsigned long)sys_addr, | ||
484 | (unsigned long)dram_addr); | ||
485 | |||
486 | return dram_addr; | ||
487 | } | ||
488 | } | ||
489 | |||
490 | /* | ||
491 | * Translate the SysAddr to a DramAddr as shown near the start of | ||
492 | * section 3.4.4 (p. 70). Although sys_addr is a 64-bit value, the k8 | ||
493 | * only deals with 40-bit values. Therefore we discard bits 63-40 of | ||
494 | * sys_addr below. If bit 39 of sys_addr is 1 then the bits we | ||
495 | * discard are all 1s. Otherwise the bits we discard are all 0s. See | ||
496 | * section 3.4.2 of AMD publication 24592: AMD x86-64 Architecture | ||
497 | * Programmer's Manual Volume 1 Application Programming. | ||
498 | */ | ||
499 | dram_addr = (sys_addr & 0xffffffffffull) - dram_base; | ||
500 | |||
501 | debugf2("using DRAM Base register to translate SysAddr 0x%lx to " | ||
502 | "DramAddr 0x%lx\n", (unsigned long)sys_addr, | ||
503 | (unsigned long)dram_addr); | ||
504 | return dram_addr; | ||
505 | } | ||
506 | |||
507 | /* | ||
508 | * @intlv_en is the value of the IntlvEn field from a DRAM Base register | ||
509 | * (section 3.4.4.1). Return the number of bits from a SysAddr that are used | ||
510 | * for node interleaving. | ||
511 | */ | ||
512 | static int num_node_interleave_bits(unsigned intlv_en) | ||
513 | { | ||
514 | static const int intlv_shift_table[] = { 0, 1, 0, 2, 0, 0, 0, 3 }; | ||
515 | int n; | ||
516 | |||
517 | BUG_ON(intlv_en > 7); | ||
518 | n = intlv_shift_table[intlv_en]; | ||
519 | return n; | ||
520 | } | ||
521 | |||
522 | /* Translate the DramAddr given by @dram_addr to an InputAddr. */ | ||
523 | static u64 dram_addr_to_input_addr(struct mem_ctl_info *mci, u64 dram_addr) | ||
524 | { | ||
525 | struct amd64_pvt *pvt; | ||
526 | int intlv_shift; | ||
527 | u64 input_addr; | ||
528 | |||
529 | pvt = mci->pvt_info; | ||
530 | |||
531 | /* | ||
532 | * See the start of section 3.4.4 (p. 70, BKDG #26094, K8, revA-E) | ||
533 | * concerning translating a DramAddr to an InputAddr. | ||
534 | */ | ||
535 | intlv_shift = num_node_interleave_bits(pvt->dram_IntlvEn[0]); | ||
536 | input_addr = ((dram_addr >> intlv_shift) & 0xffffff000ull) + | ||
537 | (dram_addr & 0xfff); | ||
538 | |||
539 | debugf2(" Intlv Shift=%d DramAddr=0x%lx maps to InputAddr=0x%lx\n", | ||
540 | intlv_shift, (unsigned long)dram_addr, | ||
541 | (unsigned long)input_addr); | ||
542 | |||
543 | return input_addr; | ||
544 | } | ||
545 | |||
546 | /* | ||
547 | * Translate the SysAddr represented by @sys_addr to an InputAddr. It is | ||
548 | * assumed that @sys_addr maps to the node given by mci. | ||
549 | */ | ||
550 | static u64 sys_addr_to_input_addr(struct mem_ctl_info *mci, u64 sys_addr) | ||
551 | { | ||
552 | u64 input_addr; | ||
553 | |||
554 | input_addr = | ||
555 | dram_addr_to_input_addr(mci, sys_addr_to_dram_addr(mci, sys_addr)); | ||
556 | |||
557 | debugf2("SysAdddr 0x%lx translates to InputAddr 0x%lx\n", | ||
558 | (unsigned long)sys_addr, (unsigned long)input_addr); | ||
559 | |||
560 | return input_addr; | ||
561 | } | ||
562 | |||
563 | |||
564 | /* | ||
565 | * @input_addr is an InputAddr associated with the node represented by mci. | ||
566 | * Translate @input_addr to a DramAddr and return the result. | ||
567 | */ | ||
568 | static u64 input_addr_to_dram_addr(struct mem_ctl_info *mci, u64 input_addr) | ||
569 | { | ||
570 | struct amd64_pvt *pvt; | ||
571 | int node_id, intlv_shift; | ||
572 | u64 bits, dram_addr; | ||
573 | u32 intlv_sel; | ||
574 | |||
575 | /* | ||
576 | * Near the start of section 3.4.4 (p. 70, BKDG #26094, K8, revA-E) | ||
577 | * shows how to translate a DramAddr to an InputAddr. Here we reverse | ||
578 | * this procedure. When translating from a DramAddr to an InputAddr, the | ||
579 | * bits used for node interleaving are discarded. Here we recover these | ||
580 | * bits from the IntlvSel field of the DRAM Limit register (section | ||
581 | * 3.4.4.2) for the node that input_addr is associated with. | ||
582 | */ | ||
583 | pvt = mci->pvt_info; | ||
584 | node_id = pvt->mc_node_id; | ||
585 | BUG_ON((node_id < 0) || (node_id > 7)); | ||
586 | |||
587 | intlv_shift = num_node_interleave_bits(pvt->dram_IntlvEn[0]); | ||
588 | |||
589 | if (intlv_shift == 0) { | ||
590 | debugf1(" InputAddr 0x%lx translates to DramAddr of " | ||
591 | "same value\n", (unsigned long)input_addr); | ||
592 | |||
593 | return input_addr; | ||
594 | } | ||
595 | |||
596 | bits = ((input_addr & 0xffffff000ull) << intlv_shift) + | ||
597 | (input_addr & 0xfff); | ||
598 | |||
599 | intlv_sel = pvt->dram_IntlvSel[node_id] & ((1 << intlv_shift) - 1); | ||
600 | dram_addr = bits + (intlv_sel << 12); | ||
601 | |||
602 | debugf1("InputAddr 0x%lx translates to DramAddr 0x%lx " | ||
603 | "(%d node interleave bits)\n", (unsigned long)input_addr, | ||
604 | (unsigned long)dram_addr, intlv_shift); | ||
605 | |||
606 | return dram_addr; | ||
607 | } | ||
608 | |||
609 | /* | ||
610 | * @dram_addr is a DramAddr that maps to the node represented by mci. Convert | ||
611 | * @dram_addr to a SysAddr. | ||
612 | */ | ||
613 | static u64 dram_addr_to_sys_addr(struct mem_ctl_info *mci, u64 dram_addr) | ||
614 | { | ||
615 | struct amd64_pvt *pvt = mci->pvt_info; | ||
616 | u64 hole_base, hole_offset, hole_size, base, limit, sys_addr; | ||
617 | int ret = 0; | ||
618 | |||
619 | ret = amd64_get_dram_hole_info(mci, &hole_base, &hole_offset, | ||
620 | &hole_size); | ||
621 | if (!ret) { | ||
622 | if ((dram_addr >= hole_base) && | ||
623 | (dram_addr < (hole_base + hole_size))) { | ||
624 | sys_addr = dram_addr + hole_offset; | ||
625 | |||
626 | debugf1("using DHAR to translate DramAddr 0x%lx to " | ||
627 | "SysAddr 0x%lx\n", (unsigned long)dram_addr, | ||
628 | (unsigned long)sys_addr); | ||
629 | |||
630 | return sys_addr; | ||
631 | } | ||
632 | } | ||
633 | |||
634 | amd64_get_base_and_limit(pvt, pvt->mc_node_id, &base, &limit); | ||
635 | sys_addr = dram_addr + base; | ||
636 | |||
637 | /* | ||
638 | * The sys_addr we have computed up to this point is a 40-bit value | ||
639 | * because the k8 deals with 40-bit values. However, the value we are | ||
640 | * supposed to return is a full 64-bit physical address. The AMD | ||
641 | * x86-64 architecture specifies that the most significant implemented | ||
642 | * address bit through bit 63 of a physical address must be either all | ||
643 | * 0s or all 1s. Therefore we sign-extend the 40-bit sys_addr to a | ||
644 | * 64-bit value below. See section 3.4.2 of AMD publication 24592: | ||
645 | * AMD x86-64 Architecture Programmer's Manual Volume 1 Application | ||
646 | * Programming. | ||
647 | */ | ||
648 | sys_addr |= ~((sys_addr & (1ull << 39)) - 1); | ||
649 | |||
650 | debugf1(" Node %d, DramAddr 0x%lx to SysAddr 0x%lx\n", | ||
651 | pvt->mc_node_id, (unsigned long)dram_addr, | ||
652 | (unsigned long)sys_addr); | ||
653 | |||
654 | return sys_addr; | ||
655 | } | ||
656 | |||
657 | /* | ||
658 | * @input_addr is an InputAddr associated with the node given by mci. Translate | ||
659 | * @input_addr to a SysAddr. | ||
660 | */ | ||
661 | static inline u64 input_addr_to_sys_addr(struct mem_ctl_info *mci, | ||
662 | u64 input_addr) | ||
663 | { | ||
664 | return dram_addr_to_sys_addr(mci, | ||
665 | input_addr_to_dram_addr(mci, input_addr)); | ||
666 | } | ||
667 | |||
668 | /* | ||
669 | * Find the minimum and maximum InputAddr values that map to the given @csrow. | ||
670 | * Pass back these values in *input_addr_min and *input_addr_max. | ||
671 | */ | ||
672 | static void find_csrow_limits(struct mem_ctl_info *mci, int csrow, | ||
673 | u64 *input_addr_min, u64 *input_addr_max) | ||
674 | { | ||
675 | struct amd64_pvt *pvt; | ||
676 | u64 base, mask; | ||
677 | |||
678 | pvt = mci->pvt_info; | ||
679 | BUG_ON((csrow < 0) || (csrow >= CHIPSELECT_COUNT)); | ||
680 | |||
681 | base = base_from_dct_base(pvt, csrow); | ||
682 | mask = mask_from_dct_mask(pvt, csrow); | ||
683 | |||
684 | *input_addr_min = base & ~mask; | ||
685 | *input_addr_max = base | mask | pvt->dcs_mask_notused; | ||
686 | } | ||
687 | |||
688 | /* | ||
689 | * Extract error address from MCA NB Address Low (section 3.6.4.5) and MCA NB | ||
690 | * Address High (section 3.6.4.6) register values and return the result. Address | ||
691 | * is located in the info structure (nbeah and nbeal), the encoding is device | ||
692 | * specific. | ||
693 | */ | ||
694 | static u64 extract_error_address(struct mem_ctl_info *mci, | ||
695 | struct amd64_error_info_regs *info) | ||
696 | { | ||
697 | struct amd64_pvt *pvt = mci->pvt_info; | ||
698 | |||
699 | return pvt->ops->get_error_address(mci, info); | ||
700 | } | ||
701 | |||
702 | |||
703 | /* Map the Error address to a PAGE and PAGE OFFSET. */ | ||
704 | static inline void error_address_to_page_and_offset(u64 error_address, | ||
705 | u32 *page, u32 *offset) | ||
706 | { | ||
707 | *page = (u32) (error_address >> PAGE_SHIFT); | ||
708 | *offset = ((u32) error_address) & ~PAGE_MASK; | ||
709 | } | ||
710 | |||
711 | /* | ||
712 | * @sys_addr is an error address (a SysAddr) extracted from the MCA NB Address | ||
713 | * Low (section 3.6.4.5) and MCA NB Address High (section 3.6.4.6) registers | ||
714 | * of a node that detected an ECC memory error. mci represents the node that | ||
715 | * the error address maps to (possibly different from the node that detected | ||
716 | * the error). Return the number of the csrow that sys_addr maps to, or -1 on | ||
717 | * error. | ||
718 | */ | ||
719 | static int sys_addr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr) | ||
720 | { | ||
721 | int csrow; | ||
722 | |||
723 | csrow = input_addr_to_csrow(mci, sys_addr_to_input_addr(mci, sys_addr)); | ||
724 | |||
725 | if (csrow == -1) | ||
726 | amd64_mc_printk(mci, KERN_ERR, | ||
727 | "Failed to translate InputAddr to csrow for " | ||
728 | "address 0x%lx\n", (unsigned long)sys_addr); | ||
729 | return csrow; | ||
730 | } | ||
437 | 731 | ||