Home | History | Annotate | Download | only in network_EthCaps
      1 # Copyright (c) 2011-2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import collections, logging, os
      6 
      7 from autotest_lib.client.bin import test, utils
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.cros import rtc, sys_power
     10 
     11 # TODO(tbroch) WOL:
     12 # - Should we test any of the other modes?  I chose magic as it meant that only
     13 #   the target device should be awaken.
     14 
     15 class network_EthCaps(test.test):
     16     """Base class of EthCaps test.
     17 
     18     Verify Capabilities advertised by an ethernet device work.
     19     We can't verify much in reality though. But we can verify
     20     WOL for built-in devices which is expected to work.
     21 
     22     @param test.test: test instance
     23     """
     24     version = 1
     25 
     26     # If WOL setting changed during test then restore to original during cleanup
     27     _restore_wol = False
     28 
     29 
     30     def _is_usb(self):
     31         """Determine if device is USB (or not)
     32 
     33         Add-on USB devices won't report the same 'Supports Wake-on' value
     34         as built-in (ie PCI) ethernet devices.
     35         """
     36         if not self._bus_info:
     37             cmd = "ethtool -i %s | awk '/bus-info/ {print $2}'" % self._ethname
     38             self._bus_info = utils.system_output(cmd)
     39             logging.debug("bus_info is %s", self._bus_info)
     40             if not self._bus_info:
     41                 logging.error("ethtool -i %s has no bus-info", self._ethname)
     42 
     43         # Two bus_info formats are reported by different device drivers:
     44         # 1) "usb-0000:00:1d.0-1.2"
     45         #    "0000:00:1d.0" is the "platform" info of the USB host controller
     46         #    But it's obvious it's USB since that's the prefix. :)
     47         if self._bus_info.startswith('usb-'):
     48             return True
     49 
     50         # 2) "2-1.2" where "2-" is USB host controller instance
     51         return os.path.exists("/sys/bus/usb/devices/%s" % self._bus_info)
     52 
     53     def _parse_ethtool_caps(self):
     54         """Retrieve ethernet capabilities.
     55 
     56         Executes ethtool command and parses various capabilities into a
     57         dictionary.
     58         """
     59         caps = collections.defaultdict(list)
     60 
     61         cmd = "ethtool %s" % self._ethname
     62         prev_keyname = None
     63         for ln in utils.system_output(cmd).splitlines():
     64             cap_str = ln.strip()
     65             try:
     66                 (keyname, value) = cap_str.split(': ')
     67                 caps[keyname].extend(value.split())
     68                 prev_keyname = keyname
     69             except ValueError:
     70                 # keyname from previous line, add there
     71                 if prev_keyname:
     72                     caps[prev_keyname].extend(cap_str.split())
     73 
     74         for keyname in caps:
     75             logging.debug("cap['%s'] = %s", keyname, caps[keyname])
     76 
     77         self._caps = caps
     78 
     79 
     80     def _check_eth_caps(self):
     81         """Check necessary LAN capabilities are present.
     82 
     83         Hardware and driver should support the following functionality:
     84           1000baseT, 100baseT, 10baseT, half-duplex, full-duplex, auto-neg, WOL
     85 
     86         Raises:
     87           error.TestError if above LAN capabilities are NOT supported.
     88         """
     89         default_eth_caps = {
     90             'Supported link modes': ['10baseT/Half', '100baseT/Half',
     91                                       '1000baseT/Half', '10baseT/Full',
     92                                       '100baseT/Full', '1000baseT/Full'],
     93             'Supports auto-negotiation': ['Yes'],
     94             # TODO(tbroch): Other WOL caps: 'a': arp and 's': magicsecure are
     95             # they important?  Are any of these undesirable/security holes?
     96             'Supports Wake-on': ['pumbg']
     97             }
     98         errors = 0
     99 
    100         for keyname in default_eth_caps:
    101             if keyname not in self._caps:
    102                 logging.error("\'%s\' not a capability of %s", keyname,
    103                               self._ethname)
    104                 errors += 1
    105                 continue
    106 
    107             for value in default_eth_caps[keyname]:
    108                 if value not in self._caps[keyname]:
    109                     # WOL not required for USB Ethernet plug-in devices
    110                     # But all USB Ethernet devices to date report "pg".
    111                     # Enforce that.
    112                     # RTL8153 can report 'pumbag'.
    113                     # AX88178 can report 'pumbg'.
    114                     if self._is_usb() and keyname == 'Supports Wake-on':
    115                         if (self._caps[keyname][0].find('p') >= 0) and \
    116                             (self._caps[keyname][0].find('g') >= 0):
    117                             continue
    118 
    119                     logging.error("\'%s\' not a supported mode in \'%s\' of %s",
    120                                   value, keyname, self._ethname)
    121                     errors += 1
    122 
    123         if errors:
    124             raise error.TestError("Eth capability checks.  See errors")
    125 
    126 
    127     def _test_wol_magic_packet(self):
    128         """Check the Wake-on-LAN (WOL) magic packet capabilities of a device.
    129 
    130         Raises:
    131           error.TestError if WOL functionality fails
    132         """
    133         # Magic number WOL supported
    134         capname = 'Supports Wake-on'
    135         if self._caps[capname][0].find('g') != -1:
    136             logging.info("%s support magic number WOL", self._ethname)
    137         else:
    138             raise error.TestError('%s should support magic number WOL' %
    139                             self._ethname)
    140 
    141         # Check that WOL works
    142         if self._caps['Wake-on'][0] != 'g':
    143             utils.system_output("ethtool -s %s wol g" % self._ethname)
    144             self._restore_wol = True
    145 
    146         # Set RTC as backup to WOL
    147         before_secs = rtc.get_seconds()
    148         alarm_secs =  before_secs + self._suspend_secs + self._threshold_secs
    149         rtc.set_wake_alarm(alarm_secs)
    150 
    151         sys_power.do_suspend(self._suspend_secs)
    152 
    153         after_secs = rtc.get_seconds()
    154         # flush RTC as it may not work subsequently if wake was not RTC
    155         rtc.set_wake_alarm(0)
    156 
    157         suspended_secs = after_secs - before_secs
    158         if suspended_secs >= (self._suspend_secs + self._threshold_secs):
    159             raise error.TestError("Device woke due to RTC not WOL")
    160 
    161 
    162     def _verify_wol_magic(self):
    163         """If possible identify wake source was caused by WOL.
    164 
    165         The bits identifying the wake source may be cleared by the time
    166         userspace gets a chance to query the kernel.  However, firmware
    167         might have a log and expose the wake source.  Attempt to interrogate
    168         the wake source details if they are present on the system.
    169 
    170         Returns:
    171           True if verified or unable to verify due to system limitations
    172           False otherwise
    173         """
    174         fw_log = "/sys/firmware/log"
    175         if not os.path.isfile(fw_log):
    176             logging.warning("Unable to verify wake in s/w due to missing log %s",
    177                          fw_log)
    178             return True
    179 
    180         log_info_str = utils.system_output("egrep '(SMI|PM1|GPE0)_STS:' %s" %
    181                                            fw_log)
    182         status_dict = {}
    183         for ln in log_info_str.splitlines():
    184             logging.debug("f/w line = %s", ln)
    185             try:
    186                 (status_reg, status_values) = ln.strip().split(":")
    187                 status_dict[status_reg] = status_values.split()
    188             except ValueError:
    189                 # no bits asserted ... empty list
    190                 status_dict[status_reg] = list()
    191 
    192         for status_reg in status_dict:
    193             logging.debug("status_dict[%s] = %s", status_reg,
    194                           status_dict[status_reg])
    195 
    196         return ('PM1' in status_dict['SMI_STS']) and \
    197             ('WAK' in status_dict['PM1_STS']) and \
    198             ('PCIEXPWAK' in status_dict['PM1_STS']) and \
    199             len(status_dict['GPE0_STS']) == 0
    200 
    201 
    202     def cleanup(self):
    203         if self._restore_wol:
    204             utils.system_output("ethtool -s %s wol %s" %
    205                                 (self._ethname, self._caps['Wake-on'][0]))
    206 
    207 
    208     def run_once(self, ethname=None, suspend_secs=5, threshold_secs=10):
    209         """Run the test.
    210 
    211         Args:
    212           ethname: string of ethernet device under test
    213           threshold_secs: integer of seconds to determine whether wake occurred
    214             due to WOL versus RTC
    215         """
    216         if not ethname:
    217             raise error.TestError("Name of ethernet device must be declared")
    218 
    219         self._ethname = ethname
    220         self._threshold_secs = threshold_secs
    221         self._suspend_secs = suspend_secs
    222         self._bus_info = None
    223 
    224         self._parse_ethtool_caps()
    225         self._check_eth_caps()
    226 
    227         # ChromeOS does not require WOL support for any USB Ethernet Adapters.
    228         # In fact, WoL only known to work for PCIe Ethernet devices.
    229         # We know _some_ platforms power off all USB ports when suspended.
    230         # USB adapters with "pg" capabilities _might_ WoL on _some_ platforms.
    231         # White list/black listing of platforms will be required to test
    232         # WoL against USB dongles in the future.
    233         if self._is_usb():
    234             logging.debug("Skipping WOL test on USB Ethernet device.")
    235             return
    236 
    237         self._test_wol_magic_packet()
    238         # TODO(tbroch) There is evidence in the filesystem of the wake source
    239         # for coreboot but its still being flushed out.  For now only produce a
    240         # warning for this check.
    241         if not self._verify_wol_magic():
    242             logging.warning("Unable to see evidence of WOL wake in filesystem")
    243