| # Copyright (c) 2011 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. |
| # |
| # Expects to be run in an environment with sudo and no interactive password |
| # prompt, such as within the Chromium OS development chroot. |
| |
| |
| import logging |
| import time |
| import xmlrpclib |
| import subprocess |
| |
| |
| class Servo: |
| """Manages control of a Servo board. |
| |
| Servo is a board developed by hardware group to aide in the debug and |
| control of various partner devices. Servo's features include the simulation |
| of pressing the power button, closing the lid, and pressing Ctrl-d. This |
| class manages setting up and communicating with a servo demon (servod) |
| process. It provides both high-level functions for common servo tasks and |
| low-level functions for directly setting and reading gpios. |
| """ |
| |
| _server = None |
| _servod = None |
| |
| # Power button press delays in seconds. |
| LONG_DELAY = 8 |
| SHORT_DELAY = 0.1 |
| NORMAL_TRANSITION_DELAY = 1.2 |
| |
| # Delays to deal with computer transitions. |
| SLEEP_DELAY = 6 |
| BOOT_DELAY = 10 |
| |
| # Servo-specific delays. |
| MAX_SERVO_STARTUP_DELAY = 10 |
| SERVO_SEND_SIGNAL_DELAY = 0.5 |
| |
| def __init__(self, servo_port, xml_config='servo.xml', servo_vid=None, |
| servo_pid=None, servo_serial=None, cold_reset=False): |
| """Sets up the servo communication infrastructure. |
| |
| Args: |
| servo_port: Port the servod process should listen on. |
| xml_config: Configuration XML file for servod. |
| servo_vid: USB vendor id of servo. |
| servo_pid: USB product id of servo. |
| servo_serial: USB serial id in device descriptor to host to |
| distinguish and control multiple servos. Note servo's EEPROM must |
| be programmed to use this feature. |
| cold_reset: If True, cold reset device and boot during init, |
| otherwise perform init with device running. |
| """ |
| # launch servod |
| self._launch_servod(servo_port, xml_config, servo_vid, servo_pid, |
| servo_serial) |
| |
| |
| # connect to servod |
| assert servo_port |
| |
| self._do_cold_reset = cold_reset |
| |
| self._connect_servod(servo_port) |
| |
| |
| def initialize_dut(self): |
| """Initializes a dut for testing purposes.""" |
| if self._do_cold_reset: |
| self._init_seq_cold_reset_devmode() |
| else: |
| self._init_seq() |
| |
| |
| def power_long_press(self): |
| """Simulate a long power button press.""" |
| self.power_key(Servo.LONG_DELAY) |
| |
| |
| def power_normal_press(self): |
| """Simulate a normal power button press.""" |
| self.power_key() |
| |
| |
| def power_short_press(self): |
| """Simulate a short power button press.""" |
| self.power_key(Servo.SHORT_DELAY) |
| |
| |
| def power_key(self, secs=NORMAL_TRANSITION_DELAY): |
| """Simulate a power button press. |
| |
| Args: |
| secs: Time in seconds to simulate the keypress. |
| """ |
| self.set_nocheck('pwr_button', 'press') |
| time.sleep(secs) |
| self.set('pwr_button', 'release') |
| |
| |
| def lid_open(self): |
| """Simulate opening the lid.""" |
| self.set_nocheck('lid_open', 'yes') |
| |
| |
| def lid_close(self): |
| """Simulate closing the lid. |
| |
| Waits 6 seconds to ensure the device is fully asleep before returning. |
| """ |
| self.set_nocheck('lid_open', 'no') |
| time.sleep(Servo.SLEEP_DELAY) |
| |
| |
| def ctrl_d(self): |
| """Simulate Ctrl-d simultaneous button presses.""" |
| self.set_nocheck('kbd_en', 'on') |
| self.set_nocheck('kbd_m1', 'r2_c2') |
| self.set_nocheck('kbd_m2', 'r1_c1') |
| time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY) |
| self.set_nocheck('kbd_en', 'off') |
| |
| |
| def enter_key(self): |
| """Simulate Enter key button press.""" |
| self.set_nocheck('kbd_en', 'on') |
| self.set_nocheck('kbd_m1', 'r3_c2') |
| time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY) |
| self.set_nocheck('kbd_en', 'off') |
| |
| |
| def refresh_key(self): |
| """Simulate Refresh key (F3) button press.""" |
| self.set_nocheck('kbd_en', 'on') |
| self.set_nocheck('kbd_m2', 'r2_c1') |
| time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY) |
| self.set_nocheck('kbd_en', 'off') |
| |
| |
| def imaginary_key(self): |
| """Simulate imaginary key button press. |
| |
| Maps to a key that doesn't physically exist. |
| """ |
| self.set_nocheck('kbd_en', 'on') |
| self.set_nocheck('kbd_m2', 'r3_c1') |
| time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY) |
| self.set_nocheck('kbd_en', 'off') |
| |
| |
| def enable_recovery_mode(self): |
| """Enable recovery mode on device.""" |
| self.set('rec_mode', 'on') |
| |
| |
| def disable_recovery_mode(self): |
| """Disable recovery mode on device.""" |
| self.set('rec_mode', 'off') |
| |
| |
| def enable_development_mode(self): |
| """Enable development mode on device.""" |
| self.set('dev_mode', 'on') |
| |
| |
| def disable_development_mode(self): |
| """Disable development mode on device.""" |
| self.set('dev_mode', 'off') |
| |
| |
| def boot_devmode(self): |
| """Boot a dev-mode device that is powered off.""" |
| self.power_normal_press() |
| self.pass_devmode() |
| |
| |
| def pass_devmode(self): |
| """Pass through boot screens in dev-mode.""" |
| time.sleep(Servo.BOOT_DELAY) |
| self.ctrl_d() |
| time.sleep(Servo.BOOT_DELAY) |
| |
| |
| def cold_reset(self): |
| """Perform a cold reset of the EC. |
| |
| Has the side effect of shutting off the device. |
| """ |
| self.set('cold_reset', 'on') |
| time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY) |
| self.set('cold_reset', 'off') |
| |
| |
| def warm_reset(self): |
| """Perform a warm reset of the device. |
| |
| Has the side effect of restarting the device. |
| """ |
| self.set('warm_reset', 'on') |
| time.sleep(Servo.SERVO_SEND_SIGNAL_DELAY) |
| self.set('warm_reset', 'off') |
| |
| |
| def get(self, gpio_name): |
| """Get the value of a gpio from Servod.""" |
| assert gpio_name |
| return self._server.get(gpio_name) |
| |
| |
| def set(self, gpio_name, gpio_value): |
| """Set and check the value of a gpio using Servod.""" |
| self.set_nocheck(gpio_name, gpio_value) |
| assert gpio_value == self.get(gpio_name), \ |
| 'Servo failed to set %s to %s' % (gpio_name, gpio_value) |
| |
| |
| def set_nocheck(self, gpio_name, gpio_value): |
| """Set the value of a gpio using Servod.""" |
| assert gpio_name and gpio_value |
| logging.info('Setting %s to %s' % (gpio_name, gpio_value)) |
| self._server.set(gpio_name, gpio_value) |
| |
| |
| def _init_seq_cold_reset_devmode(self): |
| """Cold reset, init device, and boot in dev-mode.""" |
| self.cold_reset() |
| self._init_seq() |
| self.set('dev_mode', 'on') |
| self.boot_devmode() |
| |
| |
| def __del__(self): |
| """Kill the Servod process.""" |
| assert self._servod |
| # kill servod one way or another |
| try: |
| # won't work without superuser privileges |
| self._servod.terminate() |
| except: |
| # should work without superuser privileges |
| assert subprocess.call(['sudo', 'kill', str(self._servod.pid)]) |
| |
| |
| def _launch_servod(self, servo_port, xml_config, servo_vid, servo_pid, |
| servo_serial): |
| """Launch the servod process. |
| |
| Args: |
| servo_port: Port to start servod listening on. |
| xml_config: XML configuration file for servod. |
| servo_vid: USB vendor id of servo. |
| servo_pid: USB product id of servo. |
| servo_serial: USB serial id in device descriptor to host to |
| distinguish and control multiple servos. Note servo's EEPROM must |
| be programmed to use this feature. |
| """ |
| cmdlist = ['sudo', 'servod', '-c', str(xml_config), '--host=localhost', |
| '--port=' + str(servo_port)] |
| if servo_vid is not None: |
| cmdlist.append('--vendor=%s' % str(servo_vid)) |
| if servo_pid is not None: |
| cmdlist.append('--product=%s' % str(servo_pid)) |
| if servo_serial is not None: |
| cmdlist.append('--serialname=%s' % str(servo_serial)) |
| logging.info('starting servod w/ cmd :: %s' % ' '.join(cmdlist)) |
| self._servod = subprocess.Popen(cmdlist, 0, None, None, None, |
| subprocess.PIPE) |
| # wait for servod to initialize |
| timeout = Servo.MAX_SERVO_STARTUP_DELAY |
| while ('Listening' not in self._servod.stderr.readline() and |
| self._servod.returncode is None and timeout > 0): |
| time.sleep(1) |
| timeout -= 1 |
| assert self._servod.returncode is None and timeout |
| |
| |
| def _init_seq(self): |
| """Initiate starting state for servo.""" |
| self.set('tx_dir', 'input') |
| self.set_nocheck('servo_dut_tx', 'off') |
| self.set('lid_open', 'yes') |
| self.set('rec_mode', 'off') |
| |
| |
| def _connect_servod(self, servo_port=''): |
| """Connect to the Servod process with XMLRPC. |
| |
| Args: |
| servo_port: Port the Servod process is listening on. |
| """ |
| remote = 'http://localhost:%s' % servo_port |
| self._server = xmlrpclib.ServerProxy(remote) |