From 2bcbfea6fcd76a7947a639c13b41be50f1e5f6c8 Mon Sep 17 00:00:00 2001 From: Martin Elshuber Date: Fri, 16 Oct 2015 11:57:05 +0200 Subject: can: ucan: added support for UCAN devices Driver for UCAN based USB CAN devices. Signed-off-by: Klaus Goger --- drivers/net/can/Kconfig | 9 + drivers/net/can/dev.c | 32 +- drivers/net/can/usb/Kconfig | 7 + drivers/net/can/usb/Makefile | 1 + drivers/net/can/usb/ucan.c | 1062 ++++++++++++++++++++++++++++++++++++++ drivers/net/can/usb/ucan.h | 216 ++++++++ include/linux/can/dev.h | 9 +- include/uapi/linux/can/netlink.h | 22 + 8 files changed, 1356 insertions(+), 2 deletions(-) create mode 100644 drivers/net/can/usb/ucan.c create mode 100644 drivers/net/can/usb/ucan.h diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index 58808f651452..1602a1ac0a16 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -52,6 +52,15 @@ config CAN_CALC_BITTIMING arguments "tq", "prop_seg", "phase_seg1", "phase_seg2" and "sjw". If unsure, say Y. +config CAN_HWFILTER + bool "Enable CAN hardware filtering support" + default n + ---help--- + This enables, netlink options to configure CAN hardware + filtering. This feature is supported by the uCAN + driver. If enabled hardware filters can be set with the a + patched version of ip(8). + config CAN_LEDS bool "Enable LED triggers for Netlink based drivers" depends on LEDS_CLASS diff --git a/drivers/net/can/dev.c b/drivers/net/can/dev.c index b0f69248cb71..802a6bf1ca8b 100644 --- a/drivers/net/can/dev.c +++ b/drivers/net/can/dev.c @@ -869,6 +869,30 @@ static int can_changelink(struct net_device *dev, } } +#ifdef CONFIG_CAN_HWFILTER + if (data[IFLA_CAN_HARDWARE_FILTER]) { + struct can_hwfilter cf; + + /* Do not allow changing hardware filter while running */ + if (dev->flags & IFF_UP) + return -EBUSY; + + memcpy(&cf, nla_data(data[IFLA_CAN_HARDWARE_FILTER]), sizeof(cf)); + + if (cf.count > priv->hwfilterbanks) + return -EINVAL; + + memcpy(&priv->filter, &cf, sizeof(cf)); + + if (priv->do_set_hwfilter) { + /* Finally, set the bit-timing registers */ + err = priv->do_set_hwfilter(dev); + if (err) + return err; + } + } +#endif + return 0; } @@ -929,7 +953,13 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev) (priv->data_bittiming_const && nla_put(skb, IFLA_CAN_DATA_BITTIMING_CONST, sizeof(*priv->data_bittiming_const), - priv->data_bittiming_const))) + priv->data_bittiming_const)) +#ifdef CONFIG_CAN_HWFILTER + || nla_put(skb, IFLA_CAN_HARDWARE_FILTER, + sizeof(priv->filter), + &priv->filter) +#endif + ) return -EMSGSIZE; return 0; diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig index bcb272f6c68a..2334170d6686 100644 --- a/drivers/net/can/usb/Kconfig +++ b/drivers/net/can/usb/Kconfig @@ -78,4 +78,11 @@ config CAN_8DEV_USB This driver supports the USB2CAN interface from 8 devices (http://www.8devices.com). +config CAN_UCAN + tristate "uCAN Device Driver" + ---help--- + This driver supports UCAN based USB CAN devices + + * STM32 based CAN controller on A31 uQ7 (Pangolin) + endmenu diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile index a64cf983fb87..3afde989139b 100644 --- a/drivers/net/can/usb/Makefile +++ b/drivers/net/can/usb/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_CAN_GS_USB) += gs_usb.o obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb.o obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/ obj-$(CONFIG_CAN_8DEV_USB) += usb_8dev.o +obj-$(CONFIG_CAN_UCAN) += ucan.o diff --git a/drivers/net/can/usb/ucan.c b/drivers/net/can/usb/ucan.c new file mode 100644 index 000000000000..e1fd896a29e9 --- /dev/null +++ b/drivers/net/can/usb/ucan.c @@ -0,0 +1,1062 @@ +/* + * CAN driver for uCAN + * + * Copyright (C) 2015 Theobroma Systems Design und Consulting GmbH + * + * 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; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. + * + * This driver is inspired by the 4.0.0 version of drivers/net/can/usb/ems_usb.c + * + */ + +/***************************************************** + * General Description: + ***************************************************** + * + * The USB Device uses three Endpoints: + * + * Interrupt Endpoint: Once the device is started the device sends + * its TX FIFO status (space left) on this endpoint. The driver uses + * this information for flow control. + * + * IN Enpoint: The device sends CAN Frame Messages and Device + * Information using the IN endpoint. + * + * OUT Endpoint: The driver sends configuration requests, and CAN + * Frames on the out endpoint. + * + * Error Handling: If error reporting is turned on the device + * encodes error into CAN error frames (see uapi/linux/can/error.h) + * and sends it using the IN Endpoint. The driver updates statistics + * and forward it. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucan.h" + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Martin Elshuber, Theobroma Systems Design und Consulting GmbH" + ""); +MODULE_DESCRIPTION("CAN driver for uCAN devices"); + +#define USB_CPCUSB_VENDOR_ID 0x0483 +#define USB_CPCUSB_UCAN_PRODUCT_ID 0x5720 + +#define MAX_TX_URBS 8 +#define MAX_RX_URBS 8 +#define TX_QUEUE_STOP_TRESHOLD 1 + +static struct usb_device_id uCAN_table[] = { + {USB_DEVICE(USB_CPCUSB_VENDOR_ID, USB_CPCUSB_UCAN_PRODUCT_ID)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, uCAN_table); + +struct uCAN; + +/* Context Information for transmission URBs */ +struct uCAN_urb_context { + struct uCAN *up; + u32 echo_index; + int size; + u8 dlc; +}; + +/* Information reported by the USB device */ +struct uCAN_device_info { + struct can_bittiming_const bittiming_const; + int tx_fifo; +}; + +/* Driver private data */ +struct uCAN { + struct can_priv can; /* must be the first member */ + + struct usb_device *udev; + struct net_device *netdev; + + struct usb_endpoint_descriptor *out_ep; + struct usb_endpoint_descriptor *in_ep; + struct usb_endpoint_descriptor *irq_ep; + + struct usb_anchor rx_urbs; + struct usb_anchor tx_urbs; + struct urb *irq_urb; + u32 *irq_data; + + u8 *tx_msg_buffer; + u8 *rx_msg_buffer; + + struct uCAN_device_info device_info; + + atomic_t active_tx_urbs; + struct uCAN_urb_context tx_urb_contexts[MAX_TX_URBS]; + int free_slots; +}; + +/* Sends a command to the device */ +static int uCAN_command(struct uCAN *up, u8 cmd, u8 subcmd, u16 value) +{ + int len; + struct uCAN_message_out *m = (struct uCAN_message_out *)up->tx_msg_buffer; + dev_dbg(&up->udev->dev, "%s\n", __func__); + + m->type = __cpu_to_le16(UCAN_OUT_COMMAND); + m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.command)); + m->msg.command.cmd = cmd; + m->msg.command.subcmd = subcmd; + m->msg.command.val = __cpu_to_le16(value); + + return usb_bulk_msg(up->udev, + usb_sndbulkpipe(up->udev, up->out_ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK), + m, + __le16_to_cpu(m->len), + &len, 1000); +} + +/* Request the device Information */ +static int uCAN_get_device_info(struct uCAN *up) +{ + int ret; + int len; + struct uCAN_message_in *m = (struct uCAN_message_in *)up->rx_msg_buffer; + dev_dbg(&up->udev->dev, "%s\n", __func__); + + // send the request command + ret = uCAN_command(up, UCAN_COMMAND_GET, + UCAN_COMMAND_GET_INFO, 0); + if (ret) + return ret; + + // retrieve the information + ret = usb_bulk_msg(up->udev, + usb_rcvbulkpipe(up->udev, + up->in_ep->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK), + m, + up->in_ep->wMaxPacketSize, + &len, 1000); + if (ret) + return ret; + + // check sanity + if (m->type!=__cpu_to_le16(UCAN_IN_DEVICE_INFO)) + return -EINVAL; + + if (__le16_to_cpu(m->len) != UCAN_IN_LEN(m->msg.device_info) ) + return -EINVAL; + + // store the data + strcpy(up->device_info.bittiming_const.name, "uCAN"); + up->can.clock.freq = __le32_to_cpu(m->msg.device_info.freq); + up->device_info.tx_fifo = m->msg.device_info.tx_fifo; + up->device_info.bittiming_const.tseg1_min = m->msg.device_info.tseg1_min; + up->device_info.bittiming_const.tseg1_max = m->msg.device_info.tseg1_max; + up->device_info.bittiming_const.tseg2_min = m->msg.device_info.tseg2_min; + up->device_info.bittiming_const.tseg2_max = m->msg.device_info.tseg2_max; + up->device_info.bittiming_const.sjw_max = m->msg.device_info.sjw_max; + up->device_info.bittiming_const.brp_min = __le32_to_cpu(m->msg.device_info.brp_min); + up->device_info.bittiming_const.brp_max = __le32_to_cpu(m->msg.device_info.brp_max); + up->device_info.bittiming_const.brp_inc = __le16_to_cpu(m->msg.device_info.brp_inc); +#ifdef CONFIG_CAN_HWFILTER + up->can.hwfilterbanks = __le16_to_cpu(m->msg.device_info.hwfilter); + up->can.rxmailboxes = __le16_to_cpu(m->msg.device_info.rxmboxes); +#endif + + up->can.ctrlmode_supported = 0; + + if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_LOOPBACK ) + up->can.ctrlmode_supported |= CAN_CTRLMODE_LOOPBACK; + if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_SILENT ) + up->can.ctrlmode_supported |= CAN_CTRLMODE_LISTENONLY; + if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_3_SAMPLES ) + up->can.ctrlmode_supported |= CAN_CTRLMODE_3_SAMPLES; + if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_ONE_SHOT ) + up->can.ctrlmode_supported |= CAN_CTRLMODE_ONE_SHOT; + if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_BERR_REPORT ) + up->can.ctrlmode_supported |= CAN_CTRLMODE_BERR_REPORTING; + + return 0; +} + +/* Callback when the device sends the IRQ sate * + * + * This function simply stores the current TX fifo state for flow + * control */ +static void uCAN_read_irq_callback(struct urb *urb) +{ + int ret; + struct uCAN *up = urb->context; + struct net_device *netdev = up->netdev; + + dev_dbg(&up->udev->dev, "%s %d\n", __func__,urb->status ); + + switch (urb->status) { + case 0: + ACCESS_ONCE(up->free_slots) = __le32_to_cpu(*up->irq_data); + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ETIME: + dev_dbg(&up->udev->dev, "%s ENOENT|ESHUTDOWN|ETIME\n", __func__); + return; + default: + dev_warn(&up->udev->dev, "%s error (%d)\n", __func__, urb->status); + break; + } + + usb_fill_int_urb(urb, + up->udev, + usb_rcvintpipe(up->udev, + up->irq_ep->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK), + urb->transfer_buffer, + up->irq_ep->wMaxPacketSize, + uCAN_read_irq_callback, + up, + up->irq_ep->bInterval); + + ret = usb_submit_urb(urb, GFP_KERNEL); + + if (ret == -ENODEV) + netif_device_detach(netdev); + else if (ret) + dev_err(&up->udev->dev, + "failed resubmitting read bulk urb: %d\n", ret); +} + +/* Callback on reception of a can frame via the IN endpoint + * + * This function allocates an skb and transferres it to the Linux + * network stack + */ +static void uCAN_rx_can_msg(struct uCAN *up, struct uCAN_message_in *m) +{ + int len; + struct can_frame *cf; + struct sk_buff *skb; + struct net_device_stats *stats = &up->netdev->stats; + + dev_dbg(&up->udev->dev, "%s\n", __func__); + + // get the contents of the length field + len = __le16_to_cpu(m->len); + + // check sanity + if (lenmsg.can_msg.id)) { + dev_warn(&up->udev->dev, "invalid input message len\n"); + return; + } + + // allocate skb + skb = alloc_can_skb(up->netdev, &cf); + if (skb == NULL) + return; + + // fill the can frame + cf->can_id = __le32_to_cpu(m->msg.can_msg.id); + cf->can_dlc = len - (UCAN_IN_HDR_SIZE+sizeof(m->msg.can_msg.id)); + + if (cf->can_dlc > sizeof(m->msg.can_msg.data)) + goto err_freeskb; + + if (cf->can_dlc < 0) + goto err_freeskb; + + if (cf->can_id & CAN_EFF_FLAG ) + cf->can_id &= (CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_ERR_FLAG); + else + cf->can_id &= (CAN_SFF_MASK | CAN_RTR_FLAG | CAN_ERR_FLAG); + + + if (cf->can_id & CAN_RTR_FLAG ) { + cf->can_id |= CAN_RTR_FLAG; + cf->can_dlc = m->msg.can_rtr_msg.dlc; + } else { + memcpy(cf->data, m->msg.can_msg.data, cf->can_dlc); + } + + // handle can error frames + if (cf->can_id & CAN_ERR_FLAG ) { + if ( cf->can_id & CAN_ERR_BUSOFF ) { + up->can.can_stats.bus_off++; + can_bus_off(up->netdev); + } + + if ( cf->data[1] & ( CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING ) ) { + up->can.can_stats.error_warning++; + } + + if ( cf->data[1] & ( CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING ) ) { + up->can.can_stats.error_passive++; + } + + if ( cf->data[1] & CAN_ERR_CRTL_RX_OVERFLOW ) + stats->rx_over_errors++; + + if ( cf->can_id & CAN_ERR_LOSTARB ) + up->can.can_stats.arbitration_lost++; + + if ( cf->can_id & CAN_ERR_BUSERROR ) + up->can.can_stats.bus_error++; + + if ( cf->data[2] & CAN_ERR_PROT_TX ) { + stats->tx_errors++; + } + else { + stats->rx_errors++; + } + cf->can_id |= CAN_ERR_FLAG; + } + + // pass it to Linux + netif_receive_skb(skb); + + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + + return; +err_freeskb: + kfree_skb(skb); +} + +/* callback on reception of a USB message */ +static void uCAN_read_bulk_callback(struct urb *urb) +{ + int ret; + int len; + int pos; + struct uCAN *up = urb->context; + struct net_device *netdev = up->netdev; + struct uCAN_message_in *m; + + dev_dbg(&up->udev->dev, "%s %p\n", __func__, up); + + // check URB status + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ESHUTDOWN: + case -ETIME: + dev_dbg(&up->udev->dev, "%s ENOENT|ESHUTDOWN|ETIME\n", __func__); + return; + default: + goto resubmit; + } + + // iterate ove input + pos = 0; + while (pos < urb->actual_length) { + + // check sanity + if ( ( urb->actual_length - pos ) < UCAN_IN_HDR_SIZE) { + dev_warn(&up->udev->dev, "invalid input message 1 %d\n", urb->actual_length); + goto resubmit; + } + + // get the pointer to the message + m = (struct uCAN_message_in *)( ( (u8*)urb->transfer_buffer ) + pos ); + len = __le16_to_cpu(m->len); + + // check sanity + if (len > (urb->actual_length - pos) ) { + dev_warn(&up->udev->dev, "invalid input message 2 %d\n", len); + goto resubmit; + } + + switch (__le16_to_cpu(m->type)) { + case UCAN_IN_RX: + uCAN_rx_can_msg(up, m); + break; + default: + dev_warn(&up->udev->dev, "invalid input message type\n"); + break; + } + + // proceed to next message + pos += len; + // allign to 4 byte boundary + pos = ( pos + 3 ) & ~3; + } + +resubmit: + // resubmit urb when done + usb_fill_bulk_urb(urb, + up->udev, + usb_rcvbulkpipe(up->udev, + up->in_ep->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK), + urb->transfer_buffer, + up->in_ep->wMaxPacketSize, + uCAN_read_bulk_callback, + up); + + ret = usb_submit_urb(urb, GFP_KERNEL); + + if (ret == -ENODEV) + netif_device_detach(netdev); + else if (ret) + dev_err(&up->udev->dev, + "failed resubmitting read bulk urb: %d\n", ret); + +} + +/* callback after transmission of a USB message */ +static void uCAN_write_bulk_callback(struct urb *urb) +{ + struct uCAN_urb_context *context = urb->context; + struct uCAN *up; + + // get the urb context + BUG_ON(!context); + up = context->up; + + // free up our allocated buffer + usb_free_coherent(urb->dev, context->size, + urb->transfer_buffer, urb->transfer_dma); + + atomic_dec(&up->active_tx_urbs); + + // sanity check + if (!netif_device_present(up->netdev)) + return; + + // urb state check + if (urb->status) + netdev_info(up->netdev, "Tx URB aborted (%d)\n", urb->status); + + up->netdev->trans_start = jiffies; + + // update statistics + up->netdev->stats.tx_packets++; + up->netdev->stats.tx_bytes += context->dlc; + + // echo can frame + can_get_echo_skb(up->netdev, context->echo_index); + + // Release context + context->echo_index = -1; + + // restart the queue if necessary + if (netif_queue_stopped(up->netdev)) + netif_wake_queue(up->netdev); +} + +/* Open the network device */ +static int uCAN_open(struct net_device *netdev) +{ + int i; + int ret; + void *buf; + u16 ctrlmode; + struct urb *urb; + struct uCAN *up = netdev_priv(netdev); + dev_dbg(&up->udev->dev, "%s\n", __func__); + + // call CAN layer open + ret = open_candev(netdev); + if (ret) + goto err; + + ret = -ENOMEM; + + // set the queue state as empty + up->free_slots = up->device_info.tx_fifo; + + // initiallze IRQ endpoint + up->irq_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!up->irq_urb) { + goto err; + } + + up->irq_data = kzalloc(up->irq_ep->wMaxPacketSize, GFP_KERNEL); + if (!up->irq_data) { + usb_free_urb(up->irq_urb); + goto err; + } + + usb_fill_int_urb(up->irq_urb, + up->udev, + usb_rcvintpipe(up->udev, up->irq_ep->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK), + up->irq_data, + up->irq_ep->wMaxPacketSize, + uCAN_read_irq_callback, + up, + up->irq_ep->bInterval); + + ret = usb_submit_urb(up->irq_urb, GFP_KERNEL); + if (ret) { + kfree(up->irq_data); + usb_free_urb(up->irq_urb); + goto err; + } + + // intialize IN enpoint + for (i=0; iudev, + up->in_ep->wMaxPacketSize, + GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + usb_free_urb(urb); + goto err; + } + + usb_fill_bulk_urb(urb, + up->udev, + usb_rcvbulkpipe(up->udev, + up->in_ep->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK), + buf, + up->in_ep->wMaxPacketSize, + uCAN_read_bulk_callback, + up); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + usb_anchor_urb(urb, &up->rx_urbs); + + ret = usb_submit_urb(urb, GFP_KERNEL); + + if (ret) { + usb_unanchor_urb(urb); + usb_free_coherent(up->udev, + up->in_ep->wMaxPacketSize, + buf, + urb->transfer_dma); + usb_free_urb(urb); + goto err; + } + + /* Drop reference, USB core will take care of freeing it */ + usb_free_urb(urb); + } + + // check the control mode + ctrlmode = 0; + if (up->can.ctrlmode & CAN_CTRLMODE_LOOPBACK ) + ctrlmode |= UCAN_MODE_LOOPBACK; + if (up->can.ctrlmode & CAN_CTRLMODE_LISTENONLY ) + ctrlmode |= UCAN_MODE_SILENT; + if (up->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES ) + ctrlmode |= UCAN_MODE_3_SAMPLES; + if (up->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT ) + ctrlmode |= UCAN_MODE_ONE_SHOT; + if (up->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING ) + ctrlmode |= UCAN_MODE_BERR_REPORT; + + // start the USB device + ret = uCAN_command(up, UCAN_COMMAND_START,0,__cpu_to_le16(ctrlmode)); + if (ret) + goto err; + + // start the network queue + netif_start_queue(netdev); + + return 0; + +err: + usb_kill_anchored_urbs(&up->rx_urbs); + return ret; +} + +/* callback when Linux needs to send a can frame */ +static netdev_tx_t uCAN_start_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + int i,ret; + struct urb *urb; + struct uCAN_urb_context *context; + struct uCAN *up = netdev_priv(netdev); + struct can_frame *cf = (struct can_frame *)skb->data; + struct uCAN_message_out *m = (struct uCAN_message_out *)up->tx_msg_buffer; + size_t size = UCAN_OUT_HDR_SIZE+cf->can_dlc; + + dev_dbg(&up->udev->dev, "%s\n", __func__); + + // check skb + if (can_dropped_invalid_skb(netdev, skb)) + return NETDEV_TX_OK; + + /* create a URB, and a buffer for it, and copy the data to the URB */ + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + netdev_err(netdev, "No memory left for URBs\n"); + goto drop; + } + + m = usb_alloc_coherent(up->udev, size, GFP_ATOMIC, &urb->transfer_dma); + if (!m) { + netdev_err(netdev, "No memory left for USB buffer\n"); + usb_free_urb(urb); + goto drop; + } + + // build the USB message + if (cf->can_dlc>sizeof(m->msg.can_msg.data)) + cf->can_dlc = sizeof(m->msg.can_msg.data); + + m->type = __cpu_to_le16(UCAN_OUT_TX); + m->msg.can_msg.id = __cpu_to_le32(cf->can_id); + + if (cf->can_id & CAN_RTR_FLAG) { + m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.can_rtr_msg)); + m->msg.can_rtr_msg.dlc = cf->can_dlc; + } + else { + m->len = __cpu_to_le16(UCAN_OUT_HDR_SIZE+ + sizeof(m->msg.can_msg.id)+cf->can_dlc); + memcpy(m->msg.can_msg.data, cf->data, cf->can_dlc); + } + + // allocate a context + context = NULL; + for (i = 0; i < MAX_TX_URBS; i++) { + if (up->tx_urb_contexts[i].echo_index == -1) { + context = &up->tx_urb_contexts[i]; + context->up = up; + context->echo_index=i; + context->size = size; + context->dlc = cf->can_dlc; + atomic_inc(&up->active_tx_urbs); + break; + } + } + + WARN_ON_ONCE(!context); + if (!context) { + usb_free_coherent(up->udev, size, m, urb->transfer_dma); + usb_free_urb(urb); + goto drop; + } + + // build the urb + usb_fill_bulk_urb(urb, + up->udev, + usb_sndbulkpipe(up->udev, + up->out_ep->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK), + m, + __le16_to_cpu(m->len), + uCAN_write_bulk_callback, + context); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + usb_anchor_urb(urb, &up->tx_urbs); + can_put_echo_skb(skb, up->netdev, context->echo_index); + + // transmit it + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + // on error, clean up + can_free_echo_skb(up->netdev, context->echo_index); + + usb_unanchor_urb(urb); + usb_free_coherent(up->udev, size, m, urb->transfer_dma); + dev_kfree_skb(skb); + + context->echo_index = -1; + atomic_dec(&up->active_tx_urbs); + + if (ret == -ENODEV) { + netif_device_detach(up->netdev); + } else { + netdev_warn(up->netdev, "failed tx_urb %d\n", ret); + up->netdev->stats.tx_dropped++; + } + } else { + netdev->trans_start = jiffies; + + /* Slow down tx path, if fifo state is low */ + if ( (atomic_read(&up->active_tx_urbs) >= MAX_TX_URBS) + || (ACCESS_ONCE(up->free_slots) < TX_QUEUE_STOP_TRESHOLD) ) + { + netif_stop_queue(netdev); + } + } + + // release ref, as we do not need the urb anymore + usb_free_urb(urb); + + return 0; +drop: + dev_kfree_skb(skb); + up->netdev->stats.tx_dropped++; + + return NETDEV_TX_OK; +} + +/* Device goes down + * + * Cleanup used resources */ +static int uCAN_close(struct net_device *netdev) +{ + int ret; + struct uCAN *up = netdev_priv(netdev); + dev_dbg(&up->udev->dev, "%s\n", __func__); + + netif_stop_queue(netdev); + + if ( (ret = uCAN_command(up, UCAN_COMMAND_STOP,0,0)) ) { + dev_warn(&up->udev->dev, "Could not stop USB CAN device code: %d\n", ret); + } + + usb_kill_urb(up->irq_urb); + usb_kill_anchored_urbs(&up->rx_urbs); + usb_kill_anchored_urbs(&up->tx_urbs); + atomic_set(&up->active_tx_urbs, 0); + + kfree(up->irq_data); + + close_candev(up->netdev); + return 0; +} + +/* CAN driver callbacks */ +static const struct net_device_ops uCAN_netdev_ops = { + .ndo_open = uCAN_open, + .ndo_stop = uCAN_close, + .ndo_start_xmit = uCAN_start_xmit, + .ndo_change_mtu = can_change_mtu, +}; + +/* Request to set bittiming + * + * This function generates an USB set bittiming message and transmits + * it to the device */ +static int uCAN_set_bittiming(struct net_device *netdev) +{ + int len; + struct uCAN *up = netdev_priv(netdev); + struct uCAN_message_out *m = (struct uCAN_message_out *)up->tx_msg_buffer; + dev_dbg(&up->udev->dev, "%s\n", __func__); + + m->type = __cpu_to_le16(UCAN_OUT_SET_BITTIMING); + m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.bittiming)); + m->msg.bittiming.tq = __cpu_to_le32(up->can.bittiming.tq); + m->msg.bittiming.brp = __cpu_to_le16(up->can.bittiming.brp); + m->msg.bittiming.sample_point = __cpu_to_le32(up->can.bittiming.sample_point); + m->msg.bittiming.prop_seg = up->can.bittiming.prop_seg; + m->msg.bittiming.phase_seg1 = up->can.bittiming.phase_seg1; + m->msg.bittiming.phase_seg2 = up->can.bittiming.phase_seg2; + m->msg.bittiming.sjw = up->can.bittiming.sjw; + + dev_dbg(&up->udev->dev, + "Setup bittiming\n" + " bitrate: %d\n" + " sample-point: %d\n" + " tq: %d\n" + " prop_seg: %d\n" + " phase_seg1 %d\n" + " phase_seg2 %d\n" + " sjw %d\n" + " brp %d\n", + up->can.bittiming.bitrate, + up->can.bittiming.sample_point, + up->can.bittiming.tq, + up->can.bittiming.prop_seg, + up->can.bittiming.phase_seg1, + up->can.bittiming.phase_seg2, + up->can.bittiming.sjw, + up->can.bittiming.brp); + + return usb_bulk_msg(up->udev, + usb_sndbulkpipe(up->udev, up->out_ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK), + m, + __le16_to_cpu(m->len), + &len, 1000); +} + +#ifdef CONFIG_CAN_HWFILTER +/* Request to setup hardware filtering + * + * First al current harware filters are cleard. + * Second e seires of messages are sent to enable specific filters + */ +static int uCAN_set_hwfilter(struct net_device *netdev) +{ + int len; + int ret, i; + struct uCAN *up = netdev_priv(netdev); + struct uCAN_message_out *m = (struct uCAN_message_out *)up->tx_msg_buffer; + + // clear filters + ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_CLEAR, 0); + if (ret) + return ret; + + // setup filters + if (up->can.filter.count) { + for (i = 0; i < up->can.filter.count; i++) { + m->type = __cpu_to_le16(UCAN_OUT_ENABLE_FILTER); + m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.enable_filter)); + m->msg.enable_filter.id = __cpu_to_le32(up->can.filter.filter[i].id); + m->msg.enable_filter.mask = __cpu_to_le32(up->can.filter.filter[i].mask); + m->msg.enable_filter.mbox = __cpu_to_le16(up->can.filter.filter[i].mbox); + dev_dbg(&up->udev->dev, + "Setup hwfilter [ (ID & %x) = ( %x & %x ) ] => #%d\n", + m->msg.enable_filter.mask, + m->msg.enable_filter.id, + m->msg.enable_filter.mask, + m->msg.enable_filter.mbox); + ret = usb_bulk_msg(up->udev, + usb_sndbulkpipe(up->udev, + up->out_ep->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK), + m, + __le16_to_cpu(m->len), + &len, 1000); + if (ret) + return ret; + } + } + else { + // disable flters if none are set + ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_DISABLE, 0); + } + return 0; +} +#endif + +/* Probe the device, reset it and gather general device informations */ +static int uCAN_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret; + int i; + struct usb_device *udev; + struct net_device *netdev; + struct usb_host_interface *iface_desc; + struct uCAN *up; + struct usb_endpoint_descriptor *ep; + + ret = -EINVAL; + udev = interface_to_usbdev(intf); + if (!udev) + goto err; + + dev_dbg(&udev->dev, "%s\n", __func__); + + // check if the interface is sane + ret = -EINVAL; + iface_desc = intf->cur_altsetting; + if (!iface_desc) + goto err; + + if (iface_desc->desc.bInterfaceClass!=UCAN_CLASS) + return 0; + + if (iface_desc->desc.bInterfaceSubClass!=UCAN_SUBCLASS) + return 0; + + if (iface_desc->desc.bInterfaceProtocol!=0) + return 0; + + // Infvalid interface Settings + if (iface_desc->desc.bNumEndpoints!=3) + goto err; + + dev_info(&udev->dev, "Found USB CAN Class on Interface #%d\n", iface_desc->desc.iInterface); + + // allocate driver resources + ret = -ENOMEM; + netdev = alloc_candev(sizeof(struct uCAN), MAX_TX_URBS); + if (!netdev) + goto err; + up = netdev_priv(netdev); + + // get interface descriptors + for (i=0; idesc.bNumEndpoints; i++) { + ep = &iface_desc->endpoint[i].desc; + + if ( ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != 0 ) && + ( (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK ) ) { + /* In Endpoint */ + up->in_ep = ep; + } + else if ( ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == 0) && + ( (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK ) ) { + /* Out Endpoint */ + up->out_ep = ep; + } + else if ( ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != 0) && + ( (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT ) ) { + /* Out Endpoint */ + up->irq_ep = ep; + } + } + + // check if all interfaces are sane + ret = -EINVAL; + if ( (!up->in_ep) || (!up->out_ep) || (!up->irq_ep) ) { + dev_err(&udev->dev, "Invalid Endpoint Configuration\n"); + goto err_free_candev; + } + + if (up->in_ep->wMaxPacketSize < sizeof(struct uCAN_message_in) ) + goto err_free_candev; + if (up->out_ep->wMaxPacketSize < sizeof(struct uCAN_message_out) ) + goto err_free_candev; + if (up->irq_ep->wMaxPacketSize < sizeof(u32) ) + goto err_free_candev; + + dev_info(&udev->dev, " using EP %02x for input with max packet size #0x%x\n", + up->in_ep->bEndpointAddress, up->in_ep->wMaxPacketSize); + dev_info(&udev->dev, " using EP %02x for output with max packet size #0x%x\n", + up->out_ep->bEndpointAddress, up->out_ep->wMaxPacketSize); + dev_info(&udev->dev, " using EP %02x for irq input with max packet size #0x%x\n", + up->irq_ep->bEndpointAddress, up->irq_ep->wMaxPacketSize); + + // initialze data + up->udev = udev; + up->netdev = netdev; + + up->can.state = CAN_STATE_STOPPED; + up->can.bittiming_const = &up->device_info.bittiming_const; + up->can.do_set_bittiming = uCAN_set_bittiming; +#ifdef CONFIG_CAN_HWFILTER + up->can.do_set_hwfilter = uCAN_set_hwfilter; +#endif + netdev->netdev_ops = &uCAN_netdev_ops; + + usb_set_intfdata(intf, up); + SET_NETDEV_DEV(netdev, &intf->dev); + + // allocate memory for cammand messages + up->tx_msg_buffer = devm_kzalloc(&udev->dev, up->out_ep->wMaxPacketSize, GFP_KERNEL); + if (!up->tx_msg_buffer) + goto err_free_candev; + up->rx_msg_buffer = devm_kzalloc(&udev->dev, up->in_ep->wMaxPacketSize, GFP_KERNEL); + if (!up->rx_msg_buffer) + goto err_free_candev; + + // reset the device + ret = uCAN_command(up, UCAN_COMMAND_RESET, 0, 0); + if (ret) + goto err_free_candev; + + // gather device information + ret = uCAN_get_device_info(up); + if (ret) + goto err_free_candev; + +#ifdef CONFIG_CAN_HWFILTER + // disable hardware filter by default + ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_CLEAR, 0); + if (ret) + goto err_free_candev; + + ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_DISABLE, 0); + if (ret) + goto err_free_candev; +#endif + + dev_info(&up->udev->dev, + "Device Reports:\n" + " Frequency [Hz] : %d\n" + " TX Fifo [length] : %d\n" + " Time Segment 1 [min,max] : %d - %d\n" + " Time Segment 2 [min,max] : %d - %d\n" + " SWJ [max] : %d\n" + " Prescale [min-max,step] : %d - %d, %d\n" +#ifdef CONFIG_CAN_HWFILTER + " Hardware filter banks : %d\n" + " Receive mailboxes : %d\n" +#endif + " Supported modes :%s%s%s%s%s [%x]\n", + up->can.clock.freq, + up->device_info.tx_fifo, + up->can.bittiming_const->tseg1_min, + up->can.bittiming_const->tseg1_max, + up->can.bittiming_const->tseg2_min, + up->can.bittiming_const->tseg2_max, + up->can.bittiming_const->sjw_max, + up->can.bittiming_const->brp_min, + up->can.bittiming_const->brp_max, + up->can.bittiming_const->brp_inc, +#ifdef CONFIG_CAN_HWFILTER + up->can.hwfilterbanks, + up->can.rxmailboxes, +#endif + (up->can.ctrlmode_supported&CAN_CTRLMODE_LOOPBACK)?" Loopback":"", + (up->can.ctrlmode_supported&CAN_CTRLMODE_LISTENONLY)?" Silent":"", + (up->can.ctrlmode_supported&CAN_CTRLMODE_3_SAMPLES)?" 3-Sampling":"", + (up->can.ctrlmode_supported&CAN_CTRLMODE_ONE_SHOT)?" OneShot":"", + (up->can.ctrlmode_supported&CAN_CTRLMODE_BERR_REPORTING)?" BusErrReport":"", + up->can.ctrlmode_supported); + + atomic_set(&up->active_tx_urbs, 0); + for (i=0; itx_urb_contexts[i].echo_index=-1; + + init_usb_anchor(&up->rx_urbs); + init_usb_anchor(&up->tx_urbs); + + // register the device + ret = register_candev(netdev); + if (ret) + goto err_free_candev; + + return 0; +err_free_candev: + free_candev(netdev); +err: + return ret; +} + +/* disconnect the device */ +static void uCAN_disconnect(struct usb_interface *intf) +{ + struct usb_device *udev; + struct uCAN *up = usb_get_intfdata(intf); + + udev = interface_to_usbdev(intf); + dev_dbg(&udev->dev, "%s\n", __func__); + + usb_set_intfdata(intf, NULL); + + if (up) { + unregister_netdev(up->netdev); + free_candev(up->netdev); + } +} + +/* driver callbacks */ +static struct usb_driver uCAN_driver = { + .name = "uCAN", + .probe = uCAN_probe, + .disconnect = uCAN_disconnect, + .id_table = uCAN_table, +}; + +module_usb_driver(uCAN_driver); + diff --git a/drivers/net/can/usb/ucan.h b/drivers/net/can/usb/ucan.h new file mode 100644 index 000000000000..dfe33088e405 --- /dev/null +++ b/drivers/net/can/usb/ucan.h @@ -0,0 +1,216 @@ +#ifndef __UCAN_H__ + +/* + * Header file for CAN driver for uCAN + * + * Copyright (C) 2015 Theobroma Systems Design und Consulting GmbH + * + * 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; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. + * + * This driver is inspired by the 4.0.0 version of drivers/net/can/usb/ems_usb.c + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + + +#define UCAN_CLASS 0xA0 +#define UCAN_SUBCLASS 0x00 + +/* uCAN message definitions -------------------------------------------- + * + * uCAN_message_out_t and uCAN_message_in_t define the messages + * transmitted on the OUT and IN endpoint. + * + * Multibyte fields are transmitted with little endianess + * + * INTR Endpoint: a single uint32_t storing the current space in the fifo + * + * OUT Enpoint: single message of type uCAN_message_out_t is + * transmitted on the out endpoint + * + * IN Endpoint: multiple messages uCAN_message_in_t concateted in + * the following way: + * + * m[n].len <=> the length if message n(including the header in bytes) + * m[n] is is aligned to a 4 byte boundary, hence + * offset(m[0]) := 0; + * offset(m[n+1]) := offset(m[n]) + (m[n].len + 3) & 3 + * + * this implies that + * offset(m[n]) % 4 <=> 0 + */ + +// UCAN Commands +enum { + UCAN_COMMAND_START = 0, + UCAN_COMMAND_STOP = 1, + UCAN_COMMAND_SLEEP = 2, + UCAN_COMMAND_WAKEUP = 3, + UCAN_COMMAND_RESET = 4, + UCAN_COMMAND_GET = 5, + UCAN_COMMAND_GET_INFO = 0, + UCAN_COMMAND_FILTER = 6, + UCAN_FILTER_CLEAR = 0, + UCAN_FILTER_DISABLE = 1, +}; + +enum { + UCAN_MODE_LOOPBACK = (1<<0), + UCAN_MODE_SILENT = (1<<1), + UCAN_MODE_3_SAMPLES = (1<<2), + UCAN_MODE_ONE_SHOT = (1<<3), + UCAN_MODE_BERR_REPORT = (1<<4), +}; + +#define UCAN_OUT_COMMAND 0 +#define UCAN_OUT_SET_BITTIMING 1 +#define UCAN_OUT_TX 2 +#define UCAN_OUT_ENABLE_FILTER 3 + +/* OUT Enpoint, outbound messages */ +struct uCAN_message_out { + u16 len; /* Length of the content include header */ + u16 type; /* UCAN_OUT_COMMAND and friends */ + union { + /*************************************************** + * Device Command + * (type = UCAN_OUT_SET_BITTIMING) + ***************************************************/ + struct { + u8 cmd; /* UCAN_COMMAND_START and friends */ + u8 subcmd; + u16 val; + } __attribute__((packed)) command; + + /*************************************************** + * Set Bittiming + * (type = UCAN_OUT_SET_BITTIMING) + ***************************************************/ + struct { + u32 tq; /* Time quanta (TQ) in nanoseconds */ + u16 brp; /* TQ Prescaler */ + u16 sample_point; /* Samplepoint on tenth percent */ + u8 prop_seg; /* Propagation segment in TQs */ + u8 phase_seg1; /* Phase buffer segment 1 in TQs */ + u8 phase_seg2; /* Phase buffer segment 2 in TQs */ + u8 sjw; /* Synchronisation jump width in TQs */ + } __attribute__((packed)) bittiming; + + /*************************************************** + * Transmit CAN frame + * (type = UCAN_TX) && ((msg.can_msg.id & CAN_RTR_FLAG) == 0) + ***************************************************/ + struct { /* note DLC is computed by + * msg.len - sizeof (msg.len) + * - sizeof (msg.type) + * - sizeof (msg.can_msg.id); */ + u32 id; + u8 data[8]; // ensure data aligment to 4, by moving dlc after data + } __attribute__((packed)) can_msg; + + /*************************************************** + * Transmit RTR CAN frame + * (type = UCAN_TX) && ((msg.can_msg.id & CAN_RTR_FLAG) != 0) + ***************************************************/ + struct { + u32 id; + u8 dlc; + } __attribute__((packed)) can_rtr_msg; + + /*************************************************** + * Enable Filter + * (type = UCAN_OUT_ENABLE_FILTER) + ***************************************************/ + struct { + u32 id; + u32 mask; + u16 mbox; + } enable_filter; + } __attribute__((aligned(0x4))) msg; +} __attribute__((packed)); + +#define UCAN_IN_DEVICE_INFO 0 +#define UCAN_IN_RX 2 + +/* IN Enpoint, inbound messages */ +struct uCAN_message_in { + u16 len; /* Length of the content include header */ + u16 type; /* UCAN_IN_DEVICE_INFO and friends */ + + union { + /*************************************************** + * Device Information + * (type = UCAN_IN_DEVICE_INFO) + ***************************************************/ + struct { + u32 freq; /* Clock Frequency for tq + * generation */ + u8 tx_fifo; /* Size of the transmission + * fifo */ + u8 sjw_max; + u8 tseg1_min; + u8 tseg1_max; + u8 tseg2_min; + u8 tseg2_max; + u16 brp_inc; + u32 brp_min; + u32 brp_max; + u16 ctrlmodes; /* supported control modes + * ors of UCAN_MODE_* */ + u16 hwfilter; /* Number of HW filter + * banks */ + u16 rxmboxes; /* Number Receive + * Mailboxes */ + } __attribute__((packed)) device_info; + + /*************************************************** + * CAN RTR Frame received + * (type == UCAN_IN_RX) && ((msg.can_msg.id & CAN_RTR_FLAG) == 0) + ***************************************************/ + struct { /* note DLC is computed by + * msg.len - sizeof (msg.len) + * - sizeof (msg.type) + * - sizeof (msg.can_msg.id); */ + u32 id; + u8 data[8]; // ensure data aligment to 4, by moving dlc after data + } __attribute__((packed)) can_msg; + + /*************************************************** + * CAN RTR Frame received + * (type == UCAN_IN_RX) && ((msg.can_msg.id & CAN_RTR_FLAG) != 0) + ***************************************************/ + struct { + u32 id; + u8 dlc; + } __attribute__((packed)) can_rtr_msg; + } __attribute__((aligned(0x4))) msg; +} __attribute__((packed)); + + +/* Macros to calculate message lengths */ +#define UCAN_OUT_HDR_SIZE offsetof(struct uCAN_message_out, msg) +#define UCAN_OUT_LEN(member) (UCAN_OUT_HDR_SIZE + sizeof(member) ) + +#define UCAN_IN_HDR_SIZE offsetof(struct uCAN_message_in, msg) +#define UCAN_IN_LEN(member) (UCAN_OUT_HDR_SIZE + sizeof(member) ) + +#endif diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index c05ff0f9f9a5..83f92af80b12 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -37,7 +37,11 @@ struct can_priv { const struct can_bittiming_const *bittiming_const, *data_bittiming_const; struct can_clock clock; - +#ifdef CONFIG_CAN_HWFILTER + struct can_hwfilter filter; + int hwfilterbanks; + int rxmailboxes; +#endif enum can_state state; u32 ctrlmode; u32 ctrlmode_supported; @@ -52,6 +56,9 @@ struct can_priv { enum can_state *state); int (*do_get_berr_counter)(const struct net_device *dev, struct can_berr_counter *bec); +#ifdef CONFIG_CAN_HWFILTER + int (*do_set_hwfilter)(struct net_device *dev); +#endif unsigned int echo_skb_max; struct sk_buff **echo_skb; diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index 94ffe0c83ce7..0456aac91d3e 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -20,6 +20,25 @@ #include +#ifdef CONFIG_CAN_HWFILTER +/* + * CAN hardware filter parameters + */ + +#define CAN_MAX_FILTERS 32 + +struct __can_hwfilter { + __u32 id; + __u32 mask; + __u16 mbox; +}; + +struct can_hwfilter { + __u32 count; + struct __can_hwfilter filter[CAN_MAX_FILTERS]; +}; +#endif + /* * CAN bit-timing parameters * @@ -127,6 +146,9 @@ enum { IFLA_CAN_BERR_COUNTER, IFLA_CAN_DATA_BITTIMING, IFLA_CAN_DATA_BITTIMING_CONST, +#ifdef CONFIG_CAN_HWFILTER + IFLA_CAN_HARDWARE_FILTER, +#endif __IFLA_CAN_MAX }; -- cgit v1.2.3