Commit bdd50411 authored by Daniel Stone's avatar Daniel Stone

Initial commit

Add roughly-working servo code. udev integration is still a little
hairy, and the Python is still very ugly, but it works.
Signed-off-by: Daniel Stone's avatarDaniel Stone <daniels@collabora.com>
parents
How do I shot Servo
-------------------
This set of tools lets you use a Servo control board for Chromebooks in some
kind of vaguely automated fashion. After installing these and configuring for
your local boards, you should have access to the CPU UART as
/dev/google-servo/$devicename/cpu-uart, and the EC as
/dev/google-servo/$devicename/ec-uart, as well as having servod itself
listening for more complex commands on a predictable port.
hdctools
--------
In order to actually communicate with the servo, you'll need to have hdctools
installed somewhere we can see it. This is available at:
https://chromium.googlesource.com/chromiumos/third_party/hdctools
You'll need libftdi-dev, tidy, and python-setuptools.
make
sudo make install
python setup.py build
sudo python setup.py install
Kernel module
-------------
Build and install this kernel module. Those of you who pay attention to the
source may notice that it, in fact, does nothing. This is just here so the
kernel will actually bind to it, which will trigger udev rules, which will in
turn trigger systemd to start servod. There might be a better way to do this.
Anyway, just build and install it.
udev rules
----------
This udev rule is blindingly simple. Install it to /etc/udev/rules.d/.
systemd
-------
As above, but /etc/systemd/system/.
servod configuration
--------------------
The configuration, in /etc/google-servo.conf takes the form:
localalias,serial,port,boardtype
The localalias is what gets aliased into /dev/google-servo/$devicename/*, e.g.
peach-pi-on-my-desk, peach-pi-over-there, big-one, blaze-two. You get the
picture. The serial number comes from /sys/devices/*/serial; I recommend
discovering this with:
udevadm info --attribute-walk --name /dev/usb/google-servoN
The immediate parent of that device shown, should have an ATTR{serial} entry
in the form of "123456-12345".
Port is the port number to bind to for dut-control to use later. boardtype is
an internal value used by servo to work out how to communicate with the target
device; see hdctools/servo/data/ for something resembling a list of possible
values.
servod wrapper
--------------
This fairly trivial wrapper runs out of systemd and creates the device links
for /dev/google-servo/$devicename/*. You'll need python-numpy, python-pexpect,
python-pyudev, python-serial, and python-usb to run it.
Putting it all together
-----------------------
Once you've done all this, servod should turn up whenever you plug your board
in, and disappear once you haven't. Brilliant.
# This file describes servo boards connected to this host.
#
# Line text starting with # to end of line is ignored, as well as empy lines.
#
# Configuration lines consist of up to 4 comma separated fields, the last
# two are optional:
#
# name serial-number port-number board
#
big, 905537-00427, 9900, nyan, 192.168.15.23
#blaze, 905537-00427, 9901, nyan, 192.168.15.23
#pi, 905537-00427, 9902, peach_pi, 192.168.15.23
ifneq ($(KERNELRELEASE),)
obj-m := google-servo.o
else
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(CURDIR) modules
clean:
$(MAKE) -C $(KDIR) M=$(CURDIR) clean
endif
/*
* Google Servo USB driver
*
* Copyright (C) 2003 David Glance <davidgsf@sourceforge.net>
* 2001-2004 Juergen Stuber <starblue@users.sourceforge.net>
* 2015 Collabora Ltd. <daniels@collabora.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* derived from USB Lego Tower driver
* derived from USB Skeleton driver - 0.5
* Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#define GOOGLE_SERVO_MINOR_BASE 160
#define GOOGLE_USB_VENDOR_ID 0x18d1
#define SERVO_V2_PRODUCT_ID 0x5002
static const struct usb_device_id servo_table[] = {
{ USB_DEVICE(GOOGLE_USB_VENDOR_ID, SERVO_V2_PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE (usb, servo_table);
static const struct file_operations servo_fops = {
.owner = THIS_MODULE,
.read = NULL,
.write = NULL,
.open = NULL,
.release = NULL,
.poll = NULL,
.llseek = NULL,
};
static char *servo_devnode(struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
}
static struct usb_class_driver servo_class = {
.name = "google-servo%d",
.devnode = servo_devnode,
.fops = &servo_fops,
.minor_base = GOOGLE_SERVO_MINOR_BASE,
};
static DEFINE_MUTEX(open_disc_mutex);
/**
* Register the Servo device with the USB core
*/
static int servo_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct device *idev = &interface->dev;
struct usb_device *udev = interface_to_usbdev(interface);
int retval = 0;
if (interface->cur_altsetting->desc.bInterfaceNumber != 0) {
printk("servo: ignoring interface %d\n", interface->cur_altsetting->desc.bInterfaceNumber);
return -ENODEV;
}
mutex_lock(&open_disc_mutex);
usb_set_intfdata(interface, udev);
retval = usb_register_dev(interface, &servo_class);
if (retval) {
/* something prevented us from registering this driver */
dev_err(idev, "Not able to get a minor for this device.\n");
goto error;
}
mutex_unlock(&open_disc_mutex);
return retval;
error:
usb_set_intfdata(interface, NULL);
mutex_unlock(&open_disc_mutex);
return retval;
}
/**
* Unregister a device from the USB core
*/
static void servo_disconnect(struct usb_interface *interface)
{
mutex_lock(&open_disc_mutex);
usb_set_intfdata(interface, NULL);
usb_deregister_dev(interface, &servo_class);
mutex_unlock(&open_disc_mutex);
}
static struct usb_driver servo_driver = {
.name = "google-servo",
.probe = servo_probe,
.disconnect = servo_disconnect,
.id_table = servo_table,
};
module_usb_driver(servo_driver);
/* Version Information */
#define DRIVER_VERSION "v0.1"
#define DRIVER_AUTHOR "Daniel Stone <daniels@collabora.com>"
#define DRIVER_DESC "Google Servo USB Driver"
MODULE_AUTHOR("Daniel Stone <daniels@collabora.com>");
MODULE_DESCRIPTION("Google Servo USB control board");
MODULE_LICENSE("GPL");
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora, Ltd.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# Author: Daniel Stone <daniels@collabora.com>
#
#
# A wrapper around servod: fork servod itself, wait for it to start, and create
# any device links.
import logging
import os
import pyudev
import sys
from servo import client, multiservo, servod
def run_servod():
try:
dev_path = sys.argv[1]
except:
print "usage: %s device-path" % sys.argv[0]
sys.exit(1)
ud_ctx = pyudev.Context()
if not ud_ctx:
print "couldn't create udev context"
sys.exit(1)
# XXX: This isn't very pleasant. What I'd hoped to do is use %P from
# the udev rule to get the parent device, but this didn't play
# well with systemd's BindsTo: the service just never started.
# Would be good to fix this.
#
# Anyway, the thinking is: get the device path from the usbmisc
# device, walk up to the usb subdevice which actually represents
# the interface, then walk up to its parent which represents the
# entire device, and thus has the serial property.
try:
ud_dev = pyudev.Device.from_device_file(ud_ctx, dev_path).parent.parent
except:
print "couldn't find udev device from %s" % dev_path
sys.exit(1)
if not "serial" in ud_dev.attributes:
print "parent-of-parent device %s has no serial attribute" % dev_path
sys.exit(1)
print ud_dev.attributes["serial"]
logger = logging.getLogger()
board_name = None
all_boards = multiservo.parse_rc(logger, "/etc/google-servo.conf")
for board in all_boards:
if not all_boards[board]["sn"] == ud_dev.attributes["serial"]:
continue
board_name = board
if not board_name:
print "Couldn't get board name for serial %s" % ud_dev.attributes["serial"]
sys.exit(91)
print "Board name: %s" % board_name
servod_output = os.pipe()
pid = os.fork()
if pid == 0: # child
os.dup2(servod_output[1], 1)
os.dup2(servod_output[1], 2)
# A comment in servod says they should fix it to not parse
# sys.argv directly. Quite.
sys.argv = ['servod', '--rcfile', '/etc/google-servo.conf',
'--serialname', ud_dev.attributes["serial"]]
servod.main_function()
else: # parent
# Run servod and pull its stdout/stderr.
# FIXME: Smarter mainloop: a) poll on servo output, b) poll on servo process status, c) sd_notify.
servod_in = os.fdopen(servod_output[0])
line = servod_in.readline()
while line:
# Oh dear.
print line
if 'INFO - Listening on' in line:
try:
port = int(line.split(' ')[11])
except:
print "Couldn't get port - shut it all down"
sys.exit(99)
try:
servo_client = client.ServoClient(host="127.0.0.1",
port=int(port))
except:
print "Could not connect to servo daemon"
sys.exit(90)
results = servo_client.set_get_all(["ec_uart_pty", "cpu_uart_pty"])
print "EC UART: %s" % results[0]
print "CPU UART: %s" % results[1]
try:
os.stat("/dev/google-servo/%s" % board_name)
except:
os.makedirs("/dev/google-servo/%s" % board_name)
try:
os.stat("/dev/google-servo/%s/ec-uart" % board_name)
os.unlink("/dev/google-servo/%s/ec-uart" % board_name)
except:
pass
try:
os.stat("/dev/google-servo/%s/cpu-uart" % board_name)
os.unlink("/dev/google-servo/%s/cpu-uart" % board_name)
except:
pass
os.symlink(results[0], "/dev/google-servo/%s/ec-uart" % board_name)
os.symlink(results[1], "/dev/google-servo/%s/cpu-uart" % board_name)
# Yes, this should really be python-systemd; however, that's
# only available for python3, and servo only works with
# python2 ...
os.system("systemd-notify --ready --status='Board %s on port %d'" % (board_name, port))
line = servod_in.readline()
if __name__ == '__main__':
run_servod()
[Unit]
Description="Google Servo control board manager"
ConditionPathExists=/etc/google-servo.conf
BindsTo=dev-usb-googleservo%i.device
[Service]
Type=notify
NotifyAccess=all
ExecStart=/usr/bin/run-servod /dev/usb/google-servo%i
Restart=on-failure
# google-servo* matches on our usbmisc child device, then we specify
# SUBSYSTEMS/DRIVERS to walk up from the specific interface, to the overall
# device, which will actually have a serial
ACTION=="add",KERNEL=="google-servo*",SUBSYSTEMS=="usb",DRIVERS=="usb",TAG+="systemd",ENV{SYSTEMD_WANTS}+="google-servo@%n.service",ENV{ID_MODEL}="Google Servo control board %n"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment