blob: 69471b19c4a37aa9b01d9b83cd3e7b0e22b70ab1 [file] [log] [blame]
Todd Broche505b8d2011-03-21 18:19:54 -07001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Servo Server."""
5import imp
6import logging
7import SimpleXMLRPCServer
Todd Broch7a91c252012-02-03 12:37:45 -08008import time
Todd Broche505b8d2011-03-21 18:19:54 -07009
10# TODO(tbroch) deprecate use of relative imports
Todd Broche505b8d2011-03-21 18:19:54 -070011import ftdigpio
12import ftdii2c
Todd Brochdbb09982011-10-02 07:14:26 -070013import ftdi_common
Todd Broch47c43f42011-05-26 15:11:31 -070014import ftdiuart
Todd Broche505b8d2011-03-21 18:19:54 -070015
16MAX_I2C_CLOCK_HZ = 100000
17
Todd Brochdbb09982011-10-02 07:14:26 -070018
Todd Broche505b8d2011-03-21 18:19:54 -070019class ServodError(Exception):
20 """Exception class for servod."""
21
22class Servod(object):
23 """Main class for Servo debug/controller Daemon."""
Todd Brochdbb09982011-10-02 07:14:26 -070024 def __init__(self, config, vendor, product, serialname=None, interfaces=None):
Todd Broche505b8d2011-03-21 18:19:54 -070025 """Servod constructor.
26
27 Args:
28 config: instance of SystemConfig containing all controls for
29 particular Servod invocation
30 vendor: usb vendor id of FTDI device
31 product: usb product id of FTDI device
Todd Brochad034442011-05-25 15:05:29 -070032 serialname: string of device serialname/number as defined in FTDI eeprom.
Todd Brochdbb09982011-10-02 07:14:26 -070033 interfaces: list of strings of interface types the server will instantiate
34
35 Raises:
36 ServodError: if unable to locate init method for particular interface
Todd Broche505b8d2011-03-21 18:19:54 -070037 """
38 self._logger = logging.getLogger("Servod")
39 self._logger.debug("")
40 self._vendor = vendor
41 self._product = product
Todd Brochad034442011-05-25 15:05:29 -070042 self._serialname = serialname
Todd Broche505b8d2011-03-21 18:19:54 -070043 self._syscfg = config
44 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
45 # interfaces are mapped to
46 self._interface_list = []
47 # Dict of Dict to map control name, function name to to tuple (params, drv)
48 # Ex) _drv_dict[name]['get'] = (params, drv)
49 self._drv_dict = {}
50
Todd Brochdbb09982011-10-02 07:14:26 -070051 # Note, interface i is (i - 1) in list
52 if not interfaces:
53 interfaces = ftdi_common.INTERFACE_DEFAULTS[vendor][product]
54
55 for i, name in enumerate(interfaces):
Todd Broch8a77a992012-01-27 09:46:08 -080056 # servos with multiple FTDI are guaranteed to have contiguous USB PIDs
57 if i and ((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) == 0):
58 self._product += 1
59 self._logger.info("Changing to next FTDI part @ pid = 0x%04x",
60 self._product)
61
Todd Brochdbb09982011-10-02 07:14:26 -070062 self._logger.info("Initializing FTDI interface %d to %s", i + 1, name)
63 try:
64 func = getattr(self, '_init_%s' % name)
65 except AttributeError:
66 raise ServodError("Unable to locate init for interface %s" % name)
Todd Brocha9c74692012-01-24 22:54:22 -080067 result = func((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) + 1)
Todd Broch888da782011-10-07 14:29:09 -070068 if isinstance(result, tuple):
69 self._interface_list.extend(result)
70 else:
71 self._interface_list.append(result)
Todd Broche505b8d2011-03-21 18:19:54 -070072
Todd Brochb3048492012-01-15 21:52:41 -080073 def _init_dummy(self, interface):
74 """Initialize dummy interface.
75
76 Dummy interface is just a mechanism to reserve that interface for non servod
77 interaction. Typically the interface will be managed by external
78 third-party tools like openOCD or urjtag for JTAG or flashrom for SPI
79 interfaces.
80
81 TODO(tbroch): Investigate merits of incorporating these third-party
82 interfaces into servod or creating a communication channel between them
83
84 Returns: None
85 """
86 return None
87
Todd Broche505b8d2011-03-21 18:19:54 -070088 def _init_gpio(self, interface):
89 """Initialize gpio driver interface and open for use.
90
91 Args:
92 interface: interface number of FTDI device to use.
93
94 Returns:
95 Instance object of interface.
Todd Broch6de9dc62012-04-09 15:23:53 -070096
97 Raises:
98 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -070099 """
Todd Brochad034442011-05-25 15:05:29 -0700100 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
101 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700102 try:
103 fobj.open()
104 except ftdigpio.FgpioError as e:
105 raise ServodError('Opening gpio interface. %s ( %d )' % (e.msg, e.value))
106
Todd Broche505b8d2011-03-21 18:19:54 -0700107 return fobj
108
109 def _init_i2c(self, interface):
110 """Initialize i2c interface and open for use.
111
112 Args:
113 interface: interface number of FTDI device to use
114
115 Returns:
116 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700117
118 Raises:
119 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700120 """
Todd Brochad034442011-05-25 15:05:29 -0700121 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
122 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700123 try:
124 fobj.open()
125 except ftdii2c.Fi2cError as e:
126 raise ServodError('Opening i2c interface. %s ( %d )' % (e.msg, e.value))
127
Todd Broche505b8d2011-03-21 18:19:54 -0700128 # Set the frequency of operation of the i2c bus.
129 # TODO(tbroch) make configureable
130 fobj.setclock(MAX_I2C_CLOCK_HZ)
Todd Broch6de9dc62012-04-09 15:23:53 -0700131
Todd Broche505b8d2011-03-21 18:19:54 -0700132 return fobj
133
Todd Broch47c43f42011-05-26 15:11:31 -0700134 def _init_uart(self, interface):
135 """Initialize uart inteface and open for use
136
137 Note, the uart runs in a separate thread (pthreads). Users wishing to
138 interact with it will query control for the pty's pathname and connect
139 with there favorite console program. For example:
140 cu -l /dev/pts/22
141
142 Args:
143 interface: interface number of FTDI device to use
144
145 Returns:
146 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700147
148 Raises:
149 ServodError: If init fails
Todd Broch47c43f42011-05-26 15:11:31 -0700150 """
151 fobj = ftdiuart.Fuart(self._vendor, self._product, interface)
Todd Broch6de9dc62012-04-09 15:23:53 -0700152 try:
153 fobj.run()
154 except ftdiuart.FuartError as e:
155 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
156
Todd Broch47c43f42011-05-26 15:11:31 -0700157 self._logger.info("%s" % fobj.get_pty())
158 return fobj
159
Todd Broch888da782011-10-07 14:29:09 -0700160 def _init_gpiouart(self, interface):
161 """Initialize special gpio + uart interface and open for use
162
163 Note, the uart runs in a separate thread (pthreads). Users wishing to
164 interact with it will query control for the pty's pathname and connect
165 with there favorite console program. For example:
166 cu -l /dev/pts/22
167
168 Args:
169 interface: interface number of FTDI device to use
170
171 Returns:
172 Instance objects of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700173
174 Raises:
175 ServodError: If init fails
Todd Broch888da782011-10-07 14:29:09 -0700176 """
177 fgpio = self._init_gpio(interface)
178 fuart = ftdiuart.Fuart(self._vendor, self._product, interface, fgpio._fc)
Todd Broch6de9dc62012-04-09 15:23:53 -0700179 try:
180 fuart.run()
181 except ftdiuart.FuartError as e:
182 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
183
Todd Broch888da782011-10-07 14:29:09 -0700184 self._logger.info("uart pty: %s" % fuart.get_pty())
185 return fgpio, fuart
186
Todd Broche505b8d2011-03-21 18:19:54 -0700187 def _get_param_drv(self, control_name, is_get=True):
188 """Get access to driver for a given control.
189
190 Note, some controls have different parameter dictionaries for 'getting' the
191 control's value versus 'setting' it. Boolean is_get distinguishes which is
192 being requested.
193
194 Args:
195 control_name: string name of control
196 is_get: boolean to determine
197
198 Returns:
199 tuple (param, drv) where:
200 param: param dictionary for control
201 drv: instance object of driver for particular control
202
203 Raises:
204 ServodError: Error occurred while examining params dict
205 """
206 self._logger.debug("")
207 # if already setup just return tuple from driver dict
208 if control_name in self._drv_dict:
209 if is_get and ('get' in self._drv_dict[control_name]):
210 return self._drv_dict[control_name]['get']
211 if not is_get and ('set' in self._drv_dict[control_name]):
212 return self._drv_dict[control_name]['set']
213
214 params = self._syscfg.lookup_control_params(control_name, is_get)
215 if 'drv' not in params:
216 self._logger.error("Unable to determine driver for %s" % control_name)
217 raise ServodError("'drv' key not found in params dict")
218 if 'interface' not in params:
219 self._logger.error("Unable to determine interface for %s" %
220 control_name)
221
222 raise ServodError("'interface' key not found in params dict")
223 index = int(params['interface']) - 1
224 interface = self._interface_list[index]
225 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
226 drv_pkg = imp.load_module('drv',
227 *imp.find_module('drv', servo_pkg.__path__))
228 drv_name = params['drv']
229 drv_module = getattr(drv_pkg, drv_name)
230 drv_class = getattr(drv_module, drv_name)
231 drv = drv_class(interface, params)
232 if control_name not in self._drv_dict:
233 self._drv_dict[control_name] = {}
234 if is_get:
235 self._drv_dict[control_name]['get'] = (params, drv)
236 else:
237 self._drv_dict[control_name]['set'] = (params, drv)
238 return (params, drv)
239
240 def doc_all(self):
241 """Return all documenation for controls.
242
243 Returns:
244 string of <doc> text in config file (xml) and the params dictionary for
245 all controls.
246
247 For example:
248 warm_reset :: Reset the device warmly
249 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
250 """
251 return self._syscfg.display_config()
252
253 def doc(self, name):
254 """Retreive doc string in system config file for given control name.
255
256 Args:
257 name: name string of control to get doc string
258
259 Returns:
260 doc string of name
261
262 Raises:
263 NameError: if fails to locate control
264 """
265 self._logger.debug("name(%s)" % (name))
266 if self._syscfg.is_control(name):
267 return self._syscfg.get_control_docstring(name)
268 else:
269 raise NameError("No control %s" %name)
270
271 def get(self, name):
272 """Get control value.
273
274 Args:
275 name: name string of control
276
277 Returns:
278 Response from calling drv get method. Value is reformatted based on
279 control's dictionary parameters
280
281 Raises:
282 HwDriverError: Error occurred while using drv
283 """
284 self._logger.debug("name(%s)" % (name))
285 (param, drv) = self._get_param_drv(name)
286 try:
287 val = drv.get()
288 rd_val = self._syscfg.reformat_val(param, val)
Todd Brochb042e7a2011-12-14 17:41:36 -0800289 self._logger.debug("%s = %s" % (name, rd_val))
Todd Broche505b8d2011-03-21 18:19:54 -0700290 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700291 except AttributeError, error:
292 self._logger.error("Getting %s: %s" % (name, error))
293 raise
Todd Broche505b8d2011-03-21 18:19:54 -0700294 except drv.hw_driver.HwDriverError:
295 self._logger.error("Getting %s" % (name))
296 raise
Todd Brochd6061672012-05-11 15:52:47 -0700297
Todd Broche505b8d2011-03-21 18:19:54 -0700298 def get_all(self, verbose):
299 """Get all controls values.
300
301 Args:
302 verbose: Boolean on whether to return doc info as well
303
304 Returns:
305 string creating from trying to get all values of all controls. In case of
306 error attempting access to control, response is 'ERR'.
307 """
308 rsp = ""
309 for name in self._syscfg.syscfg_dict['control']:
310 self._logger.debug("name = %s" %name)
311 try:
312 value = self.get(name)
313 except Exception:
314 value = "ERR"
315 pass
316 if verbose:
317 rsp += "GET %s = %s :: %s\n" % (name, value, self.doc(name))
318 else:
319 rsp += "%s:%s\n" % (name, value)
320 return rsp
321
322 def set(self, name, wr_val_str):
323 """Set control.
324
325 Args:
326 name: name string of control
327 wr_val_str: value string to write. Can be integer, float or a
328 alpha-numerical that is mapped to a integer or float.
329
330 Raises:
331 HwDriverError: Error occurred while using driver
332 """
Todd Broch7a91c252012-02-03 12:37:45 -0800333 if name == 'sleep':
334 time.sleep(float(wr_val_str))
335 return True
336
Todd Broche505b8d2011-03-21 18:19:54 -0700337 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
338 (params, drv) = self._get_param_drv(name, False)
339 wr_val = self._syscfg.resolve_val(params, wr_val_str)
340 try:
341 drv.set(wr_val)
342 except drv.hw_driver.HwDriverError:
343 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
344 raise
345 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
346 # & client I still have to return something to appease the
347 # marshall/unmarshall
348 return True
349
Todd Brochd6061672012-05-11 15:52:47 -0700350 def hwinit(self, verbose=False):
351 """Initialize all controls.
352
353 These values are part of the system config XML files of the form
354 init=<value>. This command should be used by clients wishing to return the
355 servo and DUT its connected to a known good/safe state.
356
357 Args:
358 verbose: boolean, if True prints info about control initialized.
359 Otherwise prints nothing.
360 """
361 for control_name, value in self._syscfg.hwinit():
362 self.set(control_name, value)
363 if verbose:
364 self._logger.info('Initialized %s to %s', control_name, value)
365
366 return True
367
Todd Broche505b8d2011-03-21 18:19:54 -0700368 def echo(self, echo):
369 """Dummy echo function for testing/examples.
370
371 Args:
372 echo: string to echo back to client
373 """
374 self._logger.debug("echo(%s)" % (echo))
375 return "ECH0ING: %s" % (echo)
376
Todd Brochdbb09982011-10-02 07:14:26 -0700377
Todd Broche505b8d2011-03-21 18:19:54 -0700378def test():
379 """Integration testing.
380
381 TODO(tbroch) Enhance integration test and add unittest (see mox)
382 """
383 logging.basicConfig(level=logging.DEBUG,
384 format="%(asctime)s - %(name)s - " +
385 "%(levelname)s - %(message)s")
386 # configure server & listen
387 servod_obj = Servod(1)
388 # 4 == number of interfaces on a FT4232H device
389 for i in xrange(4):
390 if i == 1:
391 # its an i2c interface ... see __init__ for details and TODO to make
392 # this configureable
393 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
394 else:
395 # its a gpio interface
396 servod_obj._interface_list[i].wr_rd(0)
397
398 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
399 allow_none=True)
400 server.register_introspection_functions()
401 server.register_multicall_functions()
402 server.register_instance(servod_obj)
403 logging.info("Listening on localhost port 9999")
404 server.serve_forever()
405
406if __name__ == "__main__":
407 test()
408
409 # simple client transaction would look like
410 """
411 remote_uri = 'http://localhost:9999'
412 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
413 send_str = "Hello_there"
414 print "Sent " + send_str + ", Recv " + client.echo(send_str)
415 """