This is intended to give a brief overview of how the servo code works, enabling developers to quickly make additions, and improve the servo framework.
.xml files that outline what controls a servod instance will expose. These can be found inside data/. Example.
The drivers used to execute controls. Example.
<control> <name>ppvar_vbat_ma</name> <doc>milliamps being consumed (discharging/positive) or supplied (charging/negative) to the battery</doc> <params cmd="get" subtype="milliamps" interface="10" drv="ec"> </params> </control>
A control - like ppvar_vbat_ma - is defined in a configuration file and executes code on invocation through dut-control or an RPC proxy. `
Params are a dictionary of parameters that controls are defined with, and are used to execute the control. The params list is passed to the drv on initialization for a specific control.
The interface describes what interface the drv should use to execute a control. Some important interfaces are: 8 == AP console, and 10 == EC console.
In the servod world a
<map> is used to map a human readable name to a numerial value. E.g. the “onoff” map defines on = 1 and off = 0.
The servo framework works by having a servod instance (a server to process requests) running and executing controls with the help of physical servo devices (v2, v4 etc).
The servod instance is invoked with a couple of implicit configuration files (like common.xml) and some explicit configuration files (like when invoking sudo servod -b lulu -c lulu_r2.xml). These configuration files define the controls this servod instance can handle, and configure how to execute them.
The following graphic shows how a call to dut-control ec_board works.
The dut-control control issues a request to the servo server, asking it to get the control ‘ec_board’.
The server initializes and keeps around an ec drv instance to execute the ec_board control.
Note: This is crucial, because it means that one can share state between two invocations of ec_board - since they use the same drv instance to execute them - but not as easily between two invocations of different controls, since they will use different drv instances to execute.
The server then dispatches an attempt to retrieve the information by calling .get() on the drv.
The return value then gets propagated all the way back up until finally dut-control prints out the response on the terminal.
A configuration file is an xml file that has
<include> elements as top level tags.
These elements define what you would use for dut-control. They are for instance pwr_button, ec_board, etc.
<control> elements can have the following subtags:
<name> Defines the control’s name. required.
<doc> A docstring to display.
<alias> Alias is another name to use for this control.
<params> Params used to instantiate a driver.
This allows for generic drivers that get the information they need passed through by the params dictionary. required. See more below
<remap> Remap makes the control at
<remap> an alias for the control. See FAQ for details.
Note: two params may be defined if the params for the set version of the control is different from the get version of the control. In that case, the params are required to have a ‘cmd’ attribute each, one defined as ‘get’ the other defined as ‘set’ to distinguish between them.
These elements indicate a config file to source before this config file. They only have one subtag,
<name> with the config file name.
These elements string to numerial value mappings that can be used when setting a control. When the user calls pwr_button:press, there is a config file loaded into the servod instance that defines a map to map press to a numerial value. The pwr_button control uses that map in its params.
Maps use a
<name> tag to indicate their name, and one
<params> tag to configure the transformations.
Drv (drivers) are classes used to perform the actions listed out in the configuration file. These drivers handle low level communication, and execution of the control.
HwDriver is the source of all drivers, and needs to be inherited from when building out a new driver. It contains the logic for calling the
_Set_|control_name| of a derived class when a control with a subtype is defined.
Another important driver is ptyDriver that the EC, AP, and Cr50 console controls use.
As mentioned above, it’s crucial to note that drivers get instantiated with a set of params for each control.
Interfaces are what get passed to a driver to use for control execution. These can either be real interfaces, or the servod instance, if interface=“servo” is defined in the params.
Inside servo_interfaces.py one can see all the interfaces defined for each servo type (v2, v4, ccd, etc). The interface index in the list is what the interface attribute in the params dictionary specifies.
The driver needs to know what interface it is expecting in order to meaningfully execute a control.
In general, params can be any parameters that the config file writer decides to add that are needed for a driver. However, there are a couple special parameters that one should be aware of:
As a parameter map tells servod what map to use for input on this control.
Either set or get. On controls with different params for get and set method this needs to be defined to associate the right params dictionar to the right method.
fmt function to execute on output values. Currently only supports hex.
If a driver has more than one method it exposes, then subtype defines what method should be called to execute a given control. The method called on the driver instance then is drv.
Input on set methods will be cast to |input_type|. Currently float, int, and str are supported.
Index of the interface to use for this control. “servo” if the interface is intended to be the servod instance.
String of the python module that contains the driver for this control.
There are two mechanism in servod to allow for controls to have different configurations depending on the type of servo device used: type specific drivers and interfaces in the control's configuration.
A control can specify a more specific interface or drv instead of using the global ‘interface’ and ‘drv’ params its params dictionary for a specific servo type. This would look like
servo_micro_drv These params would be used in a servo_micro servod instance before the general ‘interface’ and ‘drv’ params, but ignored in a non servo_micro instance. Take a look at ftdi_common.py to see the exact name strings.
servodutil is a cmdline tool (and library) to manage servod instances. It supports listing all instances running on a system and their info (e.g. what port they run on, what the main process' PID is, what the serial numbers of the attached servo devices are), and gracefully stopping an instance.
It works by writing all the information into a file at /tmp/servoscratch on invocation & clearing out the entry when servod turns off. This flow is supported for almost all methods of turning off servod: Ctrl-C, servodutil stop, or sending a SIGTERM to the main process.
Should it become necessary for servod to be killed using SIGKILL, servod cannot perform a clean-up and stale entries will be left around. On each usage of servodutil, or invocation of servod, the system attempts to clean out stale entries.
The inverse is also problematic: should it for some reason become necessary to delete /tmp/servoscratch, then existing instances are not tracked. For this,
servodutil rebuild provides a way to try and rebuild lost entries.
ServoDeviceWatchdog is a thread that regularly polls to ensure all devices that a servod instance started with are still connected. Should this not be the case, it will issue a signal to the servod instance to turn itself off.
The one exeption here is
ccd: for ccd as it is hosted by cr50, the watchdog allows for a reinit period, where if the connection to cr50 is lost (cr50 reboot, cable unplug, etc) and the device is found again within the timeout period, the interface is reinitialized. Controls issued during the reinitalization phase will block until the interface is reinitialized.