diff --git a/docs/CANBus.md b/docs/CANBus.md new file mode 100644 index 0000000000..71c56029db --- /dev/null +++ b/docs/CANBus.md @@ -0,0 +1,395 @@ +# CAN Bus support + +## Introduction + +The Controller Area Network (CAN) is a vehicle standard initially designed by +Bosch to allow the communication between microcontrollers from different ECUs +(Electrical Control Units) inside a vehicle. The architecture is structured in a +OSI model implementing the first two layers: Physical and Data link layers. The +CAN bus evolved into two ISO standards: [ISO11898-1](https://www.iso.org/standard/63648.html), which covers the Data link +layer, and the [ISO11898-2](https://www.iso.org/standard/67244.html), which covers the Physical layer (electric properties, +etc). There are basically two types for the Physical layer of CAN bus: + +* __Low speed CAN bus__ (also referred to as fault tolerant CAN): It enables low + bit rate transfers, from 40 kbit/s to 125 kbit/s, but it can continue to + work even if one of the two wires of the bus fail. + +* __High speed CAN bus__: Supporting bit rates from 40 kbit/s to 1 Mbit/s + (Classical CAN) this is the most popular CAN bus standard and the base for + upper layer protocols, such as OBDII, J1939, etc. The second generation of + CAN is known as CAN with Flexible Data-rate, or __CAN FD__. This standard is + also compatible with Classical CAN and enables bit rates up to 8 Mbit/s. + +## CAN bus on Linux + +The Linux kernel supports CAN network through the SocketCAN framework. +SocketCAN is built upon the Linux network layer, so CAN interfaces are +listed as network interfaces in the system, as shown below: + +```text +$ ip addr show can0 +7: can0: mtu 16 qdisc noop state DOWN group default qlen 10 + link/can +``` + +Therefore, the communication is performed through sockets. The regular +network system tools, like _ifconfig_ or _ip_ can be used to setup CAN +interfaces, and other userspace utilities can be used to send and receive +CAN data. These are the settings for a CAN interface: + +```text +$ ip link set can0 type can help +Usage: ip link set DEVICE type can + [ bitrate BITRATE [ sample-point SAMPLE-POINT] ] | + [ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1 + phase-seg2 PHASE-SEG2 [ sjw SJW ] ] + + [ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] | + [ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1 + dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ] + [ tdcv TDCV tdco TDCO tdcf TDCF ] + + [ loopback { on | off } ] + [ listen-only { on | off } ] + [ triple-sampling { on | off } ] + [ one-shot { on | off } ] + [ berr-reporting { on | off } ] + [ fd { on | off } ] + [ fd-non-iso { on | off } ] + [ presume-ack { on | off } ] + [ cc-len8-dlc { on | off } ] + [ tdc-mode { auto | manual | off } ] + + [ restart-ms TIME-MS ] + [ restart ] + + [ termination { 0..65535 } ] + + Where: BITRATE := { NUMBER in bps } + SAMPLE-POINT := { 0.000..0.999 } + TQ := { NUMBER in ns } + PROP-SEG := { NUMBER in tq } + PHASE-SEG1 := { NUMBER in tq } + PHASE-SEG2 := { NUMBER in tq } + SJW := { NUMBER in tq } + TDCV := { NUMBER in tc } + TDCO := { NUMBER in tc } + TDCF := { NUMBER in tc } + RESTART-MS := { 0 | NUMBER in ms } +``` + +## Common use cases + +This section describes the main use cases with different configurations +and/or uses of CAN bus by Edge Applications and EVE-OS. In all examples it +will be considered a hypothetical device running EVE-OS with three Edge +Applications deployed (no matter their type, i.e., if they are Containers +and/or VMs). However, all these examples can be expanded to different +combinations and setups. + +In the use case of Fig. 1 the hardware is equipped with one physical CAN +interface which is [passed-through](#edge-application-guest-requirements) to +the Edge Application EdgeApp 1. Thus, this Guest will have the physical CAN +interface exposed through an emulated device, so it should see a regular can +interface (_can0_, for instance) through where the communication can be +established. + +![Fig. 1: Only one Edge Application has access to the physical CAN interface from the host.](./images/eve-can-case1.png) + +*Fig. 1: Only one Edge Application has access to the physical CAN interface from the host.* + +In the use case of Fig. 2 the hardware is equipped with one physical CAN +interface which is passed-through to all Edge Applications deployed on the +system (EdgeApp 1 to 3). In this case, all Guests should have the physical CAN +exposed through an emulated device. Due to the broadcast nature of the CAN bus, +all frames sent to this interface shall be seen by all Guests. + +![Fig. 2: All Edge Applications have access to the physical CAN interface from the host.](./images/eve-can-case2.png) + +*Fig. 2: All Edge Applications have access to the physical CAN interface from the host.* + +The use case of Fig. 3 illustrates a more complex scenario, where the +hardware is equipped with one physical CAN interface and one Virtual CAN +interface that is configured by EVE-OS. Both physical and virtual CAN +interfaces are passed-through to EdgeApp1 while the Virtual interface is +also passed-through to EdgeApp 2 and Edge App 3. In this setup the EdgeApp +1 could act as a proxy/firewall for the CAN bus traffic of EdgeApp 2 and +EdgeApp3, forwarding only the desired frames to the physical CAN interface +(on both directions, for instance). + +![Fig. 3: One Edge Application has access to the physical CAN interface from the host and one virtual CAN interface, which is also accessed by the other two Edge Applications. In this case, EdgeApp 1 could act as a proxy between VCAN->CAN.](./images/eve-can-case3.png) + +*Fig. 3: One Edge Application has access to the physical CAN interface from the host and one virtual CAN interface, which is also accessed by the other two Edge Applications. In this case, EdgeApp 1 could act as a proxy between VCAN->CAN.* + +## Device model + +CAN interfaces (physical or virtual) are specified in the Device Model +file as members of the list of I/O devices (_ioMemberList_), for instance, a +physical CAN interface (_can0_) can be configured by the following code: + +```json +"ioMemberList": [ + { + "ztype": "IO_TYPE_CAN", + "phylabel": "can0", + "logicallabel" : "can0", + "assigngrp" : "", + "phyaddrs" : { + "ifname" : "can0" + }, + "cbattr" : { + "bitrate": "125000", + "sample-point": "0.875", + "tq": "29", + "prop_seg": "118", + "phase_seg1": "119", + "phase_seg2": "34", + "sjw": "1", + "dbitrate": "250000", + "dsample-point": "0.875", + "dtq": "29", + "dprop_seg": "118", + "dphase_seg1": "119", + "dphase_seg2": "34", + "dsjw": "1", + "loopback": "on", + "listen-only": "off", + "triple-sampling": "off", + "one-shot": "off", + "berr-reporting": "on", + "fd": "on", + "fd-non-iso": "off", + "presume-ack": "on", + "cc-len8-dlc": "off", + "tdc-mode": "auto" + } + } +] +``` + +The parameter _ztype_ shall be: + +* __IO_TYPE_CAN__: For physical CAN interfaces + +* __IO_TYPE_VCAN__: For virtual CAN interfaces + +* __IO_TYPE_LCAN__: For a logical CAN device, i.e., this type of device doesn’t + define any configuration for a CAN controller, but it just points to + another (physical or virtual) CAN interface. It should be particularly + used to duplicate a CAN interface in the device model so it can be + passed-through to more than one Edge Application + +Only the following parameters are mandatory: + +* ztype + +* phylabel + +* logicallabel + +* cbattr (at least bitrate should be present for IO_TYPE_CAN type) + +Parameters not supported by the CAN interface shall be omitted from the device model file. + +### Giving access to host's CAN controllers to Edge Applications + +It’s important to point out that in case of CAN interfaces, the same CAN +interface can be assigned to one or more Edge Applications. This +requirement breaks the logic for device passthrough on EVE + Cloud +Controller. So the alternative approach is to use the logical CAN +definition, which points to a physical/virtual CAN interface in the device +model. For instance, the use case from Fig. 2 would have the following +configuration in the device model: + +```json +"ioMemberList": [ + { + "ztype": "IO_TYPE_CAN", + "phylabel": "can0", + "logicallabel": "can0", + "assigngrp" : "", + "phyaddrs" : { + "ifname" : "can0" + }, + "cbattr" : { + "bitrate": "125000", + } + }, + { + "ztype": "IO_TYPE_LCAN", + "phylabel": "can0.1", + "logicallabel": "can0.1", + "assigngrp" : "can0.1", + "phyaddrs" : { + "ifname" : "can0" + }, + }, + { + "ztype": "IO_TYPE_LCAN", + "phylabel": "can0.2", + "logicallabel": "can0.2", + "assigngrp" : "can0.2", + "phyaddrs" : { + "ifname" : "can0" + }, + }, + { + "ztype": "IO_TYPE_LCAN", + "phylabel": "can0.3", + "logicallabel": "can0.3", + "assigngrp" : "can0.3", + "phyaddrs" : { + "ifname" : "can0" + }, + } +] +``` + +Notice that each _LCAN_ device should have an unique _assigngrp_ value in +order to be exclusively accessed by an Edge Application. Fore more +information, see the documentation for [I/O Adapters](./HARDWARE-MODEL.md#io-adapters). + +The correspondence between the I/O devices declared in the device model and +all CAN interfaces + Edge Applications are illustrated in the Fig. 4. + +![Fig. 4: Use case 2 with I/O CAN devices from device model illustrated.](./images/eve-can-case2-model.png) + +*Fig. 4: Use case 2 with I/O CAN devices from device model illustrated.* + +So logically the device is presented with 4 CAN interfaces, the physical +(real) controller plus 3 logical interfaces, where each one can be +passed-through to each Edge Application. However, all of them point to the +same physical _can0_. In the device model, the interface _can0_ is the one that +describes the physical CAN interface and contains the proper parameters to +set up the controller. The logical interfaces _can0.1_, _can0.2_ and _can0.3_ +point to _can0_ and can be passed-through to the applications. + +The use case from Fig. 3 would have the following configuration in the device +model (illustrated by Fig. 5): + +```json +"ioMemberList": [ + { + "ztype": "IO_TYPE_VCAN", + "phylabel": "vcan0", + "logicallabel": "vcan0", + "assigngrp" : "", + "phyaddrs" : { + "ifname" : "vcan0" + }, + "cbattr" : { + "bitrate": "125000", + } + }, + { + "ztype": "IO_TYPE_LCAN", + "phylabel": "vcan0.1", + "logicallabel": "vcan0.1", + "assigngrp" : "vcan0.1", + "phyaddrs" : { + "ifname" : "vcan0" + }, + }, + { + "ztype": "IO_TYPE_LCAN", + "phylabel": "vcan0.2", + "logicallabel": "vcan0.2", + "assigngrp" : "vcan0.2", + "phyaddrs" : { + "ifname" : "vcan0" + }, + }, + { + "ztype": "IO_TYPE_LCAN", + "phylabel": "vcan0.3", + "logicallabel": "vcan0.3", + "assigngrp" : "vcan0.3", + "phyaddrs" : { + "ifname" : "vcan0" + }, + }, + { + "ztype": "IO_TYPE_CAN", + "phylabel": "can0", + "logicallabel": "can0", + "assigngrp" : "", + "phyaddrs" : { + "ifname" : "can0" + }, + "cbattr" : { + "bitrate": "125000", + } + }, + { + "ztype": "IO_TYPE_LCAN", + "phylabel": "can0.1", + "logicallabel": "can0.1", + "assigngrp" : "can0.1", + "phyaddrs" : { + "ifname" : "can0" + }, + } +] +``` + +![Fig. 5: Use case 3 with I/O CAN devices from device model illustrated.](./images/eve-can-case3-model.png) + +*Fig. 5: Use case 3 with I/O CAN devices from device model illustrated.* + +Notice that only one interface should have the type IO_TYPE_VCAN, which is the +one that creates the virtual interface on the system. The other interfaces that +point to _vcan0_ should be created as logical CAN interfaces. A single logical +CAN interface should also be created for _can0_, even though it’s going to be +accessed by only one application. Thus, a total of six CAN interfaces will be +presented to the user: _can0_ (physical) and _can0.1_ (which can be +passed-through to EdgeApp 1); _vcan0_, which defines the virtual CAN interface; +_vcan0.1_, _vcan0.2_ and _vcan0.3_, which can be passed-through to EdgeApp 1, +EdgeApp 2 and EdgeApp 3, respectively. + +Finally, the use case 1 follows the same logic, two I/O devices are +declared for the _can0_ interface: one that defines the CAN controller and +one logical CAN (_can0.1_) to be passed-through to Edge App1. Fig. 6 +illustrates this use case. + +![Fig. 6: Use case 1 with I/O CAN devices from device model illustrated.](./images/eve-can-case1-model.png) + +*Fig. 6: Use case 1 with I/O CAN devices from device model illustrated.* + +## Edge Application (Guest) requirements + +Although the term passthrough is used in this document with respect to CAN +interfaces being accessed by Edge Applications, it does not correspond to +the default passthrough mechanism used for PCI devices. Actually, the CAN +interfaces presented to Guests are emulated and connected to the respective +physical/virtual CAN interface from the host. The device emulated to the +Guests is the [Kvaser CAN PCI](https://www.kvaser.com/). This is a well +known device, supported by Linux and other standard Operating Systems. +Thus, in order to use the CAN Bus, Guests are required to at least support +CAN Bus and this particular Kvaser device. + +### USB CAN dongles + +USB CAN dongles can be managed from the host (EVE-OS) if they are supported by +EVE's kernel and declared in the device model through the regular corresponding +CAN interfaces (e.g., _can0_, _can1_, etc). However, they can also be +passed-through as regular USB devices to Edge Applications. In this case, the +host will not be aware anymore about the device and the whole device management +should be done by the Edge Application. Notice, however, that no emulation or +exposure to other Guests will be possible in this case. + +## CAN Bus resources on the web + +1. [https://www.ti.com/lit/an/sloa101b/sloa101b.pdf?ts=1699421104828](https://www.ti.com/lit/an/sloa101b/sloa101b.pdf?ts=1699421104828) +1. [https://www.csselectronics.com/pages/can-bus-simple-intro-tutorial](https://www.csselectronics.com/pages/can-bus-simple-intro-tutorial) +1. [https://www.can-cia.org/can-knowledge/can/can-fd/](https://www.can-cia.org/can-knowledge/can/can-fd/) +1. [https://www.kernel.org/doc/html/latest/networking/can.html](https://www.kernel.org/doc/html/latest/networking/can.html) +1. [https://gitlab.com/hjoertel/can4linux](https://gitlab.com/hjoertel/can4linux) +1. [https://www.kampis-elektroecke.de/can-hausbus/can-bus-bit-timing/](https://www.kampis-elektroecke.de/can-hausbus/can-bus-bit-timing/) +1. [https://www.ti.com/lit/an/sprac35/sprac35.pdf?ts=1702295040569](https://www.ti.com/lit/an/sprac35/sprac35.pdf?ts=1702295040569) +1. [http://www.oertel-halle.de/files/cia99paper.pdf](http://www.oertel-halle.de/files/cia99paper.pdf) +1. [https://www.can-cia.org/fileadmin/resources/documents/proceedings/2013_hartwich_v2.pdf](https://www.can-cia.org/fileadmin/resources/documents/proceedings/2013_hartwich_v2.pdf) +1. [https://www.ni.com/docs/de-DE/bundle/ni-xnet/page/can-fd-iso-versus-non-iso.html](https://www.ni.com/docs/de-DE/bundle/ni-xnet/page/can-fd-iso-versus-non-iso.html) +1. [https://www.nxp.com/docs/en/application-note/AN12728.pdf](https://www.nxp.com/docs/en/application-note/AN12728.pdf) +1. [https://docs.espressif.com/projects/esp-idf/en/v4.1.1/api-reference/peripherals/can.html](https://docs.espressif.com/projects/esp-idf/en/v4.1.1/api-reference/peripherals/can.html) +1. [https://python-can.readthedocs.io/en/2.2.1/interfaces/socketcan.html](https://python-can.readthedocs.io/en/2.2.1/interfaces/socketcan.html) +1. [https://www.qemu.org/docs/master/system/devices/can.html](https://www.qemu.org/docs/master/system/devices/can.html) diff --git a/docs/DEVICE-CONNECTIVITY.md b/docs/DEVICE-CONNECTIVITY.md index fec3b29ac0..cd9e0cab76 100644 --- a/docs/DEVICE-CONNECTIVITY.md +++ b/docs/DEVICE-CONNECTIVITY.md @@ -32,7 +32,6 @@ the edge-node, not globally. ### Physical network ports EVE supports ethernet NICs, WiFi modules and cellular modems. -Support for [CAN devices is a work in-progress](https://wiki.lfedge.org/display/EVE/CAN+Bus+support). Physical network IO devices are listed as part of the device model. This should include all devices that will be used by EVE (for management) or by applications (directly or shared). diff --git a/docs/images/eve-can-case1-model.png b/docs/images/eve-can-case1-model.png new file mode 100644 index 0000000000..c95831f821 Binary files /dev/null and b/docs/images/eve-can-case1-model.png differ diff --git a/docs/images/eve-can-case1.png b/docs/images/eve-can-case1.png new file mode 100644 index 0000000000..cd6d3e48fb Binary files /dev/null and b/docs/images/eve-can-case1.png differ diff --git a/docs/images/eve-can-case2-model.png b/docs/images/eve-can-case2-model.png new file mode 100644 index 0000000000..5bc5ccfc1a Binary files /dev/null and b/docs/images/eve-can-case2-model.png differ diff --git a/docs/images/eve-can-case2.png b/docs/images/eve-can-case2.png new file mode 100644 index 0000000000..e0ee206f3d Binary files /dev/null and b/docs/images/eve-can-case2.png differ diff --git a/docs/images/eve-can-case3-model.png b/docs/images/eve-can-case3-model.png new file mode 100644 index 0000000000..5dc6beed65 Binary files /dev/null and b/docs/images/eve-can-case3-model.png differ diff --git a/docs/images/eve-can-case3.png b/docs/images/eve-can-case3.png new file mode 100644 index 0000000000..145d8d6dfb Binary files /dev/null and b/docs/images/eve-can-case3.png differ diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 587c26d432..a762ba326f 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -25,6 +25,7 @@ nav: - 'Verifying EVE-OS': 'docs/HARDWARE-VERIFICATION.md' - 'Booting EVE': 'docs/BOOTING.md' - Communications: 'docs/COMMS.md' + - 'CAN Bus': 'docs/CANBus.md' - Registration: 'docs/REGISTRATION.md' - 'Update base image': 'docs/BASEIMAGE-UPDATE.md' - Build-tools: 'build-tools/ver-update/README.md' diff --git a/pkg/debug/spec.sh b/pkg/debug/spec.sh index 4f8b3649ab..75807d2bd0 100755 --- a/pkg/debug/spec.sh +++ b/pkg/debug/spec.sh @@ -530,6 +530,30 @@ __EOT__ COMMA="}," fi done +#enumerate physical CAN devices +for CAN in /sys/class/net/*; do + TYPE=$(cat "${CAN}/type") + if [ "$TYPE" != 280 ]; then + continue + fi + ZTYPE="IO_TYPE_CAN" + IFNAME=$(basename "${CAN}") + cat <<__EOT__ + ${COMMA} + { + "ztype": ${ZTYPE}, + "phylabel": "${IFNAME}", + "phyaddrs": { + "Ifname": "${IFNAME}" + }, + "logicallabel": "${IFNAME}", + "assigngrp": "", + "cbattr": { + "bitrate": "150000" + } +__EOT__ + COMMA="}," +done #enumerate Audio ID="" for audio in $(echo "$LSPCI_D" | grep Audio | cut -f1 -d\ ); do diff --git a/pkg/pillar/canbus/canbus.go b/pkg/pillar/canbus/canbus.go new file mode 100644 index 0000000000..78741b199d --- /dev/null +++ b/pkg/pillar/canbus/canbus.go @@ -0,0 +1,960 @@ +// Copyright (c) 2024 Zededa, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// CAN Bus support package +// This package provides functions to setup physical and/or create virtual +// CAN interfaces +package canbus + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "strconv" + "syscall" + "unsafe" + + "github.com/vishvananda/netlink" + "github.com/vishvananda/netlink/nl" + "golang.org/x/sys/unix" +) + +// Types of CAN interfaces +const ( + // LinkVCAN Virtual CAN interface type + LinkVCAN = "vcan" + // LinkCAN Physical CAN interface type + LinkCAN = "can" +) + +// The following constants are related to TDC (Transmitter Delay +// Compensation) and were taken from Linux's kernel header: +// include/uapi/linux/can/netlink.h (version 6.6) +// PS: All of them are treated as uint32 in netlink packages +// +//revive:disable:var-naming +const ( + IFLA_CAN_TDC = unix.IFLA_CAN_BITRATE_MAX + 1 + IFLA_CAN_TDC_TDCV_MIN = 0x1 + IFLA_CAN_TDC_TDCV_MAX = 0x2 + IFLA_CAN_TDC_TDCO_MIN = 0x3 + IFLA_CAN_TDC_TDCO_MAX = 0x4 + IFLA_CAN_TDC_TDCF_MIN = 0x5 + IFLA_CAN_TDC_TDCF_MAX = 0x6 + IFLA_CAN_TDC_TDCV = 0x7 + IFLA_CAN_TDC_TDCO = 0x8 + IFLA_CAN_TDC_TDCF = 0x9 +) + +// CAN Interface, it implements netlink.Link interface +type CAN struct { + LinkAttrs netlink.LinkAttrs + CanType string + State uint32 + BitTiming unix.CANBitTiming + DataBitTiming unix.CANBitTiming + BitTimingConst unix.CANBitTimingConst + DataBitTimingConst unix.CANBitTimingConst + ErrorCounters unix.CANBusErrorCounters + Clock unix.CANClock + CtrlMode unix.CANCtrlMode + DeviceStats unix.CANDeviceStats + Restart uint32 + RestartMs uint32 + Termination uint16 + TDCV uint32 + TDCO uint32 + TDCF uint32 +} + +// Attrs returns Link attributes +func (d CAN) Attrs() *netlink.LinkAttrs { + return &d.LinkAttrs +} + +// Type returns the type of the CAN interface, i.e. "can" or "vcan" +func (d CAN) Type() string { + return d.CanType +} + +// Prepare data to be sent through a netlink package +// Returns a new buffer with the data +func makeBuffer(data any) (*bytes.Buffer, error) { + raw := make([]byte, 0, unsafe.Sizeof(data)) + buf := bytes.NewBuffer(raw) + err := binary.Write(buf, nl.NativeEndian(), data) + if err != nil { + return nil, err + } + return buf, nil +} + +// Send a netlink request with a single attribute +// attrType: Attribute ID +// data: Attribute's data +func execSingleNetlinkReq(link CAN, attrType int, data []byte) error { + attrsType := []int{attrType} + payload := [][]byte{data} + return execNetlinkReq(link, attrsType, payload) +} + +// Send a netlink request +// attrsType: Array of attributes to change +// data: Array of the data for each attribute +func execNetlinkReq(link CAN, attrsType []int, data [][]byte) error { + // Ensure the link index + lattrs := link.Attrs() + if lattrs == nil { + return errors.New("Provided link has no attributes") + } + nlink, err := netlink.LinkByName(lattrs.Name) + if err != nil { + return err + } + linkIndex := nlink.Attrs().Index + + // Check if we have data + la := len(attrsType) + ld := len(data) + if la == 0 || ld == 0 { + return errors.New("No attributes and/or data provided") + } + if la != ld { + return errors.New("Attributes and Data array must be of the same length") + } + + // Build netlink message + req := nl.NewNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_REQUEST|unix.NLM_F_ACK) + msg := nl.NewIfInfomsg(unix.AF_UNSPEC) + msg.Index = int32(linkIndex) + req.AddData(msg) + + linkInfo := nl.NewRtAttr(unix.IFLA_LINKINFO, nil) + linkInfo.AddRtAttr(nl.IFLA_INFO_KIND, nl.NonZeroTerminated(link.Type())) + + nlData := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil) + for i, att := range attrsType { + payload := data[i] + nlData.AddRtAttr(att, payload) + } + req.AddData(linkInfo) + + // Execute request + _, err = req.Execute(unix.NETLINK_ROUTE, 0) + return err +} + +// Parse CAN data replied through netlink message +// This function was adapted and incremented from the netlink package version v1.2.0-beta +func parseCanData(link *CAN, data []syscall.NetlinkRouteAttr) { + native := nl.NativeEndian() + for _, datum := range data { + switch datum.Attr.Type { + case unix.IFLA_CAN_BITTIMING: + link.BitTiming.Bitrate = native.Uint32(datum.Value) + link.BitTiming.Sample_point = native.Uint32(datum.Value[4:]) + link.BitTiming.Tq = native.Uint32(datum.Value[8:]) + link.BitTiming.Prop_seg = native.Uint32(datum.Value[12:]) + link.BitTiming.Phase_seg1 = native.Uint32(datum.Value[16:]) + link.BitTiming.Phase_seg2 = native.Uint32(datum.Value[20:]) + link.BitTiming.Sjw = native.Uint32(datum.Value[24:]) + link.BitTiming.Brp = native.Uint32(datum.Value[28:]) + case unix.IFLA_CAN_BITTIMING_CONST: + link.BitTimingConst.Tseg1_min = native.Uint32(datum.Value[16:]) + link.BitTimingConst.Tseg1_max = native.Uint32(datum.Value[20:]) + link.BitTimingConst.Tseg2_min = native.Uint32(datum.Value[24:]) + link.BitTimingConst.Tseg2_max = native.Uint32(datum.Value[28:]) + link.BitTimingConst.Sjw_max = native.Uint32(datum.Value[32:]) + link.BitTimingConst.Brp_min = native.Uint32(datum.Value[36:]) + link.BitTimingConst.Brp_max = native.Uint32(datum.Value[40:]) + link.BitTimingConst.Brp_inc = native.Uint32(datum.Value[44:]) + case unix.IFLA_CAN_DATA_BITTIMING: + link.DataBitTiming.Bitrate = native.Uint32(datum.Value) + link.DataBitTiming.Sample_point = native.Uint32(datum.Value[4:]) + link.DataBitTiming.Tq = native.Uint32(datum.Value[8:]) + link.DataBitTiming.Prop_seg = native.Uint32(datum.Value[12:]) + link.DataBitTiming.Phase_seg1 = native.Uint32(datum.Value[16:]) + link.DataBitTiming.Phase_seg2 = native.Uint32(datum.Value[20:]) + link.DataBitTiming.Sjw = native.Uint32(datum.Value[24:]) + link.DataBitTiming.Brp = native.Uint32(datum.Value[28:]) + case unix.IFLA_CAN_DATA_BITTIMING_CONST: + link.DataBitTimingConst.Tseg1_min = native.Uint32(datum.Value[16:]) + link.DataBitTimingConst.Tseg1_max = native.Uint32(datum.Value[20:]) + link.DataBitTimingConst.Tseg2_min = native.Uint32(datum.Value[24:]) + link.DataBitTimingConst.Tseg2_max = native.Uint32(datum.Value[28:]) + link.DataBitTimingConst.Sjw_max = native.Uint32(datum.Value[32:]) + link.DataBitTimingConst.Brp_min = native.Uint32(datum.Value[36:]) + link.DataBitTimingConst.Brp_max = native.Uint32(datum.Value[40:]) + link.DataBitTimingConst.Brp_inc = native.Uint32(datum.Value[44:]) + case unix.IFLA_CAN_BERR_COUNTER: + link.ErrorCounters.Txerr = native.Uint16(datum.Value) + link.ErrorCounters.Rxerr = native.Uint16(datum.Value[2:]) + case unix.IFLA_CAN_CLOCK: + link.Clock.Freq = native.Uint32(datum.Value) + case unix.IFLA_CAN_CTRLMODE: + link.CtrlMode.Mask = native.Uint32(datum.Value) + link.CtrlMode.Flags = native.Uint32(datum.Value[4:]) + case unix.IFLA_CAN_STATE: + link.State = native.Uint32(datum.Value) + case unix.IFLA_CAN_RESTART_MS: + link.RestartMs = native.Uint32(datum.Value) + case unix.IFLA_CAN_RESTART: + link.Restart = native.Uint32(datum.Value) + case unix.IFLA_CAN_TERMINATION: + link.Termination = native.Uint16(datum.Value) + case IFLA_CAN_TDC | nl.NLA_F_NESTED: + nmsg, err := nl.ParseRouteAttr(datum.Value) + if err != nil { + continue + } + for _, nested := range nmsg { + switch nested.Attr.Type { + case IFLA_CAN_TDC_TDCV: + link.TDCV = native.Uint32(nested.Value) + case IFLA_CAN_TDC_TDCO: + link.TDCO = native.Uint32(nested.Value) + case IFLA_CAN_TDC_TDCF: + link.TDCF = native.Uint32(nested.Value) + } + } + } + } +} + +// Set a bit flag +func setBitFlag(flags uint32, mask uint32) uint32 { + return (flags | mask) +} + +// Clear a bit flag +func clearBitFlag(flags uint32, mask uint32) uint32 { + return (flags & (^mask)) +} + +// Convert Sample Point string (float format) value to uint32 +// (multiplying by 1000) +func convSamplePoint(str string) (uint32, error) { + value, err := strconv.ParseFloat(str, 32) + if err != nil { + return 0, err + } + return uint32(value * 1000), nil +} + +// Convert string value to uint32 +func convUint32(str string) (uint32, error) { + value, err := strconv.ParseUint(str, 0, 32) + if err != nil { + return 0, err + } + return uint32(value), nil +} + +// Convert string value to uint16 +func convUint16(str string) (uint16, error) { + value, err := strconv.ParseUint(str, 0, 16) + if err != nil { + return 0, err + } + return uint16(value), nil +} + +// Convert restart string value to uint32 +func convRestart(str string) (uint32, error) { + var err error + val := uint32(0) + switch str { + case "true": + fallthrough + case "1": + val = 1 + case "false": + fallthrough + case "0": + val = 0 + default: + err = errors.New("Invalid value") + } + return val, err +} + +// Set uint16 CAN properties that were passed as strings +func setUint16Prop(props map[string]string, attrs map[string]*uint16) (bool, error) { + var err error + change := false + for prop, value := range props { + ptr, found := attrs[prop] + if found { + change = true + *ptr, err = convUint16(value) + if err != nil { + return change, err + } + } + } + return change, nil +} + +// Set uint32 CAN properties that were passed as strings +func setUint32Prop(props map[string]string, attrs map[string]*uint32, + convFunc func(string) (uint32, error)) (bool, error) { + var err error + change := false + for prop, value := range props { + ptr, found := attrs[prop] + if found { + change = true + *ptr, err = convFunc(value) + if err != nil { + return change, err + } + } + } + return change, nil +} + +// Set CtrlMode properties that were passed as strings +func setCtrlModeProp(props map[string]string, attrs map[string]uint32, + ptr *unix.CANCtrlMode) (bool, error) { + var err error + change := false + for prop, value := range props { + flag, found := attrs[prop] + if found { + change = true + err = setCtrlModeFlags(flag, value, ptr) + if err != nil { + return change, err + } + } else if prop == "tdc-mode" { + change = true + err = setCtrlModeTDC(value, ptr) + if err != nil { + return change, err + } + } + } + return change, nil +} + +// Set the proper flag of unix.CANCtrlMode according to the state (e.g. +// "on" or "off"), except the TDC mode +func setCtrlModeFlags(flagBit uint32, state string, ctrlMode *unix.CANCtrlMode) error { + if ctrlMode == nil { + return errors.New("No unix.CANCtrlMode provided") + } + mask := ctrlMode.Mask + flag := ctrlMode.Flags + + if state == "on" { + mask = setBitFlag(mask, flagBit) + flag = setBitFlag(flag, flagBit) + } else { + mask = clearBitFlag(mask, flagBit) + flag = clearBitFlag(flag, flagBit) + } + + ctrlMode.Mask = mask + ctrlMode.Flags = flag + return nil +} + +// Set TDC mode flags of unix.CANCtrlMode according to the state provided as +// string +func setCtrlModeTDC(state string, ctrlMode *unix.CANCtrlMode) error { + if ctrlMode == nil { + return errors.New("No unix.CANCtrlMode provided") + } + mask := ctrlMode.Mask + flag := ctrlMode.Flags + + switch state { + case "auto": + mask = setBitFlag(mask, unix.CAN_CTRLMODE_TDC_AUTO) + flag = setBitFlag(flag, unix.CAN_CTRLMODE_TDC_AUTO) + case "manual": + mask = setBitFlag(mask, unix.CAN_CTRLMODE_TDC_MANUAL) + flag = setBitFlag(flag, unix.CAN_CTRLMODE_TDC_MANUAL) + case "off": + mask = setBitFlag(mask, + unix.CAN_CTRLMODE_TDC_AUTO|unix.CAN_CTRLMODE_TDC_MANUAL) + flag = setBitFlag(flag, + unix.CAN_CTRLMODE_TDC_AUTO|unix.CAN_CTRLMODE_TDC_MANUAL) + } + + ctrlMode.Mask = mask + ctrlMode.Flags = flag + return nil +} + +// Validate property names from a list of CAN properties +func validatePropertyNames(properties map[string]string) error { + allowedProps := map[string]struct{}{ + "bitrate": {}, + "sample-point": {}, + "tq": {}, + "prop_seg": {}, + "phase_seg1": {}, + "phase_seg2": {}, + "sjw": {}, + "dbitrate": {}, + "dsample-point": {}, + "dtq": {}, + "dprop_seg": {}, + "dphase_seg1": {}, + "dphase_seg2": {}, + "dsjw": {}, + "loopback": {}, + "listen-only": {}, + "triple-sampling": {}, + "one-shot": {}, + "berr-reporting": {}, + "fd": {}, + "fd-non-iso": {}, + "presume-ack": {}, + "cc-len8-dlc": {}, + "termination": {}, + "tdc-mode": {}, + "tdco": {}, + "tdcv": {}, + "tdcf": {}, + "restart": {}, + "restart-ms": {}, + } + for prop := range properties { + _, found := allowedProps[prop] + if !found { + return fmt.Errorf("Invalid property: %s", prop) + } + } + return nil +} + +// GetCANLinks fetches all CAN interfaces (virtual or physical) +func GetCANLinks() ([]netlink.Link, error) { + var canIfs []netlink.Link + ifs, err := netlink.LinkList() + + if err != nil { + return nil, err + } + + for _, dev := range ifs { + if dev.Type() == LinkCAN || dev.Type() == LinkVCAN { + canIfs = append(canIfs, dev) + } + } + + return canIfs, err +} + +// GetCANLink returns a CAN interface from the interface's name (e.g. "can0", +// "vcan0"). All the information about the interface will be fetched from the +// system +func GetCANLink(name string) (*CAN, error) { + nlink, err := netlink.LinkByName(name) + if err != nil { + return nil, err + } + if nlink.Type() != LinkCAN && nlink.Type() != LinkVCAN { + return nil, errors.New("Not a CAN interface. Type:" + nlink.Type()) + } + linkIndex := nlink.Attrs().Index + + // Build netlink message + req := nl.NewNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_REQUEST|unix.NLM_F_ACK) + msg := nl.NewIfInfomsg(unix.AF_PACKET) + msg.Index = int32(linkIndex) + req.AddData(msg) + + ifname := nl.NewRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(name)) + req.AddData(ifname) + + // Send request + msgs, err := req.Execute(unix.NETLINK_ROUTE, 0) + if err != nil { + return nil, err + } + if len(msgs) == 0 { + return nil, errors.New("Link not found") + } + + // Parse replied message + reply := nl.DeserializeIfInfomsg(msgs[0]) + attrs, err := nl.ParseRouteAttr(msgs[0][reply.Len():]) + if err != nil { + return nil, err + } + + // Create CAN interface object + ifcan := CAN{} + ifcan.LinkAttrs = *nlink.Attrs() + ifcan.CanType = nlink.Type() + + // At this point, we are interested only in the specific CAN options + native := nl.NativeEndian() + for _, attr := range attrs { + switch attr.Attr.Type { + case unix.IFLA_LINKINFO: + infos, err := nl.ParseRouteAttr(attr.Value) + if err != nil { + return nil, err + } + for _, info := range infos { + switch info.Attr.Type { + case unix.IFLA_INFO_DATA: + payload, err := nl.ParseRouteAttr(info.Value) + if err != nil { + return nil, err + } + parseCanData(&ifcan, payload) + case unix.IFLA_INFO_XSTATS: + ifcan.DeviceStats.Bus_error = native.Uint32(info.Value) + ifcan.DeviceStats.Error_warning = native.Uint32(info.Value[4:]) + ifcan.DeviceStats.Error_passive = native.Uint32(info.Value[8:]) + ifcan.DeviceStats.Bus_off = native.Uint32(info.Value[12:]) + ifcan.DeviceStats.Arbitration_lost = native.Uint32(info.Value[16:]) + ifcan.DeviceStats.Restarts = native.Uint32(info.Value[20:]) + } + } + default: + continue + } + } + + return &ifcan, nil +} + +// AddVCANLink adds a new Virtual CAN interface +// name: Interface's name, e.g. "vcan0", "vcan1" +func AddVCANLink(name string) (*CAN, error) { + vcan := CAN{} + vcan.LinkAttrs.Name = name + vcan.CanType = "vcan" + err := netlink.LinkAdd(&vcan) + return &vcan, err +} + +// DelVCANLink deletes a Virtual CAN interface +// name: Interface's name, e.g. "vcan0", "vcan1" +func DelVCANLink(name string) error { + cdev, err := netlink.LinkByName(name) + if err != nil || cdev == nil { + return err + } + return netlink.LinkDel(cdev) +} + +// LinkSetUp brings up a CAN interface +func LinkSetUp(link *CAN) error { + return netlink.LinkSetUp(link) +} + +// LinkSetDown brings down a CAN interface +func LinkSetDown(link *CAN) error { + return netlink.LinkSetDown(link) +} + +// StateToString returns the string corresponding to a CAN State +func StateToString(state uint32) string { + st2str := map[uint32]string{ + unix.CAN_STATE_ERROR_ACTIVE: "Error Active", + unix.CAN_STATE_ERROR_WARNING: "Error Warning", + unix.CAN_STATE_ERROR_PASSIVE: "Error Passive", + unix.CAN_STATE_BUS_OFF: "Bus off", + unix.CAN_STATE_STOPPED: "Stopped", + unix.CAN_STATE_SLEEPING: "Sleeping", + } + return st2str[state] +} + +// SetBitTiming sets bit timing properties of a CAN interface +func SetBitTiming(link *CAN, bitTiming unix.CANBitTiming) error { + if link == nil { + return errors.New("No link provided") + } + + buf, err := makeBuffer(bitTiming) + if err != nil { + return err + } + + // Execute request + err = execSingleNetlinkReq(*link, unix.IFLA_CAN_BITTIMING, buf.Bytes()) + if err != nil { + return err + } + link.BitTiming = bitTiming + return nil +} + +// SetDataBitTiming sets data bit timing properties of a CAN interface +func SetDataBitTiming(link *CAN, dataBitTiming unix.CANBitTiming) error { + if link == nil { + return errors.New("No link provided") + } + + buf, err := makeBuffer(dataBitTiming) + if err != nil { + return err + } + + // Execute request + err = execSingleNetlinkReq(*link, unix.IFLA_CAN_DATA_BITTIMING, buf.Bytes()) + if err != nil { + return err + } + link.DataBitTiming = dataBitTiming + return nil +} + +// SetBitTimingConst sets bit timing constant properties of a CAN interface +func SetBitTimingConst(link *CAN, bitTimingConst unix.CANBitTimingConst) error { + if link == nil { + return errors.New("No link provided") + } + + buf, err := makeBuffer(bitTimingConst) + if err != nil { + return err + } + + // Execute request + err = execSingleNetlinkReq(*link, unix.IFLA_CAN_BITTIMING_CONST, buf.Bytes()) + if err != nil { + return err + } + link.BitTimingConst = bitTimingConst + return nil +} + +// SetDataBitTimingConst sets data bit timing constant properties of a CAN interface +func SetDataBitTimingConst(link *CAN, dataBitTimingConst unix.CANBitTimingConst) error { + if link == nil { + return errors.New("No link provided") + } + + buf, err := makeBuffer(dataBitTimingConst) + if err != nil { + return err + } + + // Execute request + err = execSingleNetlinkReq(*link, unix.IFLA_CAN_DATA_BITTIMING_CONST, buf.Bytes()) + if err != nil { + return err + } + link.DataBitTimingConst = dataBitTimingConst + return nil +} + +// SetCtrlMode sets control mode flags of a CAN interface +func SetCtrlMode(link *CAN, ctrlMode unix.CANCtrlMode) error { + if link == nil { + return errors.New("No link provided") + } + + buf, err := makeBuffer(ctrlMode) + if err != nil { + return err + } + + // Execute request + err = execSingleNetlinkReq(*link, unix.IFLA_CAN_CTRLMODE, buf.Bytes()) + if err != nil { + return err + } + link.CtrlMode = ctrlMode + return nil +} + +// SetRestart sets restart property of a CAN interface +func SetRestart(link *CAN, restart uint32) error { + if link == nil { + return errors.New("No link provided") + } + + // Execute request + err := execSingleNetlinkReq(*link, + unix.IFLA_CAN_RESTART, nl.Uint32Attr(restart)) + if err != nil { + return err + } + link.Restart = restart + return nil +} + +// SetRestartMs sets restart-ms property of a CAN interface +func SetRestartMs(link *CAN, restartMs uint32) error { + if link == nil { + return errors.New("No link provided") + } + + // Execute request + err := execSingleNetlinkReq(*link, + unix.IFLA_CAN_RESTART_MS, nl.Uint32Attr(restartMs)) + if err != nil { + return err + } + link.RestartMs = restartMs + return nil +} + +// SetTermination sets termination property of a CAN interface +func SetTermination(link *CAN, termination uint16) error { + if link == nil { + return errors.New("No link provided") + } + + // Execute request + err := execSingleNetlinkReq(*link, + unix.IFLA_CAN_TERMINATION, nl.Uint16Attr(termination)) + if err != nil { + return err + } + link.Termination = termination + return nil +} + +// SetTDC sets TDC parameters +func SetTDC(link *CAN, tdcv uint32, tdco uint32, tdcf uint32) error { + if link == nil { + return errors.New("No link provided") + } + // Ensure the link index + lattrs := link.Attrs() + if lattrs == nil { + return errors.New("Provided link has no attributes") + } + nlink, err := netlink.LinkByName(lattrs.Name) + if err != nil { + return err + } + linkIndex := nlink.Attrs().Index + + // Build netlink message + req := nl.NewNetlinkRequest(unix.RTM_NEWLINK, unix.NLM_F_REQUEST|unix.NLM_F_ACK) + msg := nl.NewIfInfomsg(unix.AF_UNSPEC) + msg.Index = int32(linkIndex) + req.AddData(msg) + + linkInfo := nl.NewRtAttr(unix.IFLA_LINKINFO, nil) + linkInfo.AddRtAttr(nl.IFLA_INFO_KIND, nl.NonZeroTerminated(link.Type())) + + tdcAttr := linkInfo.AddRtAttr(IFLA_CAN_TDC|unix.NLA_F_NESTED, nil) + tdcAttr.AddRtAttr(IFLA_CAN_TDC_TDCV, nl.Uint32Attr(tdcv)) + tdcAttr.AddRtAttr(IFLA_CAN_TDC_TDCO, nl.Uint32Attr(tdco)) + tdcAttr.AddRtAttr(IFLA_CAN_TDC_TDCF, nl.Uint32Attr(tdcf)) + req.AddData(linkInfo) + + // Execute request + _, err = req.Execute(unix.NETLINK_ROUTE, 0) + if err != nil { + return err + } + + link.TDCV = tdcv + link.TDCO = tdco + link.TDCF = tdcf + return nil +} + +// SetupCAN setup CAN interface from a map of properties described as +// strings, e.g.: +// +// properties := map[string]string{ +// "bitrate": "125000", +// "sample-point": "0.875", +// "tq": "29", +// "prop_seg": "118", +// "phase_seg1": "119", +// "phase_seg2": "34", +// "sjw": "1", +// } +func SetupCAN(link *CAN, properties map[string]string) error { + if link == nil { + return errors.New("No link provided") + } + if len(properties) == 0 { + return errors.New("No properties were provided") + } + // Try to fetch current properties from the interface + var canProps CAN + iface, err := GetCANLink(link.Attrs().Name) + if err == nil { + canProps = *iface + } else { + canProps = CAN{} + } + + // First, validate all properties passed + err = validatePropertyNames(properties) + if err != nil { + return err + } + + // Properties changed + var changeBitTiming bool + var changeBitTimingSp bool + var changeDataBitTiming bool + var changeDataBitTimingSp bool + var changeCtrlMode bool + var changeTermination bool + var changeRestart bool + var changeRestartMs bool + var changeTDC bool + + // BitTiming properties + uint32BitTimingProps := map[string]*uint32{ + "bitrate": &canProps.BitTiming.Bitrate, + "tq": &canProps.BitTiming.Tq, + "prop_seg": &canProps.BitTiming.Prop_seg, + "phase_seg1": &canProps.BitTiming.Phase_seg1, + "phase_seg2": &canProps.BitTiming.Phase_seg2, + "sjw": &canProps.BitTiming.Sjw, + } + btSamplePointProps := map[string]*uint32{ + "sample-point": &canProps.BitTiming.Sample_point, + } + changeBitTiming, err = setUint32Prop(properties, uint32BitTimingProps, convUint32) + if err != nil { + return err + } + changeBitTimingSp, err = setUint32Prop(properties, btSamplePointProps, convSamplePoint) + if err != nil { + return err + } + + // Set BiTiming + if changeBitTiming || changeBitTimingSp { + err = SetBitTiming(link, canProps.BitTiming) + if err != nil { + return err + } + } + + // DataBitTiming properties + uint32DataBitTimingProps := map[string]*uint32{ + "dbitrate": &canProps.DataBitTiming.Bitrate, + "dtq": &canProps.DataBitTiming.Tq, + "dprop_seg": &canProps.DataBitTiming.Prop_seg, + "dprop_seg1": &canProps.DataBitTiming.Phase_seg1, + "dprop_seg2": &canProps.DataBitTiming.Phase_seg2, + "dsjw": &canProps.DataBitTiming.Sjw, + } + dbtSamplePointProps := map[string]*uint32{ + "dsample-point": &canProps.DataBitTiming.Sample_point, + } + changeDataBitTiming, err = setUint32Prop(properties, uint32DataBitTimingProps, convUint32) + if err != nil { + return err + } + changeDataBitTimingSp, err = setUint32Prop(properties, dbtSamplePointProps, convSamplePoint) + if err != nil { + return err + } + + // Set DataBitTiming + if changeDataBitTiming || changeDataBitTimingSp { + err = SetDataBitTiming(link, canProps.DataBitTiming) + if err != nil { + return err + } + } + + // CtrlMode properties + ctrlModeProps := map[string]uint32{ + "loopback": unix.CAN_CTRLMODE_LOOPBACK, + "listen-only": unix.CAN_CTRLMODE_LISTENONLY, + "triple-sampling": unix.CAN_CTRLMODE_3_SAMPLES, + "one-shot": unix.CAN_CTRLMODE_ONE_SHOT, + "berr-reporting": unix.CAN_CTRLMODE_BERR_REPORTING, + "fd": unix.CAN_CTRLMODE_FD, + "fd-non-iso": unix.CAN_CTRLMODE_FD_NON_ISO, + "presume-ack": unix.CAN_CTRLMODE_PRESUME_ACK, + "cc-len8-dlc": unix.CAN_CTRLMODE_CC_LEN8_DLC, + // we don't need a pointer for "tdc-mode", flag is handled by setCtrlModeProp() + } + changeCtrlMode, err = setCtrlModeProp(properties, ctrlModeProps, &canProps.CtrlMode) + if err != nil { + return err + } + + // Set CtrlMode + if changeCtrlMode { + err = SetCtrlMode(link, canProps.CtrlMode) + if err != nil { + return err + } + } + + // Restart property + restartProps := map[string]*uint32{ + "restart": &canProps.Restart, + } + changeRestart, err = setUint32Prop(properties, restartProps, convRestart) + if err != nil { + return err + } + + // Set Restart + if changeRestart { + err = SetRestartMs(link, canProps.Restart) + if err != nil { + return err + } + } + + // Restart Ms property + restartMsProps := map[string]*uint32{ + "restart-ms": &canProps.RestartMs, + } + changeRestartMs, err = setUint32Prop(properties, restartMsProps, convUint32) + if err != nil { + return err + } + + // Set Restart Ms + if changeRestartMs { + err = SetRestartMs(link, canProps.RestartMs) + if err != nil { + return err + } + + } + + // Termination property + terminationProp := map[string]*uint16{ + "termination": &canProps.Termination, + } + changeTermination, err = setUint16Prop(properties, terminationProp) + if err != nil { + return err + } + + // Set Termination + if changeTermination { + err = SetTermination(link, canProps.Termination) + if err != nil { + return err + } + } + + // TDC mode property + mTDCProps := map[string]*uint32{ + "tdcv": &canProps.TDCV, + "tdco": &canProps.TDCO, + "tdcf": &canProps.TDCF, + } + changeTDC, err = setUint32Prop(properties, mTDCProps, convUint32) + if err != nil { + return err + } + + // Set TDC mode + if changeTDC { + err = SetTDC(link, canProps.TDCV, canProps.TDCO, canProps.TDCF) + } + return err +} diff --git a/pkg/pillar/cmd/domainmgr/domainmgr.go b/pkg/pillar/cmd/domainmgr/domainmgr.go index 1f00d14e76..334cd70163 100644 --- a/pkg/pillar/cmd/domainmgr/domainmgr.go +++ b/pkg/pillar/cmd/domainmgr/domainmgr.go @@ -29,6 +29,7 @@ import ( "github.com/lf-edge/eve/pkg/pillar/agentbase" "github.com/lf-edge/eve/pkg/pillar/agentlog" "github.com/lf-edge/eve/pkg/pillar/base" + "github.com/lf-edge/eve/pkg/pillar/canbus" "github.com/lf-edge/eve/pkg/pillar/cas" "github.com/lf-edge/eve/pkg/pillar/cipher" "github.com/lf-edge/eve/pkg/pillar/containerd" @@ -2818,6 +2819,24 @@ func handlePhysicalIOAdapterListImpl(ctxArg interface{}, key string, } aa.AddOrUpdateIoBundle(log, vfIb) } + } else if ib.Type == types.IoVCAN { + // Initialize (create and enable) Virtual CAN device + err := setupVCAN(ib) + if err != nil { + err = fmt.Errorf("setupVCAN: %w", err) + log.Error(err) + ib.Error = err.Error() + ib.ErrorTime = time.Now() + } + } else if ib.Type == types.IoCAN { + // Initialize physical CAN device + err := setupCAN(ib) + if err != nil { + err = fmt.Errorf("setupCAN: %w", err) + log.Error(err) + ib.Error = err.Error() + ib.ErrorTime = time.Now() + } } } log.Functionf("handlePhysicalIOAdapterListImpl: initialized to get len %d", @@ -3005,6 +3024,9 @@ func updatePortAndPciBackIoBundle(ctx *domainContext, ib *types.IoBundle) (chang if ib.Type == types.IoNetEthPF { keepInHost = true } + if ib.Type == types.IoCAN || ib.Type == types.IoVCAN { + keepInHost = true + } } log.Functionf("updatePortAndPciBackIoBundle(%d %s %s) isPort %t keepInHost %t members %d", @@ -3309,6 +3331,36 @@ func removeUSBfromKernel() bool { return ret } +// Initialize (create and enable) Virtual CAN device +func setupVCAN(ib *types.IoBundle) error { + vcan, err := canbus.AddVCANLink(ib.Ifname) + if err != nil { + return err + } + err = canbus.LinkSetUp(vcan) + if err != nil { + return err + } + return nil +} + +// Initialize physical CAN device +func setupCAN(ib *types.IoBundle) error { + canIf, err := canbus.GetCANLink(ib.Ifname) + if err != nil { + return err + } + err = canbus.SetupCAN(canIf, ib.Cbattr) + if err != nil { + return err + } + err = canbus.LinkSetUp(canIf) + if err != nil { + return err + } + return nil +} + func doModprobe(driver string, add bool) error { cmd := "modprobe" args := []string{} diff --git a/pkg/pillar/cmd/zedagent/parseconfig.go b/pkg/pillar/cmd/zedagent/parseconfig.go index 56d86eec40..84dc235ba2 100644 --- a/pkg/pillar/cmd/zedagent/parseconfig.go +++ b/pkg/pillar/cmd/zedagent/parseconfig.go @@ -650,6 +650,8 @@ func parseAppInstanceConfig(getconfigCtx *getconfigContext, ioa.EthVf = sriov.EthVF{ Mac: hwaddr.String(), VlanID: uint16(adapter.EthVf.VlanId)} + } else if ioa.Type == types.IoCAN || ioa.Type == types.IoVCAN || ioa.Type == types.IoLCAN { + log.Functionf("Got CAN adapter") } appInstance.IoAdapterList = append(appInstance.IoAdapterList, ioa) } @@ -1297,6 +1299,7 @@ func parseDeviceIoListConfig(getconfigCtx *getconfigContext, Assigngrp: ioDevicePtr.Assigngrp, Parentassigngrp: ioDevicePtr.Parentassigngrp, Usage: ioDevicePtr.Usage, + Cbattr: ioDevicePtr.Cbattr, } if ioDevicePtr.UsagePolicy != nil { // Need to keep this to make proper determination diff --git a/pkg/pillar/hypervisor/kvm.go b/pkg/pillar/hypervisor/kvm.go index cc8b431722..17c5998d18 100644 --- a/pkg/pillar/hypervisor/kvm.go +++ b/pkg/pillar/hypervisor/kvm.go @@ -368,6 +368,20 @@ const qemuSerialTemplate = ` chardev = "charserial-usr{{.ID}}" ` +const qemuCANBusTemplate = ` +[object "canbus{{.ID}}"] + qom-type = "can-bus" + +[device "{{.IfName}}"] + driver = "kvaser_pci" + canbus = "canbus{{.ID}}" + +[object "canhost{{.ID}}"] + qom-type = "can-host-socketcan" + canbus = "canbus{{.ID}}" + if = "{{.HostIfName}}" +` + const kvmStateDir = "/run/hypervisor/kvm/" const sysfsVfioPciBind = "/sys/bus/pci/drivers/vfio-pci/bind" const sysfsPciDriversProbe = "/sys/bus/pci/drivers_probe" @@ -773,6 +787,8 @@ func (ctx KvmContext) CreateDomConfig(domainName string, config types.DomainConf var pciAssignments []pciDevice // Gather all serial assignments into a single line var serialAssignments []string + // Gather all CAN Bus assignments into a single line + canBusAssignments := make(map[string]string) for _, adapter := range config.IoAdapterList { logrus.Debugf("processing adapter %d %s\n", adapter.Type, adapter.Name) @@ -800,6 +816,20 @@ func (ctx KvmContext) CreateDomConfig(domainName string, config types.DomainConf logrus.Infof("Adding serial <%s>\n", ib.Serial) serialAssignments = addNoDuplicate(serialAssignments, ib.Serial) } + if ib.Type == types.IoLCAN && ib.Ifname != "" { + var canIfName string + if ib.Logicallabel == "" { + canIfName = ib.Phylabel + } else { + canIfName = ib.Logicallabel + } + if canIfName != "" { + logrus.Infof("Adding CAN interface <%s>", canIfName) + if canBusAssignments[canIfName] == "" { + canBusAssignments[canIfName] = ib.Ifname + } + } + } } } if len(pciAssignments) != 0 { @@ -852,6 +882,30 @@ func (ctx KvmContext) CreateDomConfig(domainName string, config types.DomainConf } } } + if len(canBusAssignments) != 0 { + canIfContext := struct { + Machine string + IfName string + HostIfName string + ID int + }{Machine: ctx.devicemodel, IfName: "", HostIfName: "", ID: 0} + + t, err := template.New("qemuCANBus").Parse(qemuCANBusTemplate) + if err != nil { + return logError("can't create CAN Bus configuration template: %v", err) + } + id := 0 + for canIf, canHostIf := range canBusAssignments { + logrus.Infof("CAN interface %s connected to host CAN %s\n", canIf, canHostIf) + canIfContext.IfName = canIf + canIfContext.HostIfName = canHostIf + canIfContext.ID = id + id++ + if err := t.Execute(file, canIfContext); err != nil { + return logError("can't write CAN Bus assignment to config file %s (%v)", file.Name(), err) + } + } + } return nil } diff --git a/pkg/pillar/types/assignableadapters.go b/pkg/pillar/types/assignableadapters.go index 6963004f7c..25d46ed4a3 100644 --- a/pkg/pillar/types/assignableadapters.go +++ b/pkg/pillar/types/assignableadapters.go @@ -105,6 +105,8 @@ type IoBundle struct { Vfs sriov.VFList // Only used in PhyIoNetEthVF VfParams VfInfo + // Used for additional attributes + Cbattr map[string]string } // VfInfo Stores information about Virtual Function (VF) @@ -206,6 +208,7 @@ func IoBundleFromPhyAdapter(log *base.LogObject, phyAdapter PhysicalIOAdapter) * ib.Ioports = phyAdapter.Phyaddr.Ioports ib.Serial = phyAdapter.Phyaddr.Serial ib.Usage = phyAdapter.Usage + ib.Cbattr = phyAdapter.Cbattr // We're making deep copy ib.Vfs.Data = make([]sriov.EthVF, len(phyAdapter.Vfs.Data)) copy(ib.Vfs.Data, phyAdapter.Vfs.Data) @@ -238,12 +241,17 @@ const ( IoNetWWAN IoType = 6 IoHDMI IoType = 7 // enum 8 is reserved for backward compatibility with controller API - IoNVMEStorage IoType = 9 - IoSATAStorage IoType = 10 - IoNetEthPF IoType = 11 - IoNetEthVF IoType = 12 - IoNVME IoType = 255 - IoOther IoType = 255 + IoNVMEStorage IoType = 9 + IoSATAStorage IoType = 10 + IoNetEthPF IoType = 11 + IoNetEthVF IoType = 12 + IoUSBController IoType = 13 + IoUSBDevice IoType = 14 + IoCAN IoType = 15 + IoVCAN IoType = 16 + IoLCAN IoType = 17 + IoNVME IoType = 255 + IoOther IoType = 255 ) // IsNet checks if the type is any of the networking types. @@ -378,6 +386,11 @@ func (aa *AssignableAdapters) AddOrUpdateIoBundle(log *base.LogObject, ib IoBund ib.Type, ib.Phylabel, ib.AssignmentGroup, curIbPtr.MacAddr) ib.MacAddr = curIbPtr.MacAddr } + if len(curIbPtr.Cbattr) > 0 { + log.Functionf("AddOrUpdateIoBundle(%d %s %s) preserve cbattr", + ib.Type, ib.Phylabel, ib.AssignmentGroup) + ib.Cbattr = curIbPtr.Cbattr + } *curIbPtr = ib } diff --git a/pkg/pillar/types/physicalioadapters.go b/pkg/pillar/types/physicalioadapters.go index 15f66234d0..9ab0ad054f 100644 --- a/pkg/pillar/types/physicalioadapters.go +++ b/pkg/pillar/types/physicalioadapters.go @@ -46,12 +46,8 @@ type PhysicalIOAdapter struct { Parentassigngrp string Usage zcommon.PhyIoMemberUsage UsagePolicy PhyIOUsagePolicy - //nolint:godox - // FIXME: cbattr - This needs to be thought through to be made into - // a structure OR may be even various attributes in PhysicalIO structure - // itself. - // map cbattr = 8; - Vfs sriov.VFList // Used only for Physical Functions PFs + Vfs sriov.VFList // Used only for Physical Functions PFs + Cbattr map[string]string // Used for additional attributes } // PhysicalIOAdapterList - List of Physical Adapters to be used on the