Skip to contentSkip to conversation

can: virtio: Add virtio CAN driver

PATCH Superseded Matias Ezequiel Vara Larsen 4 participants 7 messages Feb 17 – Mar 2

Subsystems (3)

v10 of 15 Superseded Latest is v15 →

lore
Share & tools
lore

Matias Ezequiel Vara Larsen

author maintainer
#0 lore raw

PATCH v10 1/1 can: virtio: Add virtio CAN driver

Add virtio CAN driver based on Virtio 1.4 specification (see
https://github.com/oasis-tcs/virtio-spec/tree/virtio-1.4). The driver implements a complete CAN bus interface over Virtio transport, supporting both CAN Classic and CAN-FD Ids. In term of frames, it supports classic and CAN FD. RTR frames are only supported with classic CAN.

Usage:
- "ip link set up can0" - start controller
- "ip link set down can0" - stop controller
- "candump can0" - receive frames
- "cansend can0 123#DEADBEEF" - send frames

---
V10:
* Follow Reverse Christmas Tree convention

V9:
* Remove unnecessary comments
* Update maintainer list

V8:
* Address nits

V7:
* Address nits
* Remove unnecessary comments
* Remove io_callbacks[]
* Use guard() syntax
* Remove kicking for each inbuf
* replace sdu_len with rpkt_len
* Use devm_kzalloc()
* Use scoped_guard() to protect virtqueue_add_sgs() and virtqueue_kicks() for tx queue
* Tested with vhost-device-can
(see https://github.com/rust-vmm/vhost-device/tree/main/vhost-device-can) and Qemu (942b0d3) with [1]. A reviewer observed that the device stops to work after flooding from host. This issue is still present.

[1]
https://lore.kernel.org/qemu-devel/[email protected]/

V6:
* Address nits
* Check for error during register_virtio_can()
* Remove virtio_device_ready()
* Allocate virtio_can_rx rpkt[] at probe
* Define virtio_can_control struct
* Return VIRTIO_CAN_RESULT_NOT_OK after unlocking
* Define sdu[] as a flex array for both tx and rx. For rx, use VIRTIO_CAN_F_CAN_FD to figure out the max len for sdu
* Fix statistics in virtio_can_read_tx_queue() and
how we indicate error to the user when getting VIRTIO_CAN_RESULT_NOT_OK
* Fix syntax of virtio_find_vqs()
* Drop tx_list
* Fix values of VIRTIO_CAN_F_LATE_TX_ACK and VIRTIO_CAN_F_RTR_FRAMES
* Tested with vhost-device-can
(see
https://github.com/rust-vmm/vhost-device/tree/main/vhost-device-can)
and qemu (see
https://github.com/virtualopensystems/qemu/tree/vhu-can-rfc)

V5:
* Re-base on top of linux-next (next-20240103)
* Tested with https://github.com/OpenSynergy/qemu/tree/virtio-can-spec-rfc-v3

RFC V4:
* Apply reverse Christmas tree style
* Add member *classic_dlc to RX and TX CAN frames
* Fix race causing a NETDEV_TX_BUSY return

---
 MAINTAINERS                     |   9 +
 drivers/net/can/Kconfig         |  12 +
 drivers/net/can/Makefile        |   1 +
 drivers/net/can/virtio_can.c    | 934 ++++++++++++++++++++++++++++++++
 include/uapi/linux/virtio_can.h |  78 +++
 5 files changed, 1034 insertions(+)
 create mode 100644 drivers/net/can/virtio_can.c
 create mode 100644 include/uapi/linux/virtio_can.h
MAINTAINERS +9 -0
index 80cd3498c293..f295a904c93e 100644
@@ -27068,6 +27068,15 @@ F:	drivers/scsi/virtio_scsi.c
 F:	include/uapi/linux/virtio_blk.h
 F:	include/uapi/linux/virtio_scsi.h
 
+VIRTIO CAN DRIVER
+M:	"Harald Mommer" <[email protected]>
+M:	"Matias Ezequiel Vara Larsen" <[email protected]>
+L:	[email protected]
+L:	[email protected]
+S:	Maintained
+F:	drivers/net/can/virtio_can.c
+F:	include/uapi/linux/virtio_can.h
+
 VIRTIO CONSOLE DRIVER
 M:	Amit Shah <[email protected]>
 L:	[email protected]
drivers/net/can/Kconfig +12 -0
index d43d56694667..7b5806f11853 100644
@@ -217,6 +217,18 @@ config CAN_XILINXCAN
 	  Xilinx CAN driver. This driver supports both soft AXI CAN IP and
 	  Zynq CANPS IP.
 
+config CAN_VIRTIO_CAN
+	depends on VIRTIO
+	tristate "Virtio CAN device support"
+	default n
+	help
+	  Say Y here if you want to support for Virtio CAN.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called virtio-can.
+
+	  If unsure, say N.
+
 source "drivers/net/can/c_can/Kconfig"
 source "drivers/net/can/cc770/Kconfig"
 source "drivers/net/can/ctucanfd/Kconfig"
drivers/net/can/Makefile +1 -0
index 56138d8ddfd2..2ddea733ed5d 100644
@@ -32,6 +32,7 @@ obj-$(CONFIG_CAN_PEAK_PCIEFD)	+= peak_canfd/
 obj-$(CONFIG_CAN_SJA1000)	+= sja1000/
 obj-$(CONFIG_CAN_SUN4I)		+= sun4i_can.o
 obj-$(CONFIG_CAN_TI_HECC)	+= ti_hecc.o
+obj-$(CONFIG_CAN_VIRTIO_CAN)	+= virtio_can.o
 obj-$(CONFIG_CAN_XILINXCAN)	+= xilinx_can.o
 
 subdir-ccflags-$(CONFIG_CAN_DEBUG_DEVICES) += -DDEBUG
drivers/net/can/virtio_can.c new file +934 -0

934 lines not on this page · raw message

include/uapi/linux/virtio_can.h new file +78 -0

78 lines not on this page · raw message

Signature · 1 line
2.42.0
maintainer
#1 lore raw

Hello Matias,

Quoted 2 lines · Matias Ezequiel Vara Larsen
drivers/net/can/virtio_can.c new file +2 -0
+static int virtio_can_probe(struct virtio_device *vdev)
+{

...

drivers/net/can/virtio_can.c line 831

Quoted 3 lines
drivers/net/can/virtio_can.c new file +3 -0
+	priv->rpkt = devm_kzalloc(&vdev->dev, priv->rpkt_len *
+						priv->vqs[VIRTIO_CAN_QUEUE_RX]->num_free,
+						GFP_KERNEL);

...
We're here clearly beyond column 80 with something which is not a string or a comment. Elsewhere we are also sometimes beyond column 80 but then only 1 or 2 characters which may or may not be acceptable by the kernel community. No idea. You may re-check the formatting.

Besides formatting:

I compared your actual v10 of the virtio CAN driver against my last v5 and and I saw nothing in your code which may cause a problem. Only learned doing the review this way that at some corners my old v5 code was not this bug free as I thought when I saw some changes. Nothing serious because in some "not expected to happen" corner case code paths but anyway I'll occasionally have to take over some of your changes into an internal project repository here.

We will see whether this virtio CAN device will be accepted in some near future for integration in the mainline kernel...

Regards
Harald

#2 lore raw
Quoted 4 lines · Harald Mommer

Hello Matias,

On 2/17/26 11:24, Matias Ezequiel Vara Larsen wrote:

Quoted 8 lines
Quoted 2 lines
drivers/net/can/virtio_can.c new file +2 -0
+static int virtio_can_probe(struct virtio_device *vdev)
+{

...

drivers/net/can/virtio_can.c line 831

Quoted 3 lines
drivers/net/can/virtio_can.c new file +3 -0
+	priv->rpkt = devm_kzalloc(&vdev->dev, priv->rpkt_len *
+						priv->vqs[VIRTIO_CAN_QUEUE_RX]->num_free,
+						GFP_KERNEL);

...
We're here clearly beyond column 80 with something which is not a string or a comment. Elsewhere we are also sometimes beyond column 80 but then only 1 or 2 characters which may or may not be acceptable by the kernel community. No idea. You may re-check the formatting.

it's relaxed recently - does not make sense to uglify code to fit in 80 chars.

Quoted 8 lines

Besides formatting:

I compared your actual v10 of the virtio CAN driver against my last v5 and and I saw nothing in your code which may cause a problem. Only learned doing the review this way that at some corners my old v5 code was not this bug free as I thought when I saw some changes. Nothing serious because in some "not expected to happen" corner case code paths but anyway I'll occasionally have to take over some of your changes into an internal project repository here.

We will see whether this virtio CAN device will be accepted in some near future for integration in the mainline kernel...

Regards
Harald

#3 lore raw
In reply to Matias Ezequiel Vara Larsen

The nipa/ai-review bot found these problems:

| https://netdev-ai.bots.linux.dev/ai-review.html?id=e2753951-0455-4ce7-ad95-3da80456b40e

commit bab72ffd354779765bf84bc17cc185e0662d3717
Author: Matias Ezequiel Vara Larsen <[email protected]>

can: virtio: Add virtio CAN driver

This commit adds a new virtio CAN driver implementing a complete CAN bus interface over Virtio transport, supporting both CAN Classic and CAN-FD frames.

drivers/net/can/Kconfig config CAN_XILINXCAN · line 239

Quoted 11 lines
drivers/net/can/Kconfig +3 -0
index e15e320db476..ec941f3c25f6 100644
@@ -234,6 +234,18 @@ config CAN_XILINXCAN
 	  Xilinx CAN driver. This driver supports both soft AXI CAN IP and
 	  Zynq CANPS IP.
 
+config CAN_VIRTIO_CAN
+	depends on VIRTIO
+	tristate "Virtio CAN device support"

The CAN_VIRTIO_CAN config entry is placed after CAN_XILINXCAN, breaking the alphabetical ordering maintained by all other config entries in this file. Since V comes before X alphabetically, should CAN_VIRTIO_CAN be placed between CAN_TI_HECC and CAN_XILINXCAN? The Makefile entry is correctly ordered (virtio_can.o appears before xilinx_can.o), which makes this inconsistency more evident.

Quoted 9 lines
drivers/net/can/Kconfig +9 -0
+	default n
+	help
+	  Say Y here if you want to support for Virtio CAN.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called virtio-can.
+
+	  If unsure, say N.
+

[ ... ]

Quoted 6 lines
drivers/net/can/virtio_can.c new file +0 -0
new file mode 100644
index 000000000000..8d60336b3ebe
@@ -0,0 +1,934 @@

[ ... ]

drivers/net/can/virtio_can.c line 117

Quoted 6 lines
drivers/net/can/virtio_can.c new file +6 -0
+static int virtio_can_alloc_tx_idx(struct virtio_can_priv *priv)
+{
+	int tx_idx;
+
+	tx_idx = ida_alloc_max(&priv->tx_putidx_ida,
+			 priv->can.echo_skb_max - 1, GFP_KERNEL);

Is GFP_KERNEL safe here? This function is called from virtio_can_start_xmit(), which is the ndo_start_xmit callback. This callback can be invoked from softirq context with BH disabled, where sleeping is not allowed. Should this use GFP_ATOMIC to match the calling context?

Quoted 5 lines
drivers/net/can/virtio_can.c new file +5 -0
+	if (tx_idx >= 0)
+		atomic_inc(&priv->tx_inflight);
+
+	return tx_idx;
+}

[ ... ]

drivers/net/can/virtio_can.c line 226

Quoted 8 lines
drivers/net/can/virtio_can.c new file +8 -0
+static int virtio_can_open(struct net_device *ndev)
+{
+	virtio_can_start(ndev);
+
+	netif_start_queue(ndev);
+
+	return 0;
+}

Is open_candev() missing? The CAN framework requires drivers to call open_candev() in their ndo_open handler. open_candev() validates that bittiming has been configured (returns -EINVAL if bitrate is 0), checks CAN FD data bitrate requirements, and manages carrier state. Without this call, can the device be brought up without configuring bitrate? The driver does call close_candev() in virtio_can_close(), showing awareness of the CAN lifecycle. Every other CAN driver in the kernel calls open_candev() in its open function.

[ ... ]

Quoted 3 lines
drivers/net/can/virtio_can.c new file +3 -0
+static netdev_tx_t virtio_can_start_xmit(struct sk_buff *skb,
+					 struct net_device *dev)
+{

[ ... ]

drivers/net/can/virtio_can.c line 283

Quoted 5 lines
drivers/net/can/virtio_can.c new file +5 -0
+	can_tx_msg = kzalloc(sizeof(*can_tx_msg) + cf->len, GFP_ATOMIC);
+	if (!can_tx_msg) {
+		dev->stats.tx_dropped++;
+		goto kick; /* No way to return NET_XMIT_DROP here */
+	}

Does this leak the skb? When can_dev_dropped_skb() returns false (valid frame, skb not consumed) and kzalloc() for can_tx_msg fails, the code jumps to 'kick' without freeing the skb. The skb is still owned by the caller at this point. Should kfree_skb(skb) be called before goto kick?

Quoted 3 lines
drivers/net/can/virtio_can.c new file +3 -0
+
+	can_tx_msg->tx_out.msg_type = cpu_to_le16(VIRTIO_CAN_TX);
+	can_flags = 0;

[ ... ]

drivers/net/can/virtio_can.c line 317

Quoted 11 lines
drivers/net/can/virtio_can.c new file +11 -0
+	putidx = virtio_can_alloc_tx_idx(priv);
+
+	if (unlikely(putidx < 0)) {
+		/* -ENOMEM or -ENOSPC here. -ENOSPC should not be possible as
+		 * tx_inflight >= can.echo_skb_max is checked in flow control
+		 */
+		WARN_ON_ONCE(putidx == -ENOSPC);
+		kfree(can_tx_msg);
+		dev->stats.tx_dropped++;
+		goto kick; /* No way to return NET_XMIT_DROP here */
+	}

Does this also leak the skb? When ida_alloc_max() returns -ENOMEM, can_tx_msg is correctly freed but the skb is not. can_put_echo_skb() has not been called yet at this point, so the skb is still owned by virtio_can_start_xmit. Should kfree_skb(skb) be called before goto kick?

Quoted 5 lines
drivers/net/can/virtio_can.c new file +5 -0
+
+	can_tx_msg->putidx = (unsigned int)putidx;
+
+	/* Push loopback echo. Will be looped back on TX interrupt/TX NAPI */
+	can_put_echo_skb(skb, dev, can_tx_msg->putidx, 0);

[ ... ]

Quoted 9 lines
drivers/net/can/virtio_can.c new file +9 -0
+static void virtio_can_populate_rx_vq(struct virtio_device *vdev)
+{
+	struct virtio_can_priv *priv = vdev->priv;
+	struct virtqueue *vq = priv->vqs[VIRTIO_CAN_QUEUE_RX];
+	struct virtio_can_rx *buf = priv->rpkt;
+	unsigned int buf_size = priv->rpkt_len;
+	int num_elements = vq->num_free;
+	unsigned int idx;
+	int ret;

drivers/net/can/virtio_can.c line 704

Quoted 10 lines
drivers/net/can/virtio_can.c new file +10 -0
+
+	for (idx = 0; idx < num_elements; idx++) {
+		ret = virtio_can_add_inbuf(vq, buf, buf_size);
+		if (ret < 0) {
+			dev_dbg(&vdev->dev, "rpkt fill: ret=%d, idx=%u, size=%u\n",
+				ret, idx, buf_size);
+			break;
+		}
+		buf += buf_size;
+	}

Does this cause an out-of-bounds memory access? 'buf' is declared as 'struct virtio_can_rx *' and 'buf_size' equals 'priv->rpkt_len' (sizeof(struct virtio_can_rx) + CAN[FD]_MAX_DLEN). The expression 'buf += buf_size' advances by buf_size * sizeof(struct virtio_can_rx) bytes instead of the intended buf_size bytes, because C pointer arithmetic scales by the pointed-to type size.

For CAN classic (rpkt_len=24, sizeof(struct virtio_can_rx)=16), this advances 384 bytes per iteration instead of 24. The allocation in virtio_can_probe only provides rpkt_len bytes per element, so doesn't the second and all subsequent loop iterations pass out-of-bounds pointers to the virtqueue as RX buffers? When the device writes to these buffers, can this corrupt adjacent kernel memory?

virtio_can_probe():

    priv->rpkt = devm_kzalloc(&vdev->dev, priv->rpkt_len *
                                priv->vqs[VIRTIO_CAN_QUEUE_RX]->num_free,
                                GFP_KERNEL);

Should a 'char *' or 'u8 *' pointer be used for the byte-level iteration instead, e.g. 'u8 *p = (u8 *)priv->rpkt; ... p += buf_size;' and pass 'p' to virtio_can_add_inbuf?

drivers/net/can/virtio_can.c

Quoted 2 lines
drivers/net/can/virtio_can.c new file +2 -0
+
+	if (!ret)

Does this use an uninitialized variable? The variable 'ret' is declared as 'int ret;' without initialization. If vq->num_free equals 0, the for loop body never executes, and 'ret' is read uninitialized here. Should this be initialized to 0?

Quoted 4 lines
drivers/net/can/virtio_can.c new file +4 -0
+		virtqueue_kick(vq);
+
+	dev_dbg(&vdev->dev, "%u rpkt added\n", idx);
+}

[ ... ]

drivers/net/can/virtio_can.c line 733

Quoted 9 lines
drivers/net/can/virtio_can.c new file +9 -0
+/* Function must not be called before virtio_can_find_vqs() has been run */
+static void virtio_can_del_vq(struct virtio_device *vdev)
+{
+	struct virtio_can_priv *priv = vdev->priv;
+	int q;
+
+	/* Reset the device */
+	if (vdev->config->reset)
+		vdev->config->reset(vdev);

Should this use virtio_reset_device() instead? When CONFIG_VIRTIO_HARDEN_NOTIFICATION is enabled, virtio_reset_device() calls virtio_break_device() to mark all virtqueues as broken and virtio_synchronize_cbs() to wait for in-flight callbacks to complete before performing the actual reset. Calling config->reset directly skips these hardening steps, meaning virtqueue callbacks may still be executing when del_vqs destroys the queues. All other virtio network drivers (virtio_net.c, caif_virtio.c, mac80211_hwsim.c) use virtio_reset_device(). The NULL check is also unnecessary as this op is always present in virtio devices.

Quoted 11 lines
drivers/net/can/virtio_can.c new file +11 -0
+
+	/* From here we have dead silence from the device side so no locks
+	 * are needed to protect against device side events.
+	 */
+
+	for (q = 0; q < VIRTIO_CAN_QUEUE_COUNT; q++)
+		while (virtqueue_detach_unused_buf(priv->vqs[q]))
+			;
+
+	if (vdev->config->del_vqs)
+		vdev->config->del_vqs(vdev);

drivers/net/can/virtio_can.c line 754

Quoted 10 lines
drivers/net/can/virtio_can.c new file +10 -0
+}
+
+static void virtio_can_remove(struct virtio_device *vdev)
+{
+	struct virtio_can_priv *priv = vdev->priv;
+	struct net_device *dev = priv->dev;
+
+	unregister_candev(dev);
+
+	virtio_can_del_vq(vdev);

Are napi_disable() calls missing here? The probe function enables both NAPI instances:

virtio_can_probe():

    napi_enable(&priv->napi);
    napi_enable(&priv->napi_tx);

And virtio_can_freeze() correctly disables them:

virtio_can_freeze():

    napi_disable(&priv->napi);
    napi_disable(&priv->napi_tx);

But virtio_can_remove() never disables NAPI. The ndo_stop callback (virtio_can_close) intentionally leaves NAPI active per its comment. Can the NAPI poll callbacks (virtio_can_rx_poll, virtio_can_tx_poll) execute concurrently with virtio_can_del_vq() deleting virtqueues and virtio_can_free_candev() freeing the net_device? Should napi_disable() be called for both instances before unregister_candev()?

drivers/net/can/virtio_can.c line 757

Quoted 3 lines
drivers/net/can/virtio_can.c new file +3 -0
+
+	virtio_can_free_candev(dev);
+}

[ ... ]
--

Pengutronix e.K.                 | Marc Kleine-Budde          |
Embedded Linux                   | https://www.pengutronix.de |

Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |

#4 lore raw
Quoted 9 lines · Marc Kleine-Budde

drivers/net/can/virtio_can.c line 226

Quoted 8 lines
drivers/net/can/virtio_can.c new file +8 -0
+static int virtio_can_open(struct net_device *ndev)
+{
+	virtio_can_start(ndev);
+
+	netif_start_queue(ndev);
+
+	return 0;
+}
Quoted 8 lines

Is open_candev() missing? The CAN framework requires drivers to call open_candev() in their ndo_open handler. open_candev() validates that bittiming has been configured (returns -EINVAL if bitrate is 0), checks CAN FD data bitrate requirements, and manages carrier state. Without this call, can the device be brought up without configuring bitrate? The driver does call close_candev() in virtio_can_close(), showing awareness of the CAN lifecycle. Every other CAN driver in the kernel calls open_candev() in its open function.

This is true for physical CAN devices, so let's clarify things for virtio.

As far as I can see, there's no interface to get/set the bit timing/bit rate from the guest, right?

You can set bittiming.bitrate to CAN_BITRATE_UNKNOWN, remove the netif_carrier handling from virtio_can_start() and use open_candev().

regards,
Marc

Signature · 4 lines
Pengutronix e.K.                 | Marc Kleine-Budde          |
Embedded Linux                   | https://www.pengutronix.de |
Vertretung Nürnberg              | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax:   +49-5121-206917-9   |
author maintainer
#5 lore raw
Quoted 19 lines · Marc Kleine-Budde
Quoted 9 lines · Marc Kleine-Budde

drivers/net/can/virtio_can.c line 226

Quoted 8 lines
drivers/net/can/virtio_can.c new file +8 -0
+static int virtio_can_open(struct net_device *ndev)
+{
+	virtio_can_start(ndev);
+
+	netif_start_queue(ndev);
+
+	return 0;
+}
Quoted 8 lines

Is open_candev() missing? The CAN framework requires drivers to call open_candev() in their ndo_open handler. open_candev() validates that bittiming has been configured (returns -EINVAL if bitrate is 0), checks CAN FD data bitrate requirements, and manages carrier state. Without this call, can the device be brought up without configuring bitrate? The driver does call close_candev() in virtio_can_close(), showing awareness of the CAN lifecycle. Every other CAN driver in the kernel calls open_candev() in its open function.

Quoted 6 lines

This is true for physical CAN devices, so let's clarify things for virtio.

As far as I can see, there's no interface to get/set the bit timing/bit rate from the guest, right?

AFAIU there is not such a interface.

Quoted 3 lines

You can set bittiming.bitrate to CAN_BITRATE_UNKNOWN, remove the netif_carrier handling from virtio_can_start() and use open_candev().

Will do. Thanks!

Matias

#6 lore raw
In reply to Matias Ezequiel Vara Larsen

drivers/net/can/virtio_can.c line 195

Quoted 8 lines · Matias Ezequiel Vara Larsen
drivers/net/can/virtio_can.c new file +8 -0
+static void virtio_can_start(struct net_device *ndev)
+{
+	struct virtio_can_priv *priv = netdev_priv(ndev);
+	u8 result;
+
+	result = virtio_can_send_ctrl_msg(ndev, VIRTIO_CAN_SET_CTRL_MODE_START);
+	if (result != VIRTIO_CAN_RESULT_OK)
+		netdev_err(ndev, "CAN controller start failed\n");

propagate the error value and evaluate it in the caller

[...]

drivers/net/can/virtio_can.c line 236

Quoted 9 lines
drivers/net/can/virtio_can.c new file +9 -0
+static void virtio_can_stop(struct net_device *ndev)
+{
+	struct virtio_can_priv *priv = netdev_priv(ndev);
+	struct device *dev = &priv->vdev->dev;
+	u8 result;
+
+	result = virtio_can_send_ctrl_msg(ndev, VIRTIO_CAN_SET_CTRL_MODE_STOP);
+	if (result != VIRTIO_CAN_RESULT_OK)
+		dev_err(dev, "CAN controller stop failed\n");

propagate the error value and evaluate it in the caller

drivers/net/can/virtio_can.c line 244

Quoted 8 lines
drivers/net/can/virtio_can.c new file +8 -0
+
+	priv->busoff_pending = false;
+	priv->can.state = CAN_STATE_STOPPED;
+
+	/* Switch carrier off if device was connected to the bus */
+	if (netif_carrier_ok(ndev))
+		netif_carrier_off(ndev);
+}

regards,
Marc

--

Pengutronix e.K.                 | Marc Kleine-Budde          |
Embedded Linux                   | https://www.pengutronix.de |

Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |