#!/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
import isi.hw.hwmon.adaptivecooling as adaptivecooling

LSIEXPUTIL_CMDERR = 2

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 main(args):

    # Prior to clearing any error logs, check for adaptive cooling events
    # and block if appropriate
    if adaptivecooling.is_adaptive_cooling_boot():
        # For initial work, run in verbose mode. Turn off later.
        adaptivecooling.boot_handler(verbose=True)

    ## For LSI/PMC 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.  For now, add a cdesexputil call for Colossus nodes.
    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
        if hal.supportsCDES():
            cmd = '/usr/bin/isi_hwtools/isi_cdesexputil -pC all'
            (error, output) = procs.get_cmd_output(cmd)
            if (error != 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
    #------------------------------

    # L3 does not cache data on the HD and high cap NL platforms
    # Define 4.5TB limit to mean >4TB drive, magic number is 1TB block count
    HIGH_CAPACITY_BLOCKS = 1953509292 * 4.5
    if (hal.isHDSeriesFamilyCode() or
      ((hal.isNlSeries() or hal.isNLSeriesFamilyCode()) and
      (hal.getSupportedHddBlocks(0) > HIGH_CAPACITY_BLOCKS))):
        sysctl('efs.l3.evict.IDI_CLASS_UNPROTECTED', 0)
    else:
        sysctl('efs.l3.evict.IDI_CLASS_UNPROTECTED', 1)

    #------------------------------
    # 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)
