#! /usr/bin/env python

import re
import sys

import isi.app.lib.procs as procs
import isi.hw.pci as pci
import isi.hw.check.lib.args as argslib
import isi.hw.check.lib.misc as misc

pci_dev_dict = None

def get_pci_sel_components(sel):
    error = None
    match = re.match('^pci(\d+):(\d+):(\d+):(\d+)', sel)
    if match is None:
        error = 'Invalid pci sel "%s"' % sel
    else:
        dom,bus,dev,func = map(int, match.groups())
    return error, (dom,bus,dev,func)

def get_pci_dev_dict():
    global pci_dev_dict
    if pci_dev_dict == None:
        devs = {}
        reg_pci = '\s+'.join([
            "(?P<pci_dev>\S+)@(?P<pci_sel>\S+):",
            "class=(?P<dev_class>\w+)",
            "card=(?P<card>\w+)",
            "chip=(?P<chip>\w+)",
            "rev=(?P<rev>\w+)",
            "hdr=(?P<hdr>\w+)"])
        reg_vendor = "\s+vendor\s+=\s+'(?P<vendor>.+)'"
        reg_device = "\s+device\s+=\s+'(?P<device>.+)'"
        reg_class = "\s+class\s+=\s+(?P<class>.+)"
        reg_subclass = "\s+subclass\s+=\s+(?P<subclass>.+)"
        # order is important:
        indices = PCI,VENDOR,DEVICE,CLASS,SUBCLASS,DONE = range(6)
        keys = ['pci_sel','vendor','device','class','subclass']
        dev_keys = ['dev_class','card','chip','rev','hdr']
        regex = [reg_pci,reg_vendor,reg_device,reg_class,reg_subclass]
        reg = map(re.compile, regex)
        cmd = '/usr/sbin/pciconf -vl'
        (error, out) = procs.get_cmd_output(cmd)
        if error or len(out) == 0:
            print 'Failed access %s' % cmd
            print error
            sys.exit(1)
        # dont assume the output lines will come in any particular order.
        # just walk all possible index for each line...
        for line in out:
            for index in indices:
                if index == DONE:
                    break
                match = reg[index].match(line)
                if match:
                    break
            if index == DONE:
                continue
            if index == PCI:
                pci_dev = match.group('pci_dev')
                devs[pci_dev] = {}
                for key in dev_keys:
                    devs[pci_dev][key] = match.group(key)
                # fall through for key = keys[PCI] below...
            # all other index/key/regex pairs have single key:
            key = keys[index]
            devs[pci_dev][key] = match.group(key)
        pci_dev_dict = devs.copy()
    return pci_dev_dict

def pci_dev_to_sel(pci_dev):
    pci_devs = get_pci_dev_dict()
    if pci_dev in pci_devs:
        return pci_devs[pci_dev]['pci_sel']
    return None

def reduce_list(original):
    d = {}
    map(lambda a: d.__setitem__(a,1), original)
    return d.keys()

supported_devs = map(lambda d: (d,pci_dev_to_sel(d)), ['mlx4_core0', 'mthca0'])
first_dev = supported_devs.pop()
if not first_dev[1]:
  DEFAULT_PCI_DEV = supported_devs.pop()[0]
else:
  DEFAULT_PCI_DEV = first_dev[0]

def get_pci_sel(pci_sel_list=[], pci_dev_list=[]):
    """Returns dictionary of 'name':'pci_sel' entries, given lists of
       pci_sel and pci_dev values to use.
       Any specified pci_dev that are not found in pciconf dump have
       'pci_sel' == None.
       If neither pci_sel_list or pci_dev_list specified, uses default dev.

       NOTE we use a dict and not a list so we can report those -d dev_list
       entries which were not found in the pciconf dump; otherwise, there
       would be NO reporting of this, you'd just have a list with a single
       'None' entry representing ALL such unfound devs.
    """
    # break up any comma-separated lists within lists first
    pci_sel_list = reduce(lambda a,b: a+b.split(','), pci_sel_list, [])
    pci_dev_list = reduce(lambda a,b: a+b.split(','), pci_dev_list, [])
    if not (pci_sel_list or pci_dev_list):
        pci_dev_list = [DEFAULT_PCI_DEV]
    result = {}
    map(lambda a: result.__setitem__(a,a), pci_sel_list)
    # remove any devs that match existing pci_sel_list entries
    devs = map(lambda d: (d,pci_dev_to_sel(d)), pci_dev_list)
    devs = filter(lambda (d,s): s not in pci_sel_list, devs)
    pci_dev = {}
    map(lambda (d,s): pci_dev.__setitem__(d,s), devs)
    result.update(pci_dev)
    return result

def xfer_pci_bytes(sel, offset, size=1, wrdata=None):
    error, (dom,bus,dev,func) = get_pci_sel_components(sel)
    if error is None:
        try:
            if wrdata != None:
                if size == 1:
                    error = pci.writeByte(dom,bus,dev,func,offset,wrdata)
                elif size == 2:
                    error = pci.writeWord(dom,bus,dev,func,offset,wrdata)
                else:
                    error = pci.writeLong(dom,bus,dev,func,offset,wrdata)
            else:
                if size == 1:
                    data = pci.readByte(dom,bus,dev,func,offset)
                elif size == 2:
                    data = pci.readWord(dom,bus,dev,func,offset)
                else:
                    data = pci.readLong(dom,bus,dev,func,offset)
                if data is None:
                    error = "Unable to read PCI data at offset %02x" % offset
        except IOError, e:
            error = e
    if error:
        print 'Failed access to %s' % sel
        print error
        sys.exit(1)
    if wrdata != None:
        return 0
    return data

def read_pci_bytes(sel, offset, size=1):
    return xfer_pci_bytes(sel, offset, size)

def read_pci_byte(sel, offset):
    return read_pci_bytes(sel, offset, size=1)

def read_pci_word(sel, offset):
    return read_pci_bytes(sel, offset, size=2)

def read_pci_long(sel, offset):
    return read_pci_bytes(sel, offset, size=4)

def write_pci_word(sel, offset, data):
    return xfer_pci_bytes(sel, offset, size=2, wrdata=data)

def find_capability_offset(sel, find_cap_id):
    next_off = read_pci_byte(sel, 0x34) #by default in the pci spec
    while next_off != 0:
        capability_id = read_pci_byte(sel, next_off)
        if capability_id == find_cap_id:
            return next_off
        if capability_id == 0xff:
            return None
        next_off = read_pci_byte(sel, next_off+1)
    return None

def find_vpd_offset(sel):
    # capability_id == 0x03 for VPD
    return find_capability_offset(sel, 0x03)

def main(options):
    result = 0
    args = argslib.Args(options)
    non_opts = args.process(sys.argv[1:])
    if len(non_opts) > 0:
        raise argslib.Error('too many arguments: %s' % ' '.join(non_opts))

    sel_list = get_pci_sel(args['pci_sel'], args['dev_name'])

    for sel_name,sel in sel_list.iteritems():
        if sel == None:
            print 'Could not find matching sel for pci device %s' % sel_name
            result = 1
            continue
        error, (dom,bus,dev,func) = get_pci_sel_components(sel)
        if error:
            print 'Invalid sel for pci device %s' % sel_name
            result = 1
            continue
        vpdoff = find_vpd_offset(sel)
        if vpdoff == None:
            if sel == sel_name:
                print 'Could not find vpdoff for sel %s' % sel
            else:
                print 'Could not find vpdoff for sel %s (%s)' % (sel,sel_name)
            result = 1
        else:
            print 'sel %s vpdoff 0x%x' % (sel, vpdoff)
            try:
                pci.vpdSelDump(dom,bus,dev,func)
            except IOError, e:
                print e
                result = 1

    return result

if __name__ == '__main__':
    usage = \
"""Usage: %s [OPTIONS] [<interfaces>]

OPTIONS:
  -s <pci_sel>       : Check given pci selector (format: pciW:X:Y:Z)
  -d <dev_name>      : Check given pci dev_name (e.g. mthca0, mlx4_core0)
  -h, --help         : Show this text

Dump pci2.3-spec-compliant VPD (vital product data) info.
Default IB card location searched is 'mlx4_core0' or 'mthca0' match.
Useful for verifying build and serial# for IB issues.
""" % argslib.appname()
    
    options = {
        'pci_sel':
        {'opts'    : ['s:', 'pci_sel'],
         'default' : [],
         'func_attrs' : argslib.append_func(),
         },
        
        'dev_name':
        {'opts'    : ['d:', 'dev_name'],
         'default' : [],
         'func_attrs' : argslib.append_func(),
         },
        
        'help':
        {'opts'    : ['h', 'help'],
         'func'    : argslib.help_func(usage),
         },
        }

    sys.exit(argslib.run(lambda :main(options), usage))
