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