blob: 3498c8ea3188deb8063a8ed9c6ebe0acaf3d3788 [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."""
Simran Basia9f41032012-05-11 14:21:58 -07005import fnmatch
Todd Broche505b8d2011-03-21 18:19:54 -07006import imp
7import logging
Simran Basia9f41032012-05-11 14:21:58 -07008import os
9import shutil
Todd Broche505b8d2011-03-21 18:19:54 -070010import SimpleXMLRPCServer
Simran Basia9f41032012-05-11 14:21:58 -070011import subprocess
12import tempfile
Todd Broch7a91c252012-02-03 12:37:45 -080013import time
Simran Basia9f41032012-05-11 14:21:58 -070014import urllib
Todd Broche505b8d2011-03-21 18:19:54 -070015
16# TODO(tbroch) deprecate use of relative imports
Vic Yangbe6cf262012-09-10 10:40:56 +080017from drv.hw_driver import HwDriverError
Aaron.Chuang88eff332014-07-31 08:32:00 +080018import bbadc
Simran Basia9ad25e2013-04-23 11:57:00 -070019import bbi2c
Simran Basi5492bde2013-05-16 17:08:47 -070020import bbgpio
Simran Basi949309b2013-05-31 15:12:15 -070021import bbuart
Todd Broche505b8d2011-03-21 18:19:54 -070022import ftdigpio
23import ftdii2c
Todd Brochdbb09982011-10-02 07:14:26 -070024import ftdi_common
Todd Broch47c43f42011-05-26 15:11:31 -070025import ftdiuart
Rong Changc6c8c022014-08-11 14:07:11 +080026import i2cbus
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -080027import keyboard_handlers
Simran Basie750a342013-03-12 13:45:26 -070028import servo_interfaces
Todd Broche505b8d2011-03-21 18:19:54 -070029
30MAX_I2C_CLOCK_HZ = 100000
31
Todd Brochdbb09982011-10-02 07:14:26 -070032
Todd Broche505b8d2011-03-21 18:19:54 -070033class ServodError(Exception):
34 """Exception class for servod."""
35
36class Servod(object):
37 """Main class for Servo debug/controller Daemon."""
Simran Basia9f41032012-05-11 14:21:58 -070038 _USB_DETECTION_DELAY = 10
Fang Deng90377712013-06-03 15:51:48 -070039 _USB_POWEROFF_DELAY = 2
Simran Basia9f41032012-05-11 14:21:58 -070040 _HTTP_PREFIX = "http://"
Fang Deng90377712013-06-03 15:51:48 -070041 _USB_J3 = "usb_mux_sel1"
42 _USB_J3_TO_SERVO = "servo_sees_usbkey"
43 _USB_J3_TO_DUT = "dut_sees_usbkey"
44 _USB_J3_PWR = "prtctl4_pwren"
45 _USB_J3_PWR_ON = "on"
46 _USB_J3_PWR_OFF = "off"
Simran Basia9f41032012-05-11 14:21:58 -070047
J. Richard Barnettee2820552013-03-14 16:13:46 -070048 def __init__(self, config, vendor, product, serialname=None,
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -080049 interfaces=None, board="", version=None, usbkm232=None):
Todd Broche505b8d2011-03-21 18:19:54 -070050 """Servod constructor.
51
52 Args:
53 config: instance of SystemConfig containing all controls for
54 particular Servod invocation
55 vendor: usb vendor id of FTDI device
56 product: usb product id of FTDI device
Todd Brochad034442011-05-25 15:05:29 -070057 serialname: string of device serialname/number as defined in FTDI eeprom.
Todd Brochdbb09982011-10-02 07:14:26 -070058 interfaces: list of strings of interface types the server will instantiate
Simran Basia23c1392013-08-06 14:59:10 -070059 version: String. Servo board version. Examples: servo_v1, servo_v2,
60 servo_v2_r0, servo_v3
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -080061 usbkm232: String. Optional. Path to USB-KM232 device which allow for
62 sending keyboard commands to DUTs that do not have built in
Danny Chan662b6022015-11-04 17:34:53 -080063 keyboards. Used in FAFT tests. Use 'atmega' for on board AVR MCU.
64 e.g. '/dev/ttyUSB0' or 'atmega'
Todd Brochdbb09982011-10-02 07:14:26 -070065
66 Raises:
67 ServodError: if unable to locate init method for particular interface
Todd Broche505b8d2011-03-21 18:19:54 -070068 """
69 self._logger = logging.getLogger("Servod")
70 self._logger.debug("")
71 self._vendor = vendor
72 self._product = product
Todd Brochad034442011-05-25 15:05:29 -070073 self._serialname = serialname
Todd Broche505b8d2011-03-21 18:19:54 -070074 self._syscfg = config
75 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
76 # interfaces are mapped to
77 self._interface_list = []
78 # Dict of Dict to map control name, function name to to tuple (params, drv)
79 # Ex) _drv_dict[name]['get'] = (params, drv)
80 self._drv_dict = {}
J. Richard Barnettee2820552013-03-14 16:13:46 -070081 self._board = board
Simran Basia23c1392013-08-06 14:59:10 -070082 self._version = version
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -080083 self._usbkm232 = usbkm232
Todd Brochdbb09982011-10-02 07:14:26 -070084 # Note, interface i is (i - 1) in list
85 if not interfaces:
Todd Brochb21d8042014-05-15 12:54:54 -070086 try:
87 interfaces = servo_interfaces.INTERFACE_BOARDS[board][vendor][product]
88 except KeyError:
89 interfaces = servo_interfaces.INTERFACE_DEFAULTS[vendor][product]
Todd Brochdbb09982011-10-02 07:14:26 -070090
Simran Basia9ad25e2013-04-23 11:57:00 -070091 for i, interface in enumerate(interfaces):
92 is_ftdi_interface = False
93 if type(interface) is dict:
94 name = interface['name']
95 elif type(interface) is str:
96 # Its FTDI related interface
97 name = interface
98 interface = (i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) + 1
99 is_ftdi_interface = True
100 else:
101 raise ServodError("Illegal interface type %s" % type(interface))
102
Todd Broch8a77a992012-01-27 09:46:08 -0800103 # servos with multiple FTDI are guaranteed to have contiguous USB PIDs
Simran Basia9ad25e2013-04-23 11:57:00 -0700104 if is_ftdi_interface and i and \
105 ((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) == 0):
Todd Broch8a77a992012-01-27 09:46:08 -0800106 self._product += 1
107 self._logger.info("Changing to next FTDI part @ pid = 0x%04x",
108 self._product)
109
Simran Basia9ad25e2013-04-23 11:57:00 -0700110 self._logger.info("Initializing interface %d to %s", i + 1, name)
Todd Brochdbb09982011-10-02 07:14:26 -0700111 try:
112 func = getattr(self, '_init_%s' % name)
113 except AttributeError:
114 raise ServodError("Unable to locate init for interface %s" % name)
Simran Basia9ad25e2013-04-23 11:57:00 -0700115 result = func(interface)
Todd Broch888da782011-10-07 14:29:09 -0700116 if isinstance(result, tuple):
117 self._interface_list.extend(result)
118 else:
119 self._interface_list.append(result)
Todd Broche505b8d2011-03-21 18:19:54 -0700120
Danny Chan662b6022015-11-04 17:34:53 -0800121
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800122 def _init_keyboard_handler(self, servo, board=''):
123 """Initialize the correct keyboard handler for board.
124
125 @param servo: servo object.
126 @param board: string, board name.
127
128 """
129 if board == 'parrot':
130 return keyboard_handlers.ParrotHandler(servo)
131 elif board == 'stout':
132 return keyboard_handlers.StoutHandler(servo)
PeggyChuang4f07d872015-08-07 12:11:38 +0800133 elif board in ('buddy', 'cranky', 'guado', 'jecht', 'mccloud', 'monroe',
Tom Wai-Hong Tamd64164c2015-04-29 07:59:45 +0800134 'ninja', 'nyan_kitty', 'panther', 'rikku', 'stumpy',
Jason Simmonse5cccd12015-08-25 16:05:25 -0700135 'sumo', 'tidus', 'tricky', 'veyron_mickey', 'veyron_rialto',
136 'zako'):
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800137 if self._usbkm232 is None:
Yusuf Mohsinally66844692014-05-22 10:37:52 -0700138 logging.warn("No device path specified for usbkm232 handler. Returning "
Tom Wai-Hong Tamd64164c2015-04-29 07:59:45 +0800139 "the MatrixKeyboardHandler, which is likely the wrong "
140 "keyboard handler for the board type specified.")
141 return keyboard_handlers.MatrixKeyboardHandler(servo)
Danny Chan662b6022015-11-04 17:34:53 -0800142 if self._usbkm232 == 'atmega':
143 # Use servo onboard keyboard emulator.
Nick Sanders78423782015-11-09 14:28:19 -0800144 self.set('atmega_rst', 'on')
Nick Sandersbc836282015-12-08 21:19:23 -0800145 self.set('at_hwb', 'off')
Nick Sanders78423782015-11-09 14:28:19 -0800146 self.set('atmega_rst', 'off')
Danny Chan662b6022015-11-04 17:34:53 -0800147 self._usbkm232 = self.get('atmega_pty')
148 self.set('atmega_baudrate', '9600')
149 self.set('atmega_bits', 'eight')
150 self.set('atmega_parity', 'none')
151 self.set('atmega_sbits', 'one')
152 self.set('usb_mux_sel4', 'on')
Nick Sandersbc836282015-12-08 21:19:23 -0800153 self.set('usb_mux_oe4', 'on')
Nick Sanders78423782015-11-09 14:28:19 -0800154 # Allow atmega bootup time.
155 time.sleep(1.0)
Danny Chan662b6022015-11-04 17:34:53 -0800156 self._logger.info('USBKM232: %s', self._usbkm232)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800157 return keyboard_handlers.USBkm232Handler(servo, self._usbkm232)
158 else:
Tom Wai-Hong Tamd64164c2015-04-29 07:59:45 +0800159 # The following boards don't use Chrome EC.
160 if board in ('alex', 'butterfly', 'lumpy', 'zgb'):
161 return keyboard_handlers.MatrixKeyboardHandler(servo)
162 return keyboard_handlers.ChromeECHandler(servo)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800163
Todd Broch3ec8df02012-11-20 10:53:03 -0800164 def __del__(self):
165 """Servod deconstructor."""
166 for interface in self._interface_list:
167 del(interface)
168
Todd Brochb3048492012-01-15 21:52:41 -0800169 def _init_dummy(self, interface):
170 """Initialize dummy interface.
171
172 Dummy interface is just a mechanism to reserve that interface for non servod
173 interaction. Typically the interface will be managed by external
174 third-party tools like openOCD or urjtag for JTAG or flashrom for SPI
175 interfaces.
176
177 TODO(tbroch): Investigate merits of incorporating these third-party
178 interfaces into servod or creating a communication channel between them
179
180 Returns: None
181 """
182 return None
183
Simran Basie750a342013-03-12 13:45:26 -0700184 def _init_ftdi_gpio(self, interface):
Todd Broche505b8d2011-03-21 18:19:54 -0700185 """Initialize gpio driver interface and open for use.
186
187 Args:
188 interface: interface number of FTDI device to use.
189
190 Returns:
191 Instance object of interface.
Todd Broch6de9dc62012-04-09 15:23:53 -0700192
193 Raises:
194 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700195 """
Todd Brochad034442011-05-25 15:05:29 -0700196 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
197 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700198 try:
199 fobj.open()
200 except ftdigpio.FgpioError as e:
201 raise ServodError('Opening gpio interface. %s ( %d )' % (e.msg, e.value))
202
Todd Broche505b8d2011-03-21 18:19:54 -0700203 return fobj
204
Aaron.Chuang88eff332014-07-31 08:32:00 +0800205 def _init_bb_adc(self, interface):
206 """Initalize beaglebone ADC interface."""
207 return bbadc.BBadc()
208
Simran Basie750a342013-03-12 13:45:26 -0700209 def _init_bb_gpio(self, interface):
210 """Initalize beaglebone gpio interface."""
Simran Basi5492bde2013-05-16 17:08:47 -0700211 return bbgpio.BBgpio()
Simran Basie750a342013-03-12 13:45:26 -0700212
213 def _init_ftdi_i2c(self, interface):
Todd Broche505b8d2011-03-21 18:19:54 -0700214 """Initialize i2c interface and open for use.
215
216 Args:
217 interface: interface number of FTDI device to use
218
219 Returns:
220 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700221
222 Raises:
223 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700224 """
Todd Brochad034442011-05-25 15:05:29 -0700225 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
226 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700227 try:
228 fobj.open()
229 except ftdii2c.Fi2cError as e:
230 raise ServodError('Opening i2c interface. %s ( %d )' % (e.msg, e.value))
231
Todd Broche505b8d2011-03-21 18:19:54 -0700232 # Set the frequency of operation of the i2c bus.
233 # TODO(tbroch) make configureable
234 fobj.setclock(MAX_I2C_CLOCK_HZ)
Todd Broch6de9dc62012-04-09 15:23:53 -0700235
Todd Broche505b8d2011-03-21 18:19:54 -0700236 return fobj
237
Simran Basie750a342013-03-12 13:45:26 -0700238 # TODO (sbasi) crbug.com/187489 - Implement bb_i2c.
239 def _init_bb_i2c(self, interface):
240 """Initalize beaglebone i2c interface."""
Simran Basia9ad25e2013-04-23 11:57:00 -0700241 return bbi2c.BBi2c(interface)
Simran Basie750a342013-03-12 13:45:26 -0700242
Rong Changc6c8c022014-08-11 14:07:11 +0800243 def _init_dev_i2c(self, interface):
244 """Initalize Linux i2c-dev interface."""
245 return i2cbus.I2CBus('/dev/i2c-%d' % interface['bus_num'])
246
Simran Basie750a342013-03-12 13:45:26 -0700247 def _init_ftdi_uart(self, interface):
248 """Initialize ftdi uart inteface and open for use
Todd Broch47c43f42011-05-26 15:11:31 -0700249
250 Note, the uart runs in a separate thread (pthreads). Users wishing to
251 interact with it will query control for the pty's pathname and connect
252 with there favorite console program. For example:
253 cu -l /dev/pts/22
254
255 Args:
256 interface: interface number of FTDI device to use
257
258 Returns:
259 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700260
261 Raises:
262 ServodError: If init fails
Todd Broch47c43f42011-05-26 15:11:31 -0700263 """
Jeremy Thorpe9e110062012-10-25 10:40:00 -0700264 fobj = ftdiuart.Fuart(self._vendor, self._product, interface,
265 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700266 try:
267 fobj.run()
268 except ftdiuart.FuartError as e:
269 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
270
Todd Broch47c43f42011-05-26 15:11:31 -0700271 self._logger.info("%s" % fobj.get_pty())
272 return fobj
273
Simran Basie750a342013-03-12 13:45:26 -0700274 # TODO (sbasi) crbug.com/187492 - Implement bbuart.
275 def _init_bb_uart(self, interface):
276 """Initalize beaglebone uart interface."""
Simran Basi949309b2013-05-31 15:12:15 -0700277 logging.debug('UART INTERFACE: %s', interface)
278 return bbuart.BBuart(interface)
Simran Basie750a342013-03-12 13:45:26 -0700279
280 def _init_ftdi_gpiouart(self, interface):
Todd Broch888da782011-10-07 14:29:09 -0700281 """Initialize special gpio + uart interface and open for use
282
283 Note, the uart runs in a separate thread (pthreads). Users wishing to
284 interact with it will query control for the pty's pathname and connect
285 with there favorite console program. For example:
286 cu -l /dev/pts/22
287
288 Args:
289 interface: interface number of FTDI device to use
290
291 Returns:
292 Instance objects of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700293
294 Raises:
295 ServodError: If init fails
Todd Broch888da782011-10-07 14:29:09 -0700296 """
Simran Basie750a342013-03-12 13:45:26 -0700297 fgpio = self._init_ftdi_gpio(interface)
Jeremy Thorpe9e110062012-10-25 10:40:00 -0700298 fuart = ftdiuart.Fuart(self._vendor, self._product, interface,
299 self._serialname, fgpio._fc)
Todd Broch6de9dc62012-04-09 15:23:53 -0700300 try:
301 fuart.run()
302 except ftdiuart.FuartError as e:
303 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
304
Todd Broch888da782011-10-07 14:29:09 -0700305 self._logger.info("uart pty: %s" % fuart.get_pty())
306 return fgpio, fuart
307
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800308 def _camel_case(self, string):
309 output = ''
310 for s in string.split('_'):
311 if output:
312 output += s.capitalize()
313 else:
314 output = s
315 return output
316
Todd Broche505b8d2011-03-21 18:19:54 -0700317 def _get_param_drv(self, control_name, is_get=True):
318 """Get access to driver for a given control.
319
320 Note, some controls have different parameter dictionaries for 'getting' the
321 control's value versus 'setting' it. Boolean is_get distinguishes which is
322 being requested.
323
324 Args:
325 control_name: string name of control
326 is_get: boolean to determine
327
328 Returns:
329 tuple (param, drv) where:
330 param: param dictionary for control
331 drv: instance object of driver for particular control
332
333 Raises:
334 ServodError: Error occurred while examining params dict
335 """
336 self._logger.debug("")
337 # if already setup just return tuple from driver dict
338 if control_name in self._drv_dict:
339 if is_get and ('get' in self._drv_dict[control_name]):
340 return self._drv_dict[control_name]['get']
341 if not is_get and ('set' in self._drv_dict[control_name]):
342 return self._drv_dict[control_name]['set']
343
344 params = self._syscfg.lookup_control_params(control_name, is_get)
345 if 'drv' not in params:
346 self._logger.error("Unable to determine driver for %s" % control_name)
347 raise ServodError("'drv' key not found in params dict")
348 if 'interface' not in params:
349 self._logger.error("Unable to determine interface for %s" %
350 control_name)
Todd Broche505b8d2011-03-21 18:19:54 -0700351 raise ServodError("'interface' key not found in params dict")
Simran Basi668be0e2013-08-07 11:54:50 -0700352
J. Richard Barnette275d9fd2014-02-11 14:38:54 -0800353 interface_id = params.get(
354 '%s_interface' % self._version, params['interface'])
355 if interface_id == 'servo':
356 interface = self
Simran Basi668be0e2013-08-07 11:54:50 -0700357 else:
J. Richard Barnette275d9fd2014-02-11 14:38:54 -0800358 index = int(interface_id) - 1
359 interface = self._interface_list[index]
Simran Basi668be0e2013-08-07 11:54:50 -0700360
Todd Broche505b8d2011-03-21 18:19:54 -0700361 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
362 drv_pkg = imp.load_module('drv',
363 *imp.find_module('drv', servo_pkg.__path__))
364 drv_name = params['drv']
365 drv_module = getattr(drv_pkg, drv_name)
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800366 drv_class = getattr(drv_module, self._camel_case(drv_name))
Todd Broche505b8d2011-03-21 18:19:54 -0700367 drv = drv_class(interface, params)
368 if control_name not in self._drv_dict:
369 self._drv_dict[control_name] = {}
370 if is_get:
371 self._drv_dict[control_name]['get'] = (params, drv)
372 else:
373 self._drv_dict[control_name]['set'] = (params, drv)
374 return (params, drv)
375
376 def doc_all(self):
377 """Return all documenation for controls.
378
379 Returns:
380 string of <doc> text in config file (xml) and the params dictionary for
381 all controls.
382
383 For example:
384 warm_reset :: Reset the device warmly
385 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
386 """
387 return self._syscfg.display_config()
388
389 def doc(self, name):
390 """Retreive doc string in system config file for given control name.
391
392 Args:
393 name: name string of control to get doc string
394
395 Returns:
396 doc string of name
397
398 Raises:
399 NameError: if fails to locate control
400 """
401 self._logger.debug("name(%s)" % (name))
402 if self._syscfg.is_control(name):
403 return self._syscfg.get_control_docstring(name)
404 else:
405 raise NameError("No control %s" %name)
406
Fang Deng90377712013-06-03 15:51:48 -0700407 def _switch_usbkey(self, mux_direction):
408 """Connect USB flash stick to either servo or DUT.
409
410 This function switches 'usb_mux_sel1' to provide electrical
411 connection between the USB port J3 and either servo or DUT side.
412
413 Switching the usb mux is accompanied by powercycling
414 of the USB stick, because it sometimes gets wedged if the mux
415 is switched while the stick power is on.
416
417 Args:
418 mux_direction: "servo_sees_usbkey" or "dut_sees_usbkey".
419 """
420 self.set(self._USB_J3_PWR, self._USB_J3_PWR_OFF)
421 time.sleep(self._USB_POWEROFF_DELAY)
422 self.set(self._USB_J3, mux_direction)
423 time.sleep(self._USB_POWEROFF_DELAY)
424 self.set(self._USB_J3_PWR, self._USB_J3_PWR_ON)
425 if mux_direction == self._USB_J3_TO_SERVO:
426 time.sleep(self._USB_DETECTION_DELAY)
427
Simran Basia9f41032012-05-11 14:21:58 -0700428 def _get_usb_port_set(self):
429 """Gets a set of USB disks currently connected to the system
430
431 Returns:
432 A set of USB disk paths.
433 """
434 usb_set = fnmatch.filter(os.listdir("/dev/"), "sd[a-z]")
435 return set(["/dev/" + dev for dev in usb_set])
436
437 def _probe_host_usb_dev(self):
438 """Probe the USB disk device plugged in the servo from the host side.
439
440 Method can fail by:
441 1) Having multiple servos connected and returning incorrect /dev/sdX of
442 another servo.
443 2) Finding multiple /dev/sdX and returning None.
444
445 Returns:
446 USB disk path if one and only one USB disk path is found, otherwise None.
447 """
Fang Deng90377712013-06-03 15:51:48 -0700448 original_value = self.get(self._USB_J3)
449 original_usb_power = self.get(self._USB_J3_PWR)
Simran Basia9f41032012-05-11 14:21:58 -0700450 # Make the host unable to see the USB disk.
Fang Deng90377712013-06-03 15:51:48 -0700451 if (original_usb_power == self._USB_J3_PWR_ON and
452 original_value != self._USB_J3_TO_DUT):
453 self._switch_usbkey(self._USB_J3_TO_DUT)
Simran Basia9f41032012-05-11 14:21:58 -0700454 no_usb_set = self._get_usb_port_set()
Simran Basia9f41032012-05-11 14:21:58 -0700455
Fang Deng90377712013-06-03 15:51:48 -0700456 # Make the host able to see the USB disk.
457 self._switch_usbkey(self._USB_J3_TO_SERVO)
Simran Basia9f41032012-05-11 14:21:58 -0700458 has_usb_set = self._get_usb_port_set()
Fang Deng90377712013-06-03 15:51:48 -0700459
Simran Basia9f41032012-05-11 14:21:58 -0700460 # Back to its original value.
Fang Deng90377712013-06-03 15:51:48 -0700461 if original_value != self._USB_J3_TO_SERVO:
462 self._switch_usbkey(original_value)
463 if original_usb_power != self._USB_J3_PWR_ON:
464 self.set(self._USB_J3_PWR, self._USB_J3_PWR_OFF)
465 time.sleep(self._USB_POWEROFF_DELAY)
466
Simran Basia9f41032012-05-11 14:21:58 -0700467 # Subtract the two sets to find the usb device.
468 diff_set = has_usb_set - no_usb_set
469 if len(diff_set) == 1:
470 return diff_set.pop()
471 else:
472 return None
473
474 def download_image_to_usb(self, image_path):
475 """Download image and save to the USB device found by probe_host_usb_dev.
476 If the image_path is a URL, it will download this url to the USB path;
477 otherwise it will simply copy the image_path's contents to the USB path.
478
479 Args:
480 image_path: path or url to the recovery image.
481
482 Returns:
483 True|False: True if process completed successfully, False if error
484 occurred.
485 Can't return None because XMLRPC doesn't allow it. PTAL at tbroch's
486 comment at the end of set().
487 """
488 self._logger.debug("image_path(%s)" % image_path)
489 self._logger.debug("Detecting USB stick device...")
490 usb_dev = self._probe_host_usb_dev()
491 if not usb_dev:
492 self._logger.error("No usb device connected to servo")
493 return False
494
495 try:
496 if image_path.startswith(self._HTTP_PREFIX):
497 self._logger.debug("Image path is a URL, downloading image")
498 urllib.urlretrieve(image_path, usb_dev)
499 else:
500 shutil.copyfile(image_path, usb_dev)
501 except IOError as e:
502 self._logger.error("Failed to transfer image to USB device: %s ( %d ) ",
503 e.strerror, e.errno)
504 return False
505 except urllib.ContentTooShortError:
506 self._logger.error("Failed to download URL: %s to USB device: %s",
507 image_path, usb_dev)
508 return False
509 except BaseException as e:
510 self._logger.error("Unexpected exception downloading %s to %s: %s",
511 image_path, usb_dev, str(e))
512 return False
J. Richard Barnettee4125af2013-02-26 18:31:56 -0800513 finally:
514 # We just plastered the partition table for a block device.
515 # Pass or fail, we mustn't go without telling the kernel about
516 # the change, or it will punish us with sporadic, hard-to-debug
517 # failures.
518 subprocess.call(["sync"])
519 subprocess.call(["blockdev", "--rereadpt", usb_dev])
Simran Basia9f41032012-05-11 14:21:58 -0700520 return True
521
522 def make_image_noninteractive(self):
523 """Makes the recovery image noninteractive.
524
525 A noninteractive image will reboot automatically after installation
526 instead of waiting for the USB device to be removed to initiate a system
527 reboot.
528
529 Mounts partition 1 of the image stored on usb_dev and creates a file
530 called "non_interactive" so that the image will become noninteractive.
531
532 Returns:
533 True|False: True if process completed successfully, False if error
534 occurred.
535 """
536 result = True
537 usb_dev = self._probe_host_usb_dev()
538 if not usb_dev:
539 self._logger.error("No usb device connected to servo")
540 return False
541 # Create TempDirectory
542 tmpdir = tempfile.mkdtemp()
543 if tmpdir:
544 # Mount drive to tmpdir.
545 partition_1 = "%s1" % usb_dev
546 rc = subprocess.call(["mount", partition_1, tmpdir])
547 if rc == 0:
548 # Create file 'non_interactive'
549 non_interactive_file = os.path.join(tmpdir, "non_interactive")
550 try:
551 open(non_interactive_file, "w").close()
552 except IOError as e:
553 self._logger.error("Failed to create file %s : %s ( %d )",
554 non_interactive_file, e.strerror, e.errno)
555 result = False
556 except BaseException as e:
557 self._logger.error("Unexpected Exception creating file %s : %s",
558 non_interactive_file, str(e))
559 result = False
560 # Unmount drive regardless if file creation worked or not.
561 rc = subprocess.call(["umount", partition_1])
562 if rc != 0:
563 self._logger.error("Failed to unmount USB Device")
564 result = False
565 else:
566 self._logger.error("Failed to mount USB Device")
567 result = False
568
569 # Delete tmpdir. May throw exception if 'umount' failed.
570 try:
571 os.rmdir(tmpdir)
572 except OSError as e:
573 self._logger.error("Failed to remove temp directory %s : %s",
574 tmpdir, str(e))
575 return False
576 except BaseException as e:
577 self._logger.error("Unexpected Exception removing tempdir %s : %s",
578 tmpdir, str(e))
579 return False
580 else:
581 self._logger.error("Failed to create temp directory.")
582 return False
583 return result
584
Todd Broch352b4b22013-03-22 09:48:40 -0700585 def set_get_all(self, cmds):
586 """Set &| get one or more control values.
587
588 Args:
589 cmds: list of control[:value] to get or set.
590
591 Returns:
592 rv: list of responses from calling get or set methods.
593 """
594 rv = []
595 for cmd in cmds:
596 if ':' in cmd:
597 (control, value) = cmd.split(':')
598 rv.append(self.set(control, value))
599 else:
600 rv.append(self.get(cmd))
601 return rv
602
Todd Broche505b8d2011-03-21 18:19:54 -0700603 def get(self, name):
604 """Get control value.
605
606 Args:
607 name: name string of control
608
609 Returns:
610 Response from calling drv get method. Value is reformatted based on
611 control's dictionary parameters
612
613 Raises:
614 HwDriverError: Error occurred while using drv
615 """
616 self._logger.debug("name(%s)" % (name))
Vadim Bendeburyc3a83cf2015-03-24 13:07:00 -0700617 if name == 'serialname':
618 if self._serialname:
619 return self._serialname
620 return 'unknown'
Todd Broche505b8d2011-03-21 18:19:54 -0700621 (param, drv) = self._get_param_drv(name)
622 try:
623 val = drv.get()
624 rd_val = self._syscfg.reformat_val(param, val)
Todd Brochb042e7a2011-12-14 17:41:36 -0800625 self._logger.debug("%s = %s" % (name, rd_val))
Todd Broche505b8d2011-03-21 18:19:54 -0700626 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700627 except AttributeError, error:
628 self._logger.error("Getting %s: %s" % (name, error))
629 raise
Vic Yangbe6cf262012-09-10 10:40:56 +0800630 except HwDriverError:
Todd Broche505b8d2011-03-21 18:19:54 -0700631 self._logger.error("Getting %s" % (name))
632 raise
Todd Brochd6061672012-05-11 15:52:47 -0700633
Todd Broche505b8d2011-03-21 18:19:54 -0700634 def get_all(self, verbose):
635 """Get all controls values.
636
637 Args:
638 verbose: Boolean on whether to return doc info as well
639
640 Returns:
641 string creating from trying to get all values of all controls. In case of
642 error attempting access to control, response is 'ERR'.
643 """
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800644 rsp = []
Todd Broche505b8d2011-03-21 18:19:54 -0700645 for name in self._syscfg.syscfg_dict['control']:
646 self._logger.debug("name = %s" %name)
647 try:
648 value = self.get(name)
649 except Exception:
650 value = "ERR"
651 pass
652 if verbose:
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800653 rsp.append("GET %s = %s :: %s" % (name, value, self.doc(name)))
Todd Broche505b8d2011-03-21 18:19:54 -0700654 else:
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800655 rsp.append("%s:%s" % (name, value))
656 return '\n'.join(sorted(rsp))
Todd Broche505b8d2011-03-21 18:19:54 -0700657
658 def set(self, name, wr_val_str):
659 """Set control.
660
661 Args:
662 name: name string of control
663 wr_val_str: value string to write. Can be integer, float or a
664 alpha-numerical that is mapped to a integer or float.
665
666 Raises:
667 HwDriverError: Error occurred while using driver
668 """
669 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
670 (params, drv) = self._get_param_drv(name, False)
671 wr_val = self._syscfg.resolve_val(params, wr_val_str)
672 try:
673 drv.set(wr_val)
Vic Yangbe6cf262012-09-10 10:40:56 +0800674 except HwDriverError:
Todd Broche505b8d2011-03-21 18:19:54 -0700675 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
676 raise
677 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
678 # & client I still have to return something to appease the
679 # marshall/unmarshall
680 return True
681
Todd Brochd6061672012-05-11 15:52:47 -0700682 def hwinit(self, verbose=False):
683 """Initialize all controls.
684
685 These values are part of the system config XML files of the form
686 init=<value>. This command should be used by clients wishing to return the
687 servo and DUT its connected to a known good/safe state.
688
Vadim Bendeburybb51dd42013-01-31 13:47:46 -0800689 Note that initialization errors are ignored (as in some cases they could
690 be caused by DUT firmware deficiencies). This might need to be fine tuned
691 later.
692
Todd Brochd6061672012-05-11 15:52:47 -0700693 Args:
694 verbose: boolean, if True prints info about control initialized.
695 Otherwise prints nothing.
Vadim Bendebury5934e4b2013-02-06 13:57:54 -0800696
697 Returns:
698 This function is called across RPC and as such is expected to return
699 something unless transferring 'none' across is allowed. Hence adding a
700 dummy return value to make things simpler.
Todd Brochd6061672012-05-11 15:52:47 -0700701 """
Todd Brochd9acf0a2012-12-05 13:43:06 -0800702 for control_name, value in self._syscfg.hwinit:
Todd Broch3ec8df02012-11-20 10:53:03 -0800703 try:
John Carey6fe2bbf2015-08-31 16:13:03 -0700704 # Workaround for bug chrome-os-partner:42349. Without this check, the
705 # gpio will briefly pulse low if we set it from high to high.
706 if self.get(control_name) != value:
707 self.set(control_name, value)
Todd Broch3ec8df02012-11-20 10:53:03 -0800708 except Exception as e:
Todd Broch3ec8df02012-11-20 10:53:03 -0800709 self._logger.error("Problem initializing %s -> %s :: %s",
710 control_name, value, str(e))
Todd Brochd6061672012-05-11 15:52:47 -0700711 if verbose:
712 self._logger.info('Initialized %s to %s', control_name, value)
Nick Sandersbc836282015-12-08 21:19:23 -0800713
714 # Init keyboard after all the intefaces are up.
715 self._keyboard = self._init_keyboard_handler(self, self._board)
Vadim Bendebury5934e4b2013-02-06 13:57:54 -0800716 return True
Todd Broch3ec8df02012-11-20 10:53:03 -0800717
Todd Broche505b8d2011-03-21 18:19:54 -0700718 def echo(self, echo):
719 """Dummy echo function for testing/examples.
720
721 Args:
722 echo: string to echo back to client
723 """
724 self._logger.debug("echo(%s)" % (echo))
725 return "ECH0ING: %s" % (echo)
726
J. Richard Barnettee2820552013-03-14 16:13:46 -0700727 def get_board(self):
728 """Return the board specified at startup, if any."""
729 return self._board
730
Simran Basia23c1392013-08-06 14:59:10 -0700731 def get_version(self):
732 """Get servo board version."""
733 return self._version
734
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800735 def power_long_press(self):
736 """Simulate a long power button press."""
737 # After a long power press, the EC may ignore the next power
738 # button press (at least on Alex). To guarantee that this
739 # won't happen, we need to allow the EC one second to
740 # collect itself.
741 self._keyboard.power_long_press()
742 return True
743
744 def power_normal_press(self):
745 """Simulate a normal power button press."""
746 self._keyboard.power_normal_press()
747 return True
748
749 def power_short_press(self):
750 """Simulate a short power button press."""
751 self._keyboard.power_short_press()
752 return True
753
754 def power_key(self, secs=''):
755 """Simulate a power button press.
756
757 Args:
758 secs: Time in seconds to simulate the keypress.
759 """
760 self._keyboard.power_key(secs)
761 return True
762
763 def ctrl_d(self, press_secs=''):
764 """Simulate Ctrl-d simultaneous button presses."""
765 self._keyboard.ctrl_d(press_secs)
766 return True
767
768 def ctrl_u(self):
769 """Simulate Ctrl-u simultaneous button presses."""
770 self._keyboard.ctrl_u()
771 return True
772
773 def ctrl_enter(self, press_secs=''):
774 """Simulate Ctrl-enter simultaneous button presses."""
775 self._keyboard.ctrl_enter(press_secs)
776 return True
777
778 def d_key(self, press_secs=''):
779 """Simulate Enter key button press."""
780 self._keyboard.d_key(press_secs)
781 return True
782
783 def ctrl_key(self, press_secs=''):
784 """Simulate Enter key button press."""
785 self._keyboard.ctrl_key(press_secs)
786 return True
787
788 def enter_key(self, press_secs=''):
789 """Simulate Enter key button press."""
790 self._keyboard.enter_key(press_secs)
791 return True
792
793 def refresh_key(self, press_secs=''):
794 """Simulate Refresh key (F3) button press."""
795 self._keyboard.refresh_key(press_secs)
796 return True
797
798 def ctrl_refresh_key(self, press_secs=''):
799 """Simulate Ctrl and Refresh (F3) simultaneous press.
800
801 This key combination is an alternative of Space key.
802 """
803 self._keyboard.ctrl_refresh_key(press_secs)
804 return True
805
806 def imaginary_key(self, press_secs=''):
807 """Simulate imaginary key button press.
808
809 Maps to a key that doesn't physically exist.
810 """
811 self._keyboard.imaginary_key(press_secs)
812 return True
813
Todd Brochdbb09982011-10-02 07:14:26 -0700814
Todd Broche505b8d2011-03-21 18:19:54 -0700815def test():
816 """Integration testing.
817
818 TODO(tbroch) Enhance integration test and add unittest (see mox)
819 """
820 logging.basicConfig(level=logging.DEBUG,
821 format="%(asctime)s - %(name)s - " +
822 "%(levelname)s - %(message)s")
823 # configure server & listen
824 servod_obj = Servod(1)
825 # 4 == number of interfaces on a FT4232H device
826 for i in xrange(4):
827 if i == 1:
828 # its an i2c interface ... see __init__ for details and TODO to make
829 # this configureable
830 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
831 else:
832 # its a gpio interface
833 servod_obj._interface_list[i].wr_rd(0)
834
835 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
836 allow_none=True)
837 server.register_introspection_functions()
838 server.register_multicall_functions()
839 server.register_instance(servod_obj)
840 logging.info("Listening on localhost port 9999")
841 server.serve_forever()
842
843if __name__ == "__main__":
844 test()
845
846 # simple client transaction would look like
847 """
848 remote_uri = 'http://localhost:9999'
849 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
850 send_str = "Hello_there"
851 print "Sent " + send_str + ", Recv " + client.echo(send_str)
852 """