blob: db9a38b62cf55fae1b72f3deb9c11e04910fbcdd [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
63 keyboards. Used in FAFT tests.
64 e.g. /dev/ttyUSB0
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
84
85 self._keyboard = self._init_keyboard_handler(self, self._board)
Todd Broche505b8d2011-03-21 18:19:54 -070086
Todd Brochdbb09982011-10-02 07:14:26 -070087 # Note, interface i is (i - 1) in list
88 if not interfaces:
Todd Brochb21d8042014-05-15 12:54:54 -070089 try:
90 interfaces = servo_interfaces.INTERFACE_BOARDS[board][vendor][product]
91 except KeyError:
92 interfaces = servo_interfaces.INTERFACE_DEFAULTS[vendor][product]
Todd Brochdbb09982011-10-02 07:14:26 -070093
Simran Basia9ad25e2013-04-23 11:57:00 -070094 for i, interface in enumerate(interfaces):
95 is_ftdi_interface = False
96 if type(interface) is dict:
97 name = interface['name']
98 elif type(interface) is str:
99 # Its FTDI related interface
100 name = interface
101 interface = (i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) + 1
102 is_ftdi_interface = True
103 else:
104 raise ServodError("Illegal interface type %s" % type(interface))
105
Todd Broch8a77a992012-01-27 09:46:08 -0800106 # servos with multiple FTDI are guaranteed to have contiguous USB PIDs
Simran Basia9ad25e2013-04-23 11:57:00 -0700107 if is_ftdi_interface and i and \
108 ((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) == 0):
Todd Broch8a77a992012-01-27 09:46:08 -0800109 self._product += 1
110 self._logger.info("Changing to next FTDI part @ pid = 0x%04x",
111 self._product)
112
Simran Basia9ad25e2013-04-23 11:57:00 -0700113 self._logger.info("Initializing interface %d to %s", i + 1, name)
Todd Brochdbb09982011-10-02 07:14:26 -0700114 try:
115 func = getattr(self, '_init_%s' % name)
116 except AttributeError:
117 raise ServodError("Unable to locate init for interface %s" % name)
Simran Basia9ad25e2013-04-23 11:57:00 -0700118 result = func(interface)
Todd Broch888da782011-10-07 14:29:09 -0700119 if isinstance(result, tuple):
120 self._interface_list.extend(result)
121 else:
122 self._interface_list.append(result)
Todd Broche505b8d2011-03-21 18:19:54 -0700123
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800124 def _init_keyboard_handler(self, servo, board=''):
125 """Initialize the correct keyboard handler for board.
126
127 @param servo: servo object.
128 @param board: string, board name.
129
130 """
131 if board == 'parrot':
132 return keyboard_handlers.ParrotHandler(servo)
133 elif board == 'stout':
134 return keyboard_handlers.StoutHandler(servo)
eddyluac1a0a12014-11-14 12:47:55 +0800135 elif board in ('cranky', 'mccloud', 'monroe', 'nyan_kitty', 'panther',
136 'tricky', '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 "
139 "the DefaultHandler, which is likely the wrong keyboard "
140 "handler for the board type specified.")
141 return keyboard_handlers.DefaultHandler(servo)
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800142 return keyboard_handlers.USBkm232Handler(servo, self._usbkm232)
143 else:
144 return keyboard_handlers.DefaultHandler(servo)
145
Todd Broch3ec8df02012-11-20 10:53:03 -0800146 def __del__(self):
147 """Servod deconstructor."""
148 for interface in self._interface_list:
149 del(interface)
150
Todd Brochb3048492012-01-15 21:52:41 -0800151 def _init_dummy(self, interface):
152 """Initialize dummy interface.
153
154 Dummy interface is just a mechanism to reserve that interface for non servod
155 interaction. Typically the interface will be managed by external
156 third-party tools like openOCD or urjtag for JTAG or flashrom for SPI
157 interfaces.
158
159 TODO(tbroch): Investigate merits of incorporating these third-party
160 interfaces into servod or creating a communication channel between them
161
162 Returns: None
163 """
164 return None
165
Simran Basie750a342013-03-12 13:45:26 -0700166 def _init_ftdi_gpio(self, interface):
Todd Broche505b8d2011-03-21 18:19:54 -0700167 """Initialize gpio driver interface and open for use.
168
169 Args:
170 interface: interface number of FTDI device to use.
171
172 Returns:
173 Instance object of interface.
Todd Broch6de9dc62012-04-09 15:23:53 -0700174
175 Raises:
176 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700177 """
Todd Brochad034442011-05-25 15:05:29 -0700178 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
179 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700180 try:
181 fobj.open()
182 except ftdigpio.FgpioError as e:
183 raise ServodError('Opening gpio interface. %s ( %d )' % (e.msg, e.value))
184
Todd Broche505b8d2011-03-21 18:19:54 -0700185 return fobj
186
Aaron.Chuang88eff332014-07-31 08:32:00 +0800187 def _init_bb_adc(self, interface):
188 """Initalize beaglebone ADC interface."""
189 return bbadc.BBadc()
190
Simran Basie750a342013-03-12 13:45:26 -0700191 def _init_bb_gpio(self, interface):
192 """Initalize beaglebone gpio interface."""
Simran Basi5492bde2013-05-16 17:08:47 -0700193 return bbgpio.BBgpio()
Simran Basie750a342013-03-12 13:45:26 -0700194
195 def _init_ftdi_i2c(self, interface):
Todd Broche505b8d2011-03-21 18:19:54 -0700196 """Initialize i2c interface and open for use.
197
198 Args:
199 interface: interface number of FTDI device to use
200
201 Returns:
202 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700203
204 Raises:
205 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700206 """
Todd Brochad034442011-05-25 15:05:29 -0700207 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
208 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700209 try:
210 fobj.open()
211 except ftdii2c.Fi2cError as e:
212 raise ServodError('Opening i2c interface. %s ( %d )' % (e.msg, e.value))
213
Todd Broche505b8d2011-03-21 18:19:54 -0700214 # Set the frequency of operation of the i2c bus.
215 # TODO(tbroch) make configureable
216 fobj.setclock(MAX_I2C_CLOCK_HZ)
Todd Broch6de9dc62012-04-09 15:23:53 -0700217
Todd Broche505b8d2011-03-21 18:19:54 -0700218 return fobj
219
Simran Basie750a342013-03-12 13:45:26 -0700220 # TODO (sbasi) crbug.com/187489 - Implement bb_i2c.
221 def _init_bb_i2c(self, interface):
222 """Initalize beaglebone i2c interface."""
Simran Basia9ad25e2013-04-23 11:57:00 -0700223 return bbi2c.BBi2c(interface)
Simran Basie750a342013-03-12 13:45:26 -0700224
Rong Changc6c8c022014-08-11 14:07:11 +0800225 def _init_dev_i2c(self, interface):
226 """Initalize Linux i2c-dev interface."""
227 return i2cbus.I2CBus('/dev/i2c-%d' % interface['bus_num'])
228
Simran Basie750a342013-03-12 13:45:26 -0700229 def _init_ftdi_uart(self, interface):
230 """Initialize ftdi uart inteface and open for use
Todd Broch47c43f42011-05-26 15:11:31 -0700231
232 Note, the uart runs in a separate thread (pthreads). Users wishing to
233 interact with it will query control for the pty's pathname and connect
234 with there favorite console program. For example:
235 cu -l /dev/pts/22
236
237 Args:
238 interface: interface number of FTDI device to use
239
240 Returns:
241 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700242
243 Raises:
244 ServodError: If init fails
Todd Broch47c43f42011-05-26 15:11:31 -0700245 """
Jeremy Thorpe9e110062012-10-25 10:40:00 -0700246 fobj = ftdiuart.Fuart(self._vendor, self._product, interface,
247 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700248 try:
249 fobj.run()
250 except ftdiuart.FuartError as e:
251 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
252
Todd Broch47c43f42011-05-26 15:11:31 -0700253 self._logger.info("%s" % fobj.get_pty())
254 return fobj
255
Simran Basie750a342013-03-12 13:45:26 -0700256 # TODO (sbasi) crbug.com/187492 - Implement bbuart.
257 def _init_bb_uart(self, interface):
258 """Initalize beaglebone uart interface."""
Simran Basi949309b2013-05-31 15:12:15 -0700259 logging.debug('UART INTERFACE: %s', interface)
260 return bbuart.BBuart(interface)
Simran Basie750a342013-03-12 13:45:26 -0700261
262 def _init_ftdi_gpiouart(self, interface):
Todd Broch888da782011-10-07 14:29:09 -0700263 """Initialize special gpio + uart interface and open for use
264
265 Note, the uart runs in a separate thread (pthreads). Users wishing to
266 interact with it will query control for the pty's pathname and connect
267 with there favorite console program. For example:
268 cu -l /dev/pts/22
269
270 Args:
271 interface: interface number of FTDI device to use
272
273 Returns:
274 Instance objects of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700275
276 Raises:
277 ServodError: If init fails
Todd Broch888da782011-10-07 14:29:09 -0700278 """
Simran Basie750a342013-03-12 13:45:26 -0700279 fgpio = self._init_ftdi_gpio(interface)
Jeremy Thorpe9e110062012-10-25 10:40:00 -0700280 fuart = ftdiuart.Fuart(self._vendor, self._product, interface,
281 self._serialname, fgpio._fc)
Todd Broch6de9dc62012-04-09 15:23:53 -0700282 try:
283 fuart.run()
284 except ftdiuart.FuartError as e:
285 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
286
Todd Broch888da782011-10-07 14:29:09 -0700287 self._logger.info("uart pty: %s" % fuart.get_pty())
288 return fgpio, fuart
289
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800290 def _camel_case(self, string):
291 output = ''
292 for s in string.split('_'):
293 if output:
294 output += s.capitalize()
295 else:
296 output = s
297 return output
298
Todd Broche505b8d2011-03-21 18:19:54 -0700299 def _get_param_drv(self, control_name, is_get=True):
300 """Get access to driver for a given control.
301
302 Note, some controls have different parameter dictionaries for 'getting' the
303 control's value versus 'setting' it. Boolean is_get distinguishes which is
304 being requested.
305
306 Args:
307 control_name: string name of control
308 is_get: boolean to determine
309
310 Returns:
311 tuple (param, drv) where:
312 param: param dictionary for control
313 drv: instance object of driver for particular control
314
315 Raises:
316 ServodError: Error occurred while examining params dict
317 """
318 self._logger.debug("")
319 # if already setup just return tuple from driver dict
320 if control_name in self._drv_dict:
321 if is_get and ('get' in self._drv_dict[control_name]):
322 return self._drv_dict[control_name]['get']
323 if not is_get and ('set' in self._drv_dict[control_name]):
324 return self._drv_dict[control_name]['set']
325
326 params = self._syscfg.lookup_control_params(control_name, is_get)
327 if 'drv' not in params:
328 self._logger.error("Unable to determine driver for %s" % control_name)
329 raise ServodError("'drv' key not found in params dict")
330 if 'interface' not in params:
331 self._logger.error("Unable to determine interface for %s" %
332 control_name)
Todd Broche505b8d2011-03-21 18:19:54 -0700333 raise ServodError("'interface' key not found in params dict")
Simran Basi668be0e2013-08-07 11:54:50 -0700334
J. Richard Barnette275d9fd2014-02-11 14:38:54 -0800335 interface_id = params.get(
336 '%s_interface' % self._version, params['interface'])
337 if interface_id == 'servo':
338 interface = self
Simran Basi668be0e2013-08-07 11:54:50 -0700339 else:
J. Richard Barnette275d9fd2014-02-11 14:38:54 -0800340 index = int(interface_id) - 1
341 interface = self._interface_list[index]
Simran Basi668be0e2013-08-07 11:54:50 -0700342
Todd Broche505b8d2011-03-21 18:19:54 -0700343 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
344 drv_pkg = imp.load_module('drv',
345 *imp.find_module('drv', servo_pkg.__path__))
346 drv_name = params['drv']
347 drv_module = getattr(drv_pkg, drv_name)
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800348 drv_class = getattr(drv_module, self._camel_case(drv_name))
Todd Broche505b8d2011-03-21 18:19:54 -0700349 drv = drv_class(interface, params)
350 if control_name not in self._drv_dict:
351 self._drv_dict[control_name] = {}
352 if is_get:
353 self._drv_dict[control_name]['get'] = (params, drv)
354 else:
355 self._drv_dict[control_name]['set'] = (params, drv)
356 return (params, drv)
357
358 def doc_all(self):
359 """Return all documenation for controls.
360
361 Returns:
362 string of <doc> text in config file (xml) and the params dictionary for
363 all controls.
364
365 For example:
366 warm_reset :: Reset the device warmly
367 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
368 """
369 return self._syscfg.display_config()
370
371 def doc(self, name):
372 """Retreive doc string in system config file for given control name.
373
374 Args:
375 name: name string of control to get doc string
376
377 Returns:
378 doc string of name
379
380 Raises:
381 NameError: if fails to locate control
382 """
383 self._logger.debug("name(%s)" % (name))
384 if self._syscfg.is_control(name):
385 return self._syscfg.get_control_docstring(name)
386 else:
387 raise NameError("No control %s" %name)
388
Fang Deng90377712013-06-03 15:51:48 -0700389 def _switch_usbkey(self, mux_direction):
390 """Connect USB flash stick to either servo or DUT.
391
392 This function switches 'usb_mux_sel1' to provide electrical
393 connection between the USB port J3 and either servo or DUT side.
394
395 Switching the usb mux is accompanied by powercycling
396 of the USB stick, because it sometimes gets wedged if the mux
397 is switched while the stick power is on.
398
399 Args:
400 mux_direction: "servo_sees_usbkey" or "dut_sees_usbkey".
401 """
402 self.set(self._USB_J3_PWR, self._USB_J3_PWR_OFF)
403 time.sleep(self._USB_POWEROFF_DELAY)
404 self.set(self._USB_J3, mux_direction)
405 time.sleep(self._USB_POWEROFF_DELAY)
406 self.set(self._USB_J3_PWR, self._USB_J3_PWR_ON)
407 if mux_direction == self._USB_J3_TO_SERVO:
408 time.sleep(self._USB_DETECTION_DELAY)
409
Simran Basia9f41032012-05-11 14:21:58 -0700410 def _get_usb_port_set(self):
411 """Gets a set of USB disks currently connected to the system
412
413 Returns:
414 A set of USB disk paths.
415 """
416 usb_set = fnmatch.filter(os.listdir("/dev/"), "sd[a-z]")
417 return set(["/dev/" + dev for dev in usb_set])
418
419 def _probe_host_usb_dev(self):
420 """Probe the USB disk device plugged in the servo from the host side.
421
422 Method can fail by:
423 1) Having multiple servos connected and returning incorrect /dev/sdX of
424 another servo.
425 2) Finding multiple /dev/sdX and returning None.
426
427 Returns:
428 USB disk path if one and only one USB disk path is found, otherwise None.
429 """
Fang Deng90377712013-06-03 15:51:48 -0700430 original_value = self.get(self._USB_J3)
431 original_usb_power = self.get(self._USB_J3_PWR)
Simran Basia9f41032012-05-11 14:21:58 -0700432 # Make the host unable to see the USB disk.
Fang Deng90377712013-06-03 15:51:48 -0700433 if (original_usb_power == self._USB_J3_PWR_ON and
434 original_value != self._USB_J3_TO_DUT):
435 self._switch_usbkey(self._USB_J3_TO_DUT)
Simran Basia9f41032012-05-11 14:21:58 -0700436 no_usb_set = self._get_usb_port_set()
Simran Basia9f41032012-05-11 14:21:58 -0700437
Fang Deng90377712013-06-03 15:51:48 -0700438 # Make the host able to see the USB disk.
439 self._switch_usbkey(self._USB_J3_TO_SERVO)
Simran Basia9f41032012-05-11 14:21:58 -0700440 has_usb_set = self._get_usb_port_set()
Fang Deng90377712013-06-03 15:51:48 -0700441
Simran Basia9f41032012-05-11 14:21:58 -0700442 # Back to its original value.
Fang Deng90377712013-06-03 15:51:48 -0700443 if original_value != self._USB_J3_TO_SERVO:
444 self._switch_usbkey(original_value)
445 if original_usb_power != self._USB_J3_PWR_ON:
446 self.set(self._USB_J3_PWR, self._USB_J3_PWR_OFF)
447 time.sleep(self._USB_POWEROFF_DELAY)
448
Simran Basia9f41032012-05-11 14:21:58 -0700449 # Subtract the two sets to find the usb device.
450 diff_set = has_usb_set - no_usb_set
451 if len(diff_set) == 1:
452 return diff_set.pop()
453 else:
454 return None
455
456 def download_image_to_usb(self, image_path):
457 """Download image and save to the USB device found by probe_host_usb_dev.
458 If the image_path is a URL, it will download this url to the USB path;
459 otherwise it will simply copy the image_path's contents to the USB path.
460
461 Args:
462 image_path: path or url to the recovery image.
463
464 Returns:
465 True|False: True if process completed successfully, False if error
466 occurred.
467 Can't return None because XMLRPC doesn't allow it. PTAL at tbroch's
468 comment at the end of set().
469 """
470 self._logger.debug("image_path(%s)" % image_path)
471 self._logger.debug("Detecting USB stick device...")
472 usb_dev = self._probe_host_usb_dev()
473 if not usb_dev:
474 self._logger.error("No usb device connected to servo")
475 return False
476
477 try:
478 if image_path.startswith(self._HTTP_PREFIX):
479 self._logger.debug("Image path is a URL, downloading image")
480 urllib.urlretrieve(image_path, usb_dev)
481 else:
482 shutil.copyfile(image_path, usb_dev)
483 except IOError as e:
484 self._logger.error("Failed to transfer image to USB device: %s ( %d ) ",
485 e.strerror, e.errno)
486 return False
487 except urllib.ContentTooShortError:
488 self._logger.error("Failed to download URL: %s to USB device: %s",
489 image_path, usb_dev)
490 return False
491 except BaseException as e:
492 self._logger.error("Unexpected exception downloading %s to %s: %s",
493 image_path, usb_dev, str(e))
494 return False
J. Richard Barnettee4125af2013-02-26 18:31:56 -0800495 finally:
496 # We just plastered the partition table for a block device.
497 # Pass or fail, we mustn't go without telling the kernel about
498 # the change, or it will punish us with sporadic, hard-to-debug
499 # failures.
500 subprocess.call(["sync"])
501 subprocess.call(["blockdev", "--rereadpt", usb_dev])
Simran Basia9f41032012-05-11 14:21:58 -0700502 return True
503
504 def make_image_noninteractive(self):
505 """Makes the recovery image noninteractive.
506
507 A noninteractive image will reboot automatically after installation
508 instead of waiting for the USB device to be removed to initiate a system
509 reboot.
510
511 Mounts partition 1 of the image stored on usb_dev and creates a file
512 called "non_interactive" so that the image will become noninteractive.
513
514 Returns:
515 True|False: True if process completed successfully, False if error
516 occurred.
517 """
518 result = True
519 usb_dev = self._probe_host_usb_dev()
520 if not usb_dev:
521 self._logger.error("No usb device connected to servo")
522 return False
523 # Create TempDirectory
524 tmpdir = tempfile.mkdtemp()
525 if tmpdir:
526 # Mount drive to tmpdir.
527 partition_1 = "%s1" % usb_dev
528 rc = subprocess.call(["mount", partition_1, tmpdir])
529 if rc == 0:
530 # Create file 'non_interactive'
531 non_interactive_file = os.path.join(tmpdir, "non_interactive")
532 try:
533 open(non_interactive_file, "w").close()
534 except IOError as e:
535 self._logger.error("Failed to create file %s : %s ( %d )",
536 non_interactive_file, e.strerror, e.errno)
537 result = False
538 except BaseException as e:
539 self._logger.error("Unexpected Exception creating file %s : %s",
540 non_interactive_file, str(e))
541 result = False
542 # Unmount drive regardless if file creation worked or not.
543 rc = subprocess.call(["umount", partition_1])
544 if rc != 0:
545 self._logger.error("Failed to unmount USB Device")
546 result = False
547 else:
548 self._logger.error("Failed to mount USB Device")
549 result = False
550
551 # Delete tmpdir. May throw exception if 'umount' failed.
552 try:
553 os.rmdir(tmpdir)
554 except OSError as e:
555 self._logger.error("Failed to remove temp directory %s : %s",
556 tmpdir, str(e))
557 return False
558 except BaseException as e:
559 self._logger.error("Unexpected Exception removing tempdir %s : %s",
560 tmpdir, str(e))
561 return False
562 else:
563 self._logger.error("Failed to create temp directory.")
564 return False
565 return result
566
Todd Broch352b4b22013-03-22 09:48:40 -0700567 def set_get_all(self, cmds):
568 """Set &| get one or more control values.
569
570 Args:
571 cmds: list of control[:value] to get or set.
572
573 Returns:
574 rv: list of responses from calling get or set methods.
575 """
576 rv = []
577 for cmd in cmds:
578 if ':' in cmd:
579 (control, value) = cmd.split(':')
580 rv.append(self.set(control, value))
581 else:
582 rv.append(self.get(cmd))
583 return rv
584
Todd Broche505b8d2011-03-21 18:19:54 -0700585 def get(self, name):
586 """Get control value.
587
588 Args:
589 name: name string of control
590
591 Returns:
592 Response from calling drv get method. Value is reformatted based on
593 control's dictionary parameters
594
595 Raises:
596 HwDriverError: Error occurred while using drv
597 """
598 self._logger.debug("name(%s)" % (name))
599 (param, drv) = self._get_param_drv(name)
600 try:
601 val = drv.get()
602 rd_val = self._syscfg.reformat_val(param, val)
Todd Brochb042e7a2011-12-14 17:41:36 -0800603 self._logger.debug("%s = %s" % (name, rd_val))
Todd Broche505b8d2011-03-21 18:19:54 -0700604 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700605 except AttributeError, error:
606 self._logger.error("Getting %s: %s" % (name, error))
607 raise
Vic Yangbe6cf262012-09-10 10:40:56 +0800608 except HwDriverError:
Todd Broche505b8d2011-03-21 18:19:54 -0700609 self._logger.error("Getting %s" % (name))
610 raise
Todd Brochd6061672012-05-11 15:52:47 -0700611
Todd Broche505b8d2011-03-21 18:19:54 -0700612 def get_all(self, verbose):
613 """Get all controls values.
614
615 Args:
616 verbose: Boolean on whether to return doc info as well
617
618 Returns:
619 string creating from trying to get all values of all controls. In case of
620 error attempting access to control, response is 'ERR'.
621 """
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800622 rsp = []
Todd Broche505b8d2011-03-21 18:19:54 -0700623 for name in self._syscfg.syscfg_dict['control']:
624 self._logger.debug("name = %s" %name)
625 try:
626 value = self.get(name)
627 except Exception:
628 value = "ERR"
629 pass
630 if verbose:
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800631 rsp.append("GET %s = %s :: %s" % (name, value, self.doc(name)))
Todd Broche505b8d2011-03-21 18:19:54 -0700632 else:
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800633 rsp.append("%s:%s" % (name, value))
634 return '\n'.join(sorted(rsp))
Todd Broche505b8d2011-03-21 18:19:54 -0700635
636 def set(self, name, wr_val_str):
637 """Set control.
638
639 Args:
640 name: name string of control
641 wr_val_str: value string to write. Can be integer, float or a
642 alpha-numerical that is mapped to a integer or float.
643
644 Raises:
645 HwDriverError: Error occurred while using driver
646 """
647 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
648 (params, drv) = self._get_param_drv(name, False)
649 wr_val = self._syscfg.resolve_val(params, wr_val_str)
650 try:
651 drv.set(wr_val)
Vic Yangbe6cf262012-09-10 10:40:56 +0800652 except HwDriverError:
Todd Broche505b8d2011-03-21 18:19:54 -0700653 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
654 raise
655 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
656 # & client I still have to return something to appease the
657 # marshall/unmarshall
658 return True
659
Todd Brochd6061672012-05-11 15:52:47 -0700660 def hwinit(self, verbose=False):
661 """Initialize all controls.
662
663 These values are part of the system config XML files of the form
664 init=<value>. This command should be used by clients wishing to return the
665 servo and DUT its connected to a known good/safe state.
666
Vadim Bendeburybb51dd42013-01-31 13:47:46 -0800667 Note that initialization errors are ignored (as in some cases they could
668 be caused by DUT firmware deficiencies). This might need to be fine tuned
669 later.
670
Todd Brochd6061672012-05-11 15:52:47 -0700671 Args:
672 verbose: boolean, if True prints info about control initialized.
673 Otherwise prints nothing.
Vadim Bendebury5934e4b2013-02-06 13:57:54 -0800674
675 Returns:
676 This function is called across RPC and as such is expected to return
677 something unless transferring 'none' across is allowed. Hence adding a
678 dummy return value to make things simpler.
Todd Brochd6061672012-05-11 15:52:47 -0700679 """
Todd Brochd9acf0a2012-12-05 13:43:06 -0800680 for control_name, value in self._syscfg.hwinit:
Todd Broch3ec8df02012-11-20 10:53:03 -0800681 try:
682 self.set(control_name, value)
683 except Exception as e:
Todd Broch3ec8df02012-11-20 10:53:03 -0800684 self._logger.error("Problem initializing %s -> %s :: %s",
685 control_name, value, str(e))
Todd Brochd6061672012-05-11 15:52:47 -0700686 if verbose:
687 self._logger.info('Initialized %s to %s', control_name, value)
Vadim Bendebury5934e4b2013-02-06 13:57:54 -0800688 return True
Todd Broch3ec8df02012-11-20 10:53:03 -0800689
Todd Broche505b8d2011-03-21 18:19:54 -0700690 def echo(self, echo):
691 """Dummy echo function for testing/examples.
692
693 Args:
694 echo: string to echo back to client
695 """
696 self._logger.debug("echo(%s)" % (echo))
697 return "ECH0ING: %s" % (echo)
698
J. Richard Barnettee2820552013-03-14 16:13:46 -0700699 def get_board(self):
700 """Return the board specified at startup, if any."""
701 return self._board
702
Simran Basia23c1392013-08-06 14:59:10 -0700703 def get_version(self):
704 """Get servo board version."""
705 return self._version
706
Yusuf Mohsinally29e30d22014-01-14 15:29:17 -0800707 def power_long_press(self):
708 """Simulate a long power button press."""
709 # After a long power press, the EC may ignore the next power
710 # button press (at least on Alex). To guarantee that this
711 # won't happen, we need to allow the EC one second to
712 # collect itself.
713 self._keyboard.power_long_press()
714 return True
715
716 def power_normal_press(self):
717 """Simulate a normal power button press."""
718 self._keyboard.power_normal_press()
719 return True
720
721 def power_short_press(self):
722 """Simulate a short power button press."""
723 self._keyboard.power_short_press()
724 return True
725
726 def power_key(self, secs=''):
727 """Simulate a power button press.
728
729 Args:
730 secs: Time in seconds to simulate the keypress.
731 """
732 self._keyboard.power_key(secs)
733 return True
734
735 def ctrl_d(self, press_secs=''):
736 """Simulate Ctrl-d simultaneous button presses."""
737 self._keyboard.ctrl_d(press_secs)
738 return True
739
740 def ctrl_u(self):
741 """Simulate Ctrl-u simultaneous button presses."""
742 self._keyboard.ctrl_u()
743 return True
744
745 def ctrl_enter(self, press_secs=''):
746 """Simulate Ctrl-enter simultaneous button presses."""
747 self._keyboard.ctrl_enter(press_secs)
748 return True
749
750 def d_key(self, press_secs=''):
751 """Simulate Enter key button press."""
752 self._keyboard.d_key(press_secs)
753 return True
754
755 def ctrl_key(self, press_secs=''):
756 """Simulate Enter key button press."""
757 self._keyboard.ctrl_key(press_secs)
758 return True
759
760 def enter_key(self, press_secs=''):
761 """Simulate Enter key button press."""
762 self._keyboard.enter_key(press_secs)
763 return True
764
765 def refresh_key(self, press_secs=''):
766 """Simulate Refresh key (F3) button press."""
767 self._keyboard.refresh_key(press_secs)
768 return True
769
770 def ctrl_refresh_key(self, press_secs=''):
771 """Simulate Ctrl and Refresh (F3) simultaneous press.
772
773 This key combination is an alternative of Space key.
774 """
775 self._keyboard.ctrl_refresh_key(press_secs)
776 return True
777
778 def imaginary_key(self, press_secs=''):
779 """Simulate imaginary key button press.
780
781 Maps to a key that doesn't physically exist.
782 """
783 self._keyboard.imaginary_key(press_secs)
784 return True
785
Todd Brochdbb09982011-10-02 07:14:26 -0700786
Todd Broche505b8d2011-03-21 18:19:54 -0700787def test():
788 """Integration testing.
789
790 TODO(tbroch) Enhance integration test and add unittest (see mox)
791 """
792 logging.basicConfig(level=logging.DEBUG,
793 format="%(asctime)s - %(name)s - " +
794 "%(levelname)s - %(message)s")
795 # configure server & listen
796 servod_obj = Servod(1)
797 # 4 == number of interfaces on a FT4232H device
798 for i in xrange(4):
799 if i == 1:
800 # its an i2c interface ... see __init__ for details and TODO to make
801 # this configureable
802 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
803 else:
804 # its a gpio interface
805 servod_obj._interface_list[i].wr_rd(0)
806
807 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
808 allow_none=True)
809 server.register_introspection_functions()
810 server.register_multicall_functions()
811 server.register_instance(servod_obj)
812 logging.info("Listening on localhost port 9999")
813 server.serve_forever()
814
815if __name__ == "__main__":
816 test()
817
818 # simple client transaction would look like
819 """
820 remote_uri = 'http://localhost:9999'
821 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
822 send_str = "Hello_there"
823 print "Sent " + send_str + ", Recv " + client.echo(send_str)
824 """