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.
To build your changes to servod
(board overlays, python code, etc), run the following command:
(chroot) $ update_chroot
If you don't run update_chroot
after every repo sync
, then manually emerging hdctools will put your chroot into an unsupported state inconsistent with any tested or supported sync point. With this caveat in mind, manually emerging is faster than running update_chroot
:
(chroot) $ cros_workon --host start dev-util/hdctools (chroot) $ sudo emerge dev-util/hdctools
To run unit tests on the hdctools currently installed in chroot:
(chroot) $ cd /usr/lib64/python3.6/site-packages # TODO(b/268735246): remove (chroot) $ sudo python3 -m pytest servo/tests/unit
Make sure your host machine has a servo plugged in. The HOST port should have a usb connecting it to your dev machine, and the SERVO port should be connected to your DUT.
If everything is connected correctly, then lsusb
should have new additions. Something like Google Inc. Servo V4
and Google Cr50
.
If those entries are missing, that might be beacuse of any of the following reasons:
Then start servod via this command:
(chroot) $ sudo servod -b lulu
Adding a -s
flag allows you to specify the serial number of your servo.
Servod is typically controlled via various dut-control
commands. In a separate chroot, while servod is running, try running the following:
(chroot) $ dut-control power_state:off (chroot) $ dut-control power_state:on
Most features of servod are accessed this way by the user. dut-control
lets the user access servo features that are exposed via xml config files, in servo/data
. Config files can be specified for various hardware components, features and DUTs.
Servo code is located in src/third_party/hdctools
in the cros repo.
After making your changes, to create a CL, run the following command:
(local) $ repo upload --cbr .
config file
.xml
files that outline what controls a servod instance will expose. These can be found inside data/
. Example.
drv
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>
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
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.
interface
The interface describes what interface the drv should use to execute a control. Some important interfaces are: 8 == AP console, and 10 == EC console.
map
In the servod world a <map>
is used to map a human readable name to a numerical 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 servod instance then looks up what the control ec_board
means, and how to execute it. It uses its system config to find the drv
and the params used to execute an ec_board
request.
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 <control>
, <map>
, and <include>
elements as top level tags.
<control>
elements
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.
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][1].
<include>
elements
These elements indicate a config file to source before this config file. They only have one subtag, <name>
with the config file name.
<map>
elements
These elements are string to numerical 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 numerical 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.
Take a look at the doc for more details on drv
.
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. Please take a look here.
In short: a control needs to provide drv
and interface
in the params at a minimum to function correctly.
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_interface
and 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.
There are two types of servod parsings: parsing for a client (e.g. dut-control) and parsing for servod itself. Additionally servod supports runtime configurations using a config file, to map serialname to symbolic name.
servod -b samus -s xxx-yyy -> servod -n my_samus //where servodrc has my_samus, xxx-yyy
With that, there are some helpers to make parsing easier and more unified. The purpose is for shared arguments and shared parsing logic (e.g. runtime configuration mappings) to live in one place, to ensure a consistent cmdline experience across servod tools, and to simplify and centralize future changes. Please see this top comment for an overview and the servodrc examples.
servodtool
is a cmdline tool (and library) to manage servod
instances and devices. It's the command-line entry point for tools inside servo/tools.
The device tool uses mainly the usb_hierarchy utility to provide query functions around the physical servo devices. So far, it provides a tool to find the /sys/bus/usb/devices/ path of a servo device given its serialname.
The instance tool uses the file-system to leave information around about running 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 /run/servoscratch
on invocation and clearing out the entry when servod
turns off. This flow is supported for almost all methods of turning off servod
: Ctrl-C
, servodtool instance 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 servodtool instance
, 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 /run/servoscratch
, then existing instances are not tracked. For this, servodtool instance 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 GSC, the watchdog allows for a reinit period, where if the connection to GSC is lost (GSC 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.