blob: a385fe678076e68f4c96d32f09b3dc16dadb8cb0 [file] [log] [blame]
Simran Basia9f41032012-05-11 14:21:58 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Todd Broche505b8d2011-03-21 18:19:54 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Servo Server."""
Kevin Cheng5595b342016-09-29 15:51:01 -07005import contextlib
Kevin Chengc49494e2016-07-25 12:13:38 -07006import datetime
7import fcntl
Simran Basia9f41032012-05-11 14:21:58 -07008import fnmatch
Todd Broche505b8d2011-03-21 18:19:54 -07009import logging
Simran Basia9f41032012-05-11 14:21:58 -070010import os
Kevin Chengc49494e2016-07-25 12:13:38 -070011import random
Aseda Aboagye1d8477b2017-05-10 17:24:31 -070012import re
Simran Basia9f41032012-05-11 14:21:58 -070013import shutil
Todd Broche505b8d2011-03-21 18:19:54 -070014import SimpleXMLRPCServer
Simran Basia9f41032012-05-11 14:21:58 -070015import subprocess
16import tempfile
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +080017import threading
Todd Broch7a91c252012-02-03 12:37:45 -080018import time
Simran Basia9f41032012-05-11 14:21:58 -070019import urllib
Wai-Hong Tam1f9e9a72017-05-02 14:14:46 -070020import usb
Todd Broche505b8d2011-03-21 18:19:54 -070021
Wai-Hong Tam4c09eff2017-02-17 11:46:19 -080022import drv as servo_drv
Aaron.Chuang88eff332014-07-31 08:32:00 +080023import bbadc
Simran Basia9ad25e2013-04-23 11:57:00 -070024import bbi2c
Simran Basi5492bde2013-05-16 17:08:47 -070025import bbgpio
Simran Basi949309b2013-05-31 15:12:15 -070026import bbuart
Aseda Aboagyea4922212015-11-20 15:19:08 -080027import ec3po_interface
Todd Broche505b8d2011-03-21 18:19:54 -070028import ftdigpio
29import ftdii2c
Todd Brochdbb09982011-10-02 07:14:26 -070030import ftdi_common
Todd Broch47c43f42011-05-26 15:11:31 -070031import ftdiuart
Rong Changc6c8c022014-08-11 14:07:11 +080032import i2cbus
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -080033import keyboard_handlers
Simran Basie750a342013-03-12 13:45:26 -070034import servo_interfaces
Kevin Cheng16304d12016-07-08 11:56:55 -070035import servo_postinit
Nick Sanders97bc4462016-01-04 15:37:31 -080036import stm32gpio
37import stm32i2c
38import stm32uart
Todd Broche505b8d2011-03-21 18:19:54 -070039
Wai-Hong Tam4c09eff2017-02-17 11:46:19 -080040HwDriverError = servo_drv.hw_driver.HwDriverError
Aseda Aboagyea4922212015-11-20 15:19:08 -080041
Todd Broche505b8d2011-03-21 18:19:54 -070042MAX_I2C_CLOCK_HZ = 100000
43
Kevin Cheng5595b342016-09-29 15:51:01 -070044# It takes about 16-17 seconds for the entire probe usb device method,
45# let's wait double plus some buffer.
46_MAX_USB_LOCK_WAIT = 40
Todd Brochdbb09982011-10-02 07:14:26 -070047
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -070048
Todd Broche505b8d2011-03-21 18:19:54 -070049class ServodError(Exception):
50 """Exception class for servod."""
51
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -070052
Todd Broche505b8d2011-03-21 18:19:54 -070053class Servod(object):
54 """Main class for Servo debug/controller Daemon."""
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -070055 _HTTP_PREFIX = 'http://'
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -070056 _USB_LOCK_FILE = '/var/lib/servod/lock_file'
Simran Basia9f41032012-05-11 14:21:58 -070057
Kevin Cheng4b4f0022016-09-09 02:37:07 -070058 # This is the key to get the main serial used in the _serialnames dict.
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -070059 MAIN_SERIAL = 'main'
60 SERVO_MICRO_SERIAL = 'servo_micro'
61 CCD_SERIAL = 'ccd'
Kevin Cheng4b4f0022016-09-09 02:37:07 -070062
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +080063 # Timeout to wait for interfaces to become available again if reinitialization
64 # is taking place. In seconds.
65 INTERFACE_AVAILABILITY_TIMEOUT = 60
66
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -070067 def init_servo_interfaces(self, vendor, product, serialname, interfaces):
Kevin Chengdc3befd2016-07-15 12:34:00 -070068 """Init the servo interfaces with the given interfaces.
69
70 We don't use the self._{vendor,product,serialname} attributes because we
71 want to allow other callers to initialize other interfaces that may not
72 be associated with the initialized attributes (e.g. a servo v4 servod object
73 that wants to also initialize a servo micro interface).
74
75 Args:
76 vendor: USB vendor id of FTDI device.
77 product: USB product id of FTDI device.
78 serialname: String of device serialname/number as defined in FTDI
79 eeprom.
80 interfaces: List of strings of interface types the server will
81 instantiate.
82
83 Raises:
84 ServodError if unable to locate init method for particular interface.
85 """
Mary Ruthven13389642017-02-14 12:15:34 -080086 # If it is a new device add it to the list
Wai-Hong Tam1f9e9a72017-05-02 14:14:46 -070087 device = (vendor, product, serialname)
Mary Ruthven13389642017-02-14 12:15:34 -080088 if device not in self._devices:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -070089 self._devices.append(device)
Mary Ruthven13389642017-02-14 12:15:34 -080090
Kevin Chengdc3befd2016-07-15 12:34:00 -070091 # Extend the interface list if we need to.
92 interfaces_len = len(interfaces)
93 interface_list_len = len(self._interface_list)
94 if interfaces_len > interface_list_len:
95 self._interface_list += [None] * (interfaces_len - interface_list_len)
96
Kevin Chengdc3befd2016-07-15 12:34:00 -070097 for i, interface in enumerate(interfaces):
98 is_ftdi_interface = False
99 if type(interface) is dict:
100 name = interface['name']
101 # Store interface index for those that care about it.
102 interface['index'] = i
103 elif type(interface) is str and interface != 'dummy':
104 name = interface
Wai-Hong Tam564c1702017-04-24 09:23:38 -0700105 # It's a FTDI related interface. #0 is reserved for no use.
106 interface = ((i - 1) % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) + 1
Kevin Chengdc3befd2016-07-15 12:34:00 -0700107 is_ftdi_interface = True
108 elif type(interface) is str and interface == 'dummy':
109 # 'dummy' reserves the interface for future use. Typically the
110 # interface will be managed by external third-party tools like
111 # openOCD for JTAG or flashrom for SPI. In the case of servo V4,
112 # it serves as a placeholder for servo micro interfaces.
113 continue
114 else:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700115 raise ServodError('Illegal interface type %s' % type(interface))
Kevin Chengdc3befd2016-07-15 12:34:00 -0700116
117 # servos with multiple FTDI are guaranteed to have contiguous USB PIDs
Wai-Hong Tam564c1702017-04-24 09:23:38 -0700118 product_increment = 0
119 if is_ftdi_interface:
120 product_increment = (i - 1) / ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE
121 if product_increment:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700122 self._logger.info('Use the next FTDI part @ pid = 0x%04x',
Wai-Hong Tam564c1702017-04-24 09:23:38 -0700123 product + product_increment)
Kevin Chengdc3befd2016-07-15 12:34:00 -0700124
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700125 self._logger.info('Initializing interface %d to %s', i, name)
Kevin Chengdc3befd2016-07-15 12:34:00 -0700126 try:
127 func = getattr(self, '_init_%s' % name)
128 except AttributeError:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700129 raise ServodError('Unable to locate init for interface %s' % name)
Wai-Hong Tam564c1702017-04-24 09:23:38 -0700130 result = func(vendor, product + product_increment, serialname, interface)
Kevin Chengdc3befd2016-07-15 12:34:00 -0700131
132 if isinstance(result, tuple):
133 result_len = len(result)
Wai-Hong Tamd8a94d62017-04-28 10:11:51 -0700134 self._interface_list[i:(i + result_len)] = result
Kevin Chengdc3befd2016-07-15 12:34:00 -0700135 else:
Wai-Hong Tamd8a94d62017-04-28 10:11:51 -0700136 self._interface_list[i] = result
Kevin Chengdc3befd2016-07-15 12:34:00 -0700137
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700138 def __init__(self, config, vendor, product, serialname=None, interfaces=None,
139 board='', version=None, usbkm232=None):
Todd Broche505b8d2011-03-21 18:19:54 -0700140 """Servod constructor.
141
142 Args:
143 config: instance of SystemConfig containing all controls for
144 particular Servod invocation
145 vendor: usb vendor id of FTDI device
146 product: usb product id of FTDI device
Todd Brochad034442011-05-25 15:05:29 -0700147 serialname: string of device serialname/number as defined in FTDI eeprom.
Todd Brochdbb09982011-10-02 07:14:26 -0700148 interfaces: list of strings of interface types the server will instantiate
Simran Basia23c1392013-08-06 14:59:10 -0700149 version: String. Servo board version. Examples: servo_v1, servo_v2,
150 servo_v2_r0, servo_v3
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800151 usbkm232: String. Optional. Path to USB-KM232 device which allow for
Kevin Chengdc3befd2016-07-15 12:34:00 -0700152 sending keyboard commands to DUTs that do not have built in
Wai-Hong Tam7b9f2992017-10-24 16:07:14 -0700153 keyboards. Used in FAFT tests. Use None for on board AVR MCU.
154 e.g. '/dev/ttyUSB0' or None.
Todd Brochdbb09982011-10-02 07:14:26 -0700155
156 Raises:
157 ServodError: if unable to locate init method for particular interface
Todd Broche505b8d2011-03-21 18:19:54 -0700158 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700159 self._logger = logging.getLogger('Servod')
160 self._logger.debug('')
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +0800161 self._ifaces_available = threading.Event()
162 # Initially interfaces should be available.
163 self._ifaces_available.set()
Todd Broche505b8d2011-03-21 18:19:54 -0700164 self._vendor = vendor
165 self._product = product
Mary Ruthven13389642017-02-14 12:15:34 -0800166 self._devices = []
Kevin Cheng4b4f0022016-09-09 02:37:07 -0700167 self._serialnames = {self.MAIN_SERIAL: serialname}
Todd Broche505b8d2011-03-21 18:19:54 -0700168 self._syscfg = config
Kevin Cheng9071ed92016-06-21 14:37:54 -0700169 # Hold the last image path so we can reduce downloads to the usb device.
170 self._image_path = None
Todd Broche505b8d2011-03-21 18:19:54 -0700171 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
172 # interfaces are mapped to
173 self._interface_list = []
174 # Dict of Dict to map control name, function name to to tuple (params, drv)
175 # Ex) _drv_dict[name]['get'] = (params, drv)
176 self._drv_dict = {}
J. Richard Barnettee2820552013-03-14 16:13:46 -0700177 self._board = board
Wai-Hong Tam416cf612017-09-19 11:39:21 -0700178 self._base_board = ''
Simran Basia23c1392013-08-06 14:59:10 -0700179 self._version = version
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800180 self._usbkm232 = usbkm232
Kevin Chengc49494e2016-07-25 12:13:38 -0700181 # Seed the random generator with the serial to differentiate from other
182 # servod processes.
183 random.seed(serialname if serialname else time.time())
Todd Brochdbb09982011-10-02 07:14:26 -0700184 if not interfaces:
Todd Brochb21d8042014-05-15 12:54:54 -0700185 try:
186 interfaces = servo_interfaces.INTERFACE_BOARDS[board][vendor][product]
187 except KeyError:
188 interfaces = servo_interfaces.INTERFACE_DEFAULTS[vendor][product]
Dino Lic89d8c82018-01-11 09:56:47 +0800189 self._interfaces = interfaces
Todd Brochdbb09982011-10-02 07:14:26 -0700190
Kevin Chengdc3befd2016-07-15 12:34:00 -0700191 self.init_servo_interfaces(vendor, product, serialname, interfaces)
Kevin Cheng16304d12016-07-08 11:56:55 -0700192 servo_postinit.post_init(self)
Danny Chan662b6022015-11-04 17:34:53 -0800193
Mary Ruthven13389642017-02-14 12:15:34 -0800194 def reinitialize(self):
195 """Reinitialize all interfaces that support reinitialization"""
Mary Ruthven13389642017-02-14 12:15:34 -0800196 for i, interface in enumerate(self._interface_list):
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700197 if hasattr(interface, 'reinitialize'):
198 interface.reinitialize()
199 else:
200 self._logger.debug('interface %d has no reset functionality', i)
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +0800201 # Indicate interfaces are safe to use again.
202 self._ifaces_available.set()
Mary Ruthven13389642017-02-14 12:15:34 -0800203
Wai-Hong Tam4544c302017-05-24 19:44:53 -0700204 def get_servo_interfaces(self, position, size):
205 """Get the list of servo interfaces.
206
207 Args:
208 position: The index the first interface to get.
209 size: The number of the interfaces.
210 """
211 return self._interface_list[position:(position + size)]
212
213 def set_servo_interfaces(self, position, interfaces):
214 """Set the list of servo interfaces.
215
216 Args:
217 position: The index the first interface to set.
218 interfaces: The list of interfaces to set.
219 """
220 size = len(interfaces)
221 self._interface_list[position:(position + size)] = interfaces
222
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800223 def _init_keyboard_handler(self, servo, board=''):
224 """Initialize the correct keyboard handler for board.
225
Kevin Chengdc3befd2016-07-15 12:34:00 -0700226 Args:
227 servo: servo object.
228 board: string, board name.
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800229
Kevin Chengdc3befd2016-07-15 12:34:00 -0700230 Returns:
Wai-Hong Tam7b9f2992017-10-24 16:07:14 -0700231 keyboard handler object, or None if no keyboard supported.
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800232 """
233 if board == 'parrot':
234 return keyboard_handlers.ParrotHandler(servo)
235 elif board == 'stout':
236 return keyboard_handlers.StoutHandler(servo)
PeggyChuang4f07d872015-08-07 12:11:38 +0800237 elif board in ('buddy', 'cranky', 'guado', 'jecht', 'mccloud', 'monroe',
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700238 'ninja', 'nyan_kitty', 'panther', 'rikku', 'stumpy', 'sumo',
239 'tidus', 'tricky', 'veyron_fievel', 'veyron_mickey',
Shelley Chen94cd2352017-07-26 11:36:45 -0700240 'veyron_rialto', 'veyron_tiger', 'zako'):
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800241 if self._usbkm232 is None:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700242 logging.info('No device path specified for usbkm232 handler. Use '
243 'the servo atmega chip to handle.')
Wai-Hong Tam7b9f2992017-10-24 16:07:14 -0700244
Danny Chan662b6022015-11-04 17:34:53 -0800245 # Use servo onboard keyboard emulator.
Wai-Hong Tam7b9f2992017-10-24 16:07:14 -0700246 if not self._syscfg.is_control('atmega_rst'):
247 logging.warn('No atmega in servo board. So no keyboard support.')
248 return None
249
Nick Sanders78423782015-11-09 14:28:19 -0800250 self.set('atmega_rst', 'on')
Nick Sandersbc836282015-12-08 21:19:23 -0800251 self.set('at_hwb', 'off')
Nick Sanders78423782015-11-09 14:28:19 -0800252 self.set('atmega_rst', 'off')
Danny Chan662b6022015-11-04 17:34:53 -0800253 self._usbkm232 = self.get('atmega_pty')
Wai-Hong Tam7b9f2992017-10-24 16:07:14 -0700254
Kevin Cheng810fc782016-11-01 12:36:46 -0700255 # We don't need to set the atmega uart settings if we're a servo v4.
Aseda Aboagye1d8477b2017-05-10 17:24:31 -0700256 if 'servo_v4' not in self._version:
Kevin Cheng810fc782016-11-01 12:36:46 -0700257 self.set('atmega_baudrate', '9600')
258 self.set('atmega_bits', 'eight')
259 self.set('atmega_parity', 'none')
260 self.set('atmega_sbits', 'one')
261 self.set('usb_mux_sel4', 'on')
262 self.set('usb_mux_oe4', 'on')
263 # Allow atmega bootup time.
264 time.sleep(1.0)
Wai-Hong Tam7b9f2992017-10-24 16:07:14 -0700265
Danny Chan662b6022015-11-04 17:34:53 -0800266 self._logger.info('USBKM232: %s', self._usbkm232)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800267 return keyboard_handlers.USBkm232Handler(servo, self._usbkm232)
268 else:
Tom Wai-Hong Tamd64164c2015-04-29 07:59:45 +0800269 # The following boards don't use Chrome EC.
270 if board in ('alex', 'butterfly', 'lumpy', 'zgb'):
271 return keyboard_handlers.MatrixKeyboardHandler(servo)
272 return keyboard_handlers.ChromeECHandler(servo)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800273
Ruben Rodriguez Buchillona16374b2018-06-20 16:45:00 -0700274 def close(self):
275 """Servod turn down logic."""
276 for i, interface in enumerate(self._interface_list):
277 self._logger.info('Turning down interface %d' % i)
278 if hasattr(interface, 'close'):
279 interface.close()
Todd Broch3ec8df02012-11-20 10:53:03 -0800280
Kevin Chengdc3befd2016-07-15 12:34:00 -0700281 def _init_ftdi_dummy(self, vendor, product, serialname, interface):
Kevin Cheng042f4932016-07-19 10:46:00 -0700282 """Dummy interface for ftdi devices.
283
284 This is a dummy function specifically for ftdi devices to not initialize
285 anything but to help pad the interface list.
286
287 Returns:
288 None.
289 """
290 return None
291
Kevin Chengdc3befd2016-07-15 12:34:00 -0700292 def _init_ftdi_gpio(self, vendor, product, serialname, interface):
Todd Broche505b8d2011-03-21 18:19:54 -0700293 """Initialize gpio driver interface and open for use.
294
295 Args:
296 interface: interface number of FTDI device to use.
297
298 Returns:
299 Instance object of interface.
Todd Broch6de9dc62012-04-09 15:23:53 -0700300
301 Raises:
302 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700303 """
Kevin Chengdc3befd2016-07-15 12:34:00 -0700304 fobj = ftdigpio.Fgpio(vendor, product, interface, serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700305 try:
306 fobj.open()
307 except ftdigpio.FgpioError as e:
308 raise ServodError('Opening gpio interface. %s ( %d )' % (e.msg, e.value))
309
Todd Broche505b8d2011-03-21 18:19:54 -0700310 return fobj
311
Kevin Chengdc3befd2016-07-15 12:34:00 -0700312 def _init_stm32_uart(self, vendor, product, serialname, interface):
Nick Sanders97bc4462016-01-04 15:37:31 -0800313 """Initialize stm32 uart interface and open for use
314
315 Note, the uart runs in a separate thread. Users wishing to
316 interact with it will query control for the pty's pathname and connect
317 with their favorite console program. For example:
318 cu -l /dev/pts/22
319
320 Args:
321 interface: dict of interface parameters.
322
323 Returns:
324 Instance object of interface
325
326 Raises:
327 ServodError: Raised on init failure.
328 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700329 self._logger.info('Suart: interface: %s' % interface)
330 sobj = stm32uart.Suart(vendor, product, interface['interface'], serialname)
Nick Sanders97bc4462016-01-04 15:37:31 -0800331
332 try:
333 sobj.run()
334 except stm32uart.SuartError as e:
335 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
336
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700337 self._logger.info('%s' % sobj.get_pty())
Nick Sanders97bc4462016-01-04 15:37:31 -0800338 return sobj
339
Kevin Chengdc3befd2016-07-15 12:34:00 -0700340 def _init_stm32_gpio(self, vendor, product, serialname, interface):
Nick Sanders97bc4462016-01-04 15:37:31 -0800341 """Initialize stm32 gpio interface.
342 Args:
Wai-Hong Tam564c1702017-04-24 09:23:38 -0700343 interface: dict of interface parameters.
Nick Sanders97bc4462016-01-04 15:37:31 -0800344
345 Returns:
346 Instance object of interface
347
348 Raises:
349 SgpioError: Raised on init failure.
350 """
Kevin Cheng71a046f2016-06-13 16:37:58 -0700351 interface_number = interface
352 # Interface could be a dict.
353 if type(interface) is dict:
354 interface_number = interface['interface']
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700355 self._logger.info('Sgpio: interface: %s' % interface_number)
Kevin Chengdc3befd2016-07-15 12:34:00 -0700356 return stm32gpio.Sgpio(vendor, product, interface_number, serialname)
Nick Sanders97bc4462016-01-04 15:37:31 -0800357
Kevin Chengdc3befd2016-07-15 12:34:00 -0700358 def _init_stm32_i2c(self, vendor, product, serialname, interface):
Nick Sanders97bc4462016-01-04 15:37:31 -0800359 """Initialize stm32 USB to I2C bridge interface and open for use
360
361 Args:
Wai-Hong Tam564c1702017-04-24 09:23:38 -0700362 interface: dict of interface parameters.
Nick Sanders97bc4462016-01-04 15:37:31 -0800363
364 Returns:
365 Instance object of interface.
366
367 Raises:
368 Si2cError: Raised on init failure.
369 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700370 self._logger.info('Si2cBus: interface: %s' % interface)
Nick Sandersa3649712016-03-01 16:53:52 -0800371 port = interface.get('port', 0)
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700372 return stm32i2c.Si2cBus(vendor, product, interface['interface'], port=port,
373 serialname=serialname)
Nick Sanders97bc4462016-01-04 15:37:31 -0800374
Kevin Chengdc3befd2016-07-15 12:34:00 -0700375 def _init_bb_adc(self, vendor, product, serialname, interface):
Aaron.Chuang88eff332014-07-31 08:32:00 +0800376 """Initalize beaglebone ADC interface."""
377 return bbadc.BBadc()
378
Kevin Chengdc3befd2016-07-15 12:34:00 -0700379 def _init_bb_gpio(self, vendor, product, serialname, interface):
Simran Basie750a342013-03-12 13:45:26 -0700380 """Initalize beaglebone gpio interface."""
Simran Basi5492bde2013-05-16 17:08:47 -0700381 return bbgpio.BBgpio()
Simran Basie750a342013-03-12 13:45:26 -0700382
Kevin Chengdc3befd2016-07-15 12:34:00 -0700383 def _init_ftdi_i2c(self, vendor, product, serialname, interface):
Todd Broche505b8d2011-03-21 18:19:54 -0700384 """Initialize i2c interface and open for use.
385
386 Args:
387 interface: interface number of FTDI device to use
388
389 Returns:
390 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700391
392 Raises:
393 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700394 """
Kevin Chengdc3befd2016-07-15 12:34:00 -0700395 fobj = ftdii2c.Fi2c(vendor, product, interface, serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700396 try:
397 fobj.open()
398 except ftdii2c.Fi2cError as e:
399 raise ServodError('Opening i2c interface. %s ( %d )' % (e.msg, e.value))
400
Todd Broche505b8d2011-03-21 18:19:54 -0700401 # Set the frequency of operation of the i2c bus.
402 # TODO(tbroch) make configureable
403 fobj.setclock(MAX_I2C_CLOCK_HZ)
Todd Broch6de9dc62012-04-09 15:23:53 -0700404
Todd Broche505b8d2011-03-21 18:19:54 -0700405 return fobj
406
Simran Basie750a342013-03-12 13:45:26 -0700407 # TODO (sbasi) crbug.com/187489 - Implement bb_i2c.
408 def _init_bb_i2c(self, interface):
409 """Initalize beaglebone i2c interface."""
Simran Basia9ad25e2013-04-23 11:57:00 -0700410 return bbi2c.BBi2c(interface)
Simran Basie750a342013-03-12 13:45:26 -0700411
Kevin Chengdc3befd2016-07-15 12:34:00 -0700412 def _init_dev_i2c(self, vendor, product, serialname, interface):
Rong Changc6c8c022014-08-11 14:07:11 +0800413 """Initalize Linux i2c-dev interface."""
414 return i2cbus.I2CBus('/dev/i2c-%d' % interface['bus_num'])
415
Kevin Chengdc3befd2016-07-15 12:34:00 -0700416 def _init_ftdi_uart(self, vendor, product, serialname, interface):
Simran Basie750a342013-03-12 13:45:26 -0700417 """Initialize ftdi uart inteface and open for use
Todd Broch47c43f42011-05-26 15:11:31 -0700418
419 Note, the uart runs in a separate thread (pthreads). Users wishing to
420 interact with it will query control for the pty's pathname and connect
421 with there favorite console program. For example:
422 cu -l /dev/pts/22
423
424 Args:
425 interface: interface number of FTDI device to use
426
427 Returns:
428 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700429
430 Raises:
431 ServodError: If init fails
Todd Broch47c43f42011-05-26 15:11:31 -0700432 """
Kevin Chengdc3befd2016-07-15 12:34:00 -0700433 fobj = ftdiuart.Fuart(vendor, product, interface, serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700434 try:
435 fobj.run()
436 except ftdiuart.FuartError as e:
437 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
438
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700439 self._logger.info('%s' % fobj.get_pty())
Todd Broch47c43f42011-05-26 15:11:31 -0700440 return fobj
441
Simran Basie750a342013-03-12 13:45:26 -0700442 # TODO (sbasi) crbug.com/187492 - Implement bbuart.
Kevin Chengdc3befd2016-07-15 12:34:00 -0700443 def _init_bb_uart(self, vendor, product, serialname, interface):
Simran Basie750a342013-03-12 13:45:26 -0700444 """Initalize beaglebone uart interface."""
Simran Basi949309b2013-05-31 15:12:15 -0700445 logging.debug('UART INTERFACE: %s', interface)
446 return bbuart.BBuart(interface)
Simran Basie750a342013-03-12 13:45:26 -0700447
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700448 def _init_ftdi_gpiouart(self, vendor, product, serialname, interface):
Todd Broch888da782011-10-07 14:29:09 -0700449 """Initialize special gpio + uart interface and open for use
450
451 Note, the uart runs in a separate thread (pthreads). Users wishing to
452 interact with it will query control for the pty's pathname and connect
453 with there favorite console program. For example:
454 cu -l /dev/pts/22
455
456 Args:
457 interface: interface number of FTDI device to use
458
459 Returns:
460 Instance objects of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700461
462 Raises:
463 ServodError: If init fails
Todd Broch888da782011-10-07 14:29:09 -0700464 """
Kevin Chengce7dafd2016-08-02 11:11:38 -0700465 fgpio = self._init_ftdi_gpio(vendor, product, serialname, interface)
Kevin Chengdc3befd2016-07-15 12:34:00 -0700466 fuart = ftdiuart.Fuart(vendor, product, interface, serialname, fgpio._fc)
Todd Broch6de9dc62012-04-09 15:23:53 -0700467 try:
468 fuart.run()
469 except ftdiuart.FuartError as e:
470 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
471
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700472 self._logger.info('uart pty: %s' % fuart.get_pty())
Todd Broch888da782011-10-07 14:29:09 -0700473 return fgpio, fuart
474
Kevin Chengdc3befd2016-07-15 12:34:00 -0700475 def _init_ec3po_uart(self, vendor, product, serialname, interface):
Aseda Aboagyea4922212015-11-20 15:19:08 -0800476 """Initialize EC-3PO console interpreter interface.
477
478 Args:
479 interface: A dictionary representing the interface.
480
481 Returns:
482 An EC3PO object representing the EC-3PO interface or None if there's no
483 interface for the USB PD UART.
484 """
Wai-Hong Tam6c0fa592017-04-21 12:41:33 -0700485 raw_uart_name = interface['raw_pty']
Nick Sanders116ed9e2018-03-09 19:05:16 -0800486 raw_uart_source = interface['source']
Wai-Hong Tam6c0fa592017-04-21 12:41:33 -0700487 if self._syscfg.is_control(raw_uart_name):
Nick Sanders97bc4462016-01-04 15:37:31 -0800488 raw_ec_uart = self.get(raw_uart_name)
Nick Sanders116ed9e2018-03-09 19:05:16 -0800489 return ec3po_interface.EC3PO(raw_ec_uart, raw_uart_source)
Aseda Aboagyea4922212015-11-20 15:19:08 -0800490 else:
Wai-Hong Tam6c0fa592017-04-21 12:41:33 -0700491 # The overlay doesn't have the raw PTY defined, therefore we can skip
492 # initializing this interface since no control relies on it.
493 self._logger.debug(
494 'Skip initializing EC3PO for %s, no control specified.',
495 raw_uart_name)
496 return None
Aseda Aboagyea4922212015-11-20 15:19:08 -0800497
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800498 def _camel_case(self, string):
499 output = ''
500 for s in string.split('_'):
501 if output:
502 output += s.capitalize()
503 else:
504 output = s
505 return output
506
Wai-Hong Tam4544c302017-05-24 19:44:53 -0700507 def clear_cached_drv(self):
508 """Clear the cached drivers.
509
510 The drivers are cached in the Dict _drv_dict when a control is got or set.
511 When the servo interfaces are relocated, the cached values may become wrong.
512 Should call this method to clear the cached values.
513 """
514 self._drv_dict = {}
515
Todd Broche505b8d2011-03-21 18:19:54 -0700516 def _get_param_drv(self, control_name, is_get=True):
517 """Get access to driver for a given control.
518
519 Note, some controls have different parameter dictionaries for 'getting' the
520 control's value versus 'setting' it. Boolean is_get distinguishes which is
521 being requested.
522
523 Args:
524 control_name: string name of control
525 is_get: boolean to determine
526
527 Returns:
528 tuple (param, drv) where:
529 param: param dictionary for control
530 drv: instance object of driver for particular control
531
532 Raises:
533 ServodError: Error occurred while examining params dict
534 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700535 self._logger.debug('')
Todd Broche505b8d2011-03-21 18:19:54 -0700536 # if already setup just return tuple from driver dict
537 if control_name in self._drv_dict:
538 if is_get and ('get' in self._drv_dict[control_name]):
539 return self._drv_dict[control_name]['get']
540 if not is_get and ('set' in self._drv_dict[control_name]):
541 return self._drv_dict[control_name]['set']
542
543 params = self._syscfg.lookup_control_params(control_name, is_get)
544 if 'drv' not in params:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700545 self._logger.error('Unable to determine driver for %s' % control_name)
Todd Broche505b8d2011-03-21 18:19:54 -0700546 raise ServodError("'drv' key not found in params dict")
547 if 'interface' not in params:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700548 self._logger.error('Unable to determine interface for %s' % control_name)
Todd Broche505b8d2011-03-21 18:19:54 -0700549 raise ServodError("'interface' key not found in params dict")
Simran Basi668be0e2013-08-07 11:54:50 -0700550
Aseda Aboagye1d8477b2017-05-10 17:24:31 -0700551 # Find the candidate servos. Using servo_v4 with a servo_micro connected as
552 # an example, the following shows the priority for selecting the interface.
553 #
554 # 1. The full name. (e.g. - 'servo_v4_with_servo_micro_interface')
555 # 2. servo_micro_interface
556 # 3. servo_v4_interface
557 # 4. Fallback to the default, interface.
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700558 candidates = [self._version]
Aseda Aboagye1d8477b2017-05-10 17:24:31 -0700559 candidates.extend(reversed(self._version.split('_with_')))
560
561 interface_id = 'unknown'
562 for c in candidates:
563 interface_name = '%s_interface' % c
564 if interface_name in params:
565 interface_id = params[interface_name]
566 self._logger.debug('Using %s parameter.' % interface_name)
567 break
568
569 # Use the default interface value if we couldn't find a more specific
570 # interface.
571 if interface_id == 'unknown':
572 interface_id = params['interface']
573 self._logger.debug('Using default interface parameter.')
574
J. Richard Barnette275d9fd2014-02-11 14:38:54 -0800575 if interface_id == 'servo':
576 interface = self
Simran Basi668be0e2013-08-07 11:54:50 -0700577 else:
Wai-Hong Tam564c1702017-04-24 09:23:38 -0700578 index = int(interface_id)
J. Richard Barnette275d9fd2014-02-11 14:38:54 -0800579 interface = self._interface_list[index]
Simran Basi668be0e2013-08-07 11:54:50 -0700580
Todd Broche505b8d2011-03-21 18:19:54 -0700581 drv_name = params['drv']
Wai-Hong Tam4c09eff2017-02-17 11:46:19 -0800582 drv_module = getattr(servo_drv, drv_name)
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800583 drv_class = getattr(drv_module, self._camel_case(drv_name))
Todd Broche505b8d2011-03-21 18:19:54 -0700584 drv = drv_class(interface, params)
585 if control_name not in self._drv_dict:
586 self._drv_dict[control_name] = {}
587 if is_get:
588 self._drv_dict[control_name]['get'] = (params, drv)
589 else:
590 self._drv_dict[control_name]['set'] = (params, drv)
591 return (params, drv)
592
593 def doc_all(self):
594 """Return all documenation for controls.
595
596 Returns:
597 string of <doc> text in config file (xml) and the params dictionary for
598 all controls.
599
600 For example:
601 warm_reset :: Reset the device warmly
602 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
603 """
604 return self._syscfg.display_config()
605
606 def doc(self, name):
607 """Retreive doc string in system config file for given control name.
608
609 Args:
610 name: name string of control to get doc string
611
612 Returns:
613 doc string of name
614
615 Raises:
616 NameError: if fails to locate control
617 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700618 self._logger.debug('name(%s)' % (name))
Todd Broche505b8d2011-03-21 18:19:54 -0700619 if self._syscfg.is_control(name):
620 return self._syscfg.get_control_docstring(name)
621 else:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700622 raise NameError('No control %s' % name)
Todd Broche505b8d2011-03-21 18:19:54 -0700623
Simran Basia9f41032012-05-11 14:21:58 -0700624 def _get_usb_port_set(self):
625 """Gets a set of USB disks currently connected to the system
626
627 Returns:
628 A set of USB disk paths.
629 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700630 usb_set = fnmatch.filter(os.listdir('/dev/'), 'sd[a-z]')
631 return set(['/dev/' + dev for dev in usb_set])
Simran Basia9f41032012-05-11 14:21:58 -0700632
Kevin Cheng5595b342016-09-29 15:51:01 -0700633 @contextlib.contextmanager
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800634 def _block_other_servod(self, timeout):
Kevin Chengc49494e2016-07-25 12:13:38 -0700635 """Block other servod processes by locking a file.
636
637 To enable multiple servods processes to safely probe_host_usb_dev, we use
638 a given lock file to signal other servod processes that we're probing
Kevin Cheng5595b342016-09-29 15:51:01 -0700639 for a usb device. This will be a context manager that will return
640 if the block was successful or not.
Kevin Chengc49494e2016-07-25 12:13:38 -0700641
642 If the lock file exists, we open it and try to lock it.
643 - If another servod processes has locked it already, we'll sleep a random
644 amount of time and try again, we'll keep doing that until
Kevin Cheng5595b342016-09-29 15:51:01 -0700645 timeout amount of time has passed.
Kevin Chengc49494e2016-07-25 12:13:38 -0700646
Kevin Cheng5595b342016-09-29 15:51:01 -0700647 - If we're able to lock the file, we'll yield that the block was successful
648 and upon return, unlock the file and exit out.
Kevin Chengc49494e2016-07-25 12:13:38 -0700649
650 This blocking behavior is only enabled if the lock file exists, if it
651 doesn't, then we pretend the block was successful.
652
Kevin Cheng5595b342016-09-29 15:51:01 -0700653 Args:
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800654 timeout: Max waiting time for the block to succeed; 0 to wait forever.
Kevin Chengc49494e2016-07-25 12:13:38 -0700655 """
Kevin Cheng5595b342016-09-29 15:51:01 -0700656 if not os.path.exists(self._USB_LOCK_FILE):
657 # No lock file so we'll pretend the block was a success.
658 yield True
659 else:
Kevin Chengc49494e2016-07-25 12:13:38 -0700660 start_time = datetime.datetime.now()
661 while True:
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800662 with open(self._USB_LOCK_FILE, 'w') as lock_file:
Kevin Cheng5595b342016-09-29 15:51:01 -0700663 try:
664 fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
665 yield True
666 fcntl.flock(lock_file, fcntl.LOCK_UN)
667 break
668 except IOError:
669 current_time = datetime.datetime.now()
670 current_wait_time = (current_time - start_time).total_seconds()
671 if timeout and current_wait_time > timeout:
672 yield False
673 break
Kevin Chengc49494e2016-07-25 12:13:38 -0700674 # Sleep random amount.
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800675 sleep_time = random.random()
676 logging.debug('sleep %.04fs and try obtaining a lock...', sleep_time)
677 time.sleep(sleep_time)
Kevin Chengc49494e2016-07-25 12:13:38 -0700678
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800679 def safe_switch_usbkey_power(self, power_state, _=None):
Kevin Cheng5595b342016-09-29 15:51:01 -0700680 """Toggle the usb power safely.
681
Kevin Chengc49494e2016-07-25 12:13:38 -0700682 Args:
Kevin Cheng5595b342016-09-29 15:51:01 -0700683 power_state: The setting to set for the usbkey power.
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800684 _: to conform to current API
Kevin Chengc49494e2016-07-25 12:13:38 -0700685
Kevin Cheng5595b342016-09-29 15:51:01 -0700686 Returns:
687 An empty string to appease the xmlrpc gods.
688 """
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800689 self.set('image_usbkey_pwr', power_state)
Kevin Cheng5595b342016-09-29 15:51:01 -0700690 return ''
691
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800692 def safe_switch_usbkey(self, mux_direction, _=0):
Kevin Cheng5595b342016-09-29 15:51:01 -0700693 """Toggle the usb direction safely.
694
Kevin Cheng5595b342016-09-29 15:51:01 -0700695 Args:
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800696 mux_direction: "servo_sees_usbkey" or "dut_sees_usbkey".
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800697 _: to conform to current API
Kevin Cheng5595b342016-09-29 15:51:01 -0700698
699 Returns:
700 An empty string to appease the xmlrpc gods.
701 """
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800702 self.set('image_usbkey_direction', mux_direction)
Kevin Cheng5595b342016-09-29 15:51:01 -0700703 return ''
704
705 def probe_host_usb_dev(self, timeout=_MAX_USB_LOCK_WAIT):
Simran Basia9f41032012-05-11 14:21:58 -0700706 """Probe the USB disk device plugged in the servo from the host side.
707
708 Method can fail by:
709 1) Having multiple servos connected and returning incorrect /dev/sdX of
Kevin Chengc49494e2016-07-25 12:13:38 -0700710 another servo unless _USB_LOCK_FILE exists on the servo host. If that
711 file exists, then it is safe to probe for usb devices among multiple
712 servod instances.
Simran Basia9f41032012-05-11 14:21:58 -0700713 2) Finding multiple /dev/sdX and returning None.
714
Kevin Cheng5595b342016-09-29 15:51:01 -0700715 Args:
716 timeout: Timeout to wait for blocking other servod processes.
717
Simran Basia9f41032012-05-11 14:21:58 -0700718 Returns:
Kevin Chengc49494e2016-07-25 12:13:38 -0700719 USB disk path if one and only one USB disk path is found, otherwise an
720 empty string.
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800721
722 Raises:
723 ServodError if failed to obtain a lock.
Simran Basia9f41032012-05-11 14:21:58 -0700724 """
Kevin Cheng5595b342016-09-29 15:51:01 -0700725 with self._block_other_servod(timeout=timeout) as block_success:
726 if not block_success:
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800727 raise ServodError('Timed out obtaining a lock to block other servod')
Kevin Chengc49494e2016-07-25 12:13:38 -0700728
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800729 original_value = self.get('image_usbkey_direction')
730 original_usb_power = self.get('image_usbkey_pwr')
Kevin Cheng5595b342016-09-29 15:51:01 -0700731 # Make the host unable to see the USB disk.
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800732 if (original_usb_power == 'on' and
733 original_value != 'dut_sees_usbkey'):
734 self.set('image_usbkey_direction', 'dut_sees_usbkey')
Kevin Cheng5595b342016-09-29 15:51:01 -0700735 no_usb_set = self._get_usb_port_set()
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800736 logging.debug('Device set when USB disk unplugged: %r', no_usb_set)
Simran Basia9f41032012-05-11 14:21:58 -0700737
Kevin Cheng5595b342016-09-29 15:51:01 -0700738 # Make the host able to see the USB disk.
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800739 self.set('image_usbkey_direction', 'servo_sees_usbkey')
Kevin Cheng5595b342016-09-29 15:51:01 -0700740 has_usb_set = self._get_usb_port_set()
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800741 logging.debug('Device set when USB disk plugged: %r', has_usb_set)
Fang Deng90377712013-06-03 15:51:48 -0700742
Kevin Cheng5595b342016-09-29 15:51:01 -0700743 # Back to its original value.
Ruben Rodriguez Buchillon247d88f2018-08-03 18:07:01 +0800744 if original_value != 'servo_sees_usbkey':
745 self.set('image_usbkey_direction', original_value)
746 if original_usb_power != 'on':
747 self.set('image_usbkey_pwr', 'off')
748 time.sleep(10)
Fang Deng90377712013-06-03 15:51:48 -0700749
Kevin Cheng5595b342016-09-29 15:51:01 -0700750 # Subtract the two sets to find the usb device.
751 diff_set = has_usb_set - no_usb_set
752 if len(diff_set) == 1:
753 return diff_set.pop()
754 else:
Wai-Hong Tamf93f9a22018-02-06 14:24:46 -0800755 logging.warn("Can't find the USB device. Diff: %r", diff_set)
Kevin Cheng5595b342016-09-29 15:51:01 -0700756 return ''
Simran Basia9f41032012-05-11 14:21:58 -0700757
Kevin Cheng85831332016-10-13 13:14:44 -0700758 def download_image_to_usb(self, image_path, probe_timeout=_MAX_USB_LOCK_WAIT):
Simran Basia9f41032012-05-11 14:21:58 -0700759 """Download image and save to the USB device found by probe_host_usb_dev.
760 If the image_path is a URL, it will download this url to the USB path;
761 otherwise it will simply copy the image_path's contents to the USB path.
762
763 Args:
764 image_path: path or url to the recovery image.
Kevin Cheng85831332016-10-13 13:14:44 -0700765 probe_timeout: timeout for the probe to take.
Simran Basia9f41032012-05-11 14:21:58 -0700766
767 Returns:
768 True|False: True if process completed successfully, False if error
769 occurred.
770 Can't return None because XMLRPC doesn't allow it. PTAL at tbroch's
771 comment at the end of set().
772 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700773 self._logger.debug('image_path(%s)' % image_path)
774 self._logger.debug('Detecting USB stick device...')
Kevin Cheng85831332016-10-13 13:14:44 -0700775 usb_dev = self.probe_host_usb_dev(timeout=probe_timeout)
Simran Basia9f41032012-05-11 14:21:58 -0700776 if not usb_dev:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700777 self._logger.error('No usb device connected to servo')
Simran Basia9f41032012-05-11 14:21:58 -0700778 return False
779
Kevin Cheng9071ed92016-06-21 14:37:54 -0700780 # Let's check if we downloaded this last time and if so assume the image is
781 # still on the usb device and return True.
782 if self._image_path == image_path:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700783 self._logger.debug('Image already on USB device, skipping transfer')
Kevin Cheng9071ed92016-06-21 14:37:54 -0700784 return True
785
Simran Basia9f41032012-05-11 14:21:58 -0700786 try:
787 if image_path.startswith(self._HTTP_PREFIX):
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700788 self._logger.debug('Image path is a URL, downloading image')
Simran Basia9f41032012-05-11 14:21:58 -0700789 urllib.urlretrieve(image_path, usb_dev)
790 else:
791 shutil.copyfile(image_path, usb_dev)
792 except IOError as e:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700793 self._logger.error('Failed to transfer image to USB device: %s ( %s ) ',
Simran Basia9f41032012-05-11 14:21:58 -0700794 e.strerror, e.errno)
795 return False
796 except urllib.ContentTooShortError:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700797 self._logger.error('Failed to download URL: %s to USB device: %s',
Simran Basia9f41032012-05-11 14:21:58 -0700798 image_path, usb_dev)
799 return False
800 except BaseException as e:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700801 self._logger.error('Unexpected exception downloading %s to %s: %s',
Simran Basia9f41032012-05-11 14:21:58 -0700802 image_path, usb_dev, str(e))
803 return False
J. Richard Barnettee4125af2013-02-26 18:31:56 -0800804 finally:
805 # We just plastered the partition table for a block device.
806 # Pass or fail, we mustn't go without telling the kernel about
807 # the change, or it will punish us with sporadic, hard-to-debug
808 # failures.
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700809 subprocess.call(['sync'])
810 subprocess.call(['blockdev', '--rereadpt', usb_dev])
Kevin Cheng9071ed92016-06-21 14:37:54 -0700811 self._image_path = image_path
Simran Basia9f41032012-05-11 14:21:58 -0700812 return True
813
814 def make_image_noninteractive(self):
815 """Makes the recovery image noninteractive.
816
817 A noninteractive image will reboot automatically after installation
818 instead of waiting for the USB device to be removed to initiate a system
819 reboot.
820
821 Mounts partition 1 of the image stored on usb_dev and creates a file
822 called "non_interactive" so that the image will become noninteractive.
823
824 Returns:
825 True|False: True if process completed successfully, False if error
826 occurred.
827 """
828 result = True
Kevin Chengc49494e2016-07-25 12:13:38 -0700829 usb_dev = self.probe_host_usb_dev()
Simran Basia9f41032012-05-11 14:21:58 -0700830 if not usb_dev:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700831 self._logger.error('No usb device connected to servo')
Simran Basia9f41032012-05-11 14:21:58 -0700832 return False
833 # Create TempDirectory
834 tmpdir = tempfile.mkdtemp()
835 if tmpdir:
836 # Mount drive to tmpdir.
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700837 partition_1 = '%s1' % usb_dev
838 rc = subprocess.call(['mount', partition_1, tmpdir])
Simran Basia9f41032012-05-11 14:21:58 -0700839 if rc == 0:
840 # Create file 'non_interactive'
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700841 non_interactive_file = os.path.join(tmpdir, 'non_interactive')
Simran Basia9f41032012-05-11 14:21:58 -0700842 try:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700843 open(non_interactive_file, 'w').close()
Simran Basia9f41032012-05-11 14:21:58 -0700844 except IOError as e:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700845 self._logger.error('Failed to create file %s : %s ( %d )',
Simran Basia9f41032012-05-11 14:21:58 -0700846 non_interactive_file, e.strerror, e.errno)
847 result = False
848 except BaseException as e:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700849 self._logger.error('Unexpected Exception creating file %s : %s',
Simran Basia9f41032012-05-11 14:21:58 -0700850 non_interactive_file, str(e))
851 result = False
852 # Unmount drive regardless if file creation worked or not.
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700853 rc = subprocess.call(['umount', partition_1])
Simran Basia9f41032012-05-11 14:21:58 -0700854 if rc != 0:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700855 self._logger.error('Failed to unmount USB Device')
Simran Basia9f41032012-05-11 14:21:58 -0700856 result = False
857 else:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700858 self._logger.error('Failed to mount USB Device')
Simran Basia9f41032012-05-11 14:21:58 -0700859 result = False
860
861 # Delete tmpdir. May throw exception if 'umount' failed.
862 try:
863 os.rmdir(tmpdir)
864 except OSError as e:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700865 self._logger.error('Failed to remove temp directory %s : %s', tmpdir,
866 str(e))
Simran Basia9f41032012-05-11 14:21:58 -0700867 return False
868 except BaseException as e:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700869 self._logger.error('Unexpected Exception removing tempdir %s : %s',
Simran Basia9f41032012-05-11 14:21:58 -0700870 tmpdir, str(e))
871 return False
872 else:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700873 self._logger.error('Failed to create temp directory.')
Simran Basia9f41032012-05-11 14:21:58 -0700874 return False
875 return result
876
Todd Broch352b4b22013-03-22 09:48:40 -0700877 def set_get_all(self, cmds):
878 """Set &| get one or more control values.
879
880 Args:
881 cmds: list of control[:value] to get or set.
882
883 Returns:
884 rv: list of responses from calling get or set methods.
885 """
886 rv = []
887 for cmd in cmds:
888 if ':' in cmd:
889 (control, value) = cmd.split(':')
890 rv.append(self.set(control, value))
891 else:
892 rv.append(self.get(cmd))
893 return rv
894
Aseda Aboagye6921f602017-08-01 14:45:38 -0700895 def get_serial_number(self, name):
896 """Returns the desired serial number from the serialnames dict.
897
898 Args:
899 name: A string which is the key into the _serialnames dictionary.
900
901 Returns:
902 A string containing the serial number or "unknown".
903 """
904 if not name:
905 name = 'main'
906
907 try:
908 return self._serialnames[name]
909 except KeyError:
910 self._logger.debug("'%s_serialname' not found!", name)
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700911 return 'unknown'
Aseda Aboagye6921f602017-08-01 14:45:38 -0700912
Todd Broche505b8d2011-03-21 18:19:54 -0700913 def get(self, name):
914 """Get control value.
915
916 Args:
917 name: name string of control
918
919 Returns:
920 Response from calling drv get method. Value is reformatted based on
921 control's dictionary parameters
922
923 Raises:
924 HwDriverError: Error occurred while using drv
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +0800925 ServodError: if interfaces are not available within timeout period
Todd Broche505b8d2011-03-21 18:19:54 -0700926 """
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +0800927 if not self._ifaces_available.wait(self.INTERFACE_AVAILABILITY_TIMEOUT):
928 raise ServodError('Timed out waiting for interfaces to become available.')
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700929 self._logger.debug('name(%s)' % (name))
Wai-Hong Tambafeca72017-10-05 14:22:12 -0700930 # This route is to retrieve serialnames on servo v4, which
931 # connects to multiple servo-micros or CCD, like the controls,
932 # 'ccd_serialname', 'servo_micro_for_soraka_serialname', etc.
933 # TODO(aaboagye): Refactor it.
934 if 'serialname' in name:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700935 return self.get_serial_number(name.split('serialname')[0].strip('_'))
Wai-Hong Tambafeca72017-10-05 14:22:12 -0700936
Todd Broche505b8d2011-03-21 18:19:54 -0700937 (param, drv) = self._get_param_drv(name)
938 try:
939 val = drv.get()
940 rd_val = self._syscfg.reformat_val(param, val)
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700941 self._logger.debug('%s = %s' % (name, rd_val))
Todd Broche505b8d2011-03-21 18:19:54 -0700942 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700943 except AttributeError, error:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700944 self._logger.error('Getting %s: %s' % (name, error))
Todd Brochfbc499d2011-06-16 16:09:58 -0700945 raise
Vic Yangbe6cf262012-09-10 10:40:56 +0800946 except HwDriverError:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700947 self._logger.error('Getting %s' % (name))
Todd Broche505b8d2011-03-21 18:19:54 -0700948 raise
Todd Brochd6061672012-05-11 15:52:47 -0700949
Todd Broche505b8d2011-03-21 18:19:54 -0700950 def get_all(self, verbose):
951 """Get all controls values.
952
953 Args:
954 verbose: Boolean on whether to return doc info as well
955
956 Returns:
957 string creating from trying to get all values of all controls. In case of
958 error attempting access to control, response is 'ERR'.
959 """
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800960 rsp = []
Todd Broche505b8d2011-03-21 18:19:54 -0700961 for name in self._syscfg.syscfg_dict['control']:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700962 self._logger.debug('name = %s' % name)
Todd Broche505b8d2011-03-21 18:19:54 -0700963 try:
964 value = self.get(name)
965 except Exception:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700966 value = 'ERR'
Todd Broche505b8d2011-03-21 18:19:54 -0700967 pass
968 if verbose:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700969 rsp.append('GET %s = %s :: %s' % (name, value, self.doc(name)))
Todd Broche505b8d2011-03-21 18:19:54 -0700970 else:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700971 rsp.append('%s:%s' % (name, value))
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800972 return '\n'.join(sorted(rsp))
Todd Broche505b8d2011-03-21 18:19:54 -0700973
974 def set(self, name, wr_val_str):
975 """Set control.
976
977 Args:
978 name: name string of control
979 wr_val_str: value string to write. Can be integer, float or a
980 alpha-numerical that is mapped to a integer or float.
981
982 Raises:
983 HwDriverError: Error occurred while using driver
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +0800984 ServodError: if interfaces are not available within timeout period
Todd Broche505b8d2011-03-21 18:19:54 -0700985 """
Ruben Rodriguez Buchillona5d8b922018-09-03 16:51:03 +0800986 if not self._ifaces_available.wait(self.INTERFACE_AVAILABILITY_TIMEOUT):
987 raise ServodError('Timed out waiting for interfaces to become available.')
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700988 self._logger.debug('name(%s) wr_val(%s)' % (name, wr_val_str))
Todd Broche505b8d2011-03-21 18:19:54 -0700989 (params, drv) = self._get_param_drv(name, False)
990 wr_val = self._syscfg.resolve_val(params, wr_val_str)
991 try:
992 drv.set(wr_val)
Vic Yangbe6cf262012-09-10 10:40:56 +0800993 except HwDriverError:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -0700994 self._logger.error('Setting %s -> %s' % (name, wr_val_str))
Todd Broche505b8d2011-03-21 18:19:54 -0700995 raise
Ruben Rodriguez Buchillonb5fe0f12018-05-09 10:19:56 +0800996 # TODO(crbug.com/841097) Figure out why despite allow_none=True for both
997 # xmlrpc server & client I still have to return something to appease the
Todd Broche505b8d2011-03-21 18:19:54 -0700998 # marshall/unmarshall
999 return True
1000
Todd Brochd6061672012-05-11 15:52:47 -07001001 def hwinit(self, verbose=False):
1002 """Initialize all controls.
1003
1004 These values are part of the system config XML files of the form
1005 init=<value>. This command should be used by clients wishing to return the
1006 servo and DUT its connected to a known good/safe state.
1007
Vadim Bendeburybb51dd42013-01-31 13:47:46 -08001008 Note that initialization errors are ignored (as in some cases they could
1009 be caused by DUT firmware deficiencies). This might need to be fine tuned
1010 later.
1011
Todd Brochd6061672012-05-11 15:52:47 -07001012 Args:
1013 verbose: boolean, if True prints info about control initialized.
1014 Otherwise prints nothing.
Vadim Bendebury5934e4b2013-02-06 13:57:54 -08001015
1016 Returns:
1017 This function is called across RPC and as such is expected to return
1018 something unless transferring 'none' across is allowed. Hence adding a
1019 dummy return value to make things simpler.
Todd Brochd6061672012-05-11 15:52:47 -07001020 """
Todd Brochd9acf0a2012-12-05 13:43:06 -08001021 for control_name, value in self._syscfg.hwinit:
Todd Broch3ec8df02012-11-20 10:53:03 -08001022 try:
John Carey6fe2bbf2015-08-31 16:13:03 -07001023 # Workaround for bug chrome-os-partner:42349. Without this check, the
1024 # gpio will briefly pulse low if we set it from high to high.
1025 if self.get(control_name) != value:
Aseda Aboagyea849d462016-05-04 17:08:16 -07001026 self.set(control_name, value)
1027 if verbose:
1028 self._logger.info('Initialized %s to %s', control_name, value)
Todd Broch3ec8df02012-11-20 10:53:03 -08001029 except Exception as e:
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -07001030 self._logger.error('Problem initializing %s -> %s :: %s', control_name,
1031 value, str(e))
Nick Sandersbc836282015-12-08 21:19:23 -08001032
1033 # Init keyboard after all the intefaces are up.
1034 self._keyboard = self._init_keyboard_handler(self, self._board)
Vadim Bendebury5934e4b2013-02-06 13:57:54 -08001035 return True
Todd Broch3ec8df02012-11-20 10:53:03 -08001036
Todd Broche505b8d2011-03-21 18:19:54 -07001037 def echo(self, echo):
1038 """Dummy echo function for testing/examples.
1039
1040 Args:
1041 echo: string to echo back to client
1042 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -07001043 self._logger.debug('echo(%s)' % (echo))
1044 return 'ECH0ING: %s' % (echo)
Todd Broche505b8d2011-03-21 18:19:54 -07001045
J. Richard Barnettee2820552013-03-14 16:13:46 -07001046 def get_board(self):
1047 """Return the board specified at startup, if any."""
1048 return self._board
1049
Wai-Hong Tam416cf612017-09-19 11:39:21 -07001050 def get_base_board(self):
1051 """Returns the board name of the base if present.
1052
1053 Returns:
1054 A string of the board name, or '' if not present.
1055 """
1056 # The value is set in servo_postinit.
1057 return self._base_board
1058
Simran Basia23c1392013-08-06 14:59:10 -07001059 def get_version(self):
1060 """Get servo board version."""
1061 return self._version
1062
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001063 def power_long_press(self):
1064 """Simulate a long power button press."""
1065 # After a long power press, the EC may ignore the next power
1066 # button press (at least on Alex). To guarantee that this
1067 # won't happen, we need to allow the EC one second to
1068 # collect itself.
Ruben Rodriguez Buchillon0f467942018-07-27 18:02:32 +08001069 return self.set('power_key', 'long_press')
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001070
1071 def power_normal_press(self):
1072 """Simulate a normal power button press."""
Ruben Rodriguez Buchillon0f467942018-07-27 18:02:32 +08001073 return self.set('power_key', 'press')
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001074
1075 def power_short_press(self):
1076 """Simulate a short power button press."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301077 return self.set('power_key', 'short_press')
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001078
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301079 def power_key(self, press_secs=''):
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001080 """Simulate a power button press.
1081
1082 Args:
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301083 press_secs: Time in seconds to simulate the keypress.
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001084 """
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301085 return self.set('power_key', 'press' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001086
1087 def ctrl_d(self, press_secs=''):
1088 """Simulate Ctrl-d simultaneous button presses."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301089 return self.set('ctrl_d', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001090
Victor Dodone539cea2016-03-29 18:50:17 -07001091 def ctrl_u(self, press_secs=''):
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001092 """Simulate Ctrl-u simultaneous button presses."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301093 return self.set('ctrl_u', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001094
1095 def ctrl_enter(self, press_secs=''):
1096 """Simulate Ctrl-enter simultaneous button presses."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301097 return self.set('ctrl_enter', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001098
1099 def d_key(self, press_secs=''):
1100 """Simulate Enter key button press."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301101 return self.set('d_key', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001102
1103 def ctrl_key(self, press_secs=''):
1104 """Simulate Enter key button press."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301105 return self.set('ctrl_key', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001106
1107 def enter_key(self, press_secs=''):
1108 """Simulate Enter key button press."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301109 return self.set('enter_key', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001110
1111 def refresh_key(self, press_secs=''):
1112 """Simulate Refresh key (F3) button press."""
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301113 return self.set('refresh_key', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001114
1115 def ctrl_refresh_key(self, press_secs=''):
1116 """Simulate Ctrl and Refresh (F3) simultaneous press.
1117
1118 This key combination is an alternative of Space key.
1119 """
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301120 return self.set('ctrl_refresh_key', ('tab' if press_secs is '' else
1121 press_secs))
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001122
1123 def imaginary_key(self, press_secs=''):
1124 """Simulate imaginary key button press.
1125
1126 Maps to a key that doesn't physically exist.
1127 """
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301128 return self.set('imaginary_key', 'tab' if press_secs is '' else press_secs)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -08001129
Vincent Palatin3acbbe52016-07-19 17:40:12 +02001130 def sysrq_x(self, press_secs=''):
1131 """Simulate Alt VolumeUp X simultaneous press.
1132
1133 This key combination is the kernel system request (sysrq) x.
1134 """
Lenine Ajagappane400d7d22018-09-05 02:29:21 +05301135 return self.set('sysrq_x', 'tab' if press_secs is '' else press_secs)
Vincent Palatin3acbbe52016-07-19 17:40:12 +02001136
Kevin Cheng4b4f0022016-09-09 02:37:07 -07001137 def get_servo_serials(self):
1138 """Return all the serials associated with this process."""
1139 return self._serialnames
1140
1141
Todd Broche505b8d2011-03-21 18:19:54 -07001142def test():
1143 """Integration testing.
1144
1145 TODO(tbroch) Enhance integration test and add unittest (see mox)
1146 """
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -07001147 logging.basicConfig(
1148 level=logging.DEBUG,
1149 format='%(asctime)s - %(name)s - ' + '%(levelname)s - %(message)s')
Todd Broche505b8d2011-03-21 18:19:54 -07001150 # configure server & listen
1151 servod_obj = Servod(1)
Wai-Hong Tam564c1702017-04-24 09:23:38 -07001152 # 5 == number of interfaces on a FT4232H device
1153 for i in xrange(1, 5):
1154 if i == 2:
Todd Broche505b8d2011-03-21 18:19:54 -07001155 # its an i2c interface ... see __init__ for details and TODO to make
1156 # this configureable
1157 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
1158 else:
1159 # its a gpio interface
1160 servod_obj._interface_list[i].wr_rd(0)
1161
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -07001162 server = SimpleXMLRPCServer.SimpleXMLRPCServer(('localhost', 9999),
Todd Broche505b8d2011-03-21 18:19:54 -07001163 allow_none=True)
1164 server.register_introspection_functions()
1165 server.register_multicall_functions()
1166 server.register_instance(servod_obj)
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -07001167 logging.info('Listening on localhost port 9999')
Todd Broche505b8d2011-03-21 18:19:54 -07001168 server.serve_forever()
1169
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -07001170
1171if __name__ == '__main__':
Todd Broche505b8d2011-03-21 18:19:54 -07001172 test()
1173
1174 # simple client transaction would look like
Puthikorn Voravootivat01ead152018-03-23 15:38:40 -07001175 """remote_uri = 'http://localhost:9999' client = xmlrpclib.ServerProxy(remote_uri, verbose=False) send_str = "Hello_there" print "Sent " + send_str + ", Recv " + client.echo(send_str)
1176
Todd Broche505b8d2011-03-21 18:19:54 -07001177 """