Skip to content

Commit a4f3ee0

Browse files
committed
driver: add CAN driver
The driver can connect to both local and remote CAN ports. For local ports, it sets up the interface. The driver is also helpful for identifying the interface name using udev. For remote ports, it creates a local vcan interface and establishes a connection to the exported resource through cannelloni. According to the cannelloni documentation, the speed of the network interface is limited by tc to prevent packet losses. XXX: See the code (many things to fix). Signed-off-by: Tomas Novotny <tomas@novotny.cz>
1 parent b8a9113 commit a4f3ee0

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

labgrid/driver/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .bareboxdriver import BareboxDriver
2+
from .candriver import CANDriver
23
from .ubootdriver import UBootDriver
34
from .smallubootdriver import SmallUBootDriver
45
from .serialdriver import SerialDriver

labgrid/driver/candriver.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import contextlib
2+
import shutil
3+
import subprocess
4+
5+
import attr
6+
7+
from ..factory import target_factory
8+
from ..resource import CANPort, NetworkCANPort
9+
from ..util.helper import processwrapper
10+
from ..util.proxy import proxymanager
11+
from .common import Driver
12+
13+
14+
@target_factory.reg_driver
15+
@attr.s(eq=False)
16+
class CANDriver(Driver):
17+
bindings = {
18+
"port": {"NetworkCANPort", "RawCANPort", "USBCANPort"},
19+
}
20+
21+
def __attrs_post_init__(self):
22+
super().__attrs_post_init__()
23+
self.ifname = None
24+
self.child = None
25+
self.cannelloni_bin = shutil.which("cannelloni")
26+
if self.cannelloni_bin is None:
27+
self.cannelloni_bin = "/usr/bin/cannelloni"
28+
warnings.warn("cannelloni binary not found, falling back to %s", self.cannelloni_bin)
29+
30+
def on_activate(self):
31+
if isinstance(self.port, NetworkCANPort):
32+
host, port = proxymanager.get_host_and_port(self.port)
33+
# XXX The port might not be unique, use something better
34+
self.ifname = f"lg_vcan{port}"
35+
# XXX How to handle permissions? sudo through helper like labgrid-raw-interface?
36+
cmd_ip_add = f"ip link add name {self.ifname} type vcan"
37+
processwrapper.check_output(cmd_ip_add.split())
38+
39+
cmd_ip_up = f"ip link set dev {self.ifname} up"
40+
processwrapper.check_output(cmd_ip_up.split())
41+
42+
# TODO Not all tc arguments are configurable
43+
cmd_tc = f"tc qdisc add dev {self.ifname} root tbf rate {self.port.speed}bit latency 100ms burst 1000"
44+
processwrapper.check_output(cmd_tc.split())
45+
cmd_cannelloni = [
46+
self.cannelloni_bin,
47+
"-C", "c",
48+
"-I", f"{self.ifname}",
49+
"-R", f"{host}",
50+
"-r", f"{port}",
51+
]
52+
self.logger.info("Running command: %s", cmd_cannelloni)
53+
self.child = subprocess.Popen(cmd_cannelloni)
54+
# XXX How to check the process? Ideally read output and find the "connected" string?
55+
else:
56+
host = None
57+
self.ifname = self.port.ifname
58+
59+
cmd_down = f"ip link set {self.ifname} down"
60+
processwrapper.check_output(cmd_down.split())
61+
62+
cmd_type_bitrate = f"ip link set {self.ifname} type can bitrate {self.port.speed}"
63+
processwrapper.check_output(cmd_type_bitrate.split())
64+
65+
cmd_up = f"ip link set {self.ifname} up"
66+
processwrapper.check_output(cmd_up.split())
67+
68+
def on_deactivate(self):
69+
ifname = self.ifname
70+
self.ifname = None
71+
if isinstance(self.port, NetworkCANPort):
72+
assert self.child
73+
child = self.child
74+
self.child = None
75+
child.terminate()
76+
try:
77+
child.wait(2.0)
78+
except subprocess.TimeoutExpired:
79+
self.logger.warning("cannelloni on %s still running after SIGTERM", ifname)
80+
log_subprocess_kernel_stack(self.logger, child)
81+
child.kill()
82+
child.wait(1.0)
83+
self.logger.info("stopped cannelloni for interface %s", ifname)
84+
85+
cmd_ip_del = f"ip link del name {ifname}"
86+
processwrapper.check_output(cmd_ip_del.split())
87+
else:
88+
cmd_down = f"ip link set {ifname} down"
89+
processwrapper.check_output(cmd_down.split())
90+
91+
@Driver.check_bound
92+
def get_export_vars(self):
93+
export_vars = {
94+
"ifname": self.ifname,
95+
"speed": str(self.port.speed),
96+
}
97+
if isinstance(self.port, NetworkCANPort):
98+
host, port = proxymanager.get_host_and_port(self.port)
99+
export_vars["host"] = host
100+
export_vars["port"] = str(port)
101+
return export_vars

0 commit comments

Comments
 (0)