OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # | 2 # |
3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 import glob | 7 import glob |
8 import os | 8 import os |
9 import pprint | 9 import pprint |
10 import re | 10 import re |
11 import sys | 11 import sys |
| 12 import threading |
12 | 13 |
13 import flashrom_util | 14 import flashrom_util |
14 import gft_common | 15 import gft_common |
15 import gft_fwhash | 16 import gft_fwhash |
16 import vblock | 17 import vblock |
17 | 18 |
18 from gft_common import DebugMsg, VerboseMsg, WarningMsg, ErrorMsg, ErrorDie | 19 from gft_common import DebugMsg, VerboseMsg, WarningMsg, ErrorMsg, ErrorDie |
19 | 20 |
20 | 21 |
21 def Memorize(f): | 22 def Memorize(f): |
22 """ Decorator for functions that need memorization.""" | 23 return gft_common.ThreadSafe(gft_common.Memorize(f)) |
23 memorize_data = {} | |
24 def memorize_call(*args): | |
25 index = repr(args) | |
26 if index in memorize_data: | |
27 value = memorize_data[index] | |
28 # DebugMsg('Memorize: using cached value for: %s %s' % (repr(f), index)) | |
29 return value | |
30 value = f(*args) | |
31 memorize_data[index] = value | |
32 return value | |
33 return memorize_call | |
34 | 24 |
35 | 25 |
36 class HardwareComponents(object): | 26 class HardwareComponents(object): |
37 """ Hardware Components Scanner """ | 27 """ Hardware Components Scanner """ |
38 | 28 |
39 # Function names in this class are used for reflection, so please don't change | 29 # Function names in this class are used for reflection, so please don't change |
40 # the function names even if they are not comliant to coding style guide. | 30 # the function names even if they are not comliant to coding style guide. |
41 | 31 |
42 version = 5 | 32 version = 6 |
43 | 33 |
44 # We divide all component IDs (cids) into 5 categories: | 34 # We divide all component IDs (cids) into 5 categories: |
45 # - enumerable: able to get the results by running specific commands; | 35 # - enumerable: able to get the results by running specific commands; |
46 # - probable: returns existed or not by given some pre-defined choices; | 36 # - probable: returns existed or not by given some pre-defined choices; |
47 # - pure data: data for some special purpose, can't be tested; | 37 # - pure data: data for some special purpose, can't be tested; |
48 | 38 |
49 _enumerable_cids = [ | 39 _enumerable_cids = [ |
50 'data_display_geometry', | 40 'data_display_geometry', |
51 'hash_ec_firmware', | 41 'hash_ec_firmware', |
52 'hash_ro_firmware', | 42 'hash_ro_firmware', |
(...skipping 28 matching lines...) Expand all Loading... |
81 _probable_cids = [ | 71 _probable_cids = [ |
82 'key_recovery', | 72 'key_recovery', |
83 'key_root', | 73 'key_root', |
84 'part_id_cardreader', | 74 'part_id_cardreader', |
85 ] | 75 ] |
86 _pure_data_cids = [ | 76 _pure_data_cids = [ |
87 'data_bitmap_fv', | 77 'data_bitmap_fv', |
88 'data_recovery_url', | 78 'data_recovery_url', |
89 ] | 79 ] |
90 | 80 |
| 81 # list of cids that should not be fetched asynchronously. |
| 82 _non_async_cids = [ |
| 83 # Reading EC will become very slow and cause inaccurate results if we try to |
| 84 # probe components that also fires EC command at the same time. |
| 85 'part_id_ec_flash_chip', |
| 86 ] |
| 87 |
91 # _not_test_cids and _to_be_tested_cids will be re-created for each match. | 88 # _not_test_cids and _to_be_tested_cids will be re-created for each match. |
92 _not_test_cids = [] | 89 _not_test_cids = [] |
93 _to_be_tested_cids = [] | 90 _to_be_tested_cids = [] |
94 | 91 |
95 # TODO(hungte) unify the 'not available' style messages | 92 # TODO(hungte) unify the 'not available' style messages |
96 _not_present = '' | 93 _not_present = '' |
97 _no_match = 'No match' | 94 _no_match = 'No match' |
98 | 95 |
99 def __init__(self, verbose=False): | 96 def __init__(self, verbose=False): |
100 self._initialized = False | 97 self._initialized = False |
(...skipping 27 matching lines...) Expand all Loading... |
128 @Memorize | 125 @Memorize |
129 def load_module(self, name): | 126 def load_module(self, name): |
130 grep_cmd = ('lsmod 2>/dev/null | grep -q %s' % name) | 127 grep_cmd = ('lsmod 2>/dev/null | grep -q %s' % name) |
131 loaded = (os.system(grep_cmd) == 0) | 128 loaded = (os.system(grep_cmd) == 0) |
132 if not loaded: | 129 if not loaded: |
133 if os.system('modprobe %s >/dev/null 2>&1' % name) != 0: | 130 if os.system('modprobe %s >/dev/null 2>&1' % name) != 0: |
134 ErrorMsg("Cannot load module: %s" % name) | 131 ErrorMsg("Cannot load module: %s" % name) |
135 return loaded | 132 return loaded |
136 | 133 |
137 @Memorize | 134 @Memorize |
| 135 def is_legacy_device_record(self, record): |
| 136 """ Returns if a matching record looks like a legacy device. """ |
| 137 # Current format: [0-9a-f]{4}:[0-9a-f]{4} |
| 138 return True if re.match('[0-9a-f]{4}:[0-9a-f]{4}', record) else False |
| 139 |
| 140 @Memorize |
| 141 def _get_legacy_device_list(self): |
| 142 # pci: cat /proc/bus/pci/devices | cut -f 2 # 0.004s < lspci=0.012s |
| 143 device_list = [] |
| 144 pci_device_file = '/proc/bus/pci/devices' |
| 145 if os.path.exists(pci_device_file): |
| 146 with open(pci_device_file) as handle: |
| 147 pci_list = [data.split('\t', 2)[1] |
| 148 for data in handle.readlines()] |
| 149 device_list += ['%s:%s' % (entry[:4], entry[4:]) |
| 150 for entry in pci_list] |
| 151 else: |
| 152 DebugMsg('Failed to read %s. Execute lspci.' % pci_device_list) |
| 153 pci_list = [entry.split()[2:4] |
| 154 for entry in |
| 155 gft_common.SystemOutput('lspci -n -mm').splitlines()] |
| 156 device_list += ['%s:%s' % (vendor.strip('"'), product.strip('"')) |
| 157 for (vendor, product) in pci_list] |
| 158 # usb: realpath(/sys/bus/usb/devices/*:*)/../id* # 0.05s < lspci=0.078s |
| 159 usb_devs = glob.glob('/sys/bus/usb/devices/*:*') |
| 160 for dev in usb_devs: |
| 161 path = os.path.join(os.path.realpath(dev), '..') |
| 162 device_list += ['%s:%s' % |
| 163 (gft_common.ReadOneLine(os.path.join(path, 'idVendor')), |
| 164 gft_common.ReadOneLine(os.path.join(path, 'idProduct')))] |
| 165 DebugMsg('Legacy device list: ' + ', '.join(device_list)) |
| 166 return device_list |
| 167 |
| 168 @Memorize |
138 def _get_all_connection_info(self): | 169 def _get_all_connection_info(self): |
139 """ Probes available connectivity and device information """ | 170 """ Probes available connectivity and device information """ |
140 connection_info = { | 171 connection_info = { |
141 'wireless': '/sys/class/net/wlan0/device', | 172 'wireless': '/sys/class/net/wlan0/device', |
142 'ethernet': '/sys/class/net/eth0/device', | 173 'ethernet': '/sys/class/net/eth0/device', |
143 '3g': '/sys/class/tty/ttyUSB0/device', | 174 '3g': '/sys/class/tty/ttyUSB0/device', |
144 } | 175 } |
145 flimflam_scripts_base = '/usr/local/lib/flimflam/test' | 176 flimflam_scripts_base = '/usr/local/lib/flimflam/test' |
146 if os.path.exists(flimflam_scripts_base): | 177 if os.path.exists(flimflam_scripts_base): |
147 # TODO(hungte) use flimflam to update the device list. | 178 # TODO(hungte) use flimflam to update the device list. |
(...skipping 478 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
626 def read_approved_from_file(self, filename): | 657 def read_approved_from_file(self, filename): |
627 approved = gft_common.LoadComponentsDatabaseFile(filename) | 658 approved = gft_common.LoadComponentsDatabaseFile(filename) |
628 for cid in self._to_be_tested_cids + self._not_test_cids: | 659 for cid in self._to_be_tested_cids + self._not_test_cids: |
629 if cid not in approved: | 660 if cid not in approved: |
630 # If we don't have any listing for this type | 661 # If we don't have any listing for this type |
631 # of part in HWID, it's not required. | 662 # of part in HWID, it's not required. |
632 WarningMsg('gft_hwcomp: Bypassing unlisted cid %s' % cid) | 663 WarningMsg('gft_hwcomp: Bypassing unlisted cid %s' % cid) |
633 approved[cid] = '*' | 664 approved[cid] = '*' |
634 return approved | 665 return approved |
635 | 666 |
636 def get_all_enumerable_components(self): | 667 def get_all_enumerable_components(self, async): |
637 results = {} | 668 results = {} |
638 for cid in self._enumerable_cids: | 669 thread_pools = [] |
| 670 |
| 671 def fetch_enumerable_component(cid): |
| 672 """ Fetch an enumerable component and update the results """ |
639 if self._verbose: | 673 if self._verbose: |
640 sys.stdout.flush() | 674 sys.stdout.flush() |
641 sys.stderr.write('<Fetching property %s>\n' % cid) | 675 sys.stderr.write('<Fetching property %s>\n' % cid) |
642 components = self.force_get_property('get_' + cid) | 676 components = self.force_get_property('get_' + cid) |
643 if not isinstance(components, list): | 677 if not isinstance(components, list): |
644 components = [components] | 678 components = [components] |
645 results[cid] = components | 679 results[cid] = components |
| 680 |
| 681 class FetchThread(threading.Thread): |
| 682 """ Thread object for parallel enumerating """ |
| 683 def __init__(self, cid): |
| 684 threading.Thread.__init__(self) |
| 685 self.cid = cid |
| 686 |
| 687 def run(self): |
| 688 fetch_enumerable_component(self.cid) |
| 689 |
| 690 for cid in self._enumerable_cids: |
| 691 if async and cid not in self._non_async_cids: |
| 692 thread_pools.append(FetchThread(cid)) |
| 693 else: |
| 694 fetch_enumerable_component(cid) |
| 695 |
| 696 # Complete the threads |
| 697 for thread in thread_pools: |
| 698 thread.start() |
| 699 for thread in thread_pools: |
| 700 thread.join() |
646 return results | 701 return results |
647 | 702 |
648 def check_enumerable_component(self, cid, exact_values, approved_values): | 703 def check_enumerable_component(self, cid, exact_values, approved_values): |
649 if '*' in approved_values: | 704 if '*' in approved_values: |
650 return | 705 return |
651 | 706 |
652 for value in exact_values: | 707 unmatched = [value for value in exact_values |
653 if value not in approved_values: | 708 if value not in approved_values] |
654 if cid in self._failures: | 709 if not unmatched: |
655 self._failures[cid].append(value) | 710 return |
656 else: | 711 |
657 self._failures[cid] = [value] | 712 # there's some error, let's try to match them in legacy list |
| 713 legacy_approved = filter(self.is_legacy_device_record, approved_values) |
| 714 if set(legacy_approved) == set(approved_values): |
| 715 DebugMsg('Start legacy search for cid: ' + cid) |
| 716 # safe for legacy match |
| 717 # TODO(hungte) prefetch this list in async batch process. |
| 718 legacy_list = self._get_legacy_device_list() |
| 719 matched = list(set(legacy_list).intersection(set(approved_values))) |
| 720 if matched: |
| 721 DebugMsg('Changed detected list: %s->%s' % (self._system[cid], matched)) |
| 722 self._system[cid] = matched |
| 723 return |
| 724 # update with remaining error. |
| 725 self._failures[cid] = unmatched |
658 | 726 |
659 @Memorize | 727 @Memorize |
660 def verify_probable_component(self, cid, approved_values): | 728 def verify_probable_component(self, cid, approved_values): |
661 if '*' in approved_values: | 729 if '*' in approved_values: |
662 return (True, ['*']) | 730 return (True, ['*']) |
663 | 731 |
664 for value in approved_values: | 732 for value in approved_values: |
665 present = getattr(self, 'probe_' + cid)(value) | 733 present = getattr(self, 'probe_' + cid)(value) |
666 if present: | 734 if present: |
667 return (True, [value]) | 735 return (True, [value]) |
668 return (False, [self._no_match]) | 736 return (False, [self._no_match]) |
669 | 737 |
670 def check_probable_component(self, cid, approved_values): | 738 def check_probable_component(self, cid, approved_values): |
671 (probed, value) = self.verify_probable_component(cid, approved_values) | 739 (probed, value) = self.verify_probable_component(cid, approved_values) |
672 if probed: | 740 if probed: |
673 self._system[cid] = value | 741 self._system[cid] = value |
674 else: | 742 else: |
675 self._failures[cid] = value | 743 self._failures[cid] = value |
676 | 744 |
677 def pformat(self, obj): | 745 def pformat(self, obj): |
678 return '\n' + self._pp.pformat(obj) + '\n' | 746 return '\n' + self._pp.pformat(obj) + '\n' |
679 | 747 |
680 def initialize(self, force=False): | 748 def initialize(self, force=False, async=False): |
681 if self._initialized and not force: | 749 if self._initialized and not force: |
682 return | 750 return |
683 # probe current system components | 751 # probe current system components |
684 DebugMsg('Starting to detect system components...') | 752 DebugMsg('Starting to detect system components...') |
685 self._enumerable_system = self.get_all_enumerable_components() | 753 self._enumerable_system = self.get_all_enumerable_components(async) |
686 self._initialized = True | 754 self._initialized = True |
687 | 755 |
688 def match_current_system(self, filename, ignored_cids=[]): | 756 def match_current_system(self, filename, ignored_cids=[]): |
689 """ Matches a given component list to current system. | 757 """ Matches a given component list to current system. |
690 Returns: (current, failures) | 758 Returns: (current, failures) |
691 """ | 759 """ |
692 | 760 |
693 # assert self._initialized, 'Not initialized.' | 761 # assert self._initialized, 'Not initialized.' |
694 self._to_be_tested_cids = (self._enumerable_cids + | 762 self._to_be_tested_cids = (self._enumerable_cids + |
695 self._probable_cids) | 763 self._probable_cids) |
(...skipping 18 matching lines...) Expand all Loading... |
714 else: | 782 else: |
715 self.check_probable_component(cid, approved[cid]) | 783 self.check_probable_component(cid, approved[cid]) |
716 | 784 |
717 return (self._system, self._failures) | 785 return (self._system, self._failures) |
718 | 786 |
719 | 787 |
720 ############################################################################# | 788 ############################################################################# |
721 # Console main entry | 789 # Console main entry |
722 @gft_common.GFTConsole | 790 @gft_common.GFTConsole |
723 def _main(self_path, args): | 791 def _main(self_path, args): |
724 if not args: | 792 do_async = True |
725 print 'Usage: %s components_db_files...\n' % self_path | 793 |
726 sys.exit(1) | 794 # preprocess args |
| 795 compdb_list = [] |
| 796 for arg in args: |
| 797 if arg == '--sync': |
| 798 do_async = False |
| 799 elif arg == '--async': |
| 800 do_async = True |
| 801 elif not os.path.exists(arg): |
| 802 print 'ERROR: unknown parameter: ' + arg |
| 803 print 'Usage: %s [--sync|--async] [components_db_files...]\n' % self_path |
| 804 sys.exit(1) |
| 805 else: |
| 806 compdb_list.append(arg) |
| 807 |
727 components = HardwareComponents(verbose=True) | 808 components = HardwareComponents(verbose=True) |
728 print 'Starting to probe system...' | 809 print 'Starting to detect%s...' % (' asynchrounously' if do_async else '') |
729 components.initialize() | 810 components.initialize(async=do_async) |
| 811 |
| 812 if not compdb_list: |
| 813 print 'Enumerable properties:' |
| 814 print components.pformat(components._enumerable_system) |
| 815 sys.exit(0) |
| 816 |
730 print 'Starting to match system...' | 817 print 'Starting to match system...' |
731 | 818 for arg in compdb_list: |
732 for arg in args: | |
733 (matched, failures) = components.match_current_system(arg) | 819 (matched, failures) = components.match_current_system(arg) |
734 print 'Probed (%s):' % arg | 820 print 'Probed (%s):' % arg |
735 print components.pformat(matched) | 821 print components.pformat(matched) |
736 print 'Failures (%s):' % arg | 822 print 'Failures (%s):' % arg |
737 print components.pformat(failures) | 823 print components.pformat(failures) |
738 | 824 |
739 if __name__ == '__main__': | 825 if __name__ == '__main__': |
740 _main(sys.argv[0], sys.argv[1:]) | 826 _main(sys.argv[0], sys.argv[1:]) |
OLD | NEW |