#! /usr/bin/python

# Copyright (C) 2008 Vincent Legoll <vincent.legoll@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

__doc__ = """
This tools has been developed to help the coreboot project

It is based on knowledge from AMD's 26094.pdf

BIOS and Kernel Developer's
Guide for AMD Athlon 64 and
AMD Opteron Processors

Chapter 3 Memory System Configuration
        3.4 Function 1 - Address Map

Use the "--help" command line parameter to see the available options

The classes can be used like that:

>>> m = K8MemMapRegs(K8MemMapFake())
>>> print '0x%02x' % m.k8memmap.getReg(0x00, 'B')
0x22
>>> print '0x%02x' % m.k8memmap.getReg(0x01, 'B')
0x10
>>> print '0x%02x' % m.k8memmap.getReg(0x02, 'B')
0x01
>>> print '0x%02x' % m.k8memmap.getReg(0x03, 'B')
0x11
>>> print '0x%04x' % m.k8memmap.getReg(0x00, 'W')
0x1022
>>> print '0x%04x' % m.getReg(0x00)
0x1022
>>> print '0x%08x' % m.k8memmap.getReg(0x00, 'L')
0x11011022
"""

import re, sys, subprocess

###############################################################################

class register():
    
    '''Base class for register decoders'''

    def isset(self, bit):
        return self.bitval(bit) == bit

    def bitval(self, bit):
        return (self.regval & bit)

    def __init__(self, regVal):
        self.regval = regVal

    def dstlnk(self, linkBits):
        if linkBits == 3:
            return 'Reserved value (0b11)'
        else:
            return str(linkBits)

###############################################################################

class configMapReg(register):

    def __str__(self):
        return '\t Bus Num Base: 0x%02X\n' % ((self.regval & 0xFF000000) >> 24) + \
               '\t Bus Num Limit: 0x%02X\n' % ((self.regval & 0x00FF0000) >> 16) + \
               '\t Read Enable: 0x%1X\n' % (self.isset(0x1)) + \
               '\t Write Enable: 0x%1X\n' % (self.isset(0x2)) + \
               '\t Device Number Compare Enable: 0x%1X\n' % (self.isset(0x4)) + \
               '\t Dst Node ID: %d\n' % (self.bitval(0x70) >> 4) + \
               '\t Dst Link ID: %s' % (self.dstlnk(self.bitval(0x300) >> 8))

###############################################################################

class pciioBaseReg(register):

    def __str__(self):
        return '\t PCI IO Base: 0x%04X\n' % ((self.regval & 0x03FFF000) >> 12) + \
               '\t Read Enable: 0x%1X\n' % (self.isset(0x1)) + \
               '\t Write Enable: 0x%1X\n' % (self.isset(0x2)) + \
               '\t VGA Enable: 0x%1X\n' % (self.isset(0x10)) + \
               '\t ISA Enable: 0x%1X' % (self.isset(0x20))

class pciioLimitReg(register):

    def __str__(self):
        return '\t PCI IO Limit: 0x%04X\n' % ((self.regval & 0x03FFF000) >> 12) + \
               '\t Dst Node ID: %d\n' % (self.bitval(0x07) >> 0) + \
               '\t Dst Link ID: %s' % (self.dstlnk(self.bitval(0x30) >> 4))

###############################################################################

class mmioBaseReg(register):

    def __str__(self):
        return '\t MMIO Base: 0x%010X\n' % (((self.regval & 0xFFFFFF00) << 8) | 0x0000) + \
               '\t Read Enable: 0x%1X\n' % (self.isset(0x1)) + \
               '\t Write Enable: 0x%1X\n' % (self.isset(0x2)) + \
               '\t CPU Disable: 0x%1X\n' % (self.isset(0x4)) + \
               '\t Lock: 0x%1X' % (self.isset(0x8))

class mmioLimitReg(register):

    def __str__(self):
        return '\t MMIO Limit: 0x%010X\n' % (((self.regval & 0xFFFFFF00) << 8) | 0xFFFF) + \
               '\t Dst Node ID: %d\n' % (self.bitval(0x07) >> 0) + \
               '\t Dst Link ID: %s\n' % (self.dstlnk(self.bitval(0x30) >> 4)) + \
               '\t Non Posted: 0x%1X' % (self.bitval(0x80) >> 7)

###############################################################################

class dramLimitReg(register):
    
    def __str__(self):
        return '\t DRAM Limit: 0x%010X\n' % (((self.regval & 0xFFFF0000) << 8) | 0xFFFFFF) + \
               '\t Dst Node ID: %d\n' % (self.bitval(0x7)) + \
               '\t Interleave Select: 0b%s' % (Denary2Binary(self.bitval(0x0700) >> 8, 3))
    
class dramBaseReg(register):
    
    def __str__(self):
        return '\t DRAM Base: 0x%010X\n' % (((self.regval & 0xFFFF0000) << 8) | 0x000000) + \
               '\t Read Enable: 0x%1X\n' % (self.isset(0x1)) + \
               '\t Write Enable: 0x%1X\n' % (self.isset(0x2)) + \
               '\t Interleave Enable: %s' % (self.interleaveEnable())
    
    def interleaveEnable(self):
        intlv = self.bitval(0x0700) >> 8
        if intlv == 0:
            return 'No interleave'
        elif intlv == 1:
            return 'Interleave on 2 nodes'
        elif intlv == 3:
            return 'Interleave on 4 nodes'
        elif intlv == 7:
            return 'Interleave on 8 nodes'
        else:
            return 'Reserved 0b%s' % Denary2Binary(intlv, 3)
    
###############################################################################

class K8MemMapReal():

    def getReg(self, offset, size):
        cmdline = ("setpci", "-s", "0:0:18.1", "0x%x.%s" % (offset, size))
        ret = subprocess.Popen(cmdline, stdout=subprocess.PIPE).communicate()[0]
        return int(ret, 16)

###############################################################################

class K8MemMapFake():
    
    '''Fake K8 Address Map registers with stored or given "lspci -xxx -s 0:0:18.1"
       text block, for use on non K8, or for validating lspci against setpci.
    '''

    # lspci -xxx -s 0:0:18.1
    __fake_data_str_default = """\
        00:18.1 Host bridge: Advanced Micro Devices [AMD] K8 [Athlon64/Opteron] Address Map
        00: 22 10 01 11 00 00 00 00 00 00 00 06 00 00 80 00
        10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
        20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
        30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
        40: 03 00 00 00 00 00 7f 00 00 00 00 00 01 00 00 00
        50: 00 00 00 00 02 00 00 00 00 00 00 00 03 00 00 00
        60: 00 00 00 00 04 00 00 00 00 00 00 00 05 00 00 00
        70: 00 00 00 00 06 00 00 00 00 00 00 00 07 00 00 00
        80: 03 00 e0 00 80 ff ef 00 03 b0 fe 00 80 c0 fe 00
        90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
        a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
        b0: 03 0a 00 00 00 0b 00 00 03 00 80 00 00 d3 fe 00
        c0: 00 00 00 00 00 00 00 00 13 10 00 00 00 f0 0f 00
        d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
        e0: 03 00 00 ff 00 00 00 00 00 00 00 00 00 00 00 00
        f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    """

    def __init__(self, fake_data_str = None):
        
        if not fake_data_str:
            fake_data_str = self.__fake_data_str_default

        # Initialize fake_data as an array of byte values from lspci -xxx
        self.__fake_data = []
        for line in fake_data_str.split('\n')[1:]:
            line = line.strip()[4:]
            if len(line) > 0:
                self.__fake_data += map(lambda x: int(x, 16), line.split(' ')) 

    def getReg(self, offset, size):
        if size == 'B':
            return self.__fake_data[offset]
        elif size == 'W':
            return (self.__fake_data[offset + 1] << 0x08) + \
                   (self.__fake_data[offset + 0] << 0x00)
        elif size == 'L':
            return (self.__fake_data[offset + 3] << 0x18) + \
                   (self.__fake_data[offset + 2] << 0x10) + \
                   (self.__fake_data[offset + 1] << 0x08) + \
                   (self.__fake_data[offset + 0] << 0x00)
        else:
            raise Exception('Wrong register size')

###############################################################################

class K8MemMapRegs:
    
    """\
    """

    # Map register offsets in PCI configuration space to register (size, description)
    regmap = {
        0x00: ('W', 'Vendor ID'),
        0x02: ('W', 'Device ID'),
           
        0x04: ('W', 'Command'),
        0x06: ('W', 'Status'),
           
        0x08: ('B', 'Revision ID'),
        0x09: ('B', 'Program Interface'),
        0x0A: ('B', 'Subclass Code'),
        0x0B: ('B', 'Base Class Code'),
           
        0x0C: ('B', 'Cache Line Size'),
        0x0D: ('B', 'Latency Timer'),
        0x0E: ('B', 'Header Type'),
        0x0F: ('B', 'Built In Self Test'),
        
        0x10: ('L', 'Base Address 0'),
        0x14: ('L', 'Base Address 1'),
        0x18: ('L', 'Base Address 2'),
        0x1C: ('L', 'Base Address 3'),
        0x20: ('L', 'Base Address 4'),
        0x24: ('L', 'Base Address 5'),
        
        0x28: ('L', 'Card Bus CIS Pointer'),
           
        0x2C: ('W', 'Subsystem Vendor ID'),
        0x2E: ('W', 'Subsystem ID'),
        
        0x30: ('L', 'ROM Base Address'),
        0x34: ('L', 'Capabilities'),
        0x38: ('L', 'Reserved'),
        
        0x3C: ('B', 'Interrupt Line'),
        0x3D: ('B', 'Interrupt Pin'),
        0x3E: ('B', 'Minimum GNT'),
        0x3F: ('B', 'Maximum Latency'),
           
        0x40: ('L', 'DRAM Base 0'),
        0x44: ('L', 'DRAM Limit 0'),
        0x48: ('L', 'DRAM Base 1'),
        0x4C: ('L', 'DRAM Limit 1'),
        0x50: ('L', 'DRAM Base 2'),
        0x54: ('L', 'DRAM Limit 2'),
        0x58: ('L', 'DRAM Base 3'),
        0x5C: ('L', 'DRAM Limit 3'),
        0x60: ('L', 'DRAM Base 4'),
        0x64: ('L', 'DRAM Limit 4'),
        0x68: ('L', 'DRAM Base 5'),
        0x6C: ('L', 'DRAM Limit 5'),
        0x70: ('L', 'DRAM Base 6'),
        0x74: ('L', 'DRAM Limit 6'),
        0x78: ('L', 'DRAM Base 7'),
        0x7C: ('L', 'DRAM Limit 7'),
        
        0x80: ('L', 'Memory Mapped I/O Base 0'),
        0x84: ('L', 'Memory Mapped I/O Limit 0'),
        0x88: ('L', 'Memory Mapped I/O Base 1'),
        0x8C: ('L', 'Memory Mapped I/O Limit 1'),
        0x90: ('L', 'Memory Mapped I/O Base 2'),
        0x94: ('L', 'Memory Mapped I/O Limit 2'),
        0x98: ('L', 'Memory Mapped I/O Base 3'),
        0x9C: ('L', 'Memory Mapped I/O Limit 3'),
        0xA0: ('L', 'Memory Mapped I/O Base 4'),
        0xA4: ('L', 'Memory Mapped I/O Limit 4'),
        0xA8: ('L', 'Memory Mapped I/O Base 5'),
        0xAC: ('L', 'Memory Mapped I/O Limit 5'),
        0xB0: ('L', 'Memory Mapped I/O Base 6'),
        0xB4: ('L', 'Memory Mapped I/O Limit 6'),
        0xB8: ('L', 'Memory Mapped I/O Base 7'),
        0xBC: ('L', 'Memory Mapped I/O Limit 7'),
           
        0xC0: ('L', 'PCI I/O Base 0'),
        0xC4: ('L', 'PCI I/O Limit 0'),
        0xC8: ('L', 'PCI I/O Base 1'),
        0xCC: ('L', 'PCI I/O Limit 1'),
        0xD0: ('L', 'PCI I/O Base 2'),
        0xD4: ('L', 'PCI I/O Limit 2'),
        0xD8: ('L', 'PCI I/O Base 3'),
        0xDC: ('L', 'PCI I/O Limit 3'),
        
        0xE0: ('L', 'Configuration Base & Limit 0'),
        0xE4: ('L', 'Configuration Base & Limit 1'),
        0xE8: ('L', 'Configuration Base & Limit 2'),
        0xEC: ('L', 'Configuration Base & Limit 3'),
           
        0xF0: ('L', 'DRAM Hole Address'),
    }
    
    sizemap = {'B': 1, 'W': 2, 'L': 4}
    
    regranges = {
        'all': (0x00, 0xF0),
        'dram': (0x40, 0x7C, dramBaseReg, dramLimitReg),
        'mmio': (0x80, 0xBC, mmioBaseReg, mmioLimitReg),
        'pciio': (0xC0, 0xDC, pciioBaseReg, pciioLimitReg),
        'config': (0xE0, 0xEC, configMapReg, configMapReg)
    }

    def regrange(self, range):
        return "(0x%02X-0x%02X)" % self.regranges[range][0:2]

    def inRange(self, reg, range):
        range = self.regranges[range]
        return reg >= range[0] and reg <= range[1]
    
    def range(self, reg):
        for range in ('dram', 'mmio', 'pciio', 'config'):
            if self.inRange(reg, range):
                return range
        return None

    def dumpRegRange(self, range):
        regs = [reg for reg in self.regmap.keys() if self.inRange(reg, range)]
        regs.sort()
        for reg in regs:
            print self.dumpReg(reg)
            if self.interp and self.range(reg):
                print str(self.regranges[self.range(reg)][((reg % 8) / 4) + 2](self.getReg(reg)))

    def size(self, reg):
        return self.regmap[reg][0]

    def desc(self, reg):
        return self.regmap[reg][1]

    def getReg(self, reg):
        return self.k8memmap.getReg(reg, self.size(reg))

    def dumpReg(self, reg):
        value_char_size = self.sizemap[self.size(reg)] * 2
        padding = ' '
        if not self.quiet:
            padding = ((9 - value_char_size) * ' ')
        format = '0x%02X:' + padding + 'Ox%0' + str(value_char_size) + 'X'
        if not self.quiet:
            format += ' - %s' % self.desc(reg)
        return format % (reg, self.getReg(reg))

    def __init__(self, k8memmap = None, quiet = False, interp = False):
        self.k8memmap = k8memmap
        self.interp = interp
        self.quiet = quiet

###############################################################################

def Denary2Binary(n, pad = 0):
    
    '''convert denary integer n to binary string bStr'''
    
    bStr = ''
    
    if n < 0: raise ValueError, "must be a positive integer"
    if n == 0: return '0'
    
    while n > 0:
        bStr = str(n % 2) + bStr
        n = n >> 1
    
    if pad > 0 and len(bStr) < pad:
        bStr = ('0' * (pad - len(bStr))) + bStr
    
    return bStr
 
###############################################################################

def isK8AddressMapAvailable():
    
    '''Test with lspci whether the PCI device 00:18.1 is present on the system, ensure that type, vendor & device are right'''
    
    ret = subprocess.Popen(("lspci", "-n"), stdout=subprocess.PIPE).communicate()[0]
    
    r_lspci = re.compile(r'^00:18.1\s+(?P<type>[\da-fA-F]{4}):\s+(?P<vendor>[\da-fA-F]{4}):(?P<device>[\da-fA-F]{4}).*$', re.MULTILINE)
    m = r_lspci.search(ret)
    
    if m:
        ret = True
        if m.group('type') != '0600':
            print 'Error: wrong device type: ' + m.group('type')
            ret = False
        if m.group('vendor') != '1022':
            print 'Error: wrong device vendor: ' + m.group('vendor')
            ret = False
        if m.group('device') != '1101':
            print 'Error: wrong device ID: ' + m.group('device')
            ret = False
        return ret
    else:
        return False

###############################################################################

def stripLeading(s):
    
    "Strip leading whitespace from an indented triplequote string"
     
    ret = ''
    for line in s.split('\n'):
        ret += line.strip() + ' '
    ret = ret.strip() 
    return ret

###############################################################################

def option_setup():
    
    version = "%prog 1.0 - Copyright (C) 2008 Vincent Legoll <vincent.legoll@gmail.com>"
    description = """\
        This program dumps the content of the AMD K8 Host bridge Address
        Map registers in a human-readable format. The same informaton is
        available in raw hexadecimal with: lspci -xxx -s 0:0:18.1
    """
    
    from optparse import OptionParser
    
    parser = OptionParser(version = version, description = stripLeading(description))
    
    parser.add_option("-t", "--test", dest="test", default=False,
                      action="store_true", help="Run autotests")
    
    parser.add_option("-f", "--fake", dest="fake", default=False,
                      action="store_true", help="Use a fake K8 Address Map")
    
    parser.add_option("-q", "--quiet", dest="quiet", default=False,
                      action="store_true", help="Don't print register descriptions, nor pad register values")
    
    parser.add_option("-i", "--interp", dest="interp", default=False,
                      action="store_true", help="Display interpreted register content")
    
    # Select which register address range you want to dump
    
    k8mmr = K8MemMapRegs()
    
    parser.add_option("-c", "--config", dest="config", default=False,
                      action="store_true", help="Show Config Map registers %s" % k8mmr.regrange("config"))
    
    parser.add_option("-d", "--dram", dest="dram", default=False,
                      action="store_true", help="Show DRAM registers %s" % k8mmr.regrange("dram"))
    
    parser.add_option("-m", "--mmio", dest="mmio", default=False,
                      action="store_true", help="Show MMIO registers %s" % k8mmr.regrange("mmio"))
    
    parser.add_option("-p", "--pciio", dest="pciio", default=False,
                      action="store_true", help="Show PCI I/O registers %s" % k8mmr.regrange("pciio"))
    
    parser.add_option("-a", "--all", dest="all", default=False,
                      action="store_true", help="Show all registers %s" % k8mmr.regrange("all"))
    
    (options, args) = parser.parse_args()
    return options 

###############################################################################

def testLspciAgainstSetpci():
    
    '''Test that "lscpi -xxx -s 0:0:18.1" output parsing is giving same register values as "setpci -s 0:0:18.1"'''
    
    lspci = subprocess.Popen(("lspci", "-xxx", "-s", "0:0:18.1"), stdout=subprocess.PIPE).communicate()[0]
    
    k8mmr_f = K8MemMapRegs(K8MemMapFake(lspci))
    k8mmr_r = K8MemMapRegs(K8MemMapReal())
    
    regs = k8mmr_r.regmap.keys()
    regs.sort()
    for reg in regs:
        fake = k8mmr_f.getReg(reg)
        real = k8mmr_r.getReg(reg)
        if fake != real:
            print 'Warning: register 0x%02X, values differ: fake = 0x%X, real = 0x%X - %s' % (reg, fake, real, k8mmr_f.desc(reg))
        else:
            print 'register 0x%02X, identical values = 0x%X - %s' % (reg, fake, k8mmr_f.desc(reg))

###############################################################################

__notK8 = """\
        Warning: This program needs to run on a AMD K8 processor.
        Defaulting to '--fake' command line parameter behaviour,
        which will not probe the real hardware.
    """

def main():
    
    options = option_setup()
    
    if options.test:
        import doctest
        doctest.testmod()
        if isK8AddressMapAvailable():
            testLspciAgainstSetpci()
    else:
        if options.fake:
            k8mm = K8MemMapFake()
        else:
            if not isK8AddressMapAvailable():
                print stripLeading(__notK8)
                k8mm = K8MemMapFake()
            else:
                k8mm = K8MemMapReal()
        k8mmr = K8MemMapRegs(k8mm, options.quiet, options.interp)
        if options.all or (not options.dram and not options.mmio and not options.pciio):
            k8mmr.dumpRegRange("all")
        else:
            if options.dram:
                k8mmr.dumpRegRange("dram")
            if options.mmio:
                k8mmr.dumpRegRange("mmio")
            if options.pciio:
                k8mmr.dumpRegRange("pciio")
            if options.config:
                k8mmr.dumpRegRange("config")

###############################################################################

if __name__ == "__main__":
    main()
