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