#!/usr/bin/env python
#
# We currently only need device drivers loaded for anything
# we do in here, so run this as early as possible.
#
# PROVIDE: isi_hw_init
# REQUIRE: isi_module_init isi_hal_init

import sys
import os
import re

import isi.app.lib.procs as procs
import isi.hw.hal as hal
import isi.hw.version as hwver
import isi.misc as misc
import isi.hw.hal as hal

LSIEXPUTIL_CMDERR = 2
READ_MSR_REGEX = re.compile("^Read\(\w+\)\s+got\s+(?P<value>\w+)$")

MSR_SET_MOBOS = [
    hwver.MOBO_INTELS2600GZ,
    hwver.MOBO_INTELS2600CP,
]

MSR_SET_LIST = [
    "0x199",
]

def print_cmd(cmd):
    (error, output) = procs.get_cmd_output(cmd)
    for line in output:
        print line

def sysctl(name, value):
    cmd = '%s=%s' % (name, value)
    misc.sysctlbyname(cmd)
    print '%s=%s' % (name, value)

def sysctl_known(name):
    cmd = 'sysctl %s' % name
    (error, output) = procs.get_cmd_output(cmd)
    return (error == 0)

def rescan():
    cmd = 'camcontrol rescan 0'
    print_cmd(cmd)

def mpt_mfg_mode_check():
    """Check to see if booted with MPT Mfg Mode, based on kernel env.
       (See hw.check.lib.lsi.MptMfgModeCheck code)
       Note /boot/device.hints 'hint.mpt.0.mfg' line can set this kenv.
       Returns True if non-zero kenv hint.
       """
    hint = 'hint.mpt.0.mfg'
    numreg = re.compile('^-?\d+$')
    
    def check_num(line):
        return numreg.match(line) and int(line) != 0

    result = False
    cmd = ("kenv %s" % (hint))
    (error, output) = procs.get_cmd_output(cmd)
    if not error and len(output) == 1:
        result = check_num(output[0])
    return result

def read_msr_value(output):
    val = None
    for line in output:
        match = READ_MSR_REGEX.match(line)
        if match:
            val = match.group('value')
            break
    return val

def set_msr(register):
    result = 0
    cpu0_val = None
    e, o = procs.get_cmd_output('/usr/bin/isi_hwtools/ioaccess -c 0'
                                ' -m %s' % register)
    if e:
        print 'ioaccess command to read MSR %s for CPU0 failed' % register
        return 1
    cpu0_val = read_msr_value(o)
    if cpu0_val is None:
        print 'Unable to read MSR %s for CPU0' % register
        return 1 
    num_cpus = hal.getCpuCount()
    if num_cpus is None:
        print 'Unable to get CPU Count for Chassis'
        return 1
    for cpu in xrange(1, num_cpus):
        cpu_val = None
        e, o = procs.get_cmd_output('/usr/bin/isi_hwtools/ioaccess -c %s'
                                    ' -m %s' % (cpu, register))
        if e:
            print ('ioaccess command to read MSR %s for CPU%s failed' %
                   (register, cpu))
            result = 1
            continue
        cpu_val = read_msr_value(o)
        if cpu_val is None:
            print 'Unable to read MSR %s for CPU%s' % (register, cpu)
            result = 1
            continue
        if cpu0_val != cpu_val:
            e, o = procs.get_cmd_output('/usr/bin/isi_hwtools/ioaccess -c %s'
                                        ' -m %s %s' % (cpu, register, cpu0_val))
            if e:
                print ('Unable to set MSR %s for CPU%s to %s' %
                       (register, cpu, cpu0_val))
                result = 1
    return result

def main(args):

    # Bug 102162 - For the short term, setting MSRs in userspace
    #              gives us the flexibility to quickly fold in any
    #              input from Intel on which are the correct
    #              registers while we're waiting on feedback
    #              on what's needed to ensure that the CPU and
    #              uncore are running at full speed. The MSR(s)
    #              should only be set for SandyBridge Platforms.
    if hal.getMotherboardType() in MSR_SET_MOBOS:
        for register in MSR_SET_LIST:
            if set_msr(register):
                print 'Unable to set MSR %s' % register

    ## For LSI expanders, we need to clear out the phy error log at boot
    ## time, as a reboot can cause the SAS HBA to reset, logging errors
    ## on the expander phys.
    ##
    ## Eventually this should probably just be a call to something like
    ## isi_exputil that can generically handle whatever expander we may
    ## have.
    if hal.supportsSasPhyMonitoring():
        if hal.supportsLsiExpander():
                cmd = '/usr/bin/isi_hwtools/isi_lsiexputil -pC all'
                (error, output) = procs.get_cmd_output(cmd)
                if (error != 0):
                    for line in output:
                        print line 
                cmd = '/usr/bin/isi_hwtools/isi_lsiexputil -pC all e1' 
                (error, output) = procs.get_cmd_output(cmd)
                # XXX: exit status 2 indicates we have no second LSI expander
                # (bad argument error from the lsiexputil), if we have
                # encountered an error besides that, print it out to console.
                # This code should probably not be hardware-specific to this
                # type of expander, HAL should abstract this
                exit_status = os.WEXITSTATUS(error)
                if (exit_status != LSIEXPUTIL_CMDERR and exit_status != 0):
                    for line in output:
                        print line

    #------------------------------
    # Note:  hw.disk_event sysctls and the schemes they represent are discussed in
    # https://svn/view/*checkout*/tech-doc/head/DDD/drive_timeout_improvements/drive_thresh_design.pdf
    #------------------------------

    #------------------------------
    # Card-specific actions
    #------------------------------
    dskctl_family = hal.getDskctlFamily()
    if dskctl_family == hwver.DISKCTL_FAMILY_TWE:
        # Turn on disk-drive power-cycle protection (panic on 3ware delay...)
        sysctl('hw.twe0.power_check_panic', 2)

        # Error frequency--aggressive since stalling is disabled
        sysctl('hw.disk_event.thresh.errfreq_max', 20)
        sysctl('hw.disk_event.thresh.errfreq_history', 2000)

        # Single slow I/O
        # Twe holds up I/Os to all drives when it flushes the cache of one drive
        # Twe also waits for drives, allowing one slow drive to make all drives
        # slow.
        sysctl('hw.disk_event.thresh.slowacc_usec', 1500000)
        sysctl('hw.disk_event.thresh.slowacc_single_act', 0)
        sysctl('hw.disk_event.thresh.slowacc_count', 0)
        sysctl('hw.disk_event.thresh.slowacc_act', 0)

        # Slow I/O frequency
        sysctl('hw.disk_event.thresh.slowfreq_usec', 150000)
        sysctl('hw.disk_event.thresh.slowfreq_max', 0)
        sysctl('hw.disk_event.thresh.slowfreq_history', 0)
        sysctl('hw.disk_event.thresh.slowfreq_act', 0)
        sysctl('hw.disk_event.thresh.slowfreq_single_act', 0)

    elif ((dskctl_family == hwver.DISKCTL_FAMILY_LSI) and 
          (hal.getDskctlType() != hwver.DISKCTL_LSI_VMWARE)):
        # Turn on rebooting if the LSI device crashes
        if sysctl_known('hw.mpt0.reboot_on_fault'):
            sysctl('hw.mpt0.reboot_on_fault', 1)

        # Error frequency--relaxed since stalling is enabled
        sysctl('hw.disk_event.thresh.errfreq_max', 200)
        sysctl('hw.disk_event.thresh.errfreq_history', 2000)
        # Cause drv to place the drive in the stalled state on a single error
        sysctl('hw.disk_event.thresh.errfreq_single_act', 4)

        # Single slow I/O
        sysctl('hw.disk_event.thresh.slowacc_usec', 1500000)
        # Cause drv to place the drive in the stalled state
        sysctl('hw.disk_event.thresh.slowacc_single_act', 4)
        sysctl('hw.disk_event.thresh.slowacc_count', 0)
        sysctl('hw.disk_event.thresh.slowacc_act', 0)

        # Slow I/O frequency
        sysctl('hw.disk_event.thresh.slowfreq_usec', 150000)
        sysctl('hw.disk_event.thresh.slowfreq_max', 50)
        sysctl('hw.disk_event.thresh.slowfreq_history', 1300)
        # Cause drv to place the drive in the stalled state
        sysctl('hw.disk_event.thresh.slowfreq_act', 4)
        sysctl('hw.disk_event.thresh.slowfreq_single_act', 0)

        # Rescan the bus to flush out any possible boot-time error conditions,
        # enable reading the SMART data during probes, and then rescan once
        # more to read it.
        # NOTE for Mfg FUT CFs (and any other boot that includes the line
        # "hint.mpt.0.mfg=1" in boot/device.hints, or has kernel env var set),
        # we shouldn't be doing any rescan()'s, since driver in MPT Mfg mode
        # won't do PortEnable's and we won't see the drives anyway, and we
        # will get boot errors:
        # camcontrol: CAMIOCOMMAND ioctl failed: Invalid argument
        mfg_mode = mpt_mfg_mode_check()
        if not mfg_mode:
            rescan()
        sysctl('kern.cam.probe_smart_data', 1)
        if not mfg_mode:
            rescan()
        sysctl('kern.cam.lost_cache_downs_drive', 1)
    else:
        sysctl('hw.disk_event.thresh.slowacc_usec', 0)
        sysctl('hw.disk_event.thresh.slowacc_single_act', 0)

    #------------------------------
    # Platform-specific actions
    #------------------------------
        
    #------------------------------
    # Card-agnostic actions
    #------------------------------

    # Set disk event thresholds
    sysctl('hw.disk_event.thresh.ecclist_max', 30)
    sysctl('hw.disk_event.thresh.ignore_idle_usec', 1000000)
    sysctl('hw.disk_event.thresh.access_avg_history', 1000)
    sysctl('hw.disk_event.thresh.access_hi_history', 1000)

    # Set disk event actions
    # 2 - "softfail"
    # 4 - set the abort_bios sysctl for the drive (put the drive into the 'stalled' state in ifs/drv)
    sysctl('hw.disk_event.thresh.smart_thresh_act', 2)
    sysctl('hw.disk_event.thresh.udma_downgrade_act', 2)
    sysctl('hw.disk_event.thresh.udma_upgrade_act', 2)
    sysctl('hw.disk_event.thresh.ecclist_act', 2)
    sysctl('hw.disk_event.thresh.errfreq_act', 2)

    # Bug 101620: Accelerator-III nodes are built at FoxConn Technology
    #             and do not go through the Isilon manufacturing process
    #             and the same is true for the spare drives for ACC-III.
    #             Therefore, in order to make sure that the drives have
    #             the optimal settings turned on, we must force them to
    #             be set now. We don't specifically do this on just
    #             ACC-III since setting the optimal settings on the older
    #             Accelerator-X nodes is essentially a NOOP since those
    #             nodes and their spare drives do go through Isilon
    #             manufacturing and do get their optimal settings turned on.
    if hal.isAcceleratorClass():
        procs.run_cmd('/usr/bin/isi_radish -e')

if __name__=='__main__':
    main(sys.argv)
