1 # Copyright 2016 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 """This class defines the CrosHost Label class.""" 6 7 import logging 8 import os 9 import re 10 11 import common 12 13 from autotest_lib.client.bin import utils 14 from autotest_lib.client.common_lib import global_config 15 from autotest_lib.client.cros.audio import cras_utils 16 from autotest_lib.client.cros.video import constants as video_test_constants 17 from autotest_lib.server.cros.dynamic_suite import constants as ds_constants 18 from autotest_lib.server.hosts import base_label 19 from autotest_lib.server.hosts import common_label 20 from autotest_lib.server.hosts import servo_host 21 from autotest_lib.site_utils import hwid_lib 22 23 # pylint: disable=missing-docstring 24 25 class BoardLabel(base_label.StringPrefixLabel): 26 """Determine the correct board label for the device.""" 27 28 _NAME = ds_constants.BOARD_PREFIX.rstrip(':') 29 30 def generate_labels(self, host): 31 # We only want to apply the board labels once, which is when they get 32 # added to the AFE. That way we don't have to worry about the board 33 # label switching on us if the wrong builds get put on the devices. 34 # crbug.com/624207 records one event of the board label switching 35 # unexpectedly on us. 36 for label in host._afe_host.labels: 37 if label.startswith(self._NAME + ':'): 38 return [label.split(':')[-1]] 39 40 # TODO(kevcheng): for now this will dup the code in CrosHost and a 41 # separate cl will refactor the get_board in CrosHost to just return the 42 # board without the BOARD_PREFIX and all the other callers will be 43 # updated to not need to clear it out and this code will be replaced to 44 # just call the host's get_board() method. 45 release_info = utils.parse_cmd_output('cat /etc/lsb-release', 46 run_method=host.run) 47 return [release_info['CHROMEOS_RELEASE_BOARD']] 48 49 50 class LightSensorLabel(base_label.BaseLabel): 51 """Label indicating if a light sensor is detected.""" 52 53 _NAME = 'lightsensor' 54 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices' 55 _LIGHTSENSOR_FILES = [ 56 "in_illuminance0_input", 57 "in_illuminance_input", 58 "in_illuminance0_raw", 59 "in_illuminance_raw", 60 "illuminance0_input", 61 ] 62 63 def exists(self, host): 64 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % ( 65 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES)) 66 # Run the search cmd following the symlinks. Stderr_tee is set to 67 # None as there can be a symlink loop, but the command will still 68 # execute correctly with a few messages printed to stderr. 69 result = host.run(search_cmd, stdout_tee=None, stderr_tee=None, 70 ignore_status=True) 71 72 return result.exit_status == 0 73 74 75 class BluetoothLabel(base_label.BaseLabel): 76 """Label indicating if bluetooth is detected.""" 77 78 _NAME = 'bluetooth' 79 80 def exists(self, host): 81 result = host.run('test -d /sys/class/bluetooth/hci0', 82 ignore_status=True) 83 84 return result.exit_status == 0 85 86 87 class ECLabel(base_label.BaseLabel): 88 """Label to determine the type of EC on this host.""" 89 90 _NAME = 'ec:cros' 91 92 def exists(self, host): 93 cmd = 'mosys ec info' 94 # The output should look like these, so that the last field should 95 # match our EC version scheme: 96 # 97 # stm | stm32f100 | snow_v1.3.139-375eb9f 98 # ti | Unknown-10de | peppy_v1.5.114-5d52788 99 # 100 # Non-Chrome OS ECs will look like these: 101 # 102 # ENE | KB932 | 00BE107A00 103 # ite | it8518 | 3.08 104 # 105 # And some systems don't have ECs at all (Lumpy, for example). 106 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$' 107 108 ecinfo = host.run(command=cmd, ignore_status=True) 109 if ecinfo.exit_status == 0: 110 res = re.search(regexp, ecinfo.stdout) 111 if res: 112 logging.info("EC version is %s", res.groups()[0]) 113 return True 114 logging.info("%s got: %s", cmd, ecinfo.stdout) 115 # Has an EC, but it's not a Chrome OS EC 116 logging.info("%s exited with status %d", cmd, ecinfo.exit_status) 117 return False 118 119 120 class AccelsLabel(base_label.BaseLabel): 121 """Determine the type of accelerometers on this host.""" 122 123 _NAME = 'accel:cros-ec' 124 125 def exists(self, host): 126 # Check to make sure we have ectool 127 rv = host.run('which ectool', ignore_status=True) 128 if rv.exit_status: 129 logging.info("No ectool cmd found; assuming no EC accelerometers") 130 return False 131 132 # Check that the EC supports the motionsense command 133 rv = host.run('ectool motionsense', ignore_status=True) 134 if rv.exit_status: 135 logging.info("EC does not support motionsense command; " 136 "assuming no EC accelerometers") 137 return False 138 139 # Check that EC motion sensors are active 140 active = host.run('ectool motionsense active').stdout.split('\n') 141 if active[0] == "0": 142 logging.info("Motion sense inactive; assuming no EC accelerometers") 143 return False 144 145 logging.info("EC accelerometers found") 146 return True 147 148 149 class ChameleonLabel(base_label.BaseLabel): 150 """Determine if a Chameleon is connected to this host.""" 151 152 _NAME = 'chameleon' 153 154 def exists(self, host): 155 return host._chameleon_host is not None 156 157 158 class ChameleonConnectionLabel(base_label.StringPrefixLabel): 159 """Return the Chameleon connection label.""" 160 161 _NAME = 'chameleon' 162 163 def exists(self, host): 164 return host._chameleon_host is not None 165 166 167 def generate_labels(self, host): 168 return [host.chameleon.get_label()] 169 170 171 class ChameleonPeripheralsLabel(base_label.StringPrefixLabel): 172 """Return the Chameleon peripherals labels. 173 174 The 'chameleon:bt_hid' label is applied if the bluetooth 175 classic hid device, i.e, RN-42 emulation kit, is detected. 176 177 Any peripherals plugged into the chameleon board would be 178 detected and applied proper labels in this class. 179 """ 180 181 _NAME = 'chameleon' 182 183 def exists(self, host): 184 return host._chameleon_host is not None 185 186 187 def generate_labels(self, host): 188 bt_hid_device = host.chameleon.get_bluetooh_hid_mouse() 189 return ['bt_hid'] if bt_hid_device.CheckSerialConnection() else [] 190 191 192 class AudioLoopbackDongleLabel(base_label.BaseLabel): 193 """Return the label if an audio loopback dongle is plugged in.""" 194 195 _NAME = 'audio_loopback_dongle' 196 197 def exists(self, host): 198 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(), 199 ignore_status=True).stdout 200 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and 201 cras_utils.node_type_is_plugged('MIC', nodes_info)): 202 return True 203 return False 204 205 206 class PowerSupplyLabel(base_label.StringPrefixLabel): 207 """ 208 Return the label describing the power supply type. 209 210 Labels representing this host's power supply. 211 * `power:battery` when the device has a battery intended for 212 extended use 213 * `power:AC_primary` when the device has a battery not intended 214 for extended use (for moving the machine, etc) 215 * `power:AC_only` when the device has no battery at all. 216 """ 217 218 _NAME = 'power' 219 220 def __init__(self): 221 self.psu_cmd_result = None 222 223 224 def exists(self, host): 225 self.psu_cmd_result = host.run(command='mosys psu type', 226 ignore_status=True) 227 return self.psu_cmd_result.stdout.strip() != 'unknown' 228 229 230 def generate_labels(self, host): 231 if self.psu_cmd_result.exit_status: 232 # The psu command for mosys is not included for all platforms. The 233 # assumption is that the device will have a battery if the command 234 # is not found. 235 return ['battery'] 236 return [self.psu_cmd_result.stdout.strip()] 237 238 239 class StorageLabel(base_label.StringPrefixLabel): 240 """ 241 Return the label describing the storage type. 242 243 Determine if the internal device is SCSI or dw_mmc device. 244 Then check that it is SSD or HDD or eMMC or something else. 245 246 Labels representing this host's internal device type: 247 * `storage:ssd` when internal device is solid state drive 248 * `storage:hdd` when internal device is hard disk drive 249 * `storage:mmc` when internal device is mmc drive 250 * None When internal device is something else or 251 when we are unable to determine the type 252 """ 253 254 _NAME = 'storage' 255 256 def __init__(self): 257 self.type_str = '' 258 259 260 def exists(self, host): 261 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi 262 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;', 263 '. /usr/share/misc/chromeos-common.sh;', 264 'load_base_vars;', 265 'get_fixed_dst_drive']) 266 rootdev = host.run(command=rootdev_cmd, ignore_status=True) 267 if rootdev.exit_status: 268 logging.info("Fail to run %s", rootdev_cmd) 269 return False 270 rootdev_str = rootdev.stdout.strip() 271 272 if not rootdev_str: 273 return False 274 275 rootdev_base = os.path.basename(rootdev_str) 276 277 mmc_pattern = '/dev/mmcblk[0-9]' 278 if re.match(mmc_pattern, rootdev_str): 279 # Use type to determine if the internal device is eMMC or somthing 280 # else. We can assume that MMC is always an internal device. 281 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base 282 type = host.run(command=type_cmd, ignore_status=True) 283 if type.exit_status: 284 logging.info("Fail to run %s", type_cmd) 285 return False 286 type_str = type.stdout.strip() 287 288 if type_str == 'MMC': 289 self.type_str = 'mmc' 290 return True 291 292 scsi_pattern = '/dev/sd[a-z]+' 293 if re.match(scsi_pattern, rootdev.stdout): 294 # Read symlink for /sys/block/sd* to determine if the internal 295 # device is connected via ata or usb. 296 link_cmd = 'readlink /sys/block/%s' % rootdev_base 297 link = host.run(command=link_cmd, ignore_status=True) 298 if link.exit_status: 299 logging.info("Fail to run %s", link_cmd) 300 return False 301 link_str = link.stdout.strip() 302 if 'usb' in link_str: 303 return False 304 305 # Read rotation to determine if the internal device is ssd or hdd. 306 rotate_cmd = str('cat /sys/block/%s/queue/rotational' 307 % rootdev_base) 308 rotate = host.run(command=rotate_cmd, ignore_status=True) 309 if rotate.exit_status: 310 logging.info("Fail to run %s", rotate_cmd) 311 return False 312 rotate_str = rotate.stdout.strip() 313 314 rotate_dict = {'0':'ssd', '1':'hdd'} 315 self.type_str = rotate_dict.get(rotate_str) 316 return True 317 318 # All other internal device / error case will always fall here 319 return False 320 321 322 def generate_labels(self, host): 323 return [self.type_str] 324 325 326 class ServoLabel(base_label.BaseLabel): 327 """Label to apply if a servo is present.""" 328 329 _NAME = 'servo' 330 331 def exists(self, host): 332 """ 333 Check if the servo label should apply to the host or not. 334 335 @returns True if a servo host is detected, False otherwise. 336 """ 337 servo_args, _ = servo_host._get_standard_servo_args(host) 338 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR) 339 return (servo_host_hostname is not None 340 and servo_host.servo_host_is_up(servo_host_hostname)) 341 342 343 class VideoLabel(base_label.StringLabel): 344 """Labels detailing video capabilities.""" 345 346 # List gathered from 347 # https://chromium.googlesource.com/chromiumos/ 348 # platform2/+/master/avtest_label_detect/main.c#19 349 _NAME = [ 350 'hw_jpeg_acc_dec', 351 'hw_video_acc_h264', 352 'hw_video_acc_vp8', 353 'hw_video_acc_vp9', 354 'hw_video_acc_enc_h264', 355 'hw_video_acc_enc_vp8', 356 'webcam', 357 ] 358 359 def generate_labels(self, host): 360 result = host.run('/usr/local/bin/avtest_label_detect', 361 ignore_status=True).stdout 362 return re.findall('^Detected label: (\w+)$', result, re.M) 363 364 365 class CTSArchLabel(base_label.StringLabel): 366 """Labels to determine CTS abi.""" 367 368 _NAME = ['cts_abi_arm', 'cts_abi_x86'] 369 370 def _get_cts_abis(self, host): 371 """Return supported CTS ABIs. 372 373 @return List of supported CTS bundle ABIs. 374 """ 375 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']} 376 return cts_abis.get(host.get_cpu_arch(), []) 377 378 379 def generate_labels(self, host): 380 return ['cts_abi_' + abi for abi in self._get_cts_abis(host)] 381 382 383 class ArcLabel(base_label.BaseLabel): 384 """Label indicates if host has ARC support.""" 385 386 _NAME = 'arc' 387 388 @base_label.forever_exists_decorate 389 def exists(self, host): 390 return 0 == host.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release', 391 ignore_status=True).exit_status 392 393 394 class VideoGlitchLabel(base_label.BaseLabel): 395 """Label indicates if host supports video glitch detection tests.""" 396 397 _NAME = 'video_glitch_detection' 398 399 def exists(self, host): 400 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '') 401 402 return board in video_test_constants.SUPPORTED_BOARDS 403 404 405 class InternalDisplayLabel(base_label.StringLabel): 406 """Label that determines if the device has an internal display.""" 407 408 _NAME = 'internal_display' 409 410 def generate_labels(self, host): 411 from autotest_lib.client.cros.graphics import graphics_utils 412 from autotest_lib.client.common_lib import utils as common_utils 413 414 def __system_output(cmd): 415 return host.run(cmd).stdout 416 417 def __read_file(remote_path): 418 return host.run('cat %s' % remote_path).stdout 419 420 # Hijack the necessary client functions so that we can take advantage 421 # of the client lib here. 422 # FIXME: find a less hacky way than this 423 original_system_output = utils.system_output 424 original_read_file = common_utils.read_file 425 utils.system_output = __system_output 426 common_utils.read_file = __read_file 427 try: 428 return ([self._NAME] 429 if graphics_utils.has_internal_display() 430 else []) 431 finally: 432 utils.system_output = original_system_output 433 common_utils.read_file = original_read_file 434 435 436 class LucidSleepLabel(base_label.BaseLabel): 437 """Label that determines if device has support for lucid sleep.""" 438 439 # TODO(kevcheng): See if we can determine if this label is applicable a 440 # better way (crbug.com/592146). 441 _NAME = 'lucidsleep' 442 LUCID_SLEEP_BOARDS = ['samus', 'lulu'] 443 444 def exists(self, host): 445 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '') 446 return board in self.LUCID_SLEEP_BOARDS 447 448 449 class HWIDLabel(base_label.StringLabel): 450 """Return all the labels generated from the hwid.""" 451 452 # We leave out _NAME because hwid_lib will generate everything for us. 453 454 def __init__(self): 455 # Grab the key file needed to access the hwid service. 456 self.key_file = global_config.global_config.get_config_value( 457 'CROS', 'HWID_KEY', type=str) 458 459 460 def generate_labels(self, host): 461 hwid_labels = [] 462 hwid = host.run_output('crossystem hwid').strip() 463 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL, 464 self.key_file).get('labels', []) 465 466 for hwid_info in hwid_info_list: 467 # If it's a prefix, we'll have: 468 # {'name': prefix_label, 'value': postfix_label} and create 469 # 'prefix_label:postfix_label'; otherwise it'll just be 470 # {'name': label} which should just be 'label'. 471 value = hwid_info.get('value', '') 472 name = hwid_info.get('name', '') 473 # There should always be a name but just in case there is not. 474 if name: 475 hwid_labels.append(name if not value else 476 '%s:%s' % (name, value)) 477 return hwid_labels 478 479 480 def get_all_labels(self): 481 """We need to try all labels as a prefix and as standalone. 482 483 We don't know for sure which labels are prefix labels and which are 484 standalone so we try all of them as both. 485 """ 486 all_hwid_labels = [] 487 try: 488 all_hwid_labels = hwid_lib.get_all_possible_dut_labels( 489 self.key_file) 490 except IOError: 491 logging.error('Can not open key file: %s', self.key_file) 492 except hwid_lib.HwIdException as e: 493 logging.error('hwid service: %s', e) 494 return all_hwid_labels, all_hwid_labels 495 496 497 CROS_LABELS = [ 498 AccelsLabel(), 499 ArcLabel(), 500 AudioLoopbackDongleLabel(), 501 BluetoothLabel(), 502 BoardLabel(), 503 ChameleonConnectionLabel(), 504 ChameleonLabel(), 505 ChameleonPeripheralsLabel(), 506 common_label.OSLabel(), 507 CTSArchLabel(), 508 ECLabel(), 509 HWIDLabel(), 510 InternalDisplayLabel(), 511 LightSensorLabel(), 512 LucidSleepLabel(), 513 PowerSupplyLabel(), 514 ServoLabel(), 515 StorageLabel(), 516 VideoGlitchLabel(), 517 VideoLabel(), 518 ] 519