• 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, 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
15class 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