blob: 5bd6127d2355d1e51663a23d476ac5a779303020 [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
8
9# TODO(tbroch) deprecate use of relative imports
Todd Broche505b8d2011-03-21 18:19:54 -070010import ftdigpio
11import ftdii2c
Todd Brochdbb09982011-10-02 07:14:26 -070012import ftdi_common
Todd Broch47c43f42011-05-26 15:11:31 -070013import ftdiuart
Todd Broche505b8d2011-03-21 18:19:54 -070014
15MAX_I2C_CLOCK_HZ = 100000
16
Todd Brochdbb09982011-10-02 07:14:26 -070017
Todd Broche505b8d2011-03-21 18:19:54 -070018class ServodError(Exception):
19 """Exception class for servod."""
20
21class Servod(object):
22 """Main class for Servo debug/controller Daemon."""
Todd Brochdbb09982011-10-02 07:14:26 -070023 def __init__(self, config, vendor, product, serialname=None, interfaces=None):
Todd Broche505b8d2011-03-21 18:19:54 -070024 """Servod constructor.
25
26 Args:
27 config: instance of SystemConfig containing all controls for
28 particular Servod invocation
29 vendor: usb vendor id of FTDI device
30 product: usb product id of FTDI device
Todd Brochad034442011-05-25 15:05:29 -070031 serialname: string of device serialname/number as defined in FTDI eeprom.
Todd Brochdbb09982011-10-02 07:14:26 -070032 interfaces: list of strings of interface types the server will instantiate
33
34 Raises:
35 ServodError: if unable to locate init method for particular interface
Todd Broche505b8d2011-03-21 18:19:54 -070036 """
37 self._logger = logging.getLogger("Servod")
38 self._logger.debug("")
39 self._vendor = vendor
40 self._product = product
Todd Brochad034442011-05-25 15:05:29 -070041 self._serialname = serialname
Todd Broche505b8d2011-03-21 18:19:54 -070042 self._syscfg = config
43 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
44 # interfaces are mapped to
45 self._interface_list = []
46 # Dict of Dict to map control name, function name to to tuple (params, drv)
47 # Ex) _drv_dict[name]['get'] = (params, drv)
48 self._drv_dict = {}
49
Todd Brochdbb09982011-10-02 07:14:26 -070050 # Note, interface i is (i - 1) in list
51 if not interfaces:
52 interfaces = ftdi_common.INTERFACE_DEFAULTS[vendor][product]
53
54 for i, name in enumerate(interfaces):
Todd Broch8a77a992012-01-27 09:46:08 -080055 # servos with multiple FTDI are guaranteed to have contiguous USB PIDs
56 if i and ((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) == 0):
57 self._product += 1
58 self._logger.info("Changing to next FTDI part @ pid = 0x%04x",
59 self._product)
60
Todd Brochdbb09982011-10-02 07:14:26 -070061 self._logger.info("Initializing FTDI interface %d to %s", i + 1, name)
62 try:
63 func = getattr(self, '_init_%s' % name)
64 except AttributeError:
65 raise ServodError("Unable to locate init for interface %s" % name)
Todd Brocha9c74692012-01-24 22:54:22 -080066 result = func((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) + 1)
Todd Broch888da782011-10-07 14:29:09 -070067 if isinstance(result, tuple):
68 self._interface_list.extend(result)
69 else:
70 self._interface_list.append(result)
Todd Broche505b8d2011-03-21 18:19:54 -070071
Todd Brocha9c74692012-01-24 22:54:22 -080072
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.
96 """
Todd Brochad034442011-05-25 15:05:29 -070097 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
98 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070099 fobj.open()
100 return fobj
101
102 def _init_i2c(self, interface):
103 """Initialize i2c interface and open for use.
104
105 Args:
106 interface: interface number of FTDI device to use
107
108 Returns:
109 Instance object of interface
110 """
Todd Brochad034442011-05-25 15:05:29 -0700111 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
112 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -0700113 fobj.open()
114 # Set the frequency of operation of the i2c bus.
115 # TODO(tbroch) make configureable
116 fobj.setclock(MAX_I2C_CLOCK_HZ)
117 return fobj
118
Todd Broch47c43f42011-05-26 15:11:31 -0700119 def _init_uart(self, interface):
120 """Initialize uart inteface and open for use
121
122 Note, the uart runs in a separate thread (pthreads). Users wishing to
123 interact with it will query control for the pty's pathname and connect
124 with there favorite console program. For example:
125 cu -l /dev/pts/22
126
127 Args:
128 interface: interface number of FTDI device to use
129
130 Returns:
131 Instance object of interface
132 """
133 fobj = ftdiuart.Fuart(self._vendor, self._product, interface)
134 fobj.run()
135 self._logger.info("%s" % fobj.get_pty())
136 return fobj
137
Todd Broch888da782011-10-07 14:29:09 -0700138 def _init_gpiouart(self, interface):
139 """Initialize special gpio + uart interface and open for use
140
141 Note, the uart runs in a separate thread (pthreads). Users wishing to
142 interact with it will query control for the pty's pathname and connect
143 with there favorite console program. For example:
144 cu -l /dev/pts/22
145
146 Args:
147 interface: interface number of FTDI device to use
148
149 Returns:
150 Instance objects of interface
151 """
152 fgpio = self._init_gpio(interface)
153 fuart = ftdiuart.Fuart(self._vendor, self._product, interface, fgpio._fc)
154 fuart.run()
155 self._logger.info("uart pty: %s" % fuart.get_pty())
156 return fgpio, fuart
157
Todd Broche505b8d2011-03-21 18:19:54 -0700158 def _get_param_drv(self, control_name, is_get=True):
159 """Get access to driver for a given control.
160
161 Note, some controls have different parameter dictionaries for 'getting' the
162 control's value versus 'setting' it. Boolean is_get distinguishes which is
163 being requested.
164
165 Args:
166 control_name: string name of control
167 is_get: boolean to determine
168
169 Returns:
170 tuple (param, drv) where:
171 param: param dictionary for control
172 drv: instance object of driver for particular control
173
174 Raises:
175 ServodError: Error occurred while examining params dict
176 """
177 self._logger.debug("")
178 # if already setup just return tuple from driver dict
179 if control_name in self._drv_dict:
180 if is_get and ('get' in self._drv_dict[control_name]):
181 return self._drv_dict[control_name]['get']
182 if not is_get and ('set' in self._drv_dict[control_name]):
183 return self._drv_dict[control_name]['set']
184
185 params = self._syscfg.lookup_control_params(control_name, is_get)
186 if 'drv' not in params:
187 self._logger.error("Unable to determine driver for %s" % control_name)
188 raise ServodError("'drv' key not found in params dict")
189 if 'interface' not in params:
190 self._logger.error("Unable to determine interface for %s" %
191 control_name)
192
193 raise ServodError("'interface' key not found in params dict")
194 index = int(params['interface']) - 1
195 interface = self._interface_list[index]
196 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
197 drv_pkg = imp.load_module('drv',
198 *imp.find_module('drv', servo_pkg.__path__))
199 drv_name = params['drv']
200 drv_module = getattr(drv_pkg, drv_name)
201 drv_class = getattr(drv_module, drv_name)
202 drv = drv_class(interface, params)
203 if control_name not in self._drv_dict:
204 self._drv_dict[control_name] = {}
205 if is_get:
206 self._drv_dict[control_name]['get'] = (params, drv)
207 else:
208 self._drv_dict[control_name]['set'] = (params, drv)
209 return (params, drv)
210
211 def doc_all(self):
212 """Return all documenation for controls.
213
214 Returns:
215 string of <doc> text in config file (xml) and the params dictionary for
216 all controls.
217
218 For example:
219 warm_reset :: Reset the device warmly
220 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
221 """
222 return self._syscfg.display_config()
223
224 def doc(self, name):
225 """Retreive doc string in system config file for given control name.
226
227 Args:
228 name: name string of control to get doc string
229
230 Returns:
231 doc string of name
232
233 Raises:
234 NameError: if fails to locate control
235 """
236 self._logger.debug("name(%s)" % (name))
237 if self._syscfg.is_control(name):
238 return self._syscfg.get_control_docstring(name)
239 else:
240 raise NameError("No control %s" %name)
241
242 def get(self, name):
243 """Get control value.
244
245 Args:
246 name: name string of control
247
248 Returns:
249 Response from calling drv get method. Value is reformatted based on
250 control's dictionary parameters
251
252 Raises:
253 HwDriverError: Error occurred while using drv
254 """
255 self._logger.debug("name(%s)" % (name))
256 (param, drv) = self._get_param_drv(name)
257 try:
258 val = drv.get()
259 rd_val = self._syscfg.reformat_val(param, val)
Todd Brochb042e7a2011-12-14 17:41:36 -0800260 self._logger.debug("%s = %s" % (name, rd_val))
Todd Broche505b8d2011-03-21 18:19:54 -0700261 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700262 except AttributeError, error:
263 self._logger.error("Getting %s: %s" % (name, error))
264 raise
Todd Broche505b8d2011-03-21 18:19:54 -0700265 except drv.hw_driver.HwDriverError:
266 self._logger.error("Getting %s" % (name))
267 raise
268 def get_all(self, verbose):
269 """Get all controls values.
270
271 Args:
272 verbose: Boolean on whether to return doc info as well
273
274 Returns:
275 string creating from trying to get all values of all controls. In case of
276 error attempting access to control, response is 'ERR'.
277 """
278 rsp = ""
279 for name in self._syscfg.syscfg_dict['control']:
280 self._logger.debug("name = %s" %name)
281 try:
282 value = self.get(name)
283 except Exception:
284 value = "ERR"
285 pass
286 if verbose:
287 rsp += "GET %s = %s :: %s\n" % (name, value, self.doc(name))
288 else:
289 rsp += "%s:%s\n" % (name, value)
290 return rsp
291
292 def set(self, name, wr_val_str):
293 """Set control.
294
295 Args:
296 name: name string of control
297 wr_val_str: value string to write. Can be integer, float or a
298 alpha-numerical that is mapped to a integer or float.
299
300 Raises:
301 HwDriverError: Error occurred while using driver
302 """
303 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
304 (params, drv) = self._get_param_drv(name, False)
305 wr_val = self._syscfg.resolve_val(params, wr_val_str)
306 try:
307 drv.set(wr_val)
308 except drv.hw_driver.HwDriverError:
309 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
310 raise
311 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
312 # & client I still have to return something to appease the
313 # marshall/unmarshall
314 return True
315
316 def echo(self, echo):
317 """Dummy echo function for testing/examples.
318
319 Args:
320 echo: string to echo back to client
321 """
322 self._logger.debug("echo(%s)" % (echo))
323 return "ECH0ING: %s" % (echo)
324
Todd Brochdbb09982011-10-02 07:14:26 -0700325
Todd Broche505b8d2011-03-21 18:19:54 -0700326def test():
327 """Integration testing.
328
329 TODO(tbroch) Enhance integration test and add unittest (see mox)
330 """
331 logging.basicConfig(level=logging.DEBUG,
332 format="%(asctime)s - %(name)s - " +
333 "%(levelname)s - %(message)s")
334 # configure server & listen
335 servod_obj = Servod(1)
336 # 4 == number of interfaces on a FT4232H device
337 for i in xrange(4):
338 if i == 1:
339 # its an i2c interface ... see __init__ for details and TODO to make
340 # this configureable
341 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
342 else:
343 # its a gpio interface
344 servod_obj._interface_list[i].wr_rd(0)
345
346 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
347 allow_none=True)
348 server.register_introspection_functions()
349 server.register_multicall_functions()
350 server.register_instance(servod_obj)
351 logging.info("Listening on localhost port 9999")
352 server.serve_forever()
353
354if __name__ == "__main__":
355 test()
356
357 # simple client transaction would look like
358 """
359 remote_uri = 'http://localhost:9999'
360 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
361 send_str = "Hello_there"
362 print "Sent " + send_str + ", Recv " + client.echo(send_str)
363 """