| # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import firmware_hash |
| import glob |
| import logging |
| import os |
| import pprint |
| import re |
| from autotest_lib.client.bin import factory |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import flashrom_util |
| from autotest_lib.client.common_lib import gbb_util |
| from autotest_lib.client.common_lib import site_fmap |
| from autotest_lib.client.cros import vblock |
| |
| |
| class hardware_Components(test.test): |
| version = 2 |
| # We divide all component IDs (cids) into 5 categories: |
| # - enumable: able to get the results by running specific commands; |
| # - PCI: PCI devices; |
| # - USB: USB devices; |
| # - probable: returns existed or not by given some pre-defined choices; |
| # - not test: only data, don't test them. |
| _enumerable_cids = [ |
| 'data_display_geometry', |
| 'hash_ec_firmware', |
| 'hash_ro_firmware', |
| 'part_id_audio_codec', |
| 'part_id_cpu', |
| 'part_id_display_panel', |
| 'part_id_dram', |
| 'part_id_embedded_controller', |
| 'part_id_ethernet', |
| 'part_id_flash_chip', |
| 'part_id_ec_flash_chip', |
| 'part_id_hwqual', |
| 'part_id_keyboard', |
| 'part_id_storage', |
| 'part_id_tpm', |
| 'part_id_wireless', |
| 'vendor_id_touchpad', |
| 'version_rw_firmware', |
| 'version_3g_firmware', |
| ] |
| _pci_cids = [ |
| 'part_id_chipset', |
| 'part_id_usb_hosts', |
| 'part_id_vga', |
| ] |
| _usb_cids = [ |
| 'part_id_bluetooth', |
| 'part_id_webcam', |
| 'part_id_3g', |
| 'part_id_gps', |
| ] |
| _probable_cids = [ |
| 'key_recovery', |
| 'key_root', |
| 'part_id_cardreader', |
| 'part_id_chrontel', |
| ] |
| _not_test_cids = [ |
| 'data_bitmap_fv', |
| 'data_recovery_url', |
| ] |
| _to_be_tested_cids_groups = [ |
| _enumerable_cids, |
| _pci_cids, |
| _usb_cids, |
| _probable_cids, |
| ] |
| _not_present = 'Not Present' |
| |
| |
| def get_all_enumerable_components(self): |
| results = {} |
| for cid in self._enumerable_cids: |
| components = self.force_get_property('get_' + cid) |
| if not isinstance(components, list): |
| components = [ components ] |
| results[cid] = components |
| return results |
| |
| |
| def get_all_pci_components(self): |
| cmd = 'lspci -n | cut -f3 -d" "' |
| return utils.system_output(cmd).split() |
| |
| |
| def get_all_usb_components(self): |
| cmd = 'lsusb | cut -f6 -d" "' |
| return utils.system_output(cmd).split() |
| |
| |
| def check_enumerable_component(self, cid, exact_values, approved_values): |
| if '*' in approved_values: |
| return |
| |
| for value in exact_values: |
| if value not in approved_values: |
| if cid in self._failures: |
| self._failures[cid].append(value) |
| else: |
| self._failures[cid] = [ value ] |
| |
| |
| def check_pci_usb_component(self, cid, system_values, approved_values): |
| if '*' in approved_values: |
| self._system[cid] = [ '*' ] |
| return |
| |
| for value in approved_values: |
| if value in system_values: |
| self._system[cid] = [ value ] |
| return |
| |
| self._failures[cid] = [ 'No match' ] |
| |
| |
| def check_probable_component(self, cid, approved_values): |
| if '*' in approved_values: |
| self._system[cid] = [ '*' ] |
| return |
| |
| for value in approved_values: |
| present = getattr(self, 'probe_' + cid)(value) |
| if present: |
| self._system[cid] = [ value ] |
| return |
| |
| self._failures[cid] = [ 'No match' ] |
| |
| |
| def get_data_display_geometry(self): |
| # Get edid from driver. TODO(nsanders): this is driver specific. |
| # TODO(waihong): read-edid is also x86 only. |
| cmd = 'find /sys/devices/ -name edid | grep LVDS' |
| edid_file = utils.system_output(cmd) |
| |
| cmd = ('cat ' + edid_file + ' | parse-edid | grep "Mode " | ' |
| 'sed \'s/^.*"\(.*\)".*$/\\1/\'') |
| data = utils.system_output(cmd).split() |
| if not data: |
| data = [ '' ] |
| return data |
| |
| |
| def get_hash_ec_firmware(self): |
| """ |
| Returns a hash of Embedded Controller firmware parts, |
| to confirm we have proper updated version of EC firmware. |
| """ |
| return firmware_hash.get_ec_hash(exception_type=error.TestError) |
| |
| |
| def get_hash_ro_firmware(self): |
| """ |
| Returns a hash of Read Only (BIOS) firmware parts, |
| to confirm we have proper keys / boot code / recovery image installed. |
| """ |
| return firmware_hash.get_bios_ro_hash(exception_type=error.TestError) |
| |
| |
| def get_part_id_audio_codec(self): |
| cmd = 'grep -R Codec: /proc/asound/* | head -n 1 | sed s/.\*Codec://' |
| part_id = utils.system_output(cmd).strip() |
| return part_id |
| |
| |
| def get_part_id_cpu(self): |
| cmd = 'grep -m 1 \'model name\' /proc/cpuinfo | sed s/.\*://' |
| part_id = utils.system_output(cmd).strip() |
| return part_id |
| |
| |
| def get_part_id_display_panel(self): |
| cmd = 'find /sys/devices/ -name edid | grep LVDS' |
| edid_file = utils.system_output(cmd) |
| |
| cmd = ('cat ' + edid_file + ' | parse-edid | grep ModelName | ' |
| 'sed \'s/^.*ModelName "\(.*\)"$/\\1/\'') |
| part_id = utils.system_output(cmd).strip() |
| return part_id |
| |
| |
| def get_part_id_embedded_controller(self): |
| # example output: |
| # Found Nuvoton WPCE775x (id=0x05, rev=0x02) at 0x2e |
| parts = [] |
| res = utils.system_output('superiotool', ignore_status=True).split('\n') |
| for line in res: |
| match = re.search(r'Found (.*) at', line) |
| if match: |
| parts.append(match.group(1)) |
| part_id = ", ".join(parts) |
| return part_id |
| |
| |
| def get_part_id_ethernet(self): |
| """ |
| Returns a colon delimited string where the first section |
| is the vendor id and the second section is the device id. |
| """ |
| # Ethernet is optional so mark it as not present. A human |
| # operator needs to decide if this is acceptable or not. |
| vendor_file = '/sys/class/net/eth0/device/vendor' |
| part_file = '/sys/class/net/eth0/device/device' |
| if os.path.exists(part_file) and os.path.exists(vendor_file): |
| vendor_id = utils.read_one_line(vendor_file).replace('0x', '') |
| part_id = utils.read_one_line(part_file).replace('0x', '') |
| return "%s:%s" % (vendor_id, part_id) |
| else: |
| return self._not_present |
| |
| |
| def get_part_id_dram(self): |
| grep_cmd = 'grep i2c_dev /proc/modules' |
| i2c_loaded = (utils.system(grep_cmd, ignore_status=True) == 0) |
| if not i2c_loaded: |
| utils.system('modprobe i2c_dev') |
| |
| cmd = ('mosys -l memory spd print geometry | ' |
| 'grep size_mb | cut -f2 -d"|"') |
| part_id = utils.system_output(cmd).strip() |
| |
| if not i2c_loaded: |
| utils.system('modprobe -r i2c_dev') |
| if part_id != '': |
| return part_id |
| else: |
| return self._not_present |
| |
| |
| def get_part_id_flash_chip(self): |
| # example output: |
| # Found chip "Winbond W25x16" (2048 KB, FWH) at physical address 0xfe |
| parts = [] |
| lines = utils.system_output('flashrom -V -p internal:bus=spi', |
| ignore_status=True).split('\n') |
| for line in lines: |
| match = re.search(r'Found chip "(.*)" .* at physical address ', |
| line) |
| if match: |
| parts.append(match.group(1)) |
| part_id = ", ".join(parts) |
| return part_id |
| |
| |
| def get_part_id_ec_flash_chip(self): |
| # example output: |
| # Found chip "Winbond W25x10" (128 KB, SPI) at physical address ... |
| parts = [] |
| lines = utils.system_output('flashrom -V -p internal:bus=lpc', |
| ignore_status=True).split('\n') |
| # Undo BBS register after call. |
| utils.system('flashrom -p internal:bus=spi', ignore_status=True) |
| for line in lines: |
| match = re.search(r'Found chip "(.*)" .* at physical address ', |
| line) |
| if match: |
| parts.append(match.group(1)) |
| part_id = ", ".join(parts) |
| return part_id |
| |
| |
| def get_part_id_hwqual(self): |
| hwid_file = '/sys/devices/platform/chromeos_acpi/HWID' |
| if os.path.exists(hwid_file): |
| part_id = utils.read_one_line(hwid_file) |
| return part_id |
| else: |
| return self._not_present |
| |
| def get_part_id_keyboard(self): |
| # VPD value "initial_locale"="en-US" should be listed. |
| cmd = 'vpd -i RO_VPD -l | grep \"keyboard_layout\" | cut -f4 -d\'"\' ' |
| part_id = utils.system_output(cmd).strip() |
| if part_id != '': |
| return part_id |
| else: |
| return self._not_present |
| |
| def get_part_id_storage(self): |
| cmd = ('cd $(find /sys/devices -name sda)/../..; ' |
| 'cat vendor model | tr "\n" " " | sed "s/ \+/ /g"') |
| part_id = utils.system_output(cmd).strip() |
| return part_id |
| |
| |
| def get_part_id_tpm(self): |
| """ |
| Returns Manufacturer_info : Chip_Version |
| """ |
| cmd = 'tpm_version' |
| tpm_output = utils.system_output(cmd) |
| tpm_lines = tpm_output.splitlines() |
| tpm_dict = {} |
| for tpm_line in tpm_lines: |
| [key, colon, value] = tpm_line.partition(':') |
| tpm_dict[key.strip()] = value.strip() |
| part_id = '' |
| key1, key2 = 'Manufacturer Info', 'Chip Version' |
| if key1 in tpm_dict and key2 in tpm_dict: |
| part_id = tpm_dict[key1] + ':' + tpm_dict[key2] |
| return part_id |
| |
| |
| def get_part_id_wireless(self): |
| """ |
| Returns a colon delimited string where the first section |
| is the vendor id and the second section is the device id. |
| """ |
| part_id = utils.read_one_line('/sys/class/net/wlan0/device/device') |
| vendor_id = utils.read_one_line('/sys/class/net/wlan0/device/vendor') |
| return "%s:%s" % (vendor_id.replace('0x',''), part_id.replace('0x','')) |
| |
| |
| def get_closed_vendor_id_touchpad(self, vendor_name): |
| """ |
| Using closed-source method to derive the vendor information |
| given the vendor name. |
| """ |
| part_id = '' |
| if vendor_name.lower() == 'synaptics': |
| detect_program = '/opt/Synaptics/bin/syndetect' |
| model_string_str = 'Model String' |
| firmware_id_str = 'Firmware ID' |
| if os.path.exists(detect_program): |
| data = utils.system_output(detect_program, ignore_status=True) |
| properties = dict(map(str.strip, line.split('=', 1)) |
| for line in data.splitlines() if '=' in line) |
| model = properties.get(model_string_str, 'UnknownModel') |
| firmware_id = properties.get(firmware_id_str, 'UnknownFWID') |
| # The pattern " on xxx Port" may vary by the detection approach, |
| # so we need to strip it. |
| model = re.sub(' on [^ ]* [Pp]ort$', '', model) |
| # Format: Model #FirmwareId |
| part_id = '%s #%s' % (model, firmware_id) |
| return part_id |
| |
| |
| def get_vendor_id_touchpad(self): |
| # First, try to use closed-source method to probe touch pad |
| part_id = self.get_closed_vendor_id_touchpad('Synaptics') |
| if part_id != '': |
| return part_id |
| # If the closed-source method above fails to find vendor infomation, |
| # try an open-source method. |
| else: |
| cmd_grep = 'grep -i Touchpad /proc/bus/input/devices | sed s/.\*=//' |
| part_id = utils.system_output(cmd_grep).strip('"') |
| return part_id |
| |
| |
| def get_vendor_id_webcam(self): |
| cmd = 'cat /sys/class/video4linux/video0/name' |
| part_id = utils.system_output(cmd).strip() |
| return part_id |
| |
| |
| def get_version_rw_firmware(self): |
| """ |
| Returns the version of Read-Write (writable) firmware from VBOOT |
| section. If A/B has different version, that means this system |
| needs a reboot + firmwar update so return value is a "error report" |
| in the form "A=x, B=y". |
| """ |
| versions = [None, None] |
| section_names = ['VBOOTA', 'VBOOTB'] |
| flashrom = flashrom_util.flashrom_util() |
| if not flashrom.select_bios_flashrom(): |
| raise error.TestError('Cannot select BIOS flashrom') |
| base_img = flashrom.read_whole() |
| flashrom_size = len(base_img) |
| # we can trust base image for layout, since it's only RW. |
| layout = flashrom.detect_chromeos_bios_layout(flashrom_size, base_img) |
| if not layout: |
| raise error.TestError('Cannot detect ChromeOS flashrom layout') |
| for index, name in enumerate(section_names): |
| data = flashrom.get_section(base_img, layout, name) |
| block = vblock.unpack_verification_block(data) |
| ver = block['VbFirmwarePreambleHeader']['firmware_version'] |
| versions[index] = ver |
| # we embed error reports in return value. |
| assert len(versions) == 2 |
| if versions[0] != versions[1]: |
| return 'A=%d, B=%d' % (versions[0], versions[1]) |
| return '%d' % (versions[0]) |
| |
| def get_version_3g_firmware(self): |
| vendor_cmd = ('modem status | ' |
| 'sed -n -e "/Manufacturer/s/.*Manufacturer: //p"') |
| vendor = utils.system_output(vendor_cmd) |
| modem_cmd = ('modem status | sed -n -e "/Modem/s/.*Modem: //p"') |
| modem = utils.system_output(modem_cmd) |
| if vendor == 'Samsung' and modem == 'GT-Y3300X': |
| cmd = ("modem status | grep Version: -A 2 | tail -1 | " |
| "awk '{print $1}'") |
| version = utils.system_output(cmd) |
| elif vendor == 'Qualcomm Incorporated': |
| cmd = ("modem status | awk '/Version: / {print $2}'") |
| version = utils.system_output(cmd) |
| else: |
| version = 'Unknown' |
| return version |
| |
| def probe_key_recovery(self, part_id): |
| current_key = self._gbb.get_recoverykey() |
| target_key = utils.read_file(part_id) |
| return current_key.startswith(target_key) |
| |
| |
| def probe_key_root(self, part_id): |
| current_key = self._gbb.get_rootkey() |
| target_key = utils.read_file(part_id) |
| return current_key.startswith(target_key) |
| |
| |
| def probe_part_id_cardreader(self, part_id): |
| # A cardreader is always power off until a card inserted. So checking |
| # it using log messages instead of lsusb can limit operator-attended. |
| # But note that it does not guarantee the cardreader presented during |
| # the time of the test. |
| [vendor_id, product_id] = part_id.split(':') |
| found_pattern = ('New USB device found, idVendor=%s, idProduct=%s' % |
| (vendor_id, product_id)) |
| cmd = 'grep -qs "%s" /var/log/messages*' % found_pattern |
| return utils.system(cmd, ignore_status=True) == 0 |
| |
| |
| def probe_part_id_chrontel(self, part_id): |
| if part_id == self._not_present: |
| return True |
| |
| if part_id == 'ch7036': |
| grep_cmd = 'grep i2c_dev /proc/modules' |
| i2c_loaded = (utils.system(grep_cmd, ignore_status=True) == 0) |
| if not i2c_loaded: |
| utils.system('modprobe i2c_dev') |
| |
| probe_cmd = 'ch7036_monitor -p' |
| present = (utils.system(probe_cmd, ignore_status=True) == 0) |
| |
| if not i2c_loaded: |
| utils.system('modprobe -r i2c_dev') |
| return present |
| |
| return False |
| |
| |
| def force_get_property(self, property_name): |
| """ Returns property value or empty string on error. """ |
| try: |
| return getattr(self, property_name)() |
| except error.TestError as e: |
| logging.error("Test error in getting property %s", property_name, |
| exc_info=1) |
| return '' |
| except: |
| logging.error("Exception getting property %s", property_name, |
| exc_info=1) |
| return '' |
| |
| |
| def pformat(self, obj): |
| return "\n" + self._pp.pformat(obj) + "\n" |
| |
| |
| def update_ignored_cids(self, ignored_cids): |
| for cid in ignored_cids: |
| for group in self._to_be_tested_cids_groups: |
| if cid in group: |
| group.remove(cid) |
| break |
| else: |
| raise error.TestError('The ignored cid %s is not defined' % cid) |
| self._not_test_cids.append(cid) |
| |
| |
| def read_approved_from_file(self, filename): |
| approved = eval(utils.read_file(filename)) |
| for group in self._to_be_tested_cids_groups + [ self._not_test_cids ]: |
| for cid in group: |
| if cid not in approved: |
| # If we don't have any listing for this type |
| # of part in HWID, it's not required. |
| factory.log('Bypassing unlisted cid %s' % cid) |
| approved[cid] = '*' |
| return approved |
| |
| |
| def select_correct_dbs(self, approved_dbs): |
| os.chdir(self.bindir) |
| id_hwqual = None |
| try: |
| id_hwqual = factory.get_shared_data('part_id_hwqual') |
| except Exception, e: |
| # hardware_Components may run without factory environment |
| factory.log('Failed getting shared data, ignored: %s' % repr(e)) |
| if id_hwqual: |
| # If HwQual ID is already specified, find the list with same ID. |
| id_hwqual = id_hwqual.replace(' ', '_') |
| approved_dbs = 'data_*/components_%s' % id_hwqual |
| else: |
| sample_approved_dbs = 'approved_components.default' |
| if (not glob.glob(approved_dbs)) and glob.glob(sample_approved_dbs): |
| # Fallback to the default (sample) version |
| approved_dbs = sample_approved_dbs |
| factory.log('Using default (sample) approved component list: %s' |
| % sample_approved_dbs) |
| |
| # approved_dbs supports shell-like filename expansion. |
| existing_dbs = glob.glob(approved_dbs) |
| if not existing_dbs: |
| raise error.TestError('Unable to find approved db: %s' % |
| approved_dbs) |
| |
| return existing_dbs |
| |
| |
| def initialize(self): |
| self._gbb = gbb_util.GBBUtility() |
| self._pp = pprint.PrettyPrinter() |
| |
| |
| def run_once(self, approved_dbs='approved_components', ignored_cids=[]): |
| self.update_ignored_cids(ignored_cids) |
| enumerable_system = self.get_all_enumerable_components() |
| pci_system = self.get_all_pci_components() |
| usb_system = self.get_all_usb_components() |
| |
| only_cardreader_failed = False |
| all_failures = 'The following components are not matched.\n' |
| correct_dbs = self.select_correct_dbs(approved_dbs) |
| for db in correct_dbs: |
| self._system = enumerable_system |
| self._failures = {} |
| approved = self.read_approved_from_file(db) |
| factory.log('Approved DB: %s' % self.pformat(approved)) |
| |
| for cid in self._enumerable_cids: |
| self.check_enumerable_component( |
| cid, enumerable_system[cid], approved[cid]) |
| |
| for cid in self._pci_cids: |
| self.check_pci_usb_component(cid, pci_system, approved[cid]) |
| |
| for cid in self._usb_cids: |
| self.check_pci_usb_component(cid, usb_system, approved[cid]) |
| |
| for cid in self._probable_cids: |
| self.check_probable_component(cid, approved[cid]) |
| |
| factory.log('System: %s' % self.pformat(self._system)) |
| |
| outdb = 'system_%s' % os.path.basename(db).replace('approved_', '') |
| outdb = os.path.join(self.resultsdir, outdb) |
| utils.open_write_close(outdb, self.pformat(self._system)) |
| |
| if self._failures: |
| if self._failures.keys() == ['part_id_cardreader']: |
| only_cardreader_failed = True |
| all_failures += 'For DB %s:' % db |
| all_failures += self.pformat(self._failures) |
| else: |
| # If one of DBs is matched, record some data in shared_data. |
| cids_need_to_be_record = ['part_id_hwqual'] |
| try: |
| for cid in cids_need_to_be_record: |
| factory.set_shared_data(cid, approved[cid][0]) |
| except Exception, e: |
| # hardware_Components may run without factory environment |
| factory.log('Failed setting shared data, ignored: %s' % |
| repr(e)) |
| return |
| |
| if only_cardreader_failed: |
| all_failures = ('You may forget to insert an SD card.\n' + |
| all_failures) |
| |
| raise error.TestFail(all_failures) |