forked from Minki/linux
linux-can-next-for-4.12-20170425
-----BEGIN PGP SIGNATURE----- iQFHBAABCgAxFiEE4bay/IylYqM/npjQHv7KIOw4HPYFAlj/Cw8THG1rbEBwZW5n dXRyb25peC5kZQAKCRAe/sog7Dgc9keOB/9dFFKUSqXEbevRCVj8Hc/tpmUnAYDP xcIpz/8GRHPrkOx/tpqtzkAQjeiNzcrT5LDPLDoMSpubZDJjNTKGfcb5sOvGqK9P IyY4dv0DO9/z1zxdpkK7CkR+g9Z3w9mEdQl2OS0yxbOXRPgX5Sl44Tp5xWgvJhOc s60m/Y60PQ22CSee7EBYWCwvJPfLIdsr5AIM6wtbEveZU13afAFbyIqoP/97RZKF sJ8NfGwQmRcD+AHw1nB/YfhNh4NEBE52IiBKf3zgC8Y8lDId/Wve/j/MnJGzeK48 eXPU3g1QaJTuEddn4xC0RRMKycR9klwfGJkY1cKSLtCWQxd1cfHKLdWY =P474 -----END PGP SIGNATURE----- Merge tag 'linux-can-next-for-4.12-20170425' of git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can-next Marc Kleine-Budde says: ==================== pull-request: can-next 2017-04-25 this is a pull request of 21 patches for net-next/master. There are 4 patches by Stephane Grosjean for the PEAK PCAN-PCIe FD CAN-FD boards. The next 7 patches are by Mario Huettel, which add support for M_CAN IP version >= v3.1.x to the m_can driver. A patch by Remigiusz Kołłątaj adds support for the Microchip CAN BUS Analyzer. 8 patches by Oliver Hartkopp complete the initial CAN network namespace support. Wei Yongjun's patch for the ti_hecc driver fixes the return value check in the probe function. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
45a6f3bca6
@ -9,6 +9,24 @@ config CAN_VCAN
|
|||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called vcan.
|
will be called vcan.
|
||||||
|
|
||||||
|
config CAN_VXCAN
|
||||||
|
tristate "Virtual CAN Tunnel (vxcan)"
|
||||||
|
---help---
|
||||||
|
Similar to the virtual ethernet driver veth, vxcan implements a
|
||||||
|
local CAN traffic tunnel between two virtual CAN network devices.
|
||||||
|
When creating a vxcan, two vxcan devices are created as pair.
|
||||||
|
When one end receives the packet it appears on its pair and vice
|
||||||
|
versa. The vxcan can be used for cross namespace communication.
|
||||||
|
|
||||||
|
In opposite to vcan loopback devices the vxcan only forwards CAN
|
||||||
|
frames to its pair and does *not* provide a local echo of sent
|
||||||
|
CAN frames. To disable a potential echo in af_can.c the vxcan driver
|
||||||
|
announces IFF_ECHO in the interface flags. To have a clean start
|
||||||
|
in each namespace the CAN GW hop counter is set to zero.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called vxcan.
|
||||||
|
|
||||||
config CAN_SLCAN
|
config CAN_SLCAN
|
||||||
tristate "Serial / USB serial CAN Adaptors (slcan)"
|
tristate "Serial / USB serial CAN Adaptors (slcan)"
|
||||||
depends on TTY
|
depends on TTY
|
||||||
@ -142,6 +160,7 @@ source "drivers/net/can/cc770/Kconfig"
|
|||||||
source "drivers/net/can/ifi_canfd/Kconfig"
|
source "drivers/net/can/ifi_canfd/Kconfig"
|
||||||
source "drivers/net/can/m_can/Kconfig"
|
source "drivers/net/can/m_can/Kconfig"
|
||||||
source "drivers/net/can/mscan/Kconfig"
|
source "drivers/net/can/mscan/Kconfig"
|
||||||
|
source "drivers/net/can/peak_canfd/Kconfig"
|
||||||
source "drivers/net/can/rcar/Kconfig"
|
source "drivers/net/can/rcar/Kconfig"
|
||||||
source "drivers/net/can/sja1000/Kconfig"
|
source "drivers/net/can/sja1000/Kconfig"
|
||||||
source "drivers/net/can/softing/Kconfig"
|
source "drivers/net/can/softing/Kconfig"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
obj-$(CONFIG_CAN_VCAN) += vcan.o
|
obj-$(CONFIG_CAN_VCAN) += vcan.o
|
||||||
|
obj-$(CONFIG_CAN_VXCAN) += vxcan.o
|
||||||
obj-$(CONFIG_CAN_SLCAN) += slcan.o
|
obj-$(CONFIG_CAN_SLCAN) += slcan.o
|
||||||
|
|
||||||
obj-$(CONFIG_CAN_DEV) += can-dev.o
|
obj-$(CONFIG_CAN_DEV) += can-dev.o
|
||||||
@ -26,6 +27,7 @@ obj-$(CONFIG_CAN_IFI_CANFD) += ifi_canfd/
|
|||||||
obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o
|
obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o
|
||||||
obj-$(CONFIG_CAN_MSCAN) += mscan/
|
obj-$(CONFIG_CAN_MSCAN) += mscan/
|
||||||
obj-$(CONFIG_CAN_M_CAN) += m_can/
|
obj-$(CONFIG_CAN_M_CAN) += m_can/
|
||||||
|
obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/
|
||||||
obj-$(CONFIG_CAN_SJA1000) += sja1000/
|
obj-$(CONFIG_CAN_SJA1000) += sja1000/
|
||||||
obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o
|
obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o
|
||||||
obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o
|
obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o
|
||||||
|
File diff suppressed because it is too large
Load Diff
13
drivers/net/can/peak_canfd/Kconfig
Normal file
13
drivers/net/can/peak_canfd/Kconfig
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
config CAN_PEAK_PCIEFD
|
||||||
|
depends on PCI
|
||||||
|
tristate "PEAK-System PCAN-PCIe FD cards"
|
||||||
|
---help---
|
||||||
|
This driver adds support for the PEAK-System PCI Express FD
|
||||||
|
CAN-FD cards family.
|
||||||
|
These 1x or 2x CAN-FD channels cards offer CAN 2.0 a/b as well as
|
||||||
|
CAN-FD access to the CAN bus. Besides the nominal bitrate of up to
|
||||||
|
1 Mbit/s, the data bytes of CAN-FD frames can be transmitted with
|
||||||
|
up to 12 Mbit/s. A galvanic isolation of the CAN ports protects the
|
||||||
|
electronics of the card and the respective computer against
|
||||||
|
disturbances of up to 500 Volts. The PCAN-PCI Express FD can be
|
||||||
|
operated with ambient temperatures in a range of -40 to +85 °C.
|
5
drivers/net/can/peak_canfd/Makefile
Normal file
5
drivers/net/can/peak_canfd/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# Makefile for the PEAK-System CAN-FD IP module drivers
|
||||||
|
#
|
||||||
|
obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_pciefd.o
|
||||||
|
peak_pciefd-y := peak_pciefd_main.o peak_canfd.o
|
801
drivers/net/can/peak_canfd/peak_canfd.c
Normal file
801
drivers/net/can/peak_canfd/peak_canfd.c
Normal file
@ -0,0 +1,801 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 2011 Wolfgang Grandegger <wg@grandegger.com>
|
||||||
|
* Copyright (C) 2012 Stephane Grosjean <s.grosjean@peak-system.com>
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 PEAK System-Technik GmbH
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the version 2 of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/dev.h>
|
||||||
|
|
||||||
|
#include "peak_canfd_user.h"
|
||||||
|
|
||||||
|
/* internal IP core cache size (used as default echo skbs max number) */
|
||||||
|
#define PCANFD_ECHO_SKB_MAX 24
|
||||||
|
|
||||||
|
/* bittiming ranges of the PEAK-System PC CAN-FD interfaces */
|
||||||
|
static const struct can_bittiming_const peak_canfd_nominal_const = {
|
||||||
|
.name = "peak_canfd",
|
||||||
|
.tseg1_min = 1,
|
||||||
|
.tseg1_max = (1 << PUCAN_TSLOW_TSGEG1_BITS),
|
||||||
|
.tseg2_min = 1,
|
||||||
|
.tseg2_max = (1 << PUCAN_TSLOW_TSGEG2_BITS),
|
||||||
|
.sjw_max = (1 << PUCAN_TSLOW_SJW_BITS),
|
||||||
|
.brp_min = 1,
|
||||||
|
.brp_max = (1 << PUCAN_TSLOW_BRP_BITS),
|
||||||
|
.brp_inc = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct can_bittiming_const peak_canfd_data_const = {
|
||||||
|
.name = "peak_canfd",
|
||||||
|
.tseg1_min = 1,
|
||||||
|
.tseg1_max = (1 << PUCAN_TFAST_TSGEG1_BITS),
|
||||||
|
.tseg2_min = 1,
|
||||||
|
.tseg2_max = (1 << PUCAN_TFAST_TSGEG2_BITS),
|
||||||
|
.sjw_max = (1 << PUCAN_TFAST_SJW_BITS),
|
||||||
|
.brp_min = 1,
|
||||||
|
.brp_max = (1 << PUCAN_TFAST_BRP_BITS),
|
||||||
|
.brp_inc = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct peak_canfd_priv *pucan_init_cmd(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
priv->cmd_len = 0;
|
||||||
|
return priv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *pucan_add_cmd(struct peak_canfd_priv *priv, int cmd_op)
|
||||||
|
{
|
||||||
|
struct pucan_command *cmd;
|
||||||
|
|
||||||
|
if (priv->cmd_len + sizeof(*cmd) > priv->cmd_maxlen)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
cmd = priv->cmd_buffer + priv->cmd_len;
|
||||||
|
|
||||||
|
/* reset all unused bit to default */
|
||||||
|
memset(cmd, 0, sizeof(*cmd));
|
||||||
|
|
||||||
|
cmd->opcode_channel = pucan_cmd_opcode_channel(priv->index, cmd_op);
|
||||||
|
priv->cmd_len += sizeof(*cmd);
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_write_cmd(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (priv->pre_cmd) {
|
||||||
|
err = priv->pre_cmd(priv);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = priv->write_cmd(priv);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (priv->post_cmd)
|
||||||
|
err = priv->post_cmd(priv);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* uCAN commands interface functions */
|
||||||
|
static int pucan_set_reset_mode(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_RESET_MODE);
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_set_normal_mode(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_NORMAL_MODE);
|
||||||
|
err = pucan_write_cmd(priv);
|
||||||
|
if (!err)
|
||||||
|
priv->can.state = CAN_STATE_ERROR_ACTIVE;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_set_listen_only_mode(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_LISTEN_ONLY_MODE);
|
||||||
|
err = pucan_write_cmd(priv);
|
||||||
|
if (!err)
|
||||||
|
priv->can.state = CAN_STATE_ERROR_ACTIVE;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_set_timing_slow(struct peak_canfd_priv *priv,
|
||||||
|
const struct can_bittiming *pbt)
|
||||||
|
{
|
||||||
|
struct pucan_timing_slow *cmd;
|
||||||
|
|
||||||
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_TIMING_SLOW);
|
||||||
|
|
||||||
|
cmd->sjw_t = PUCAN_TSLOW_SJW_T(pbt->sjw - 1,
|
||||||
|
priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES);
|
||||||
|
cmd->tseg1 = PUCAN_TSLOW_TSEG1(pbt->prop_seg + pbt->phase_seg1 - 1);
|
||||||
|
cmd->tseg2 = PUCAN_TSLOW_TSEG2(pbt->phase_seg2 - 1);
|
||||||
|
cmd->brp = cpu_to_le16(PUCAN_TSLOW_BRP(pbt->brp - 1));
|
||||||
|
|
||||||
|
cmd->ewl = 96; /* default */
|
||||||
|
|
||||||
|
netdev_dbg(priv->ndev,
|
||||||
|
"nominal: brp=%u tseg1=%u tseg2=%u sjw=%u\n",
|
||||||
|
le16_to_cpu(cmd->brp), cmd->tseg1, cmd->tseg2, cmd->sjw_t);
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_set_timing_fast(struct peak_canfd_priv *priv,
|
||||||
|
const struct can_bittiming *pbt)
|
||||||
|
{
|
||||||
|
struct pucan_timing_fast *cmd;
|
||||||
|
|
||||||
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_TIMING_FAST);
|
||||||
|
|
||||||
|
cmd->sjw = PUCAN_TFAST_SJW(pbt->sjw - 1);
|
||||||
|
cmd->tseg1 = PUCAN_TFAST_TSEG1(pbt->prop_seg + pbt->phase_seg1 - 1);
|
||||||
|
cmd->tseg2 = PUCAN_TFAST_TSEG2(pbt->phase_seg2 - 1);
|
||||||
|
cmd->brp = cpu_to_le16(PUCAN_TFAST_BRP(pbt->brp - 1));
|
||||||
|
|
||||||
|
netdev_dbg(priv->ndev,
|
||||||
|
"data: brp=%u tseg1=%u tseg2=%u sjw=%u\n",
|
||||||
|
le16_to_cpu(cmd->brp), cmd->tseg1, cmd->tseg2, cmd->sjw);
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_set_std_filter(struct peak_canfd_priv *priv, u8 row, u32 mask)
|
||||||
|
{
|
||||||
|
struct pucan_std_filter *cmd;
|
||||||
|
|
||||||
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_SET_STD_FILTER);
|
||||||
|
|
||||||
|
/* all the 11-bits CAN ID values are represented by one bit in a
|
||||||
|
* 64 rows array of 32 bits: the upper 6 bits of the CAN ID select the
|
||||||
|
* row while the lowest 5 bits select the bit in that row.
|
||||||
|
*
|
||||||
|
* bit filter
|
||||||
|
* 1 passed
|
||||||
|
* 0 discarded
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* select the row */
|
||||||
|
cmd->idx = row;
|
||||||
|
|
||||||
|
/* set/unset bits in the row */
|
||||||
|
cmd->mask = cpu_to_le32(mask);
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_tx_abort(struct peak_canfd_priv *priv, u16 flags)
|
||||||
|
{
|
||||||
|
struct pucan_tx_abort *cmd;
|
||||||
|
|
||||||
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_TX_ABORT);
|
||||||
|
|
||||||
|
cmd->flags = cpu_to_le16(flags);
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_clr_err_counters(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
struct pucan_wr_err_cnt *cmd;
|
||||||
|
|
||||||
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_WR_ERR_CNT);
|
||||||
|
|
||||||
|
cmd->sel_mask = cpu_to_le16(PUCAN_WRERRCNT_TE | PUCAN_WRERRCNT_RE);
|
||||||
|
cmd->tx_counter = 0;
|
||||||
|
cmd->rx_counter = 0;
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_set_options(struct peak_canfd_priv *priv, u16 opt_mask)
|
||||||
|
{
|
||||||
|
struct pucan_options *cmd;
|
||||||
|
|
||||||
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_SET_EN_OPTION);
|
||||||
|
|
||||||
|
cmd->options = cpu_to_le16(opt_mask);
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_clr_options(struct peak_canfd_priv *priv, u16 opt_mask)
|
||||||
|
{
|
||||||
|
struct pucan_options *cmd;
|
||||||
|
|
||||||
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_CLR_DIS_OPTION);
|
||||||
|
|
||||||
|
cmd->options = cpu_to_le16(opt_mask);
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pucan_setup_rx_barrier(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_RX_BARRIER);
|
||||||
|
|
||||||
|
return pucan_write_cmd(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle the reception of one CAN frame */
|
||||||
|
static int pucan_handle_can_rx(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_rx_msg *msg)
|
||||||
|
{
|
||||||
|
struct net_device_stats *stats = &priv->ndev->stats;
|
||||||
|
struct canfd_frame *cf;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
const u16 rx_msg_flags = le16_to_cpu(msg->flags);
|
||||||
|
u8 cf_len;
|
||||||
|
|
||||||
|
if (rx_msg_flags & PUCAN_MSG_EXT_DATA_LEN)
|
||||||
|
cf_len = can_dlc2len(get_canfd_dlc(pucan_msg_get_dlc(msg)));
|
||||||
|
else
|
||||||
|
cf_len = get_can_dlc(pucan_msg_get_dlc(msg));
|
||||||
|
|
||||||
|
/* if this frame is an echo, */
|
||||||
|
if ((rx_msg_flags & PUCAN_MSG_LOOPED_BACK) &&
|
||||||
|
!(rx_msg_flags & PUCAN_MSG_SELF_RECEIVE)) {
|
||||||
|
int n;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&priv->echo_lock, flags);
|
||||||
|
n = can_get_echo_skb(priv->ndev, msg->client);
|
||||||
|
spin_unlock_irqrestore(&priv->echo_lock, flags);
|
||||||
|
|
||||||
|
/* count bytes of the echo instead of skb */
|
||||||
|
stats->tx_bytes += cf_len;
|
||||||
|
stats->tx_packets++;
|
||||||
|
|
||||||
|
if (n) {
|
||||||
|
/* restart tx queue only if a slot is free */
|
||||||
|
netif_wake_queue(priv->ndev);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* otherwise, it should be pushed into rx fifo */
|
||||||
|
if (rx_msg_flags & PUCAN_MSG_EXT_DATA_LEN) {
|
||||||
|
/* CANFD frame case */
|
||||||
|
skb = alloc_canfd_skb(priv->ndev, &cf);
|
||||||
|
if (!skb)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (rx_msg_flags & PUCAN_MSG_BITRATE_SWITCH)
|
||||||
|
cf->flags |= CANFD_BRS;
|
||||||
|
|
||||||
|
if (rx_msg_flags & PUCAN_MSG_ERROR_STATE_IND)
|
||||||
|
cf->flags |= CANFD_ESI;
|
||||||
|
} else {
|
||||||
|
/* CAN 2.0 frame case */
|
||||||
|
skb = alloc_can_skb(priv->ndev, (struct can_frame **)&cf);
|
||||||
|
if (!skb)
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
cf->can_id = le32_to_cpu(msg->can_id);
|
||||||
|
cf->len = cf_len;
|
||||||
|
|
||||||
|
if (rx_msg_flags & PUCAN_MSG_EXT_ID)
|
||||||
|
cf->can_id |= CAN_EFF_FLAG;
|
||||||
|
|
||||||
|
if (rx_msg_flags & PUCAN_MSG_RTR)
|
||||||
|
cf->can_id |= CAN_RTR_FLAG;
|
||||||
|
else
|
||||||
|
memcpy(cf->data, msg->d, cf->len);
|
||||||
|
|
||||||
|
stats->rx_bytes += cf->len;
|
||||||
|
stats->rx_packets++;
|
||||||
|
|
||||||
|
netif_rx(skb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle rx/tx error counters notification */
|
||||||
|
static int pucan_handle_error(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_error_msg *msg)
|
||||||
|
{
|
||||||
|
priv->bec.txerr = msg->tx_err_cnt;
|
||||||
|
priv->bec.rxerr = msg->rx_err_cnt;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle status notification */
|
||||||
|
static int pucan_handle_status(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_status_msg *msg)
|
||||||
|
{
|
||||||
|
struct net_device *ndev = priv->ndev;
|
||||||
|
struct net_device_stats *stats = &ndev->stats;
|
||||||
|
struct can_frame *cf;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
/* this STATUS is the CNF of the RX_BARRIER: Tx path can be setup */
|
||||||
|
if (pucan_status_is_rx_barrier(msg)) {
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
if (priv->enable_tx_path) {
|
||||||
|
int err = priv->enable_tx_path(priv);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* restart network queue only if echo skb array is free */
|
||||||
|
spin_lock_irqsave(&priv->echo_lock, flags);
|
||||||
|
|
||||||
|
if (!priv->can.echo_skb[priv->echo_idx]) {
|
||||||
|
spin_unlock_irqrestore(&priv->echo_lock, flags);
|
||||||
|
|
||||||
|
netif_wake_queue(ndev);
|
||||||
|
} else {
|
||||||
|
spin_unlock_irqrestore(&priv->echo_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
skb = alloc_can_err_skb(ndev, &cf);
|
||||||
|
|
||||||
|
/* test state error bits according to their priority */
|
||||||
|
if (pucan_status_is_busoff(msg)) {
|
||||||
|
netdev_dbg(ndev, "Bus-off entry status\n");
|
||||||
|
priv->can.state = CAN_STATE_BUS_OFF;
|
||||||
|
priv->can.can_stats.bus_off++;
|
||||||
|
can_bus_off(ndev);
|
||||||
|
if (skb)
|
||||||
|
cf->can_id |= CAN_ERR_BUSOFF;
|
||||||
|
|
||||||
|
} else if (pucan_status_is_passive(msg)) {
|
||||||
|
netdev_dbg(ndev, "Error passive status\n");
|
||||||
|
priv->can.state = CAN_STATE_ERROR_PASSIVE;
|
||||||
|
priv->can.can_stats.error_passive++;
|
||||||
|
if (skb) {
|
||||||
|
cf->can_id |= CAN_ERR_CRTL;
|
||||||
|
cf->data[1] = (priv->bec.txerr > priv->bec.rxerr) ?
|
||||||
|
CAN_ERR_CRTL_TX_PASSIVE :
|
||||||
|
CAN_ERR_CRTL_RX_PASSIVE;
|
||||||
|
cf->data[6] = priv->bec.txerr;
|
||||||
|
cf->data[7] = priv->bec.rxerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (pucan_status_is_warning(msg)) {
|
||||||
|
netdev_dbg(ndev, "Error warning status\n");
|
||||||
|
priv->can.state = CAN_STATE_ERROR_WARNING;
|
||||||
|
priv->can.can_stats.error_warning++;
|
||||||
|
if (skb) {
|
||||||
|
cf->can_id |= CAN_ERR_CRTL;
|
||||||
|
cf->data[1] = (priv->bec.txerr > priv->bec.rxerr) ?
|
||||||
|
CAN_ERR_CRTL_TX_WARNING :
|
||||||
|
CAN_ERR_CRTL_RX_WARNING;
|
||||||
|
cf->data[6] = priv->bec.txerr;
|
||||||
|
cf->data[7] = priv->bec.rxerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (priv->can.state != CAN_STATE_ERROR_ACTIVE) {
|
||||||
|
/* back to ERROR_ACTIVE */
|
||||||
|
netdev_dbg(ndev, "Error active status\n");
|
||||||
|
can_change_state(ndev, cf, CAN_STATE_ERROR_ACTIVE,
|
||||||
|
CAN_STATE_ERROR_ACTIVE);
|
||||||
|
} else {
|
||||||
|
dev_kfree_skb(skb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skb) {
|
||||||
|
stats->rx_dropped++;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
stats->rx_packets++;
|
||||||
|
stats->rx_bytes += cf->can_dlc;
|
||||||
|
netif_rx(skb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle uCAN Rx overflow notification */
|
||||||
|
static int pucan_handle_cache_critical(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
struct net_device_stats *stats = &priv->ndev->stats;
|
||||||
|
struct can_frame *cf;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
stats->rx_over_errors++;
|
||||||
|
stats->rx_errors++;
|
||||||
|
|
||||||
|
skb = alloc_can_err_skb(priv->ndev, &cf);
|
||||||
|
if (!skb) {
|
||||||
|
stats->rx_dropped++;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
cf->can_id |= CAN_ERR_CRTL;
|
||||||
|
cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
|
||||||
|
|
||||||
|
cf->data[6] = priv->bec.txerr;
|
||||||
|
cf->data[7] = priv->bec.rxerr;
|
||||||
|
|
||||||
|
stats->rx_bytes += cf->can_dlc;
|
||||||
|
stats->rx_packets++;
|
||||||
|
netif_rx(skb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle a single uCAN message */
|
||||||
|
int peak_canfd_handle_msg(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_rx_msg *msg)
|
||||||
|
{
|
||||||
|
u16 msg_type = le16_to_cpu(msg->type);
|
||||||
|
int msg_size = le16_to_cpu(msg->size);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!msg_size || !msg_type) {
|
||||||
|
/* null packet found: end of list */
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (msg_type) {
|
||||||
|
case PUCAN_MSG_CAN_RX:
|
||||||
|
err = pucan_handle_can_rx(priv, (struct pucan_rx_msg *)msg);
|
||||||
|
break;
|
||||||
|
case PUCAN_MSG_ERROR:
|
||||||
|
err = pucan_handle_error(priv, (struct pucan_error_msg *)msg);
|
||||||
|
break;
|
||||||
|
case PUCAN_MSG_STATUS:
|
||||||
|
err = pucan_handle_status(priv, (struct pucan_status_msg *)msg);
|
||||||
|
break;
|
||||||
|
case PUCAN_MSG_CACHE_CRITICAL:
|
||||||
|
err = pucan_handle_cache_critical(priv);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return msg_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle a list of rx_count messages from rx_msg memory address */
|
||||||
|
int peak_canfd_handle_msgs_list(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_rx_msg *msg_list, int msg_count)
|
||||||
|
{
|
||||||
|
void *msg_ptr = msg_list;
|
||||||
|
int i, msg_size;
|
||||||
|
|
||||||
|
for (i = 0; i < msg_count; i++) {
|
||||||
|
msg_size = peak_canfd_handle_msg(priv, msg_ptr);
|
||||||
|
|
||||||
|
/* a null packet can be found at the end of a list */
|
||||||
|
if (msg_size <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
msg_ptr += msg_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg_size < 0)
|
||||||
|
return msg_size;
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peak_canfd_start(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = pucan_clr_err_counters(priv);
|
||||||
|
if (err)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
priv->echo_idx = 0;
|
||||||
|
|
||||||
|
priv->bec.txerr = 0;
|
||||||
|
priv->bec.rxerr = 0;
|
||||||
|
|
||||||
|
if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
|
||||||
|
err = pucan_set_listen_only_mode(priv);
|
||||||
|
else
|
||||||
|
err = pucan_set_normal_mode(priv);
|
||||||
|
|
||||||
|
err_exit:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void peak_canfd_stop(struct peak_canfd_priv *priv)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* go back to RESET mode */
|
||||||
|
err = pucan_set_reset_mode(priv);
|
||||||
|
if (err) {
|
||||||
|
netdev_err(priv->ndev, "channel %u reset failed\n",
|
||||||
|
priv->index);
|
||||||
|
} else {
|
||||||
|
/* abort last Tx (MUST be done in RESET mode only!) */
|
||||||
|
pucan_tx_abort(priv, PUCAN_TX_ABORT_FLUSH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peak_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
|
||||||
|
{
|
||||||
|
struct peak_canfd_priv *priv = netdev_priv(ndev);
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case CAN_MODE_START:
|
||||||
|
peak_canfd_start(priv);
|
||||||
|
netif_wake_queue(ndev);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peak_canfd_get_berr_counter(const struct net_device *ndev,
|
||||||
|
struct can_berr_counter *bec)
|
||||||
|
{
|
||||||
|
struct peak_canfd_priv *priv = netdev_priv(ndev);
|
||||||
|
|
||||||
|
*bec = priv->bec;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peak_canfd_open(struct net_device *ndev)
|
||||||
|
{
|
||||||
|
struct peak_canfd_priv *priv = netdev_priv(ndev);
|
||||||
|
int i, err = 0;
|
||||||
|
|
||||||
|
err = open_candev(ndev);
|
||||||
|
if (err) {
|
||||||
|
netdev_err(ndev, "open_candev() failed, error %d\n", err);
|
||||||
|
goto err_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pucan_set_reset_mode(priv);
|
||||||
|
if (err)
|
||||||
|
goto err_close;
|
||||||
|
|
||||||
|
if (priv->can.ctrlmode & CAN_CTRLMODE_FD) {
|
||||||
|
if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
|
||||||
|
err = pucan_clr_options(priv, PUCAN_OPTION_CANDFDISO);
|
||||||
|
else
|
||||||
|
err = pucan_set_options(priv, PUCAN_OPTION_CANDFDISO);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
goto err_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set option: get rx/tx error counters */
|
||||||
|
err = pucan_set_options(priv, PUCAN_OPTION_ERROR);
|
||||||
|
if (err)
|
||||||
|
goto err_close;
|
||||||
|
|
||||||
|
/* accept all standard CAN ID */
|
||||||
|
for (i = 0; i <= PUCAN_FLTSTD_ROW_IDX_MAX; i++)
|
||||||
|
pucan_set_std_filter(priv, i, 0xffffffff);
|
||||||
|
|
||||||
|
err = peak_canfd_start(priv);
|
||||||
|
if (err)
|
||||||
|
goto err_close;
|
||||||
|
|
||||||
|
/* receiving the RB status says when Tx path is ready */
|
||||||
|
err = pucan_setup_rx_barrier(priv);
|
||||||
|
if (!err)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
err_close:
|
||||||
|
close_candev(ndev);
|
||||||
|
err_exit:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peak_canfd_set_bittiming(struct net_device *ndev)
|
||||||
|
{
|
||||||
|
struct peak_canfd_priv *priv = netdev_priv(ndev);
|
||||||
|
|
||||||
|
return pucan_set_timing_slow(priv, &priv->can.bittiming);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peak_canfd_set_data_bittiming(struct net_device *ndev)
|
||||||
|
{
|
||||||
|
struct peak_canfd_priv *priv = netdev_priv(ndev);
|
||||||
|
|
||||||
|
return pucan_set_timing_fast(priv, &priv->can.data_bittiming);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int peak_canfd_close(struct net_device *ndev)
|
||||||
|
{
|
||||||
|
struct peak_canfd_priv *priv = netdev_priv(ndev);
|
||||||
|
|
||||||
|
netif_stop_queue(ndev);
|
||||||
|
peak_canfd_stop(priv);
|
||||||
|
close_candev(ndev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static netdev_tx_t peak_canfd_start_xmit(struct sk_buff *skb,
|
||||||
|
struct net_device *ndev)
|
||||||
|
{
|
||||||
|
struct peak_canfd_priv *priv = netdev_priv(ndev);
|
||||||
|
struct net_device_stats *stats = &ndev->stats;
|
||||||
|
struct canfd_frame *cf = (struct canfd_frame *)skb->data;
|
||||||
|
struct pucan_tx_msg *msg;
|
||||||
|
u16 msg_size, msg_flags;
|
||||||
|
unsigned long flags;
|
||||||
|
bool should_stop_tx_queue;
|
||||||
|
int room_left;
|
||||||
|
u8 can_dlc;
|
||||||
|
|
||||||
|
if (can_dropped_invalid_skb(ndev, skb))
|
||||||
|
return NETDEV_TX_OK;
|
||||||
|
|
||||||
|
msg_size = ALIGN(sizeof(*msg) + cf->len, 4);
|
||||||
|
msg = priv->alloc_tx_msg(priv, msg_size, &room_left);
|
||||||
|
|
||||||
|
/* should never happen except under bus-off condition and (auto-)restart
|
||||||
|
* mechanism
|
||||||
|
*/
|
||||||
|
if (!msg) {
|
||||||
|
stats->tx_dropped++;
|
||||||
|
netif_stop_queue(ndev);
|
||||||
|
return NETDEV_TX_BUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->size = cpu_to_le16(msg_size);
|
||||||
|
msg->type = cpu_to_le16(PUCAN_MSG_CAN_TX);
|
||||||
|
msg_flags = 0;
|
||||||
|
|
||||||
|
if (cf->can_id & CAN_EFF_FLAG) {
|
||||||
|
msg_flags |= PUCAN_MSG_EXT_ID;
|
||||||
|
msg->can_id = cpu_to_le32(cf->can_id & CAN_EFF_MASK);
|
||||||
|
} else {
|
||||||
|
msg->can_id = cpu_to_le32(cf->can_id & CAN_SFF_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (can_is_canfd_skb(skb)) {
|
||||||
|
/* CAN FD frame format */
|
||||||
|
can_dlc = can_len2dlc(cf->len);
|
||||||
|
|
||||||
|
msg_flags |= PUCAN_MSG_EXT_DATA_LEN;
|
||||||
|
|
||||||
|
if (cf->flags & CANFD_BRS)
|
||||||
|
msg_flags |= PUCAN_MSG_BITRATE_SWITCH;
|
||||||
|
|
||||||
|
if (cf->flags & CANFD_ESI)
|
||||||
|
msg_flags |= PUCAN_MSG_ERROR_STATE_IND;
|
||||||
|
} else {
|
||||||
|
/* CAN 2.0 frame format */
|
||||||
|
can_dlc = cf->len;
|
||||||
|
|
||||||
|
if (cf->can_id & CAN_RTR_FLAG)
|
||||||
|
msg_flags |= PUCAN_MSG_RTR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* always ask loopback for echo management */
|
||||||
|
msg_flags |= PUCAN_MSG_LOOPED_BACK;
|
||||||
|
|
||||||
|
/* set driver specific bit to differentiate with application loopback */
|
||||||
|
if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
|
||||||
|
msg_flags |= PUCAN_MSG_SELF_RECEIVE;
|
||||||
|
|
||||||
|
msg->flags = cpu_to_le16(msg_flags);
|
||||||
|
msg->channel_dlc = PUCAN_MSG_CHANNEL_DLC(priv->index, can_dlc);
|
||||||
|
memcpy(msg->d, cf->data, cf->len);
|
||||||
|
|
||||||
|
/* struct msg client field is used as an index in the echo skbs ring */
|
||||||
|
msg->client = priv->echo_idx;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&priv->echo_lock, flags);
|
||||||
|
|
||||||
|
/* prepare and save echo skb in internal slot */
|
||||||
|
can_put_echo_skb(skb, ndev, priv->echo_idx);
|
||||||
|
|
||||||
|
/* move echo index to the next slot */
|
||||||
|
priv->echo_idx = (priv->echo_idx + 1) % priv->can.echo_skb_max;
|
||||||
|
|
||||||
|
/* if next slot is not free, stop network queue (no slot free in echo
|
||||||
|
* skb ring means that the controller did not write these frames on
|
||||||
|
* the bus: no need to continue).
|
||||||
|
*/
|
||||||
|
should_stop_tx_queue = !!(priv->can.echo_skb[priv->echo_idx]);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&priv->echo_lock, flags);
|
||||||
|
|
||||||
|
/* write the skb on the interface */
|
||||||
|
priv->write_tx_msg(priv, msg);
|
||||||
|
|
||||||
|
/* stop network tx queue if not enough room to save one more msg too */
|
||||||
|
if (priv->can.ctrlmode & CAN_CTRLMODE_FD)
|
||||||
|
should_stop_tx_queue |= (room_left <
|
||||||
|
(sizeof(*msg) + CANFD_MAX_DLEN));
|
||||||
|
else
|
||||||
|
should_stop_tx_queue |= (room_left <
|
||||||
|
(sizeof(*msg) + CAN_MAX_DLEN));
|
||||||
|
|
||||||
|
if (should_stop_tx_queue)
|
||||||
|
netif_stop_queue(ndev);
|
||||||
|
|
||||||
|
return NETDEV_TX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct net_device_ops peak_canfd_netdev_ops = {
|
||||||
|
.ndo_open = peak_canfd_open,
|
||||||
|
.ndo_stop = peak_canfd_close,
|
||||||
|
.ndo_start_xmit = peak_canfd_start_xmit,
|
||||||
|
.ndo_change_mtu = can_change_mtu,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct net_device *alloc_peak_canfd_dev(int sizeof_priv, int index,
|
||||||
|
int echo_skb_max)
|
||||||
|
{
|
||||||
|
struct net_device *ndev;
|
||||||
|
struct peak_canfd_priv *priv;
|
||||||
|
|
||||||
|
/* we DO support local echo */
|
||||||
|
if (echo_skb_max < 0)
|
||||||
|
echo_skb_max = PCANFD_ECHO_SKB_MAX;
|
||||||
|
|
||||||
|
/* allocate the candev object */
|
||||||
|
ndev = alloc_candev(sizeof_priv, echo_skb_max);
|
||||||
|
if (!ndev)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
priv = netdev_priv(ndev);
|
||||||
|
|
||||||
|
/* complete now socket-can initialization side */
|
||||||
|
priv->can.state = CAN_STATE_STOPPED;
|
||||||
|
priv->can.bittiming_const = &peak_canfd_nominal_const;
|
||||||
|
priv->can.data_bittiming_const = &peak_canfd_data_const;
|
||||||
|
|
||||||
|
priv->can.do_set_mode = peak_canfd_set_mode;
|
||||||
|
priv->can.do_get_berr_counter = peak_canfd_get_berr_counter;
|
||||||
|
priv->can.do_set_bittiming = peak_canfd_set_bittiming;
|
||||||
|
priv->can.do_set_data_bittiming = peak_canfd_set_data_bittiming;
|
||||||
|
priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
|
||||||
|
CAN_CTRLMODE_LISTENONLY |
|
||||||
|
CAN_CTRLMODE_3_SAMPLES |
|
||||||
|
CAN_CTRLMODE_FD |
|
||||||
|
CAN_CTRLMODE_FD_NON_ISO |
|
||||||
|
CAN_CTRLMODE_BERR_REPORTING;
|
||||||
|
|
||||||
|
priv->ndev = ndev;
|
||||||
|
priv->index = index;
|
||||||
|
priv->cmd_len = 0;
|
||||||
|
spin_lock_init(&priv->echo_lock);
|
||||||
|
|
||||||
|
ndev->flags |= IFF_ECHO;
|
||||||
|
ndev->netdev_ops = &peak_canfd_netdev_ops;
|
||||||
|
ndev->dev_id = index;
|
||||||
|
|
||||||
|
return ndev;
|
||||||
|
}
|
55
drivers/net/can/peak_canfd/peak_canfd_user.h
Normal file
55
drivers/net/can/peak_canfd/peak_canfd_user.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* CAN driver for PEAK System micro-CAN based adapters
|
||||||
|
*
|
||||||
|
* Copyright (C) 2003-2011 PEAK System-Technik GmbH
|
||||||
|
* Copyright (C) 2011-2013 Stephane Grosjean <s.grosjean@peak-system.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published
|
||||||
|
* by the Free Software Foundation; 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.
|
||||||
|
*/
|
||||||
|
#ifndef PEAK_CANFD_USER_H
|
||||||
|
#define PEAK_CANFD_USER_H
|
||||||
|
|
||||||
|
#include <linux/can/dev/peak_canfd.h>
|
||||||
|
|
||||||
|
#define PCANFD_ECHO_SKB_DEF -1
|
||||||
|
|
||||||
|
/* data structure private to each uCAN interface */
|
||||||
|
struct peak_canfd_priv {
|
||||||
|
struct can_priv can; /* socket-can private data */
|
||||||
|
struct net_device *ndev; /* network device */
|
||||||
|
int index; /* channel index */
|
||||||
|
|
||||||
|
struct can_berr_counter bec; /* rx/tx err counters */
|
||||||
|
|
||||||
|
int echo_idx; /* echo skb free slot index */
|
||||||
|
spinlock_t echo_lock;
|
||||||
|
|
||||||
|
int cmd_len;
|
||||||
|
void *cmd_buffer;
|
||||||
|
int cmd_maxlen;
|
||||||
|
|
||||||
|
int (*pre_cmd)(struct peak_canfd_priv *priv);
|
||||||
|
int (*write_cmd)(struct peak_canfd_priv *priv);
|
||||||
|
int (*post_cmd)(struct peak_canfd_priv *priv);
|
||||||
|
|
||||||
|
int (*enable_tx_path)(struct peak_canfd_priv *priv);
|
||||||
|
void *(*alloc_tx_msg)(struct peak_canfd_priv *priv, u16 msg_size,
|
||||||
|
int *room_left);
|
||||||
|
int (*write_tx_msg)(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_tx_msg *msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct net_device *alloc_peak_canfd_dev(int sizeof_priv, int index,
|
||||||
|
int echo_skb_max);
|
||||||
|
int peak_canfd_handle_msg(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_rx_msg *msg);
|
||||||
|
int peak_canfd_handle_msgs_list(struct peak_canfd_priv *priv,
|
||||||
|
struct pucan_rx_msg *rx_msg, int rx_count);
|
||||||
|
#endif
|
842
drivers/net/can/peak_canfd/peak_pciefd_main.c
Normal file
842
drivers/net/can/peak_canfd/peak_pciefd_main.c
Normal file
@ -0,0 +1,842 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 2011 Wolfgang Grandegger <wg@grandegger.com>
|
||||||
|
* Copyright (C) 2012 Stephane Grosjean <s.grosjean@peak-system.com>
|
||||||
|
*
|
||||||
|
* Derived from the PCAN project file driver/src/pcan_pci.c:
|
||||||
|
*
|
||||||
|
* Copyright (C) 2001-2006 PEAK System-Technik GmbH
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the version 2 of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/netdevice.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/pci.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/dev.h>
|
||||||
|
|
||||||
|
#include "peak_canfd_user.h"
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Stephane Grosjean <s.grosjean@peak-system.com>");
|
||||||
|
MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCIe FD family cards");
|
||||||
|
MODULE_SUPPORTED_DEVICE("PEAK PCAN PCIe FD CAN cards");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
|
||||||
|
#define PCIEFD_DRV_NAME "peak_pciefd"
|
||||||
|
|
||||||
|
#define PEAK_PCI_VENDOR_ID 0x001c /* The PCI device and vendor IDs */
|
||||||
|
#define PEAK_PCIEFD_ID 0x0013 /* for PCIe slot cards */
|
||||||
|
|
||||||
|
/* PEAK PCIe board access description */
|
||||||
|
#define PCIEFD_BAR0_SIZE (64 * 1024)
|
||||||
|
#define PCIEFD_RX_DMA_SIZE (4 * 1024)
|
||||||
|
#define PCIEFD_TX_DMA_SIZE (4 * 1024)
|
||||||
|
|
||||||
|
#define PCIEFD_TX_PAGE_SIZE (2 * 1024)
|
||||||
|
|
||||||
|
/* System Control Registers */
|
||||||
|
#define PCIEFD_REG_SYS_CTL_SET 0x0000 /* set bits */
|
||||||
|
#define PCIEFD_REG_SYS_CTL_CLR 0x0004 /* clear bits */
|
||||||
|
|
||||||
|
/* Version info registers */
|
||||||
|
#define PCIEFD_REG_SYS_VER1 0x0040 /* version reg #1 */
|
||||||
|
#define PCIEFD_REG_SYS_VER2 0x0044 /* version reg #2 */
|
||||||
|
|
||||||
|
/* System Control Registers Bits */
|
||||||
|
#define PCIEFD_SYS_CTL_TS_RST 0x00000001 /* timestamp clock */
|
||||||
|
#define PCIEFD_SYS_CTL_CLK_EN 0x00000002 /* system clock */
|
||||||
|
|
||||||
|
/* CAN-FD channel addresses */
|
||||||
|
#define PCIEFD_CANX_OFF(c) (((c) + 1) * 0x1000)
|
||||||
|
|
||||||
|
#define PCIEFD_ECHO_SKB_MAX PCANFD_ECHO_SKB_DEF
|
||||||
|
|
||||||
|
/* CAN-FD channel registers */
|
||||||
|
#define PCIEFD_REG_CAN_MISC 0x0000 /* Misc. control */
|
||||||
|
#define PCIEFD_REG_CAN_CLK_SEL 0x0008 /* Clock selector */
|
||||||
|
#define PCIEFD_REG_CAN_CMD_PORT_L 0x0010 /* 64-bits command port */
|
||||||
|
#define PCIEFD_REG_CAN_CMD_PORT_H 0x0014
|
||||||
|
#define PCIEFD_REG_CAN_TX_REQ_ACC 0x0020 /* Tx request accumulator */
|
||||||
|
#define PCIEFD_REG_CAN_TX_CTL_SET 0x0030 /* Tx control set register */
|
||||||
|
#define PCIEFD_REG_CAN_TX_CTL_CLR 0x0038 /* Tx control clear register */
|
||||||
|
#define PCIEFD_REG_CAN_TX_DMA_ADDR_L 0x0040 /* 64-bits addr for Tx DMA */
|
||||||
|
#define PCIEFD_REG_CAN_TX_DMA_ADDR_H 0x0044
|
||||||
|
#define PCIEFD_REG_CAN_RX_CTL_SET 0x0050 /* Rx control set register */
|
||||||
|
#define PCIEFD_REG_CAN_RX_CTL_CLR 0x0058 /* Rx control clear register */
|
||||||
|
#define PCIEFD_REG_CAN_RX_CTL_WRT 0x0060 /* Rx control write register */
|
||||||
|
#define PCIEFD_REG_CAN_RX_CTL_ACK 0x0068 /* Rx control ACK register */
|
||||||
|
#define PCIEFD_REG_CAN_RX_DMA_ADDR_L 0x0070 /* 64-bits addr for Rx DMA */
|
||||||
|
#define PCIEFD_REG_CAN_RX_DMA_ADDR_H 0x0074
|
||||||
|
|
||||||
|
/* CAN-FD channel misc register bits */
|
||||||
|
#define CANFD_MISC_TS_RST 0x00000001 /* timestamp cnt rst */
|
||||||
|
|
||||||
|
/* CAN-FD channel Clock SELector Source & DIVider */
|
||||||
|
#define CANFD_CLK_SEL_DIV_MASK 0x00000007
|
||||||
|
#define CANFD_CLK_SEL_DIV_60MHZ 0x00000000 /* SRC=240MHz only */
|
||||||
|
#define CANFD_CLK_SEL_DIV_40MHZ 0x00000001 /* SRC=240MHz only */
|
||||||
|
#define CANFD_CLK_SEL_DIV_30MHZ 0x00000002 /* SRC=240MHz only */
|
||||||
|
#define CANFD_CLK_SEL_DIV_24MHZ 0x00000003 /* SRC=240MHz only */
|
||||||
|
#define CANFD_CLK_SEL_DIV_20MHZ 0x00000004 /* SRC=240MHz only */
|
||||||
|
|
||||||
|
#define CANFD_CLK_SEL_SRC_MASK 0x00000008 /* 0=80MHz, 1=240MHz */
|
||||||
|
#define CANFD_CLK_SEL_SRC_240MHZ 0x00000008
|
||||||
|
#define CANFD_CLK_SEL_SRC_80MHZ (~CANFD_CLK_SEL_SRC_240MHZ & \
|
||||||
|
CANFD_CLK_SEL_SRC_MASK)
|
||||||
|
|
||||||
|
#define CANFD_CLK_SEL_20MHZ (CANFD_CLK_SEL_SRC_240MHZ |\
|
||||||
|
CANFD_CLK_SEL_DIV_20MHZ)
|
||||||
|
#define CANFD_CLK_SEL_24MHZ (CANFD_CLK_SEL_SRC_240MHZ |\
|
||||||
|
CANFD_CLK_SEL_DIV_24MHZ)
|
||||||
|
#define CANFD_CLK_SEL_30MHZ (CANFD_CLK_SEL_SRC_240MHZ |\
|
||||||
|
CANFD_CLK_SEL_DIV_30MHZ)
|
||||||
|
#define CANFD_CLK_SEL_40MHZ (CANFD_CLK_SEL_SRC_240MHZ |\
|
||||||
|
CANFD_CLK_SEL_DIV_40MHZ)
|
||||||
|
#define CANFD_CLK_SEL_60MHZ (CANFD_CLK_SEL_SRC_240MHZ |\
|
||||||
|
CANFD_CLK_SEL_DIV_60MHZ)
|
||||||
|
#define CANFD_CLK_SEL_80MHZ (CANFD_CLK_SEL_SRC_80MHZ)
|
||||||
|
|
||||||
|
/* CAN-FD channel Rx/Tx control register bits */
|
||||||
|
#define CANFD_CTL_UNC_BIT 0x00010000 /* Uncached DMA mem */
|
||||||
|
#define CANFD_CTL_RST_BIT 0x00020000 /* reset DMA action */
|
||||||
|
#define CANFD_CTL_IEN_BIT 0x00040000 /* IRQ enable */
|
||||||
|
|
||||||
|
/* Rx IRQ Count and Time Limits */
|
||||||
|
#define CANFD_CTL_IRQ_CL_DEF 16 /* Rx msg max nb per IRQ in Rx DMA */
|
||||||
|
#define CANFD_CTL_IRQ_TL_DEF 10 /* Time before IRQ if < CL (x100 µs) */
|
||||||
|
|
||||||
|
#define CANFD_OPTIONS_SET (CANFD_OPTION_ERROR | CANFD_OPTION_BUSLOAD)
|
||||||
|
|
||||||
|
/* Tx anticipation window (link logical address should be aligned on 2K
|
||||||
|
* boundary)
|
||||||
|
*/
|
||||||
|
#define PCIEFD_TX_PAGE_COUNT (PCIEFD_TX_DMA_SIZE / PCIEFD_TX_PAGE_SIZE)
|
||||||
|
|
||||||
|
#define CANFD_MSG_LNK_TX 0x1001 /* Tx msgs link */
|
||||||
|
|
||||||
|
/* 32-bits IRQ status fields, heading Rx DMA area */
|
||||||
|
static inline int pciefd_irq_tag(u32 irq_status)
|
||||||
|
{
|
||||||
|
return irq_status & 0x0000000f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pciefd_irq_rx_cnt(u32 irq_status)
|
||||||
|
{
|
||||||
|
return (irq_status & 0x000007f0) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pciefd_irq_is_lnk(u32 irq_status)
|
||||||
|
{
|
||||||
|
return irq_status & 0x00010000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rx record */
|
||||||
|
struct pciefd_rx_dma {
|
||||||
|
__le32 irq_status;
|
||||||
|
__le32 sys_time_low;
|
||||||
|
__le32 sys_time_high;
|
||||||
|
struct pucan_rx_msg msg[0];
|
||||||
|
} __packed __aligned(4);
|
||||||
|
|
||||||
|
/* Tx Link record */
|
||||||
|
struct pciefd_tx_link {
|
||||||
|
__le16 size;
|
||||||
|
__le16 type;
|
||||||
|
__le32 laddr_lo;
|
||||||
|
__le32 laddr_hi;
|
||||||
|
} __packed __aligned(4);
|
||||||
|
|
||||||
|
/* Tx page descriptor */
|
||||||
|
struct pciefd_page {
|
||||||
|
void *vbase; /* page virtual address */
|
||||||
|
dma_addr_t lbase; /* page logical address */
|
||||||
|
u32 offset;
|
||||||
|
u32 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CANFD_IRQ_SET 0x00000001
|
||||||
|
#define CANFD_TX_PATH_SET 0x00000002
|
||||||
|
|
||||||
|
/* CAN-FD channel object */
|
||||||
|
struct pciefd_board;
|
||||||
|
struct pciefd_can {
|
||||||
|
struct peak_canfd_priv ucan; /* must be the first member */
|
||||||
|
void __iomem *reg_base; /* channel config base addr */
|
||||||
|
struct pciefd_board *board; /* reverse link */
|
||||||
|
|
||||||
|
struct pucan_command pucan_cmd; /* command buffer */
|
||||||
|
|
||||||
|
dma_addr_t rx_dma_laddr; /* DMA virtual and logical addr */
|
||||||
|
void *rx_dma_vaddr; /* for Rx and Tx areas */
|
||||||
|
dma_addr_t tx_dma_laddr;
|
||||||
|
void *tx_dma_vaddr;
|
||||||
|
|
||||||
|
struct pciefd_page tx_pages[PCIEFD_TX_PAGE_COUNT];
|
||||||
|
u16 tx_pages_free; /* free Tx pages counter */
|
||||||
|
u16 tx_page_index; /* current page used for Tx */
|
||||||
|
spinlock_t tx_lock;
|
||||||
|
|
||||||
|
u32 irq_status;
|
||||||
|
u32 irq_tag; /* next irq tag */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* PEAK-PCIe FD board object */
|
||||||
|
struct pciefd_board {
|
||||||
|
void __iomem *reg_base;
|
||||||
|
struct pci_dev *pci_dev;
|
||||||
|
int can_count;
|
||||||
|
spinlock_t cmd_lock; /* 64-bits cmds must be atomic */
|
||||||
|
struct pciefd_can *can[0]; /* array of network devices */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* supported device ids. */
|
||||||
|
static const struct pci_device_id peak_pciefd_tbl[] = {
|
||||||
|
{PEAK_PCI_VENDOR_ID, PEAK_PCIEFD_ID, PCI_ANY_ID, PCI_ANY_ID,},
|
||||||
|
{0,}
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(pci, peak_pciefd_tbl);
|
||||||
|
|
||||||
|
/* read a 32 bits value from a SYS block register */
|
||||||
|
static inline u32 pciefd_sys_readreg(const struct pciefd_board *priv, u16 reg)
|
||||||
|
{
|
||||||
|
return readl(priv->reg_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write a 32 bits value into a SYS block register */
|
||||||
|
static inline void pciefd_sys_writereg(const struct pciefd_board *priv,
|
||||||
|
u32 val, u16 reg)
|
||||||
|
{
|
||||||
|
writel(val, priv->reg_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read a 32 bits value from CAN-FD block register */
|
||||||
|
static inline u32 pciefd_can_readreg(const struct pciefd_can *priv, u16 reg)
|
||||||
|
{
|
||||||
|
return readl(priv->reg_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write a 32 bits value into a CAN-FD block register */
|
||||||
|
static inline void pciefd_can_writereg(const struct pciefd_can *priv,
|
||||||
|
u32 val, u16 reg)
|
||||||
|
{
|
||||||
|
writel(val, priv->reg_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* give a channel logical Rx DMA address to the board */
|
||||||
|
static void pciefd_can_setup_rx_dma(struct pciefd_can *priv)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
||||||
|
const u32 dma_addr_h = (u32)(priv->rx_dma_laddr >> 32);
|
||||||
|
#else
|
||||||
|
const u32 dma_addr_h = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* (DMA must be reset for Rx) */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_RST_BIT, PCIEFD_REG_CAN_RX_CTL_SET);
|
||||||
|
|
||||||
|
/* write the logical address of the Rx DMA area for this channel */
|
||||||
|
pciefd_can_writereg(priv, (u32)priv->rx_dma_laddr,
|
||||||
|
PCIEFD_REG_CAN_RX_DMA_ADDR_L);
|
||||||
|
pciefd_can_writereg(priv, dma_addr_h, PCIEFD_REG_CAN_RX_DMA_ADDR_H);
|
||||||
|
|
||||||
|
/* also indicates that Rx DMA is cacheable */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_UNC_BIT, PCIEFD_REG_CAN_RX_CTL_CLR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear channel logical Rx DMA address from the board */
|
||||||
|
static void pciefd_can_clear_rx_dma(struct pciefd_can *priv)
|
||||||
|
{
|
||||||
|
/* DMA must be reset for Rx */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_RST_BIT, PCIEFD_REG_CAN_RX_CTL_SET);
|
||||||
|
|
||||||
|
/* clear the logical address of the Rx DMA area for this channel */
|
||||||
|
pciefd_can_writereg(priv, 0, PCIEFD_REG_CAN_RX_DMA_ADDR_L);
|
||||||
|
pciefd_can_writereg(priv, 0, PCIEFD_REG_CAN_RX_DMA_ADDR_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* give a channel logical Tx DMA address to the board */
|
||||||
|
static void pciefd_can_setup_tx_dma(struct pciefd_can *priv)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
||||||
|
const u32 dma_addr_h = (u32)(priv->tx_dma_laddr >> 32);
|
||||||
|
#else
|
||||||
|
const u32 dma_addr_h = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* (DMA must be reset for Tx) */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_RST_BIT, PCIEFD_REG_CAN_TX_CTL_SET);
|
||||||
|
|
||||||
|
/* write the logical address of the Tx DMA area for this channel */
|
||||||
|
pciefd_can_writereg(priv, (u32)priv->tx_dma_laddr,
|
||||||
|
PCIEFD_REG_CAN_TX_DMA_ADDR_L);
|
||||||
|
pciefd_can_writereg(priv, dma_addr_h, PCIEFD_REG_CAN_TX_DMA_ADDR_H);
|
||||||
|
|
||||||
|
/* also indicates that Tx DMA is cacheable */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_UNC_BIT, PCIEFD_REG_CAN_TX_CTL_CLR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear channel logical Tx DMA address from the board */
|
||||||
|
static void pciefd_can_clear_tx_dma(struct pciefd_can *priv)
|
||||||
|
{
|
||||||
|
/* DMA must be reset for Tx */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_RST_BIT, PCIEFD_REG_CAN_TX_CTL_SET);
|
||||||
|
|
||||||
|
/* clear the logical address of the Tx DMA area for this channel */
|
||||||
|
pciefd_can_writereg(priv, 0, PCIEFD_REG_CAN_TX_DMA_ADDR_L);
|
||||||
|
pciefd_can_writereg(priv, 0, PCIEFD_REG_CAN_TX_DMA_ADDR_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pciefd_can_ack_rx_dma(struct pciefd_can *priv)
|
||||||
|
{
|
||||||
|
/* read value of current IRQ tag and inc it for next one */
|
||||||
|
priv->irq_tag = le32_to_cpu(*(__le32 *)priv->rx_dma_vaddr);
|
||||||
|
priv->irq_tag++;
|
||||||
|
priv->irq_tag &= 0xf;
|
||||||
|
|
||||||
|
/* write the next IRQ tag for this CAN */
|
||||||
|
pciefd_can_writereg(priv, priv->irq_tag, PCIEFD_REG_CAN_RX_CTL_ACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IRQ handler */
|
||||||
|
static irqreturn_t pciefd_irq_handler(int irq, void *arg)
|
||||||
|
{
|
||||||
|
struct pciefd_can *priv = arg;
|
||||||
|
struct pciefd_rx_dma *rx_dma = priv->rx_dma_vaddr;
|
||||||
|
|
||||||
|
/* INTA mode only to sync with PCIe transaction */
|
||||||
|
if (!pci_dev_msi_enabled(priv->board->pci_dev))
|
||||||
|
(void)pciefd_sys_readreg(priv->board, PCIEFD_REG_SYS_VER1);
|
||||||
|
|
||||||
|
/* read IRQ status from the first 32-bits of the Rx DMA area */
|
||||||
|
priv->irq_status = le32_to_cpu(rx_dma->irq_status);
|
||||||
|
|
||||||
|
/* check if this (shared) IRQ is for this CAN */
|
||||||
|
if (pciefd_irq_tag(priv->irq_status) != priv->irq_tag)
|
||||||
|
return IRQ_NONE;
|
||||||
|
|
||||||
|
/* handle rx messages (if any) */
|
||||||
|
peak_canfd_handle_msgs_list(&priv->ucan,
|
||||||
|
rx_dma->msg,
|
||||||
|
pciefd_irq_rx_cnt(priv->irq_status));
|
||||||
|
|
||||||
|
/* handle tx link interrupt (if any) */
|
||||||
|
if (pciefd_irq_is_lnk(priv->irq_status)) {
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&priv->tx_lock, flags);
|
||||||
|
priv->tx_pages_free++;
|
||||||
|
spin_unlock_irqrestore(&priv->tx_lock, flags);
|
||||||
|
|
||||||
|
/* wake producer up */
|
||||||
|
netif_wake_queue(priv->ucan.ndev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* re-enable Rx DMA transfer for this CAN */
|
||||||
|
pciefd_can_ack_rx_dma(priv);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pciefd_enable_tx_path(struct peak_canfd_priv *ucan)
|
||||||
|
{
|
||||||
|
struct pciefd_can *priv = (struct pciefd_can *)ucan;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* initialize the Tx pages descriptors */
|
||||||
|
priv->tx_pages_free = PCIEFD_TX_PAGE_COUNT - 1;
|
||||||
|
priv->tx_page_index = 0;
|
||||||
|
|
||||||
|
priv->tx_pages[0].vbase = priv->tx_dma_vaddr;
|
||||||
|
priv->tx_pages[0].lbase = priv->tx_dma_laddr;
|
||||||
|
|
||||||
|
for (i = 0; i < PCIEFD_TX_PAGE_COUNT; i++) {
|
||||||
|
priv->tx_pages[i].offset = 0;
|
||||||
|
priv->tx_pages[i].size = PCIEFD_TX_PAGE_SIZE -
|
||||||
|
sizeof(struct pciefd_tx_link);
|
||||||
|
if (i) {
|
||||||
|
priv->tx_pages[i].vbase =
|
||||||
|
priv->tx_pages[i - 1].vbase +
|
||||||
|
PCIEFD_TX_PAGE_SIZE;
|
||||||
|
priv->tx_pages[i].lbase =
|
||||||
|
priv->tx_pages[i - 1].lbase +
|
||||||
|
PCIEFD_TX_PAGE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* setup Tx DMA addresses into IP core */
|
||||||
|
pciefd_can_setup_tx_dma(priv);
|
||||||
|
|
||||||
|
/* start (TX_RST=0) Tx Path */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_RST_BIT, PCIEFD_REG_CAN_TX_CTL_CLR);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* board specific CANFD command pre-processing */
|
||||||
|
static int pciefd_pre_cmd(struct peak_canfd_priv *ucan)
|
||||||
|
{
|
||||||
|
struct pciefd_can *priv = (struct pciefd_can *)ucan;
|
||||||
|
u16 cmd = pucan_cmd_get_opcode(&priv->pucan_cmd);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* pre-process command */
|
||||||
|
switch (cmd) {
|
||||||
|
case PUCAN_CMD_NORMAL_MODE:
|
||||||
|
case PUCAN_CMD_LISTEN_ONLY_MODE:
|
||||||
|
|
||||||
|
if (ucan->can.state == CAN_STATE_BUS_OFF)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* going into operational mode: setup IRQ handler */
|
||||||
|
err = request_irq(priv->board->pci_dev->irq,
|
||||||
|
pciefd_irq_handler,
|
||||||
|
IRQF_SHARED,
|
||||||
|
PCIEFD_DRV_NAME,
|
||||||
|
priv);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
/* setup Rx DMA address */
|
||||||
|
pciefd_can_setup_rx_dma(priv);
|
||||||
|
|
||||||
|
/* setup max count of msgs per IRQ */
|
||||||
|
pciefd_can_writereg(priv, (CANFD_CTL_IRQ_TL_DEF) << 8 |
|
||||||
|
CANFD_CTL_IRQ_CL_DEF,
|
||||||
|
PCIEFD_REG_CAN_RX_CTL_WRT);
|
||||||
|
|
||||||
|
/* clear DMA RST for Rx (Rx start) */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_RST_BIT,
|
||||||
|
PCIEFD_REG_CAN_RX_CTL_CLR);
|
||||||
|
|
||||||
|
/* reset timestamps */
|
||||||
|
pciefd_can_writereg(priv, !CANFD_MISC_TS_RST,
|
||||||
|
PCIEFD_REG_CAN_MISC);
|
||||||
|
|
||||||
|
/* do an initial ACK */
|
||||||
|
pciefd_can_ack_rx_dma(priv);
|
||||||
|
|
||||||
|
/* enable IRQ for this CAN after having set next irq_tag */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_IEN_BIT,
|
||||||
|
PCIEFD_REG_CAN_RX_CTL_SET);
|
||||||
|
|
||||||
|
/* Tx path will be setup as soon as RX_BARRIER is received */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write a command */
|
||||||
|
static int pciefd_write_cmd(struct peak_canfd_priv *ucan)
|
||||||
|
{
|
||||||
|
struct pciefd_can *priv = (struct pciefd_can *)ucan;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
/* 64-bits command is atomic */
|
||||||
|
spin_lock_irqsave(&priv->board->cmd_lock, flags);
|
||||||
|
|
||||||
|
pciefd_can_writereg(priv, *(u32 *)ucan->cmd_buffer,
|
||||||
|
PCIEFD_REG_CAN_CMD_PORT_L);
|
||||||
|
pciefd_can_writereg(priv, *(u32 *)(ucan->cmd_buffer + 4),
|
||||||
|
PCIEFD_REG_CAN_CMD_PORT_H);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&priv->board->cmd_lock, flags);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* board specific CANFD command post-processing */
|
||||||
|
static int pciefd_post_cmd(struct peak_canfd_priv *ucan)
|
||||||
|
{
|
||||||
|
struct pciefd_can *priv = (struct pciefd_can *)ucan;
|
||||||
|
u16 cmd = pucan_cmd_get_opcode(&priv->pucan_cmd);
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case PUCAN_CMD_RESET_MODE:
|
||||||
|
|
||||||
|
if (ucan->can.state == CAN_STATE_STOPPED)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* controller now in reset mode: */
|
||||||
|
|
||||||
|
/* stop and reset DMA addresses in Tx/Rx engines */
|
||||||
|
pciefd_can_clear_tx_dma(priv);
|
||||||
|
pciefd_can_clear_rx_dma(priv);
|
||||||
|
|
||||||
|
/* disable IRQ for this CAN */
|
||||||
|
pciefd_can_writereg(priv, CANFD_CTL_IEN_BIT,
|
||||||
|
PCIEFD_REG_CAN_RX_CTL_CLR);
|
||||||
|
|
||||||
|
free_irq(priv->board->pci_dev->irq, priv);
|
||||||
|
|
||||||
|
ucan->can.state = CAN_STATE_STOPPED;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *pciefd_alloc_tx_msg(struct peak_canfd_priv *ucan, u16 msg_size,
|
||||||
|
int *room_left)
|
||||||
|
{
|
||||||
|
struct pciefd_can *priv = (struct pciefd_can *)ucan;
|
||||||
|
struct pciefd_page *page = priv->tx_pages + priv->tx_page_index;
|
||||||
|
unsigned long flags;
|
||||||
|
void *msg;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&priv->tx_lock, flags);
|
||||||
|
|
||||||
|
if (page->offset + msg_size > page->size) {
|
||||||
|
struct pciefd_tx_link *lk;
|
||||||
|
|
||||||
|
/* not enough space in this page: try another one */
|
||||||
|
if (!priv->tx_pages_free) {
|
||||||
|
spin_unlock_irqrestore(&priv->tx_lock, flags);
|
||||||
|
|
||||||
|
/* Tx overflow */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->tx_pages_free--;
|
||||||
|
|
||||||
|
/* keep address of the very last free slot of current page */
|
||||||
|
lk = page->vbase + page->offset;
|
||||||
|
|
||||||
|
/* next, move on a new free page */
|
||||||
|
priv->tx_page_index = (priv->tx_page_index + 1) %
|
||||||
|
PCIEFD_TX_PAGE_COUNT;
|
||||||
|
page = priv->tx_pages + priv->tx_page_index;
|
||||||
|
|
||||||
|
/* put link record to this new page at the end of prev one */
|
||||||
|
lk->size = cpu_to_le16(sizeof(*lk));
|
||||||
|
lk->type = cpu_to_le16(CANFD_MSG_LNK_TX);
|
||||||
|
lk->laddr_lo = cpu_to_le32(page->lbase);
|
||||||
|
|
||||||
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
||||||
|
lk->laddr_hi = cpu_to_le32(page->lbase >> 32);
|
||||||
|
#else
|
||||||
|
lk->laddr_hi = 0;
|
||||||
|
#endif
|
||||||
|
/* next msgs will be put from the begininng of this new page */
|
||||||
|
page->offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*room_left = priv->tx_pages_free * page->size;
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&priv->tx_lock, flags);
|
||||||
|
|
||||||
|
msg = page->vbase + page->offset;
|
||||||
|
|
||||||
|
/* give back room left in the tx ring */
|
||||||
|
*room_left += page->size - (page->offset + msg_size);
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pciefd_write_tx_msg(struct peak_canfd_priv *ucan,
|
||||||
|
struct pucan_tx_msg *msg)
|
||||||
|
{
|
||||||
|
struct pciefd_can *priv = (struct pciefd_can *)ucan;
|
||||||
|
struct pciefd_page *page = priv->tx_pages + priv->tx_page_index;
|
||||||
|
|
||||||
|
/* this slot is now reserved for writing the frame */
|
||||||
|
page->offset += le16_to_cpu(msg->size);
|
||||||
|
|
||||||
|
/* tell the board a frame has been written in Tx DMA area */
|
||||||
|
pciefd_can_writereg(priv, 1, PCIEFD_REG_CAN_TX_REQ_ACC);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* probe for CAN-FD channel #pciefd_board->can_count */
|
||||||
|
static int pciefd_can_probe(struct pciefd_board *pciefd)
|
||||||
|
{
|
||||||
|
struct net_device *ndev;
|
||||||
|
struct pciefd_can *priv;
|
||||||
|
u32 clk;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* allocate the candev object with default isize of echo skbs ring */
|
||||||
|
ndev = alloc_peak_canfd_dev(sizeof(*priv), pciefd->can_count,
|
||||||
|
PCIEFD_ECHO_SKB_MAX);
|
||||||
|
if (!ndev) {
|
||||||
|
dev_err(&pciefd->pci_dev->dev,
|
||||||
|
"failed to alloc candev object\n");
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv = netdev_priv(ndev);
|
||||||
|
|
||||||
|
/* fill-in candev private object: */
|
||||||
|
|
||||||
|
/* setup PCIe-FD own callbacks */
|
||||||
|
priv->ucan.pre_cmd = pciefd_pre_cmd;
|
||||||
|
priv->ucan.write_cmd = pciefd_write_cmd;
|
||||||
|
priv->ucan.post_cmd = pciefd_post_cmd;
|
||||||
|
priv->ucan.enable_tx_path = pciefd_enable_tx_path;
|
||||||
|
priv->ucan.alloc_tx_msg = pciefd_alloc_tx_msg;
|
||||||
|
priv->ucan.write_tx_msg = pciefd_write_tx_msg;
|
||||||
|
|
||||||
|
/* setup PCIe-FD own command buffer */
|
||||||
|
priv->ucan.cmd_buffer = &priv->pucan_cmd;
|
||||||
|
priv->ucan.cmd_maxlen = sizeof(priv->pucan_cmd);
|
||||||
|
|
||||||
|
priv->board = pciefd;
|
||||||
|
|
||||||
|
/* CAN config regs block address */
|
||||||
|
priv->reg_base = pciefd->reg_base + PCIEFD_CANX_OFF(priv->ucan.index);
|
||||||
|
|
||||||
|
/* allocate non-cacheable DMA'able 4KB memory area for Rx */
|
||||||
|
priv->rx_dma_vaddr = dmam_alloc_coherent(&pciefd->pci_dev->dev,
|
||||||
|
PCIEFD_RX_DMA_SIZE,
|
||||||
|
&priv->rx_dma_laddr,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!priv->rx_dma_vaddr) {
|
||||||
|
dev_err(&pciefd->pci_dev->dev,
|
||||||
|
"Rx dmam_alloc_coherent(%u) failure\n",
|
||||||
|
PCIEFD_RX_DMA_SIZE);
|
||||||
|
goto err_free_candev;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* allocate non-cacheable DMA'able 4KB memory area for Tx */
|
||||||
|
priv->tx_dma_vaddr = dmam_alloc_coherent(&pciefd->pci_dev->dev,
|
||||||
|
PCIEFD_TX_DMA_SIZE,
|
||||||
|
&priv->tx_dma_laddr,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!priv->tx_dma_vaddr) {
|
||||||
|
dev_err(&pciefd->pci_dev->dev,
|
||||||
|
"Tx dmaim_alloc_coherent(%u) failure\n",
|
||||||
|
PCIEFD_TX_DMA_SIZE);
|
||||||
|
goto err_free_candev;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CAN clock in RST mode */
|
||||||
|
pciefd_can_writereg(priv, CANFD_MISC_TS_RST, PCIEFD_REG_CAN_MISC);
|
||||||
|
|
||||||
|
/* read current clock value */
|
||||||
|
clk = pciefd_can_readreg(priv, PCIEFD_REG_CAN_CLK_SEL);
|
||||||
|
switch (clk) {
|
||||||
|
case CANFD_CLK_SEL_20MHZ:
|
||||||
|
priv->ucan.can.clock.freq = 20 * 1000 * 1000;
|
||||||
|
break;
|
||||||
|
case CANFD_CLK_SEL_24MHZ:
|
||||||
|
priv->ucan.can.clock.freq = 24 * 1000 * 1000;
|
||||||
|
break;
|
||||||
|
case CANFD_CLK_SEL_30MHZ:
|
||||||
|
priv->ucan.can.clock.freq = 30 * 1000 * 1000;
|
||||||
|
break;
|
||||||
|
case CANFD_CLK_SEL_40MHZ:
|
||||||
|
priv->ucan.can.clock.freq = 40 * 1000 * 1000;
|
||||||
|
break;
|
||||||
|
case CANFD_CLK_SEL_60MHZ:
|
||||||
|
priv->ucan.can.clock.freq = 60 * 1000 * 1000;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pciefd_can_writereg(priv, CANFD_CLK_SEL_80MHZ,
|
||||||
|
PCIEFD_REG_CAN_CLK_SEL);
|
||||||
|
|
||||||
|
/* fallthough */
|
||||||
|
case CANFD_CLK_SEL_80MHZ:
|
||||||
|
priv->ucan.can.clock.freq = 80 * 1000 * 1000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ndev->irq = pciefd->pci_dev->irq;
|
||||||
|
|
||||||
|
SET_NETDEV_DEV(ndev, &pciefd->pci_dev->dev);
|
||||||
|
|
||||||
|
err = register_candev(ndev);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&pciefd->pci_dev->dev,
|
||||||
|
"couldn't register CAN device: %d\n", err);
|
||||||
|
goto err_free_candev;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock_init(&priv->tx_lock);
|
||||||
|
|
||||||
|
/* save the object address in the board structure */
|
||||||
|
pciefd->can[pciefd->can_count] = priv;
|
||||||
|
|
||||||
|
dev_info(&pciefd->pci_dev->dev, "%s at reg_base=0x%p irq=%d\n",
|
||||||
|
ndev->name, priv->reg_base, pciefd->pci_dev->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_free_candev:
|
||||||
|
free_candev(ndev);
|
||||||
|
|
||||||
|
failure:
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove a CAN-FD channel by releasing all of its resources */
|
||||||
|
static void pciefd_can_remove(struct pciefd_can *priv)
|
||||||
|
{
|
||||||
|
/* unregister (close) the can device to go back to RST mode first */
|
||||||
|
unregister_candev(priv->ucan.ndev);
|
||||||
|
|
||||||
|
/* finally, free the candev object */
|
||||||
|
free_candev(priv->ucan.ndev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove all CAN-FD channels by releasing their own resources */
|
||||||
|
static void pciefd_can_remove_all(struct pciefd_board *pciefd)
|
||||||
|
{
|
||||||
|
while (pciefd->can_count > 0)
|
||||||
|
pciefd_can_remove(pciefd->can[--pciefd->can_count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* probe for the entire device */
|
||||||
|
static int peak_pciefd_probe(struct pci_dev *pdev,
|
||||||
|
const struct pci_device_id *ent)
|
||||||
|
{
|
||||||
|
struct pciefd_board *pciefd;
|
||||||
|
int err, can_count;
|
||||||
|
u16 sub_sys_id;
|
||||||
|
u8 hw_ver_major;
|
||||||
|
u8 hw_ver_minor;
|
||||||
|
u8 hw_ver_sub;
|
||||||
|
u32 v2;
|
||||||
|
|
||||||
|
err = pci_enable_device(pdev);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
err = pci_request_regions(pdev, PCIEFD_DRV_NAME);
|
||||||
|
if (err)
|
||||||
|
goto err_disable_pci;
|
||||||
|
|
||||||
|
/* the number of channels depends on sub-system id */
|
||||||
|
err = pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &sub_sys_id);
|
||||||
|
if (err)
|
||||||
|
goto err_release_regions;
|
||||||
|
|
||||||
|
dev_dbg(&pdev->dev, "probing device %04x:%04x:%04x\n",
|
||||||
|
pdev->vendor, pdev->device, sub_sys_id);
|
||||||
|
|
||||||
|
if (sub_sys_id >= 0x0012)
|
||||||
|
can_count = 4;
|
||||||
|
else if (sub_sys_id >= 0x0010)
|
||||||
|
can_count = 3;
|
||||||
|
else if (sub_sys_id >= 0x0004)
|
||||||
|
can_count = 2;
|
||||||
|
else
|
||||||
|
can_count = 1;
|
||||||
|
|
||||||
|
/* allocate board structure object */
|
||||||
|
pciefd = devm_kzalloc(&pdev->dev, sizeof(*pciefd) +
|
||||||
|
can_count * sizeof(*pciefd->can),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!pciefd) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto err_release_regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initialize the board structure */
|
||||||
|
pciefd->pci_dev = pdev;
|
||||||
|
spin_lock_init(&pciefd->cmd_lock);
|
||||||
|
|
||||||
|
/* save the PCI BAR0 virtual address for further system regs access */
|
||||||
|
pciefd->reg_base = pci_iomap(pdev, 0, PCIEFD_BAR0_SIZE);
|
||||||
|
if (!pciefd->reg_base) {
|
||||||
|
dev_err(&pdev->dev, "failed to map PCI resource #0\n");
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto err_release_regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read the firmware version number */
|
||||||
|
v2 = pciefd_sys_readreg(pciefd, PCIEFD_REG_SYS_VER2);
|
||||||
|
|
||||||
|
hw_ver_major = (v2 & 0x0000f000) >> 12;
|
||||||
|
hw_ver_minor = (v2 & 0x00000f00) >> 8;
|
||||||
|
hw_ver_sub = (v2 & 0x000000f0) >> 4;
|
||||||
|
|
||||||
|
dev_info(&pdev->dev,
|
||||||
|
"%ux CAN-FD PCAN-PCIe FPGA v%u.%u.%u:\n", can_count,
|
||||||
|
hw_ver_major, hw_ver_minor, hw_ver_sub);
|
||||||
|
|
||||||
|
/* stop system clock */
|
||||||
|
pciefd_sys_writereg(pciefd, PCIEFD_SYS_CTL_CLK_EN,
|
||||||
|
PCIEFD_REG_SYS_CTL_CLR);
|
||||||
|
|
||||||
|
pci_set_master(pdev);
|
||||||
|
|
||||||
|
/* create now the corresponding channels objects */
|
||||||
|
while (pciefd->can_count < can_count) {
|
||||||
|
err = pciefd_can_probe(pciefd);
|
||||||
|
if (err)
|
||||||
|
goto err_free_canfd;
|
||||||
|
|
||||||
|
pciefd->can_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set system timestamps counter in RST mode */
|
||||||
|
pciefd_sys_writereg(pciefd, PCIEFD_SYS_CTL_TS_RST,
|
||||||
|
PCIEFD_REG_SYS_CTL_SET);
|
||||||
|
|
||||||
|
/* wait a bit (read cycle) */
|
||||||
|
(void)pciefd_sys_readreg(pciefd, PCIEFD_REG_SYS_VER1);
|
||||||
|
|
||||||
|
/* free all clocks */
|
||||||
|
pciefd_sys_writereg(pciefd, PCIEFD_SYS_CTL_TS_RST,
|
||||||
|
PCIEFD_REG_SYS_CTL_CLR);
|
||||||
|
|
||||||
|
/* start system clock */
|
||||||
|
pciefd_sys_writereg(pciefd, PCIEFD_SYS_CTL_CLK_EN,
|
||||||
|
PCIEFD_REG_SYS_CTL_SET);
|
||||||
|
|
||||||
|
/* remember the board structure address in the device user data */
|
||||||
|
pci_set_drvdata(pdev, pciefd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_free_canfd:
|
||||||
|
pciefd_can_remove_all(pciefd);
|
||||||
|
|
||||||
|
pci_iounmap(pdev, pciefd->reg_base);
|
||||||
|
|
||||||
|
err_release_regions:
|
||||||
|
pci_release_regions(pdev);
|
||||||
|
|
||||||
|
err_disable_pci:
|
||||||
|
pci_disable_device(pdev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* free the board structure object, as well as its resources: */
|
||||||
|
static void peak_pciefd_remove(struct pci_dev *pdev)
|
||||||
|
{
|
||||||
|
struct pciefd_board *pciefd = pci_get_drvdata(pdev);
|
||||||
|
|
||||||
|
/* release CAN-FD channels resources */
|
||||||
|
pciefd_can_remove_all(pciefd);
|
||||||
|
|
||||||
|
pci_iounmap(pdev, pciefd->reg_base);
|
||||||
|
|
||||||
|
pci_release_regions(pdev);
|
||||||
|
pci_disable_device(pdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct pci_driver peak_pciefd_driver = {
|
||||||
|
.name = PCIEFD_DRV_NAME,
|
||||||
|
.id_table = peak_pciefd_tbl,
|
||||||
|
.probe = peak_pciefd_probe,
|
||||||
|
.remove = peak_pciefd_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_pci_driver(peak_pciefd_driver);
|
@ -898,9 +898,9 @@ static int ti_hecc_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
priv->base = devm_ioremap_resource(&pdev->dev, res);
|
priv->base = devm_ioremap_resource(&pdev->dev, res);
|
||||||
if (!priv->base) {
|
if (IS_ERR(priv->base)) {
|
||||||
dev_err(&pdev->dev, "hecc ioremap failed\n");
|
dev_err(&pdev->dev, "hecc ioremap failed\n");
|
||||||
return -ENOMEM;
|
return PTR_ERR(priv->base);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handle hecc-ram memory */
|
/* handle hecc-ram memory */
|
||||||
@ -911,9 +911,9 @@ static int ti_hecc_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
priv->hecc_ram = devm_ioremap_resource(&pdev->dev, res);
|
priv->hecc_ram = devm_ioremap_resource(&pdev->dev, res);
|
||||||
if (!priv->hecc_ram) {
|
if (IS_ERR(priv->hecc_ram)) {
|
||||||
dev_err(&pdev->dev, "hecc-ram ioremap failed\n");
|
dev_err(&pdev->dev, "hecc-ram ioremap failed\n");
|
||||||
return -ENOMEM;
|
return PTR_ERR(priv->hecc_ram);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handle mbx memory */
|
/* handle mbx memory */
|
||||||
@ -924,9 +924,9 @@ static int ti_hecc_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
priv->mbx = devm_ioremap_resource(&pdev->dev, res);
|
priv->mbx = devm_ioremap_resource(&pdev->dev, res);
|
||||||
if (!priv->mbx) {
|
if (IS_ERR(priv->mbx)) {
|
||||||
dev_err(&pdev->dev, "mbx ioremap failed\n");
|
dev_err(&pdev->dev, "mbx ioremap failed\n");
|
||||||
return -ENOMEM;
|
return PTR_ERR(priv->mbx);
|
||||||
}
|
}
|
||||||
|
|
||||||
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||||
|
@ -81,4 +81,10 @@ config CAN_8DEV_USB
|
|||||||
This driver supports the USB2CAN interface
|
This driver supports the USB2CAN interface
|
||||||
from 8 devices (http://www.8devices.com).
|
from 8 devices (http://www.8devices.com).
|
||||||
|
|
||||||
|
config CAN_MCBA_USB
|
||||||
|
tristate "Microchip CAN BUS Analyzer interface"
|
||||||
|
---help---
|
||||||
|
This driver supports the CAN BUS Analyzer interface
|
||||||
|
from Microchip (http://www.microchip.com/development-tools/).
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
@ -8,3 +8,4 @@ obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
|
|||||||
obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb.o
|
obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb.o
|
||||||
obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
|
obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
|
||||||
obj-$(CONFIG_CAN_8DEV_USB) += usb_8dev.o
|
obj-$(CONFIG_CAN_8DEV_USB) += usb_8dev.o
|
||||||
|
obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
|
||||||
|
904
drivers/net/can/usb/mcba_usb.c
Normal file
904
drivers/net/can/usb/mcba_usb.c
Normal file
@ -0,0 +1,904 @@
|
|||||||
|
/* SocketCAN driver for Microchip CAN BUS Analyzer Tool
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Mobica Limited
|
||||||
|
*
|
||||||
|
* 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.6.2 version of net/can/usb/usb_8dev.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <asm/unaligned.h>
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/dev.h>
|
||||||
|
#include <linux/can/error.h>
|
||||||
|
#include <linux/can/led.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/netdevice.h>
|
||||||
|
#include <linux/signal.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/usb.h>
|
||||||
|
|
||||||
|
/* vendor and product id */
|
||||||
|
#define MCBA_MODULE_NAME "mcba_usb"
|
||||||
|
#define MCBA_VENDOR_ID 0x04d8
|
||||||
|
#define MCBA_PRODUCT_ID 0x0a30
|
||||||
|
|
||||||
|
/* driver constants */
|
||||||
|
#define MCBA_MAX_RX_URBS 20
|
||||||
|
#define MCBA_MAX_TX_URBS 20
|
||||||
|
#define MCBA_CTX_FREE MCBA_MAX_TX_URBS
|
||||||
|
|
||||||
|
/* RX buffer must be bigger than msg size since at the
|
||||||
|
* beggining USB messages are stacked.
|
||||||
|
*/
|
||||||
|
#define MCBA_USB_RX_BUFF_SIZE 64
|
||||||
|
#define MCBA_USB_TX_BUFF_SIZE (sizeof(struct mcba_usb_msg))
|
||||||
|
|
||||||
|
/* MCBA endpoint numbers */
|
||||||
|
#define MCBA_USB_EP_IN 1
|
||||||
|
#define MCBA_USB_EP_OUT 1
|
||||||
|
|
||||||
|
/* Microchip command id */
|
||||||
|
#define MBCA_CMD_RECEIVE_MESSAGE 0xE3
|
||||||
|
#define MBCA_CMD_I_AM_ALIVE_FROM_CAN 0xF5
|
||||||
|
#define MBCA_CMD_I_AM_ALIVE_FROM_USB 0xF7
|
||||||
|
#define MBCA_CMD_CHANGE_BIT_RATE 0xA1
|
||||||
|
#define MBCA_CMD_TRANSMIT_MESSAGE_EV 0xA3
|
||||||
|
#define MBCA_CMD_SETUP_TERMINATION_RESISTANCE 0xA8
|
||||||
|
#define MBCA_CMD_READ_FW_VERSION 0xA9
|
||||||
|
#define MBCA_CMD_NOTHING_TO_SEND 0xFF
|
||||||
|
#define MBCA_CMD_TRANSMIT_MESSAGE_RSP 0xE2
|
||||||
|
|
||||||
|
#define MCBA_VER_REQ_USB 1
|
||||||
|
#define MCBA_VER_REQ_CAN 2
|
||||||
|
|
||||||
|
#define MCBA_SIDL_EXID_MASK 0x8
|
||||||
|
#define MCBA_DLC_MASK 0xf
|
||||||
|
#define MCBA_DLC_RTR_MASK 0x40
|
||||||
|
|
||||||
|
#define MCBA_CAN_STATE_WRN_TH 95
|
||||||
|
#define MCBA_CAN_STATE_ERR_PSV_TH 127
|
||||||
|
|
||||||
|
#define MCBA_TERMINATION_DISABLED CAN_TERMINATION_DISABLED
|
||||||
|
#define MCBA_TERMINATION_ENABLED 120
|
||||||
|
|
||||||
|
struct mcba_usb_ctx {
|
||||||
|
struct mcba_priv *priv;
|
||||||
|
u32 ndx;
|
||||||
|
u8 dlc;
|
||||||
|
bool can;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Structure to hold all of our device specific stuff */
|
||||||
|
struct mcba_priv {
|
||||||
|
struct can_priv can; /* must be the first member */
|
||||||
|
struct sk_buff *echo_skb[MCBA_MAX_TX_URBS];
|
||||||
|
struct mcba_usb_ctx tx_context[MCBA_MAX_TX_URBS];
|
||||||
|
struct usb_device *udev;
|
||||||
|
struct net_device *netdev;
|
||||||
|
struct usb_anchor tx_submitted;
|
||||||
|
struct usb_anchor rx_submitted;
|
||||||
|
struct can_berr_counter bec;
|
||||||
|
bool usb_ka_first_pass;
|
||||||
|
bool can_ka_first_pass;
|
||||||
|
bool can_speed_check;
|
||||||
|
atomic_t free_ctx_cnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* CAN frame */
|
||||||
|
struct __packed mcba_usb_msg_can {
|
||||||
|
u8 cmd_id;
|
||||||
|
__be16 eid;
|
||||||
|
__be16 sid;
|
||||||
|
u8 dlc;
|
||||||
|
u8 data[8];
|
||||||
|
u8 timestamp[4];
|
||||||
|
u8 checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* command frame */
|
||||||
|
struct __packed mcba_usb_msg {
|
||||||
|
u8 cmd_id;
|
||||||
|
u8 unused[18];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __packed mcba_usb_msg_ka_usb {
|
||||||
|
u8 cmd_id;
|
||||||
|
u8 termination_state;
|
||||||
|
u8 soft_ver_major;
|
||||||
|
u8 soft_ver_minor;
|
||||||
|
u8 unused[15];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __packed mcba_usb_msg_ka_can {
|
||||||
|
u8 cmd_id;
|
||||||
|
u8 tx_err_cnt;
|
||||||
|
u8 rx_err_cnt;
|
||||||
|
u8 rx_buff_ovfl;
|
||||||
|
u8 tx_bus_off;
|
||||||
|
__be16 can_bitrate;
|
||||||
|
__le16 rx_lost;
|
||||||
|
u8 can_stat;
|
||||||
|
u8 soft_ver_major;
|
||||||
|
u8 soft_ver_minor;
|
||||||
|
u8 debug_mode;
|
||||||
|
u8 test_complete;
|
||||||
|
u8 test_result;
|
||||||
|
u8 unused[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __packed mcba_usb_msg_change_bitrate {
|
||||||
|
u8 cmd_id;
|
||||||
|
__be16 bitrate;
|
||||||
|
u8 unused[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __packed mcba_usb_msg_termination {
|
||||||
|
u8 cmd_id;
|
||||||
|
u8 termination;
|
||||||
|
u8 unused[17];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __packed mcba_usb_msg_fw_ver {
|
||||||
|
u8 cmd_id;
|
||||||
|
u8 pic;
|
||||||
|
u8 unused[17];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct usb_device_id mcba_usb_table[] = {
|
||||||
|
{ USB_DEVICE(MCBA_VENDOR_ID, MCBA_PRODUCT_ID) },
|
||||||
|
{} /* Terminating entry */
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(usb, mcba_usb_table);
|
||||||
|
|
||||||
|
static const u16 mcba_termination[] = { MCBA_TERMINATION_DISABLED,
|
||||||
|
MCBA_TERMINATION_ENABLED };
|
||||||
|
|
||||||
|
static const u32 mcba_bitrate[] = { 20000, 33333, 50000, 80000, 83333,
|
||||||
|
100000, 125000, 150000, 175000, 200000,
|
||||||
|
225000, 250000, 275000, 300000, 500000,
|
||||||
|
625000, 800000, 1000000 };
|
||||||
|
|
||||||
|
static inline void mcba_init_ctx(struct mcba_priv *priv)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < MCBA_MAX_TX_URBS; i++) {
|
||||||
|
priv->tx_context[i].ndx = MCBA_CTX_FREE;
|
||||||
|
priv->tx_context[i].priv = priv;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_set(&priv->free_ctx_cnt, ARRAY_SIZE(priv->tx_context));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct mcba_usb_ctx *mcba_usb_get_free_ctx(struct mcba_priv *priv,
|
||||||
|
struct can_frame *cf)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
struct mcba_usb_ctx *ctx = NULL;
|
||||||
|
|
||||||
|
for (i = 0; i < MCBA_MAX_TX_URBS; i++) {
|
||||||
|
if (priv->tx_context[i].ndx == MCBA_CTX_FREE) {
|
||||||
|
ctx = &priv->tx_context[i];
|
||||||
|
ctx->ndx = i;
|
||||||
|
|
||||||
|
if (cf) {
|
||||||
|
ctx->can = true;
|
||||||
|
ctx->dlc = cf->can_dlc;
|
||||||
|
} else {
|
||||||
|
ctx->can = false;
|
||||||
|
ctx->dlc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_dec(&priv->free_ctx_cnt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!atomic_read(&priv->free_ctx_cnt))
|
||||||
|
/* That was the last free ctx. Slow down tx path */
|
||||||
|
netif_stop_queue(priv->netdev);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mcba_usb_free_ctx and mcba_usb_get_free_ctx are executed by different
|
||||||
|
* threads. The order of execution in below function is important.
|
||||||
|
*/
|
||||||
|
static inline void mcba_usb_free_ctx(struct mcba_usb_ctx *ctx)
|
||||||
|
{
|
||||||
|
/* Increase number of free ctxs before freeing ctx */
|
||||||
|
atomic_inc(&ctx->priv->free_ctx_cnt);
|
||||||
|
|
||||||
|
ctx->ndx = MCBA_CTX_FREE;
|
||||||
|
|
||||||
|
/* Wake up the queue once ctx is marked free */
|
||||||
|
netif_wake_queue(ctx->priv->netdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_usb_write_bulk_callback(struct urb *urb)
|
||||||
|
{
|
||||||
|
struct mcba_usb_ctx *ctx = urb->context;
|
||||||
|
struct net_device *netdev;
|
||||||
|
|
||||||
|
WARN_ON(!ctx);
|
||||||
|
|
||||||
|
netdev = ctx->priv->netdev;
|
||||||
|
|
||||||
|
/* free up our allocated buffer */
|
||||||
|
usb_free_coherent(urb->dev, urb->transfer_buffer_length,
|
||||||
|
urb->transfer_buffer, urb->transfer_dma);
|
||||||
|
|
||||||
|
if (ctx->can) {
|
||||||
|
if (!netif_device_present(netdev))
|
||||||
|
return;
|
||||||
|
|
||||||
|
netdev->stats.tx_packets++;
|
||||||
|
netdev->stats.tx_bytes += ctx->dlc;
|
||||||
|
|
||||||
|
can_led_event(netdev, CAN_LED_EVENT_TX);
|
||||||
|
can_get_echo_skb(netdev, ctx->ndx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urb->status)
|
||||||
|
netdev_info(netdev, "Tx URB aborted (%d)\n", urb->status);
|
||||||
|
|
||||||
|
/* Release the context */
|
||||||
|
mcba_usb_free_ctx(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send data to device */
|
||||||
|
static netdev_tx_t mcba_usb_xmit(struct mcba_priv *priv,
|
||||||
|
struct mcba_usb_msg *usb_msg,
|
||||||
|
struct mcba_usb_ctx *ctx)
|
||||||
|
{
|
||||||
|
struct urb *urb;
|
||||||
|
u8 *buf;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* create a URB, and a buffer for it, and copy the data to the URB */
|
||||||
|
urb = usb_alloc_urb(0, GFP_ATOMIC);
|
||||||
|
if (!urb)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
buf = usb_alloc_coherent(priv->udev, MCBA_USB_TX_BUFF_SIZE, GFP_ATOMIC,
|
||||||
|
&urb->transfer_dma);
|
||||||
|
if (!buf) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto nomembuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buf, usb_msg, MCBA_USB_TX_BUFF_SIZE);
|
||||||
|
|
||||||
|
usb_fill_bulk_urb(urb, priv->udev,
|
||||||
|
usb_sndbulkpipe(priv->udev, MCBA_USB_EP_OUT), buf,
|
||||||
|
MCBA_USB_TX_BUFF_SIZE, mcba_usb_write_bulk_callback,
|
||||||
|
ctx);
|
||||||
|
|
||||||
|
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||||
|
usb_anchor_urb(urb, &priv->tx_submitted);
|
||||||
|
|
||||||
|
err = usb_submit_urb(urb, GFP_ATOMIC);
|
||||||
|
if (unlikely(err))
|
||||||
|
goto failed;
|
||||||
|
|
||||||
|
/* Release our reference to this URB, the USB core will eventually free
|
||||||
|
* it entirely.
|
||||||
|
*/
|
||||||
|
usb_free_urb(urb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
usb_unanchor_urb(urb);
|
||||||
|
usb_free_coherent(priv->udev, MCBA_USB_TX_BUFF_SIZE, buf,
|
||||||
|
urb->transfer_dma);
|
||||||
|
|
||||||
|
if (err == -ENODEV)
|
||||||
|
netif_device_detach(priv->netdev);
|
||||||
|
else
|
||||||
|
netdev_warn(priv->netdev, "failed tx_urb %d\n", err);
|
||||||
|
|
||||||
|
nomembuf:
|
||||||
|
usb_free_urb(urb);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send data to device */
|
||||||
|
static netdev_tx_t mcba_usb_start_xmit(struct sk_buff *skb,
|
||||||
|
struct net_device *netdev)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = netdev_priv(netdev);
|
||||||
|
struct can_frame *cf = (struct can_frame *)skb->data;
|
||||||
|
struct mcba_usb_ctx *ctx = NULL;
|
||||||
|
struct net_device_stats *stats = &priv->netdev->stats;
|
||||||
|
u16 sid;
|
||||||
|
int err;
|
||||||
|
struct mcba_usb_msg_can usb_msg = {
|
||||||
|
.cmd_id = MBCA_CMD_TRANSMIT_MESSAGE_EV
|
||||||
|
};
|
||||||
|
|
||||||
|
if (can_dropped_invalid_skb(netdev, skb))
|
||||||
|
return NETDEV_TX_OK;
|
||||||
|
|
||||||
|
ctx = mcba_usb_get_free_ctx(priv, cf);
|
||||||
|
if (!ctx)
|
||||||
|
return NETDEV_TX_BUSY;
|
||||||
|
|
||||||
|
can_put_echo_skb(skb, priv->netdev, ctx->ndx);
|
||||||
|
|
||||||
|
if (cf->can_id & CAN_EFF_FLAG) {
|
||||||
|
/* SIDH | SIDL | EIDH | EIDL
|
||||||
|
* 28 - 21 | 20 19 18 x x x 17 16 | 15 - 8 | 7 - 0
|
||||||
|
*/
|
||||||
|
sid = MCBA_SIDL_EXID_MASK;
|
||||||
|
/* store 28-18 bits */
|
||||||
|
sid |= (cf->can_id & 0x1ffc0000) >> 13;
|
||||||
|
/* store 17-16 bits */
|
||||||
|
sid |= (cf->can_id & 0x30000) >> 16;
|
||||||
|
put_unaligned_be16(sid, &usb_msg.sid);
|
||||||
|
|
||||||
|
/* store 15-0 bits */
|
||||||
|
put_unaligned_be16(cf->can_id & 0xffff, &usb_msg.eid);
|
||||||
|
} else {
|
||||||
|
/* SIDH | SIDL
|
||||||
|
* 10 - 3 | 2 1 0 x x x x x
|
||||||
|
*/
|
||||||
|
put_unaligned_be16((cf->can_id & CAN_SFF_MASK) << 5,
|
||||||
|
&usb_msg.sid);
|
||||||
|
usb_msg.eid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_msg.dlc = cf->can_dlc;
|
||||||
|
|
||||||
|
memcpy(usb_msg.data, cf->data, usb_msg.dlc);
|
||||||
|
|
||||||
|
if (cf->can_id & CAN_RTR_FLAG)
|
||||||
|
usb_msg.dlc |= MCBA_DLC_RTR_MASK;
|
||||||
|
|
||||||
|
err = mcba_usb_xmit(priv, (struct mcba_usb_msg *)&usb_msg, ctx);
|
||||||
|
if (err)
|
||||||
|
goto xmit_failed;
|
||||||
|
|
||||||
|
return NETDEV_TX_OK;
|
||||||
|
|
||||||
|
xmit_failed:
|
||||||
|
can_free_echo_skb(priv->netdev, ctx->ndx);
|
||||||
|
mcba_usb_free_ctx(ctx);
|
||||||
|
dev_kfree_skb(skb);
|
||||||
|
stats->tx_dropped++;
|
||||||
|
|
||||||
|
return NETDEV_TX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send cmd to device */
|
||||||
|
static void mcba_usb_xmit_cmd(struct mcba_priv *priv,
|
||||||
|
struct mcba_usb_msg *usb_msg)
|
||||||
|
{
|
||||||
|
struct mcba_usb_ctx *ctx = NULL;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
ctx = mcba_usb_get_free_ctx(priv, NULL);
|
||||||
|
if (!ctx) {
|
||||||
|
netdev_err(priv->netdev,
|
||||||
|
"Lack of free ctx. Sending (%d) cmd aborted",
|
||||||
|
usb_msg->cmd_id);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mcba_usb_xmit(priv, usb_msg, ctx);
|
||||||
|
if (err)
|
||||||
|
netdev_err(priv->netdev, "Failed to send cmd (%d)",
|
||||||
|
usb_msg->cmd_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_usb_xmit_change_bitrate(struct mcba_priv *priv, u16 bitrate)
|
||||||
|
{
|
||||||
|
struct mcba_usb_msg_change_bitrate usb_msg = {
|
||||||
|
.cmd_id = MBCA_CMD_CHANGE_BIT_RATE
|
||||||
|
};
|
||||||
|
|
||||||
|
put_unaligned_be16(bitrate, &usb_msg.bitrate);
|
||||||
|
|
||||||
|
mcba_usb_xmit_cmd(priv, (struct mcba_usb_msg *)&usb_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_usb_xmit_read_fw_ver(struct mcba_priv *priv, u8 pic)
|
||||||
|
{
|
||||||
|
struct mcba_usb_msg_fw_ver usb_msg = {
|
||||||
|
.cmd_id = MBCA_CMD_READ_FW_VERSION,
|
||||||
|
.pic = pic
|
||||||
|
};
|
||||||
|
|
||||||
|
mcba_usb_xmit_cmd(priv, (struct mcba_usb_msg *)&usb_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_usb_process_can(struct mcba_priv *priv,
|
||||||
|
struct mcba_usb_msg_can *msg)
|
||||||
|
{
|
||||||
|
struct can_frame *cf;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
struct net_device_stats *stats = &priv->netdev->stats;
|
||||||
|
u16 sid;
|
||||||
|
|
||||||
|
skb = alloc_can_skb(priv->netdev, &cf);
|
||||||
|
if (!skb)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sid = get_unaligned_be16(&msg->sid);
|
||||||
|
|
||||||
|
if (sid & MCBA_SIDL_EXID_MASK) {
|
||||||
|
/* SIDH | SIDL | EIDH | EIDL
|
||||||
|
* 28 - 21 | 20 19 18 x x x 17 16 | 15 - 8 | 7 - 0
|
||||||
|
*/
|
||||||
|
cf->can_id = CAN_EFF_FLAG;
|
||||||
|
|
||||||
|
/* store 28-18 bits */
|
||||||
|
cf->can_id |= (sid & 0xffe0) << 13;
|
||||||
|
/* store 17-16 bits */
|
||||||
|
cf->can_id |= (sid & 3) << 16;
|
||||||
|
/* store 15-0 bits */
|
||||||
|
cf->can_id |= get_unaligned_be16(&msg->eid);
|
||||||
|
} else {
|
||||||
|
/* SIDH | SIDL
|
||||||
|
* 10 - 3 | 2 1 0 x x x x x
|
||||||
|
*/
|
||||||
|
cf->can_id = (sid & 0xffe0) >> 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg->dlc & MCBA_DLC_RTR_MASK)
|
||||||
|
cf->can_id |= CAN_RTR_FLAG;
|
||||||
|
|
||||||
|
cf->can_dlc = get_can_dlc(msg->dlc & MCBA_DLC_MASK);
|
||||||
|
|
||||||
|
memcpy(cf->data, msg->data, cf->can_dlc);
|
||||||
|
|
||||||
|
stats->rx_packets++;
|
||||||
|
stats->rx_bytes += cf->can_dlc;
|
||||||
|
|
||||||
|
can_led_event(priv->netdev, CAN_LED_EVENT_RX);
|
||||||
|
netif_rx(skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_usb_process_ka_usb(struct mcba_priv *priv,
|
||||||
|
struct mcba_usb_msg_ka_usb *msg)
|
||||||
|
{
|
||||||
|
if (unlikely(priv->usb_ka_first_pass)) {
|
||||||
|
netdev_info(priv->netdev, "PIC USB version %hhu.%hhu\n",
|
||||||
|
msg->soft_ver_major, msg->soft_ver_minor);
|
||||||
|
|
||||||
|
priv->usb_ka_first_pass = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg->termination_state)
|
||||||
|
priv->can.termination = MCBA_TERMINATION_ENABLED;
|
||||||
|
else
|
||||||
|
priv->can.termination = MCBA_TERMINATION_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 convert_can2host_bitrate(struct mcba_usb_msg_ka_can *msg)
|
||||||
|
{
|
||||||
|
const u32 bitrate = get_unaligned_be16(&msg->can_bitrate);
|
||||||
|
|
||||||
|
if ((bitrate == 33) || (bitrate == 83))
|
||||||
|
return bitrate * 1000 + 333;
|
||||||
|
else
|
||||||
|
return bitrate * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_usb_process_ka_can(struct mcba_priv *priv,
|
||||||
|
struct mcba_usb_msg_ka_can *msg)
|
||||||
|
{
|
||||||
|
if (unlikely(priv->can_ka_first_pass)) {
|
||||||
|
netdev_info(priv->netdev, "PIC CAN version %hhu.%hhu\n",
|
||||||
|
msg->soft_ver_major, msg->soft_ver_minor);
|
||||||
|
|
||||||
|
priv->can_ka_first_pass = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(priv->can_speed_check)) {
|
||||||
|
const u32 bitrate = convert_can2host_bitrate(msg);
|
||||||
|
|
||||||
|
priv->can_speed_check = false;
|
||||||
|
|
||||||
|
if (bitrate != priv->can.bittiming.bitrate)
|
||||||
|
netdev_err(
|
||||||
|
priv->netdev,
|
||||||
|
"Wrong bitrate reported by the device (%u). Expected %u",
|
||||||
|
bitrate, priv->can.bittiming.bitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->bec.txerr = msg->tx_err_cnt;
|
||||||
|
priv->bec.rxerr = msg->rx_err_cnt;
|
||||||
|
|
||||||
|
if (msg->tx_bus_off)
|
||||||
|
priv->can.state = CAN_STATE_BUS_OFF;
|
||||||
|
|
||||||
|
else if ((priv->bec.txerr > MCBA_CAN_STATE_ERR_PSV_TH) ||
|
||||||
|
(priv->bec.rxerr > MCBA_CAN_STATE_ERR_PSV_TH))
|
||||||
|
priv->can.state = CAN_STATE_ERROR_PASSIVE;
|
||||||
|
|
||||||
|
else if ((priv->bec.txerr > MCBA_CAN_STATE_WRN_TH) ||
|
||||||
|
(priv->bec.rxerr > MCBA_CAN_STATE_WRN_TH))
|
||||||
|
priv->can.state = CAN_STATE_ERROR_WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_usb_process_rx(struct mcba_priv *priv,
|
||||||
|
struct mcba_usb_msg *msg)
|
||||||
|
{
|
||||||
|
switch (msg->cmd_id) {
|
||||||
|
case MBCA_CMD_I_AM_ALIVE_FROM_CAN:
|
||||||
|
mcba_usb_process_ka_can(priv,
|
||||||
|
(struct mcba_usb_msg_ka_can *)msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBCA_CMD_I_AM_ALIVE_FROM_USB:
|
||||||
|
mcba_usb_process_ka_usb(priv,
|
||||||
|
(struct mcba_usb_msg_ka_usb *)msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBCA_CMD_RECEIVE_MESSAGE:
|
||||||
|
mcba_usb_process_can(priv, (struct mcba_usb_msg_can *)msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBCA_CMD_NOTHING_TO_SEND:
|
||||||
|
/* Side effect of communication between PIC_USB and PIC_CAN.
|
||||||
|
* PIC_CAN is telling us that it has nothing to send
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MBCA_CMD_TRANSMIT_MESSAGE_RSP:
|
||||||
|
/* Transmission response from the device containing timestamp */
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
netdev_warn(priv->netdev, "Unsupported msg (0x%hhX)",
|
||||||
|
msg->cmd_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callback for reading data from device
|
||||||
|
*
|
||||||
|
* Check urb status, call read function and resubmit urb read operation.
|
||||||
|
*/
|
||||||
|
static void mcba_usb_read_bulk_callback(struct urb *urb)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = urb->context;
|
||||||
|
struct net_device *netdev;
|
||||||
|
int retval;
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
netdev = priv->netdev;
|
||||||
|
|
||||||
|
if (!netif_device_present(netdev))
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (urb->status) {
|
||||||
|
case 0: /* success */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case -ENOENT:
|
||||||
|
case -ESHUTDOWN:
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
netdev_info(netdev, "Rx URB aborted (%d)\n", urb->status);
|
||||||
|
|
||||||
|
goto resubmit_urb;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pos < urb->actual_length) {
|
||||||
|
struct mcba_usb_msg *msg;
|
||||||
|
|
||||||
|
if (pos + sizeof(struct mcba_usb_msg) > urb->actual_length) {
|
||||||
|
netdev_err(priv->netdev, "format error\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = (struct mcba_usb_msg *)(urb->transfer_buffer + pos);
|
||||||
|
mcba_usb_process_rx(priv, msg);
|
||||||
|
|
||||||
|
pos += sizeof(struct mcba_usb_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
resubmit_urb:
|
||||||
|
|
||||||
|
usb_fill_bulk_urb(urb, priv->udev,
|
||||||
|
usb_rcvbulkpipe(priv->udev, MCBA_USB_EP_OUT),
|
||||||
|
urb->transfer_buffer, MCBA_USB_RX_BUFF_SIZE,
|
||||||
|
mcba_usb_read_bulk_callback, priv);
|
||||||
|
|
||||||
|
retval = usb_submit_urb(urb, GFP_ATOMIC);
|
||||||
|
|
||||||
|
if (retval == -ENODEV)
|
||||||
|
netif_device_detach(netdev);
|
||||||
|
else if (retval)
|
||||||
|
netdev_err(netdev, "failed resubmitting read bulk urb: %d\n",
|
||||||
|
retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start USB device */
|
||||||
|
static int mcba_usb_start(struct mcba_priv *priv)
|
||||||
|
{
|
||||||
|
struct net_device *netdev = priv->netdev;
|
||||||
|
int err, i;
|
||||||
|
|
||||||
|
mcba_init_ctx(priv);
|
||||||
|
|
||||||
|
for (i = 0; i < MCBA_MAX_RX_URBS; i++) {
|
||||||
|
struct urb *urb = NULL;
|
||||||
|
u8 *buf;
|
||||||
|
|
||||||
|
/* create a URB, and a buffer for it */
|
||||||
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||||
|
if (!urb) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = usb_alloc_coherent(priv->udev, MCBA_USB_RX_BUFF_SIZE,
|
||||||
|
GFP_KERNEL, &urb->transfer_dma);
|
||||||
|
if (!buf) {
|
||||||
|
netdev_err(netdev, "No memory left for USB buffer\n");
|
||||||
|
usb_free_urb(urb);
|
||||||
|
err = -ENOMEM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_fill_bulk_urb(urb, priv->udev,
|
||||||
|
usb_rcvbulkpipe(priv->udev, MCBA_USB_EP_IN),
|
||||||
|
buf, MCBA_USB_RX_BUFF_SIZE,
|
||||||
|
mcba_usb_read_bulk_callback, priv);
|
||||||
|
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||||
|
usb_anchor_urb(urb, &priv->rx_submitted);
|
||||||
|
|
||||||
|
err = usb_submit_urb(urb, GFP_KERNEL);
|
||||||
|
if (err) {
|
||||||
|
usb_unanchor_urb(urb);
|
||||||
|
usb_free_coherent(priv->udev, MCBA_USB_RX_BUFF_SIZE,
|
||||||
|
buf, urb->transfer_dma);
|
||||||
|
usb_free_urb(urb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop reference, USB core will take care of freeing it */
|
||||||
|
usb_free_urb(urb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Did we submit any URBs */
|
||||||
|
if (i == 0) {
|
||||||
|
netdev_warn(netdev, "couldn't setup read URBs\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warn if we've couldn't transmit all the URBs */
|
||||||
|
if (i < MCBA_MAX_RX_URBS)
|
||||||
|
netdev_warn(netdev, "rx performance may be slow\n");
|
||||||
|
|
||||||
|
mcba_usb_xmit_read_fw_ver(priv, MCBA_VER_REQ_USB);
|
||||||
|
mcba_usb_xmit_read_fw_ver(priv, MCBA_VER_REQ_CAN);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open USB device */
|
||||||
|
static int mcba_usb_open(struct net_device *netdev)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = netdev_priv(netdev);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* common open */
|
||||||
|
err = open_candev(netdev);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
priv->can_speed_check = true;
|
||||||
|
priv->can.state = CAN_STATE_ERROR_ACTIVE;
|
||||||
|
|
||||||
|
can_led_event(netdev, CAN_LED_EVENT_OPEN);
|
||||||
|
netif_start_queue(netdev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcba_urb_unlink(struct mcba_priv *priv)
|
||||||
|
{
|
||||||
|
usb_kill_anchored_urbs(&priv->rx_submitted);
|
||||||
|
usb_kill_anchored_urbs(&priv->tx_submitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close USB device */
|
||||||
|
static int mcba_usb_close(struct net_device *netdev)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = netdev_priv(netdev);
|
||||||
|
|
||||||
|
priv->can.state = CAN_STATE_STOPPED;
|
||||||
|
|
||||||
|
netif_stop_queue(netdev);
|
||||||
|
|
||||||
|
/* Stop polling */
|
||||||
|
mcba_urb_unlink(priv);
|
||||||
|
|
||||||
|
close_candev(netdev);
|
||||||
|
can_led_event(netdev, CAN_LED_EVENT_STOP);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set network device mode
|
||||||
|
*
|
||||||
|
* Maybe we should leave this function empty, because the device
|
||||||
|
* set mode variable with open command.
|
||||||
|
*/
|
||||||
|
static int mcba_net_set_mode(struct net_device *netdev, enum can_mode mode)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mcba_net_get_berr_counter(const struct net_device *netdev,
|
||||||
|
struct can_berr_counter *bec)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = netdev_priv(netdev);
|
||||||
|
|
||||||
|
bec->txerr = priv->bec.txerr;
|
||||||
|
bec->rxerr = priv->bec.rxerr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct net_device_ops mcba_netdev_ops = {
|
||||||
|
.ndo_open = mcba_usb_open,
|
||||||
|
.ndo_stop = mcba_usb_close,
|
||||||
|
.ndo_start_xmit = mcba_usb_start_xmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Microchip CANBUS has hardcoded bittiming values by default.
|
||||||
|
* This function sends request via USB to change the speed and align bittiming
|
||||||
|
* values for presentation purposes only
|
||||||
|
*/
|
||||||
|
static int mcba_net_set_bittiming(struct net_device *netdev)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = netdev_priv(netdev);
|
||||||
|
const u16 bitrate_kbps = priv->can.bittiming.bitrate / 1000;
|
||||||
|
|
||||||
|
mcba_usb_xmit_change_bitrate(priv, bitrate_kbps);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mcba_set_termination(struct net_device *netdev, u16 term)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = netdev_priv(netdev);
|
||||||
|
struct mcba_usb_msg_termination usb_msg = {
|
||||||
|
.cmd_id = MBCA_CMD_SETUP_TERMINATION_RESISTANCE
|
||||||
|
};
|
||||||
|
|
||||||
|
if (term == MCBA_TERMINATION_ENABLED)
|
||||||
|
usb_msg.termination = 1;
|
||||||
|
else
|
||||||
|
usb_msg.termination = 0;
|
||||||
|
|
||||||
|
mcba_usb_xmit_cmd(priv, (struct mcba_usb_msg *)&usb_msg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mcba_usb_probe(struct usb_interface *intf,
|
||||||
|
const struct usb_device_id *id)
|
||||||
|
{
|
||||||
|
struct net_device *netdev;
|
||||||
|
struct mcba_priv *priv;
|
||||||
|
int err = -ENOMEM;
|
||||||
|
struct usb_device *usbdev = interface_to_usbdev(intf);
|
||||||
|
|
||||||
|
netdev = alloc_candev(sizeof(struct mcba_priv), MCBA_MAX_TX_URBS);
|
||||||
|
if (!netdev) {
|
||||||
|
dev_err(&intf->dev, "Couldn't alloc candev\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv = netdev_priv(netdev);
|
||||||
|
|
||||||
|
priv->udev = usbdev;
|
||||||
|
priv->netdev = netdev;
|
||||||
|
priv->usb_ka_first_pass = true;
|
||||||
|
priv->can_ka_first_pass = true;
|
||||||
|
priv->can_speed_check = false;
|
||||||
|
|
||||||
|
init_usb_anchor(&priv->rx_submitted);
|
||||||
|
init_usb_anchor(&priv->tx_submitted);
|
||||||
|
|
||||||
|
usb_set_intfdata(intf, priv);
|
||||||
|
|
||||||
|
/* Init CAN device */
|
||||||
|
priv->can.state = CAN_STATE_STOPPED;
|
||||||
|
priv->can.termination_const = mcba_termination;
|
||||||
|
priv->can.termination_const_cnt = ARRAY_SIZE(mcba_termination);
|
||||||
|
priv->can.bitrate_const = mcba_bitrate;
|
||||||
|
priv->can.bitrate_const_cnt = ARRAY_SIZE(mcba_bitrate);
|
||||||
|
|
||||||
|
priv->can.do_set_termination = mcba_set_termination;
|
||||||
|
priv->can.do_set_mode = mcba_net_set_mode;
|
||||||
|
priv->can.do_get_berr_counter = mcba_net_get_berr_counter;
|
||||||
|
priv->can.do_set_bittiming = mcba_net_set_bittiming;
|
||||||
|
|
||||||
|
netdev->netdev_ops = &mcba_netdev_ops;
|
||||||
|
|
||||||
|
netdev->flags |= IFF_ECHO; /* we support local echo */
|
||||||
|
|
||||||
|
SET_NETDEV_DEV(netdev, &intf->dev);
|
||||||
|
|
||||||
|
err = register_candev(netdev);
|
||||||
|
if (err) {
|
||||||
|
netdev_err(netdev, "couldn't register CAN device: %d\n", err);
|
||||||
|
|
||||||
|
goto cleanup_free_candev;
|
||||||
|
}
|
||||||
|
|
||||||
|
devm_can_led_init(netdev);
|
||||||
|
|
||||||
|
/* Start USB dev only if we have successfully registered CAN device */
|
||||||
|
err = mcba_usb_start(priv);
|
||||||
|
if (err) {
|
||||||
|
if (err == -ENODEV)
|
||||||
|
netif_device_detach(priv->netdev);
|
||||||
|
|
||||||
|
netdev_warn(netdev, "couldn't start device: %d\n", err);
|
||||||
|
|
||||||
|
goto cleanup_unregister_candev;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&intf->dev, "Microchip CAN BUS analizer connected\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
cleanup_unregister_candev:
|
||||||
|
unregister_candev(priv->netdev);
|
||||||
|
|
||||||
|
cleanup_free_candev:
|
||||||
|
free_candev(netdev);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called by the usb core when driver is unloaded or device is removed */
|
||||||
|
static void mcba_usb_disconnect(struct usb_interface *intf)
|
||||||
|
{
|
||||||
|
struct mcba_priv *priv = usb_get_intfdata(intf);
|
||||||
|
|
||||||
|
usb_set_intfdata(intf, NULL);
|
||||||
|
|
||||||
|
netdev_info(priv->netdev, "device disconnected\n");
|
||||||
|
|
||||||
|
unregister_candev(priv->netdev);
|
||||||
|
free_candev(priv->netdev);
|
||||||
|
|
||||||
|
mcba_urb_unlink(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct usb_driver mcba_usb_driver = {
|
||||||
|
.name = MCBA_MODULE_NAME,
|
||||||
|
.probe = mcba_usb_probe,
|
||||||
|
.disconnect = mcba_usb_disconnect,
|
||||||
|
.id_table = mcba_usb_table,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_usb_driver(mcba_usb_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Remigiusz Kołłątaj <remigiusz.kollataj@mobica.com>");
|
||||||
|
MODULE_DESCRIPTION("SocketCAN driver for Microchip CAN BUS Analyzer Tool");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
@ -19,10 +19,10 @@
|
|||||||
#include <linux/can.h>
|
#include <linux/can.h>
|
||||||
#include <linux/can/dev.h>
|
#include <linux/can/dev.h>
|
||||||
#include <linux/can/error.h>
|
#include <linux/can/error.h>
|
||||||
|
#include <linux/can/dev/peak_canfd.h>
|
||||||
|
|
||||||
#include "pcan_usb_core.h"
|
#include "pcan_usb_core.h"
|
||||||
#include "pcan_usb_pro.h"
|
#include "pcan_usb_pro.h"
|
||||||
#include "pcan_ucan.h"
|
|
||||||
|
|
||||||
MODULE_SUPPORTED_DEVICE("PEAK-System PCAN-USB FD adapter");
|
MODULE_SUPPORTED_DEVICE("PEAK-System PCAN-USB FD adapter");
|
||||||
MODULE_SUPPORTED_DEVICE("PEAK-System PCAN-USB Pro FD adapter");
|
MODULE_SUPPORTED_DEVICE("PEAK-System PCAN-USB Pro FD adapter");
|
||||||
@ -238,7 +238,7 @@ static int pcan_usb_fd_build_restart_cmd(struct peak_usb_device *dev, u8 *buf)
|
|||||||
|
|
||||||
/* 1st, reset error counters: */
|
/* 1st, reset error counters: */
|
||||||
prc = (struct pucan_wr_err_cnt *)pc;
|
prc = (struct pucan_wr_err_cnt *)pc;
|
||||||
prc->opcode_channel = pucan_cmd_opcode_channel(dev,
|
prc->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PUCAN_CMD_WR_ERR_CNT);
|
PUCAN_CMD_WR_ERR_CNT);
|
||||||
|
|
||||||
/* select both counters */
|
/* select both counters */
|
||||||
@ -257,9 +257,10 @@ static int pcan_usb_fd_build_restart_cmd(struct peak_usb_device *dev, u8 *buf)
|
|||||||
|
|
||||||
puo->opcode_channel =
|
puo->opcode_channel =
|
||||||
(dev->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO) ?
|
(dev->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO) ?
|
||||||
pucan_cmd_opcode_channel(dev,
|
pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PUCAN_CMD_CLR_DIS_OPTION) :
|
PUCAN_CMD_CLR_DIS_OPTION) :
|
||||||
pucan_cmd_opcode_channel(dev, PUCAN_CMD_SET_EN_OPTION);
|
pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
|
PUCAN_CMD_SET_EN_OPTION);
|
||||||
|
|
||||||
puo->options = cpu_to_le16(PUCAN_OPTION_CANDFDISO);
|
puo->options = cpu_to_le16(PUCAN_OPTION_CANDFDISO);
|
||||||
|
|
||||||
@ -274,7 +275,7 @@ static int pcan_usb_fd_build_restart_cmd(struct peak_usb_device *dev, u8 *buf)
|
|||||||
|
|
||||||
/* next, go back to operational mode */
|
/* next, go back to operational mode */
|
||||||
cmd = (struct pucan_command *)pc;
|
cmd = (struct pucan_command *)pc;
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
(dev->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) ?
|
(dev->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) ?
|
||||||
PUCAN_CMD_LISTEN_ONLY_MODE :
|
PUCAN_CMD_LISTEN_ONLY_MODE :
|
||||||
PUCAN_CMD_NORMAL_MODE);
|
PUCAN_CMD_NORMAL_MODE);
|
||||||
@ -296,7 +297,7 @@ static int pcan_usb_fd_set_bus(struct peak_usb_device *dev, u8 onoff)
|
|||||||
struct pucan_command *cmd = (struct pucan_command *)pc;
|
struct pucan_command *cmd = (struct pucan_command *)pc;
|
||||||
|
|
||||||
/* build cmd to go back to reset mode */
|
/* build cmd to go back to reset mode */
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PUCAN_CMD_RESET_MODE);
|
PUCAN_CMD_RESET_MODE);
|
||||||
l = sizeof(struct pucan_command);
|
l = sizeof(struct pucan_command);
|
||||||
}
|
}
|
||||||
@ -332,7 +333,7 @@ static int pcan_usb_fd_set_filter_std(struct peak_usb_device *dev, int idx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (i = idx; i < n; i++, cmd++) {
|
for (i = idx; i < n; i++, cmd++) {
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PUCAN_CMD_FILTER_STD);
|
PUCAN_CMD_FILTER_STD);
|
||||||
cmd->idx = cpu_to_le16(i);
|
cmd->idx = cpu_to_le16(i);
|
||||||
cmd->mask = cpu_to_le32(mask);
|
cmd->mask = cpu_to_le32(mask);
|
||||||
@ -352,7 +353,7 @@ static int pcan_usb_fd_set_options(struct peak_usb_device *dev,
|
|||||||
{
|
{
|
||||||
struct pcan_ufd_options *cmd = pcan_usb_fd_cmd_buffer(dev);
|
struct pcan_ufd_options *cmd = pcan_usb_fd_cmd_buffer(dev);
|
||||||
|
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
(onoff) ? PUCAN_CMD_SET_EN_OPTION :
|
(onoff) ? PUCAN_CMD_SET_EN_OPTION :
|
||||||
PUCAN_CMD_CLR_DIS_OPTION);
|
PUCAN_CMD_CLR_DIS_OPTION);
|
||||||
|
|
||||||
@ -368,7 +369,7 @@ static int pcan_usb_fd_set_can_led(struct peak_usb_device *dev, u8 led_mode)
|
|||||||
{
|
{
|
||||||
struct pcan_ufd_led *cmd = pcan_usb_fd_cmd_buffer(dev);
|
struct pcan_ufd_led *cmd = pcan_usb_fd_cmd_buffer(dev);
|
||||||
|
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PCAN_UFD_CMD_LED_SET);
|
PCAN_UFD_CMD_LED_SET);
|
||||||
cmd->mode = led_mode;
|
cmd->mode = led_mode;
|
||||||
|
|
||||||
@ -382,7 +383,7 @@ static int pcan_usb_fd_set_clock_domain(struct peak_usb_device *dev,
|
|||||||
{
|
{
|
||||||
struct pcan_ufd_clock *cmd = pcan_usb_fd_cmd_buffer(dev);
|
struct pcan_ufd_clock *cmd = pcan_usb_fd_cmd_buffer(dev);
|
||||||
|
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PCAN_UFD_CMD_CLK_SET);
|
PCAN_UFD_CMD_CLK_SET);
|
||||||
cmd->mode = clk_mode;
|
cmd->mode = clk_mode;
|
||||||
|
|
||||||
@ -396,7 +397,7 @@ static int pcan_usb_fd_set_bittiming_slow(struct peak_usb_device *dev,
|
|||||||
{
|
{
|
||||||
struct pucan_timing_slow *cmd = pcan_usb_fd_cmd_buffer(dev);
|
struct pucan_timing_slow *cmd = pcan_usb_fd_cmd_buffer(dev);
|
||||||
|
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PUCAN_CMD_TIMING_SLOW);
|
PUCAN_CMD_TIMING_SLOW);
|
||||||
cmd->sjw_t = PUCAN_TSLOW_SJW_T(bt->sjw - 1,
|
cmd->sjw_t = PUCAN_TSLOW_SJW_T(bt->sjw - 1,
|
||||||
dev->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES);
|
dev->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES);
|
||||||
@ -417,7 +418,7 @@ static int pcan_usb_fd_set_bittiming_fast(struct peak_usb_device *dev,
|
|||||||
{
|
{
|
||||||
struct pucan_timing_fast *cmd = pcan_usb_fd_cmd_buffer(dev);
|
struct pucan_timing_fast *cmd = pcan_usb_fd_cmd_buffer(dev);
|
||||||
|
|
||||||
cmd->opcode_channel = pucan_cmd_opcode_channel(dev,
|
cmd->opcode_channel = pucan_cmd_opcode_channel(dev->ctrl_idx,
|
||||||
PUCAN_CMD_TIMING_FAST);
|
PUCAN_CMD_TIMING_FAST);
|
||||||
cmd->sjw = PUCAN_TFAST_SJW(bt->sjw - 1);
|
cmd->sjw = PUCAN_TFAST_SJW(bt->sjw - 1);
|
||||||
cmd->tseg2 = PUCAN_TFAST_TSEG2(bt->phase_seg2 - 1);
|
cmd->tseg2 = PUCAN_TFAST_TSEG2(bt->phase_seg2 - 1);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* vcan.c - Virtual CAN interface
|
* vcan.c - Virtual CAN interface
|
||||||
*
|
*
|
||||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
* Copyright (c) 2002-2017 Volkswagen Group Electronic Research
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@ -50,9 +50,12 @@
|
|||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <net/rtnetlink.h>
|
#include <net/rtnetlink.h>
|
||||||
|
|
||||||
|
#define DRV_NAME "vcan"
|
||||||
|
|
||||||
MODULE_DESCRIPTION("virtual CAN interface");
|
MODULE_DESCRIPTION("virtual CAN interface");
|
||||||
MODULE_LICENSE("Dual BSD/GPL");
|
MODULE_LICENSE("Dual BSD/GPL");
|
||||||
MODULE_AUTHOR("Urs Thuermann <urs.thuermann@volkswagen.de>");
|
MODULE_AUTHOR("Urs Thuermann <urs.thuermann@volkswagen.de>");
|
||||||
|
MODULE_ALIAS_RTNL_LINK(DRV_NAME);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -164,7 +167,7 @@ static void vcan_setup(struct net_device *dev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct rtnl_link_ops vcan_link_ops __read_mostly = {
|
static struct rtnl_link_ops vcan_link_ops __read_mostly = {
|
||||||
.kind = "vcan",
|
.kind = DRV_NAME,
|
||||||
.setup = vcan_setup,
|
.setup = vcan_setup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
316
drivers/net/can/vxcan.c
Normal file
316
drivers/net/can/vxcan.c
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* vxcan.c - Virtual CAN Tunnel for cross namespace communication
|
||||||
|
*
|
||||||
|
* This code is derived from drivers/net/can/vcan.c for the virtual CAN
|
||||||
|
* specific parts and from drivers/net/veth.c to implement the netlink API
|
||||||
|
* for network interface pairs in a common and established way.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 Oliver Hartkopp <socketcan@hartkopp.net>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the version 2 of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* 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; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/netdevice.h>
|
||||||
|
#include <linux/if_arp.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/dev.h>
|
||||||
|
#include <linux/can/skb.h>
|
||||||
|
#include <linux/can/vxcan.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <net/rtnetlink.h>
|
||||||
|
|
||||||
|
#define DRV_NAME "vxcan"
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Virtual CAN Tunnel");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Oliver Hartkopp <socketcan@hartkopp.net>");
|
||||||
|
MODULE_ALIAS_RTNL_LINK(DRV_NAME);
|
||||||
|
|
||||||
|
struct vxcan_priv {
|
||||||
|
struct net_device __rcu *peer;
|
||||||
|
};
|
||||||
|
|
||||||
|
static netdev_tx_t vxcan_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||||
|
{
|
||||||
|
struct vxcan_priv *priv = netdev_priv(dev);
|
||||||
|
struct net_device *peer;
|
||||||
|
struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
|
||||||
|
struct net_device_stats *peerstats, *srcstats = &dev->stats;
|
||||||
|
|
||||||
|
if (can_dropped_invalid_skb(dev, skb))
|
||||||
|
return NETDEV_TX_OK;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
peer = rcu_dereference(priv->peer);
|
||||||
|
if (unlikely(!peer)) {
|
||||||
|
kfree_skb(skb);
|
||||||
|
dev->stats.tx_dropped++;
|
||||||
|
goto out_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
skb = can_create_echo_skb(skb);
|
||||||
|
if (!skb)
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
|
/* reset CAN GW hop counter */
|
||||||
|
skb->csum_start = 0;
|
||||||
|
skb->pkt_type = PACKET_BROADCAST;
|
||||||
|
skb->dev = peer;
|
||||||
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
||||||
|
|
||||||
|
if (netif_rx_ni(skb) == NET_RX_SUCCESS) {
|
||||||
|
srcstats->tx_packets++;
|
||||||
|
srcstats->tx_bytes += cfd->len;
|
||||||
|
peerstats = &peer->stats;
|
||||||
|
peerstats->rx_packets++;
|
||||||
|
peerstats->rx_bytes += cfd->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_unlock:
|
||||||
|
rcu_read_unlock();
|
||||||
|
return NETDEV_TX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int vxcan_open(struct net_device *dev)
|
||||||
|
{
|
||||||
|
struct vxcan_priv *priv = netdev_priv(dev);
|
||||||
|
struct net_device *peer = rtnl_dereference(priv->peer);
|
||||||
|
|
||||||
|
if (!peer)
|
||||||
|
return -ENOTCONN;
|
||||||
|
|
||||||
|
if (peer->flags & IFF_UP) {
|
||||||
|
netif_carrier_on(dev);
|
||||||
|
netif_carrier_on(peer);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vxcan_close(struct net_device *dev)
|
||||||
|
{
|
||||||
|
struct vxcan_priv *priv = netdev_priv(dev);
|
||||||
|
struct net_device *peer = rtnl_dereference(priv->peer);
|
||||||
|
|
||||||
|
netif_carrier_off(dev);
|
||||||
|
if (peer)
|
||||||
|
netif_carrier_off(peer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vxcan_get_iflink(const struct net_device *dev)
|
||||||
|
{
|
||||||
|
struct vxcan_priv *priv = netdev_priv(dev);
|
||||||
|
struct net_device *peer;
|
||||||
|
int iflink;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
peer = rcu_dereference(priv->peer);
|
||||||
|
iflink = peer ? peer->ifindex : 0;
|
||||||
|
rcu_read_unlock();
|
||||||
|
|
||||||
|
return iflink;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vxcan_change_mtu(struct net_device *dev, int new_mtu)
|
||||||
|
{
|
||||||
|
/* Do not allow changing the MTU while running */
|
||||||
|
if (dev->flags & IFF_UP)
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
if (new_mtu != CAN_MTU && new_mtu != CANFD_MTU)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dev->mtu = new_mtu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct net_device_ops vxcan_netdev_ops = {
|
||||||
|
.ndo_open = vxcan_open,
|
||||||
|
.ndo_stop = vxcan_close,
|
||||||
|
.ndo_start_xmit = vxcan_xmit,
|
||||||
|
.ndo_get_iflink = vxcan_get_iflink,
|
||||||
|
.ndo_change_mtu = vxcan_change_mtu,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void vxcan_setup(struct net_device *dev)
|
||||||
|
{
|
||||||
|
dev->type = ARPHRD_CAN;
|
||||||
|
dev->mtu = CAN_MTU;
|
||||||
|
dev->hard_header_len = 0;
|
||||||
|
dev->addr_len = 0;
|
||||||
|
dev->tx_queue_len = 0;
|
||||||
|
dev->flags = (IFF_NOARP|IFF_ECHO);
|
||||||
|
dev->netdev_ops = &vxcan_netdev_ops;
|
||||||
|
dev->destructor = free_netdev;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* forward declaration for rtnl_create_link() */
|
||||||
|
static struct rtnl_link_ops vxcan_link_ops;
|
||||||
|
|
||||||
|
static int vxcan_newlink(struct net *net, struct net_device *dev,
|
||||||
|
struct nlattr *tb[], struct nlattr *data[])
|
||||||
|
{
|
||||||
|
struct vxcan_priv *priv;
|
||||||
|
struct net_device *peer;
|
||||||
|
struct net *peer_net;
|
||||||
|
|
||||||
|
struct nlattr *peer_tb[IFLA_MAX + 1], **tbp = tb;
|
||||||
|
char ifname[IFNAMSIZ];
|
||||||
|
unsigned char name_assign_type;
|
||||||
|
struct ifinfomsg *ifmp = NULL;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* register peer device */
|
||||||
|
if (data && data[VXCAN_INFO_PEER]) {
|
||||||
|
struct nlattr *nla_peer;
|
||||||
|
|
||||||
|
nla_peer = data[VXCAN_INFO_PEER];
|
||||||
|
ifmp = nla_data(nla_peer);
|
||||||
|
err = rtnl_nla_parse_ifla(peer_tb,
|
||||||
|
nla_data(nla_peer) +
|
||||||
|
sizeof(struct ifinfomsg),
|
||||||
|
nla_len(nla_peer) -
|
||||||
|
sizeof(struct ifinfomsg),
|
||||||
|
NULL);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
tbp = peer_tb;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tbp[IFLA_IFNAME]) {
|
||||||
|
nla_strlcpy(ifname, tbp[IFLA_IFNAME], IFNAMSIZ);
|
||||||
|
name_assign_type = NET_NAME_USER;
|
||||||
|
} else {
|
||||||
|
snprintf(ifname, IFNAMSIZ, DRV_NAME "%%d");
|
||||||
|
name_assign_type = NET_NAME_ENUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
peer_net = rtnl_link_get_net(net, tbp);
|
||||||
|
if (IS_ERR(peer_net))
|
||||||
|
return PTR_ERR(peer_net);
|
||||||
|
|
||||||
|
peer = rtnl_create_link(peer_net, ifname, name_assign_type,
|
||||||
|
&vxcan_link_ops, tbp);
|
||||||
|
if (IS_ERR(peer)) {
|
||||||
|
put_net(peer_net);
|
||||||
|
return PTR_ERR(peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ifmp && dev->ifindex)
|
||||||
|
peer->ifindex = ifmp->ifi_index;
|
||||||
|
|
||||||
|
err = register_netdevice(peer);
|
||||||
|
put_net(peer_net);
|
||||||
|
peer_net = NULL;
|
||||||
|
if (err < 0) {
|
||||||
|
free_netdev(peer);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
netif_carrier_off(peer);
|
||||||
|
|
||||||
|
err = rtnl_configure_link(peer, ifmp);
|
||||||
|
if (err < 0) {
|
||||||
|
unregister_netdevice(peer);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* register first device */
|
||||||
|
if (tb[IFLA_IFNAME])
|
||||||
|
nla_strlcpy(dev->name, tb[IFLA_IFNAME], IFNAMSIZ);
|
||||||
|
else
|
||||||
|
snprintf(dev->name, IFNAMSIZ, DRV_NAME "%%d");
|
||||||
|
|
||||||
|
err = register_netdevice(dev);
|
||||||
|
if (err < 0) {
|
||||||
|
unregister_netdevice(peer);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
netif_carrier_off(dev);
|
||||||
|
|
||||||
|
/* cross link the device pair */
|
||||||
|
priv = netdev_priv(dev);
|
||||||
|
rcu_assign_pointer(priv->peer, peer);
|
||||||
|
|
||||||
|
priv = netdev_priv(peer);
|
||||||
|
rcu_assign_pointer(priv->peer, dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vxcan_dellink(struct net_device *dev, struct list_head *head)
|
||||||
|
{
|
||||||
|
struct vxcan_priv *priv;
|
||||||
|
struct net_device *peer;
|
||||||
|
|
||||||
|
priv = netdev_priv(dev);
|
||||||
|
peer = rtnl_dereference(priv->peer);
|
||||||
|
|
||||||
|
/* Note : dellink() is called from default_device_exit_batch(),
|
||||||
|
* before a rcu_synchronize() point. The devices are guaranteed
|
||||||
|
* not being freed before one RCU grace period.
|
||||||
|
*/
|
||||||
|
RCU_INIT_POINTER(priv->peer, NULL);
|
||||||
|
unregister_netdevice_queue(dev, head);
|
||||||
|
|
||||||
|
if (peer) {
|
||||||
|
priv = netdev_priv(peer);
|
||||||
|
RCU_INIT_POINTER(priv->peer, NULL);
|
||||||
|
unregister_netdevice_queue(peer, head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct nla_policy vxcan_policy[VXCAN_INFO_MAX + 1] = {
|
||||||
|
[VXCAN_INFO_PEER] = { .len = sizeof(struct ifinfomsg) },
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct net *vxcan_get_link_net(const struct net_device *dev)
|
||||||
|
{
|
||||||
|
struct vxcan_priv *priv = netdev_priv(dev);
|
||||||
|
struct net_device *peer = rtnl_dereference(priv->peer);
|
||||||
|
|
||||||
|
return peer ? dev_net(peer) : dev_net(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct rtnl_link_ops vxcan_link_ops = {
|
||||||
|
.kind = DRV_NAME,
|
||||||
|
.priv_size = sizeof(struct vxcan_priv),
|
||||||
|
.setup = vxcan_setup,
|
||||||
|
.newlink = vxcan_newlink,
|
||||||
|
.dellink = vxcan_dellink,
|
||||||
|
.policy = vxcan_policy,
|
||||||
|
.maxtype = VXCAN_INFO_MAX,
|
||||||
|
.get_link_net = vxcan_get_link_net,
|
||||||
|
};
|
||||||
|
|
||||||
|
static __init int vxcan_init(void)
|
||||||
|
{
|
||||||
|
pr_info("vxcan: Virtual CAN Tunnel driver\n");
|
||||||
|
|
||||||
|
return rtnl_link_register(&vxcan_link_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
static __exit void vxcan_exit(void)
|
||||||
|
{
|
||||||
|
rtnl_link_unregister(&vxcan_link_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(vxcan_init);
|
||||||
|
module_exit(vxcan_exit);
|
@ -5,7 +5,7 @@
|
|||||||
*
|
*
|
||||||
* Authors: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
* Authors: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
||||||
* Urs Thuermann <urs.thuermann@volkswagen.de>
|
* Urs Thuermann <urs.thuermann@volkswagen.de>
|
||||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
* Copyright (c) 2002-2017 Volkswagen Group Electronic Research
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -17,7 +17,7 @@
|
|||||||
#include <linux/skbuff.h>
|
#include <linux/skbuff.h>
|
||||||
#include <linux/netdevice.h>
|
#include <linux/netdevice.h>
|
||||||
|
|
||||||
#define CAN_VERSION "20120528"
|
#define CAN_VERSION "20170425"
|
||||||
|
|
||||||
/* increment this number each time you change some user-space interface */
|
/* increment this number each time you change some user-space interface */
|
||||||
#define CAN_ABI_VERSION "9"
|
#define CAN_ABI_VERSION "9"
|
||||||
|
@ -23,11 +23,14 @@
|
|||||||
#define PUCAN_CMD_LISTEN_ONLY_MODE 0x003
|
#define PUCAN_CMD_LISTEN_ONLY_MODE 0x003
|
||||||
#define PUCAN_CMD_TIMING_SLOW 0x004
|
#define PUCAN_CMD_TIMING_SLOW 0x004
|
||||||
#define PUCAN_CMD_TIMING_FAST 0x005
|
#define PUCAN_CMD_TIMING_FAST 0x005
|
||||||
|
#define PUCAN_CMD_SET_STD_FILTER 0x006
|
||||||
|
#define PUCAN_CMD_RESERVED2 0x007
|
||||||
#define PUCAN_CMD_FILTER_STD 0x008
|
#define PUCAN_CMD_FILTER_STD 0x008
|
||||||
#define PUCAN_CMD_TX_ABORT 0x009
|
#define PUCAN_CMD_TX_ABORT 0x009
|
||||||
#define PUCAN_CMD_WR_ERR_CNT 0x00a
|
#define PUCAN_CMD_WR_ERR_CNT 0x00a
|
||||||
#define PUCAN_CMD_SET_EN_OPTION 0x00b
|
#define PUCAN_CMD_SET_EN_OPTION 0x00b
|
||||||
#define PUCAN_CMD_CLR_DIS_OPTION 0x00c
|
#define PUCAN_CMD_CLR_DIS_OPTION 0x00c
|
||||||
|
#define PUCAN_CMD_RX_BARRIER 0x010
|
||||||
#define PUCAN_CMD_END_OF_COLLECTION 0x3ff
|
#define PUCAN_CMD_END_OF_COLLECTION 0x3ff
|
||||||
|
|
||||||
/* uCAN received messages list */
|
/* uCAN received messages list */
|
||||||
@ -35,6 +38,10 @@
|
|||||||
#define PUCAN_MSG_ERROR 0x0002
|
#define PUCAN_MSG_ERROR 0x0002
|
||||||
#define PUCAN_MSG_STATUS 0x0003
|
#define PUCAN_MSG_STATUS 0x0003
|
||||||
#define PUCAN_MSG_BUSLOAD 0x0004
|
#define PUCAN_MSG_BUSLOAD 0x0004
|
||||||
|
|
||||||
|
#define PUCAN_MSG_CACHE_CRITICAL 0x0102
|
||||||
|
|
||||||
|
/* uCAN transmitted messages */
|
||||||
#define PUCAN_MSG_CAN_TX 0x1000
|
#define PUCAN_MSG_CAN_TX 0x1000
|
||||||
|
|
||||||
/* uCAN command common header */
|
/* uCAN command common header */
|
||||||
@ -43,6 +50,12 @@ struct __packed pucan_command {
|
|||||||
u16 args[3];
|
u16 args[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* return the opcode from the opcode_channel field of a command */
|
||||||
|
static inline u16 pucan_cmd_get_opcode(struct pucan_command *c)
|
||||||
|
{
|
||||||
|
return le16_to_cpu(c->opcode_channel) & 0x3ff;
|
||||||
|
}
|
||||||
|
|
||||||
#define PUCAN_TSLOW_BRP_BITS 10
|
#define PUCAN_TSLOW_BRP_BITS 10
|
||||||
#define PUCAN_TSLOW_TSGEG1_BITS 8
|
#define PUCAN_TSLOW_TSGEG1_BITS 8
|
||||||
#define PUCAN_TSLOW_TSGEG2_BITS 7
|
#define PUCAN_TSLOW_TSGEG2_BITS 7
|
||||||
@ -108,6 +121,27 @@ struct __packed pucan_filter_std {
|
|||||||
__le32 mask; /* CAN-ID bitmask in idx range */
|
__le32 mask; /* CAN-ID bitmask in idx range */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define PUCAN_FLTSTD_ROW_IDX_MAX ((1 << PUCAN_FLTSTD_ROW_IDX_BITS) - 1)
|
||||||
|
|
||||||
|
/* uCAN SET_STD_FILTER command fields */
|
||||||
|
struct __packed pucan_std_filter {
|
||||||
|
__le16 opcode_channel;
|
||||||
|
|
||||||
|
u8 unused;
|
||||||
|
u8 idx;
|
||||||
|
__le32 mask; /* CAN-ID bitmask in idx range */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* uCAN TX_ABORT commands fields */
|
||||||
|
#define PUCAN_TX_ABORT_FLUSH 0x0001
|
||||||
|
|
||||||
|
struct __packed pucan_tx_abort {
|
||||||
|
__le16 opcode_channel;
|
||||||
|
|
||||||
|
__le16 flags;
|
||||||
|
u32 unused;
|
||||||
|
};
|
||||||
|
|
||||||
/* uCAN WR_ERR_CNT command fields */
|
/* uCAN WR_ERR_CNT command fields */
|
||||||
#define PUCAN_WRERRCNT_TE 0x4000 /* Tx error cntr write Enable */
|
#define PUCAN_WRERRCNT_TE 0x4000 /* Tx error cntr write Enable */
|
||||||
#define PUCAN_WRERRCNT_RE 0x8000 /* Rx error cntr write Enable */
|
#define PUCAN_WRERRCNT_RE 0x8000 /* Rx error cntr write Enable */
|
||||||
@ -184,6 +218,12 @@ struct __packed pucan_error_msg {
|
|||||||
u8 rx_err_cnt;
|
u8 rx_err_cnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline int pucan_error_get_channel(const struct pucan_error_msg *msg)
|
||||||
|
{
|
||||||
|
return msg->channel_type_d & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PUCAN_RX_BARRIER 0x10
|
||||||
#define PUCAN_BUS_PASSIVE 0x20
|
#define PUCAN_BUS_PASSIVE 0x20
|
||||||
#define PUCAN_BUS_WARNING 0x40
|
#define PUCAN_BUS_WARNING 0x40
|
||||||
#define PUCAN_BUS_BUSOFF 0x80
|
#define PUCAN_BUS_BUSOFF 0x80
|
||||||
@ -197,6 +237,31 @@ struct __packed pucan_status_msg {
|
|||||||
u8 unused[3];
|
u8 unused[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline int pucan_status_get_channel(const struct pucan_status_msg *msg)
|
||||||
|
{
|
||||||
|
return msg->channel_p_w_b & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pucan_status_is_rx_barrier(const struct pucan_status_msg *msg)
|
||||||
|
{
|
||||||
|
return msg->channel_p_w_b & PUCAN_RX_BARRIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pucan_status_is_passive(const struct pucan_status_msg *msg)
|
||||||
|
{
|
||||||
|
return msg->channel_p_w_b & PUCAN_BUS_PASSIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pucan_status_is_warning(const struct pucan_status_msg *msg)
|
||||||
|
{
|
||||||
|
return msg->channel_p_w_b & PUCAN_BUS_WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pucan_status_is_busoff(const struct pucan_status_msg *msg)
|
||||||
|
{
|
||||||
|
return msg->channel_p_w_b & PUCAN_BUS_BUSOFF;
|
||||||
|
}
|
||||||
|
|
||||||
/* uCAN transmitted message format */
|
/* uCAN transmitted message format */
|
||||||
#define PUCAN_MSG_CHANNEL_DLC(c, d) (((c) & 0xf) | ((d) << 4))
|
#define PUCAN_MSG_CHANNEL_DLC(c, d) (((c) & 0xf) | ((d) << 4))
|
||||||
|
|
||||||
@ -213,32 +278,31 @@ struct __packed pucan_tx_msg {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* build the cmd opcode_channel field with respect to the correct endianness */
|
/* build the cmd opcode_channel field with respect to the correct endianness */
|
||||||
static inline __le16 pucan_cmd_opcode_channel(struct peak_usb_device *dev,
|
static inline __le16 pucan_cmd_opcode_channel(int index, int opcode)
|
||||||
int opcode)
|
|
||||||
{
|
{
|
||||||
return cpu_to_le16(((dev->ctrl_idx) << 12) | ((opcode) & 0x3ff));
|
return cpu_to_le16(((index) << 12) | ((opcode) & 0x3ff));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return the channel number part from any received message channel_dlc field */
|
/* return the channel number part from any received message channel_dlc field */
|
||||||
static inline int pucan_msg_get_channel(struct pucan_rx_msg *rm)
|
static inline int pucan_msg_get_channel(const struct pucan_rx_msg *msg)
|
||||||
{
|
{
|
||||||
return rm->channel_dlc & 0xf;
|
return msg->channel_dlc & 0xf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return the dlc value from any received message channel_dlc field */
|
/* return the dlc value from any received message channel_dlc field */
|
||||||
static inline int pucan_msg_get_dlc(struct pucan_rx_msg *rm)
|
static inline int pucan_msg_get_dlc(const struct pucan_rx_msg *msg)
|
||||||
{
|
{
|
||||||
return rm->channel_dlc >> 4;
|
return msg->channel_dlc >> 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int pucan_ermsg_get_channel(struct pucan_error_msg *em)
|
static inline int pucan_ermsg_get_channel(const struct pucan_error_msg *msg)
|
||||||
{
|
{
|
||||||
return em->channel_type_d & 0x0f;
|
return msg->channel_type_d & 0x0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int pucan_stmsg_get_channel(struct pucan_status_msg *sm)
|
static inline int pucan_stmsg_get_channel(const struct pucan_status_msg *msg)
|
||||||
{
|
{
|
||||||
return sm->channel_p_w_b & 0x0f;
|
return msg->channel_p_w_b & 0x0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -8,6 +8,8 @@
|
|||||||
#include <linux/spinlock.h>
|
#include <linux/spinlock.h>
|
||||||
|
|
||||||
struct dev_rcv_lists;
|
struct dev_rcv_lists;
|
||||||
|
struct s_stats;
|
||||||
|
struct s_pstats;
|
||||||
|
|
||||||
struct netns_can {
|
struct netns_can {
|
||||||
#if IS_ENABLED(CONFIG_PROC_FS)
|
#if IS_ENABLED(CONFIG_PROC_FS)
|
||||||
@ -21,11 +23,18 @@ struct netns_can {
|
|||||||
struct proc_dir_entry *pde_rcvlist_sff;
|
struct proc_dir_entry *pde_rcvlist_sff;
|
||||||
struct proc_dir_entry *pde_rcvlist_eff;
|
struct proc_dir_entry *pde_rcvlist_eff;
|
||||||
struct proc_dir_entry *pde_rcvlist_err;
|
struct proc_dir_entry *pde_rcvlist_err;
|
||||||
|
struct proc_dir_entry *bcmproc_dir;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* receive filters subscribed for 'all' CAN devices */
|
/* receive filters subscribed for 'all' CAN devices */
|
||||||
struct dev_rcv_lists *can_rx_alldev_list;
|
struct dev_rcv_lists *can_rx_alldev_list;
|
||||||
spinlock_t can_rcvlists_lock;
|
spinlock_t can_rcvlists_lock;
|
||||||
|
struct timer_list can_stattimer;/* timer for statistics update */
|
||||||
|
struct s_stats *can_stats; /* packet statistics */
|
||||||
|
struct s_pstats *can_pstats; /* receive list statistics */
|
||||||
|
|
||||||
|
/* CAN GW per-net gateway jobs */
|
||||||
|
struct hlist_head cgw_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* __NETNS_CAN_H__ */
|
#endif /* __NETNS_CAN_H__ */
|
||||||
|
12
include/uapi/linux/can/vxcan.h
Normal file
12
include/uapi/linux/can/vxcan.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef _UAPI_CAN_VXCAN_H
|
||||||
|
#define _UAPI_CAN_VXCAN_H
|
||||||
|
|
||||||
|
enum {
|
||||||
|
VXCAN_INFO_UNSPEC,
|
||||||
|
VXCAN_INFO_PEER,
|
||||||
|
|
||||||
|
__VXCAN_INFO_MAX
|
||||||
|
#define VXCAN_INFO_MAX (__VXCAN_INFO_MAX - 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -2,7 +2,7 @@
|
|||||||
* af_can.c - Protocol family CAN core module
|
* af_can.c - Protocol family CAN core module
|
||||||
* (used by different CAN protocol modules)
|
* (used by different CAN protocol modules)
|
||||||
*
|
*
|
||||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
* Copyright (c) 2002-2017 Volkswagen Group Electronic Research
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@ -75,18 +75,12 @@ static int stats_timer __read_mostly = 1;
|
|||||||
module_param(stats_timer, int, S_IRUGO);
|
module_param(stats_timer, int, S_IRUGO);
|
||||||
MODULE_PARM_DESC(stats_timer, "enable timer for statistics (default:on)");
|
MODULE_PARM_DESC(stats_timer, "enable timer for statistics (default:on)");
|
||||||
|
|
||||||
static int can_net_id;
|
|
||||||
|
|
||||||
static struct kmem_cache *rcv_cache __read_mostly;
|
static struct kmem_cache *rcv_cache __read_mostly;
|
||||||
|
|
||||||
/* table of registered CAN protocols */
|
/* table of registered CAN protocols */
|
||||||
static const struct can_proto *proto_tab[CAN_NPROTO] __read_mostly;
|
static const struct can_proto *proto_tab[CAN_NPROTO] __read_mostly;
|
||||||
static DEFINE_MUTEX(proto_tab_lock);
|
static DEFINE_MUTEX(proto_tab_lock);
|
||||||
|
|
||||||
struct timer_list can_stattimer; /* timer for statistics update */
|
|
||||||
struct s_stats can_stats; /* packet statistics */
|
|
||||||
struct s_pstats can_pstats; /* receive list statistics */
|
|
||||||
|
|
||||||
static atomic_t skbcounter = ATOMIC_INIT(0);
|
static atomic_t skbcounter = ATOMIC_INIT(0);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -223,6 +217,7 @@ int can_send(struct sk_buff *skb, int loop)
|
|||||||
{
|
{
|
||||||
struct sk_buff *newskb = NULL;
|
struct sk_buff *newskb = NULL;
|
||||||
struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
|
struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
|
||||||
|
struct s_stats *can_stats = dev_net(skb->dev)->can.can_stats;
|
||||||
int err = -EINVAL;
|
int err = -EINVAL;
|
||||||
|
|
||||||
if (skb->len == CAN_MTU) {
|
if (skb->len == CAN_MTU) {
|
||||||
@ -311,8 +306,8 @@ int can_send(struct sk_buff *skb, int loop)
|
|||||||
netif_rx_ni(newskb);
|
netif_rx_ni(newskb);
|
||||||
|
|
||||||
/* update statistics */
|
/* update statistics */
|
||||||
can_stats.tx_frames++;
|
can_stats->tx_frames++;
|
||||||
can_stats.tx_frames_delta++;
|
can_stats->tx_frames_delta++;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@ -470,6 +465,7 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
|
|||||||
struct receiver *r;
|
struct receiver *r;
|
||||||
struct hlist_head *rl;
|
struct hlist_head *rl;
|
||||||
struct dev_rcv_lists *d;
|
struct dev_rcv_lists *d;
|
||||||
|
struct s_pstats *can_pstats = net->can.can_pstats;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
/* insert new receiver (dev,canid,mask) -> (func,data) */
|
/* insert new receiver (dev,canid,mask) -> (func,data) */
|
||||||
@ -501,9 +497,9 @@ int can_rx_register(struct net *net, struct net_device *dev, canid_t can_id,
|
|||||||
hlist_add_head_rcu(&r->list, rl);
|
hlist_add_head_rcu(&r->list, rl);
|
||||||
d->entries++;
|
d->entries++;
|
||||||
|
|
||||||
can_pstats.rcv_entries++;
|
can_pstats->rcv_entries++;
|
||||||
if (can_pstats.rcv_entries_max < can_pstats.rcv_entries)
|
if (can_pstats->rcv_entries_max < can_pstats->rcv_entries)
|
||||||
can_pstats.rcv_entries_max = can_pstats.rcv_entries;
|
can_pstats->rcv_entries_max = can_pstats->rcv_entries;
|
||||||
} else {
|
} else {
|
||||||
kmem_cache_free(rcv_cache, r);
|
kmem_cache_free(rcv_cache, r);
|
||||||
err = -ENODEV;
|
err = -ENODEV;
|
||||||
@ -545,6 +541,7 @@ void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
|
|||||||
{
|
{
|
||||||
struct receiver *r = NULL;
|
struct receiver *r = NULL;
|
||||||
struct hlist_head *rl;
|
struct hlist_head *rl;
|
||||||
|
struct s_pstats *can_pstats = net->can.can_pstats;
|
||||||
struct dev_rcv_lists *d;
|
struct dev_rcv_lists *d;
|
||||||
|
|
||||||
if (dev && dev->type != ARPHRD_CAN)
|
if (dev && dev->type != ARPHRD_CAN)
|
||||||
@ -591,8 +588,8 @@ void can_rx_unregister(struct net *net, struct net_device *dev, canid_t can_id,
|
|||||||
hlist_del_rcu(&r->list);
|
hlist_del_rcu(&r->list);
|
||||||
d->entries--;
|
d->entries--;
|
||||||
|
|
||||||
if (can_pstats.rcv_entries > 0)
|
if (can_pstats->rcv_entries > 0)
|
||||||
can_pstats.rcv_entries--;
|
can_pstats->rcv_entries--;
|
||||||
|
|
||||||
/* remove device structure requested by NETDEV_UNREGISTER */
|
/* remove device structure requested by NETDEV_UNREGISTER */
|
||||||
if (d->remove_on_zero_entries && !d->entries) {
|
if (d->remove_on_zero_entries && !d->entries) {
|
||||||
@ -686,11 +683,13 @@ static int can_rcv_filter(struct dev_rcv_lists *d, struct sk_buff *skb)
|
|||||||
static void can_receive(struct sk_buff *skb, struct net_device *dev)
|
static void can_receive(struct sk_buff *skb, struct net_device *dev)
|
||||||
{
|
{
|
||||||
struct dev_rcv_lists *d;
|
struct dev_rcv_lists *d;
|
||||||
|
struct net *net = dev_net(dev);
|
||||||
|
struct s_stats *can_stats = net->can.can_stats;
|
||||||
int matches;
|
int matches;
|
||||||
|
|
||||||
/* update statistics */
|
/* update statistics */
|
||||||
can_stats.rx_frames++;
|
can_stats->rx_frames++;
|
||||||
can_stats.rx_frames_delta++;
|
can_stats->rx_frames_delta++;
|
||||||
|
|
||||||
/* create non-zero unique skb identifier together with *skb */
|
/* create non-zero unique skb identifier together with *skb */
|
||||||
while (!(can_skb_prv(skb)->skbcnt))
|
while (!(can_skb_prv(skb)->skbcnt))
|
||||||
@ -699,10 +698,10 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev)
|
|||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
|
|
||||||
/* deliver the packet to sockets listening on all devices */
|
/* deliver the packet to sockets listening on all devices */
|
||||||
matches = can_rcv_filter(dev_net(dev)->can.can_rx_alldev_list, skb);
|
matches = can_rcv_filter(net->can.can_rx_alldev_list, skb);
|
||||||
|
|
||||||
/* find receive list for this device */
|
/* find receive list for this device */
|
||||||
d = find_dev_rcv_lists(dev_net(dev), dev);
|
d = find_dev_rcv_lists(net, dev);
|
||||||
if (d)
|
if (d)
|
||||||
matches += can_rcv_filter(d, skb);
|
matches += can_rcv_filter(d, skb);
|
||||||
|
|
||||||
@ -712,8 +711,8 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev)
|
|||||||
consume_skb(skb);
|
consume_skb(skb);
|
||||||
|
|
||||||
if (matches > 0) {
|
if (matches > 0) {
|
||||||
can_stats.matches++;
|
can_stats->matches++;
|
||||||
can_stats.matches_delta++;
|
can_stats->matches_delta++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -878,8 +877,20 @@ static int can_pernet_init(struct net *net)
|
|||||||
net->can.can_rx_alldev_list =
|
net->can.can_rx_alldev_list =
|
||||||
kzalloc(sizeof(struct dev_rcv_lists), GFP_KERNEL);
|
kzalloc(sizeof(struct dev_rcv_lists), GFP_KERNEL);
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_PROC_FS))
|
net->can.can_stats = kzalloc(sizeof(struct s_stats), GFP_KERNEL);
|
||||||
|
net->can.can_pstats = kzalloc(sizeof(struct s_pstats), GFP_KERNEL);
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_PROC_FS)) {
|
||||||
|
/* the statistics are updated every second (timer triggered) */
|
||||||
|
if (stats_timer) {
|
||||||
|
setup_timer(&net->can.can_stattimer, can_stat_update,
|
||||||
|
(unsigned long)net);
|
||||||
|
mod_timer(&net->can.can_stattimer,
|
||||||
|
round_jiffies(jiffies + HZ));
|
||||||
|
}
|
||||||
|
net->can.can_stats->jiffies_init = jiffies;
|
||||||
can_init_proc(net);
|
can_init_proc(net);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -888,8 +899,11 @@ static void can_pernet_exit(struct net *net)
|
|||||||
{
|
{
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_PROC_FS))
|
if (IS_ENABLED(CONFIG_PROC_FS)) {
|
||||||
can_remove_proc(net);
|
can_remove_proc(net);
|
||||||
|
if (stats_timer)
|
||||||
|
del_timer_sync(&net->can.can_stattimer);
|
||||||
|
}
|
||||||
|
|
||||||
/* remove created dev_rcv_lists from still registered CAN devices */
|
/* remove created dev_rcv_lists from still registered CAN devices */
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
@ -903,6 +917,10 @@ static void can_pernet_exit(struct net *net)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
|
kfree(net->can.can_rx_alldev_list);
|
||||||
|
kfree(net->can.can_stats);
|
||||||
|
kfree(net->can.can_pstats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -933,8 +951,6 @@ static struct notifier_block can_netdev_notifier __read_mostly = {
|
|||||||
static struct pernet_operations can_pernet_ops __read_mostly = {
|
static struct pernet_operations can_pernet_ops __read_mostly = {
|
||||||
.init = can_pernet_init,
|
.init = can_pernet_init,
|
||||||
.exit = can_pernet_exit,
|
.exit = can_pernet_exit,
|
||||||
.id = &can_net_id,
|
|
||||||
.size = 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static __init int can_init(void)
|
static __init int can_init(void)
|
||||||
@ -952,14 +968,6 @@ static __init int can_init(void)
|
|||||||
if (!rcv_cache)
|
if (!rcv_cache)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_PROC_FS)) {
|
|
||||||
if (stats_timer) {
|
|
||||||
/* the statistics are updated every second (timer triggered) */
|
|
||||||
setup_timer(&can_stattimer, can_stat_update, 0);
|
|
||||||
mod_timer(&can_stattimer, round_jiffies(jiffies + HZ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
register_pernet_subsys(&can_pernet_ops);
|
register_pernet_subsys(&can_pernet_ops);
|
||||||
|
|
||||||
/* protocol register */
|
/* protocol register */
|
||||||
@ -973,11 +981,6 @@ static __init int can_init(void)
|
|||||||
|
|
||||||
static __exit void can_exit(void)
|
static __exit void can_exit(void)
|
||||||
{
|
{
|
||||||
if (IS_ENABLED(CONFIG_PROC_FS)) {
|
|
||||||
if (stats_timer)
|
|
||||||
del_timer_sync(&can_stattimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* protocol unregister */
|
/* protocol unregister */
|
||||||
dev_remove_pack(&canfd_packet);
|
dev_remove_pack(&canfd_packet);
|
||||||
dev_remove_pack(&can_packet);
|
dev_remove_pack(&can_packet);
|
||||||
|
@ -110,18 +110,9 @@ struct s_pstats {
|
|||||||
unsigned long rcv_entries_max;
|
unsigned long rcv_entries_max;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* receive filters subscribed for 'all' CAN devices */
|
|
||||||
extern struct dev_rcv_lists can_rx_alldev_list;
|
|
||||||
|
|
||||||
/* function prototypes for the CAN networklayer procfs (proc.c) */
|
/* function prototypes for the CAN networklayer procfs (proc.c) */
|
||||||
void can_init_proc(struct net *net);
|
void can_init_proc(struct net *net);
|
||||||
void can_remove_proc(struct net *net);
|
void can_remove_proc(struct net *net);
|
||||||
void can_stat_update(unsigned long data);
|
void can_stat_update(unsigned long data);
|
||||||
|
|
||||||
/* structures and variables from af_can.c needed in proc.c for reading */
|
|
||||||
extern struct timer_list can_stattimer; /* timer for statistics update */
|
|
||||||
extern struct s_stats can_stats; /* packet statistics */
|
|
||||||
extern struct s_pstats can_pstats; /* receive list statistics */
|
|
||||||
extern struct hlist_head can_rx_dev_list; /* rx dispatcher structures */
|
|
||||||
|
|
||||||
#endif /* AF_CAN_H */
|
#endif /* AF_CAN_H */
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* bcm.c - Broadcast Manager to filter/send (cyclic) CAN content
|
* bcm.c - Broadcast Manager to filter/send (cyclic) CAN content
|
||||||
*
|
*
|
||||||
* Copyright (c) 2002-2016 Volkswagen Group Electronic Research
|
* Copyright (c) 2002-2017 Volkswagen Group Electronic Research
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@ -77,7 +77,7 @@
|
|||||||
(CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG) : \
|
(CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG) : \
|
||||||
(CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG))
|
(CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG))
|
||||||
|
|
||||||
#define CAN_BCM_VERSION "20161123"
|
#define CAN_BCM_VERSION "20170425"
|
||||||
|
|
||||||
MODULE_DESCRIPTION("PF_CAN broadcast manager protocol");
|
MODULE_DESCRIPTION("PF_CAN broadcast manager protocol");
|
||||||
MODULE_LICENSE("Dual BSD/GPL");
|
MODULE_LICENSE("Dual BSD/GPL");
|
||||||
@ -118,8 +118,6 @@ struct bcm_op {
|
|||||||
struct net_device *rx_reg_dev;
|
struct net_device *rx_reg_dev;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct proc_dir_entry *proc_dir;
|
|
||||||
|
|
||||||
struct bcm_sock {
|
struct bcm_sock {
|
||||||
struct sock sk;
|
struct sock sk;
|
||||||
int bound;
|
int bound;
|
||||||
@ -149,7 +147,7 @@ static inline ktime_t bcm_timeval_to_ktime(struct bcm_timeval tv)
|
|||||||
/*
|
/*
|
||||||
* procfs functions
|
* procfs functions
|
||||||
*/
|
*/
|
||||||
static char *bcm_proc_getifname(char *result, int ifindex)
|
static char *bcm_proc_getifname(struct net *net, char *result, int ifindex)
|
||||||
{
|
{
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
|
|
||||||
@ -157,7 +155,7 @@ static char *bcm_proc_getifname(char *result, int ifindex)
|
|||||||
return "any";
|
return "any";
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
dev = dev_get_by_index_rcu(&init_net, ifindex);
|
dev = dev_get_by_index_rcu(net, ifindex);
|
||||||
if (dev)
|
if (dev)
|
||||||
strcpy(result, dev->name);
|
strcpy(result, dev->name);
|
||||||
else
|
else
|
||||||
@ -170,7 +168,8 @@ static char *bcm_proc_getifname(char *result, int ifindex)
|
|||||||
static int bcm_proc_show(struct seq_file *m, void *v)
|
static int bcm_proc_show(struct seq_file *m, void *v)
|
||||||
{
|
{
|
||||||
char ifname[IFNAMSIZ];
|
char ifname[IFNAMSIZ];
|
||||||
struct sock *sk = (struct sock *)m->private;
|
struct net *net = m->private;
|
||||||
|
struct sock *sk = (struct sock *)PDE_DATA(m->file->f_inode);
|
||||||
struct bcm_sock *bo = bcm_sk(sk);
|
struct bcm_sock *bo = bcm_sk(sk);
|
||||||
struct bcm_op *op;
|
struct bcm_op *op;
|
||||||
|
|
||||||
@ -178,7 +177,7 @@ static int bcm_proc_show(struct seq_file *m, void *v)
|
|||||||
seq_printf(m, " / sk %pK", sk);
|
seq_printf(m, " / sk %pK", sk);
|
||||||
seq_printf(m, " / bo %pK", bo);
|
seq_printf(m, " / bo %pK", bo);
|
||||||
seq_printf(m, " / dropped %lu", bo->dropped_usr_msgs);
|
seq_printf(m, " / dropped %lu", bo->dropped_usr_msgs);
|
||||||
seq_printf(m, " / bound %s", bcm_proc_getifname(ifname, bo->ifindex));
|
seq_printf(m, " / bound %s", bcm_proc_getifname(net, ifname, bo->ifindex));
|
||||||
seq_printf(m, " <<<\n");
|
seq_printf(m, " <<<\n");
|
||||||
|
|
||||||
list_for_each_entry(op, &bo->rx_ops, list) {
|
list_for_each_entry(op, &bo->rx_ops, list) {
|
||||||
@ -190,7 +189,7 @@ static int bcm_proc_show(struct seq_file *m, void *v)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
seq_printf(m, "rx_op: %03X %-5s ", op->can_id,
|
seq_printf(m, "rx_op: %03X %-5s ", op->can_id,
|
||||||
bcm_proc_getifname(ifname, op->ifindex));
|
bcm_proc_getifname(net, ifname, op->ifindex));
|
||||||
|
|
||||||
if (op->flags & CAN_FD_FRAME)
|
if (op->flags & CAN_FD_FRAME)
|
||||||
seq_printf(m, "(%u)", op->nframes);
|
seq_printf(m, "(%u)", op->nframes);
|
||||||
@ -219,7 +218,7 @@ static int bcm_proc_show(struct seq_file *m, void *v)
|
|||||||
list_for_each_entry(op, &bo->tx_ops, list) {
|
list_for_each_entry(op, &bo->tx_ops, list) {
|
||||||
|
|
||||||
seq_printf(m, "tx_op: %03X %s ", op->can_id,
|
seq_printf(m, "tx_op: %03X %s ", op->can_id,
|
||||||
bcm_proc_getifname(ifname, op->ifindex));
|
bcm_proc_getifname(net, ifname, op->ifindex));
|
||||||
|
|
||||||
if (op->flags & CAN_FD_FRAME)
|
if (op->flags & CAN_FD_FRAME)
|
||||||
seq_printf(m, "(%u) ", op->nframes);
|
seq_printf(m, "(%u) ", op->nframes);
|
||||||
@ -242,7 +241,7 @@ static int bcm_proc_show(struct seq_file *m, void *v)
|
|||||||
|
|
||||||
static int bcm_proc_open(struct inode *inode, struct file *file)
|
static int bcm_proc_open(struct inode *inode, struct file *file)
|
||||||
{
|
{
|
||||||
return single_open(file, bcm_proc_show, PDE_DATA(inode));
|
return single_open_net(inode, file, bcm_proc_show);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct file_operations bcm_proc_fops = {
|
static const struct file_operations bcm_proc_fops = {
|
||||||
@ -267,7 +266,7 @@ static void bcm_can_tx(struct bcm_op *op)
|
|||||||
if (!op->ifindex)
|
if (!op->ifindex)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dev = dev_get_by_index(&init_net, op->ifindex);
|
dev = dev_get_by_index(sock_net(op->sk), op->ifindex);
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
/* RFC: should this bcm_op remove itself here? */
|
/* RFC: should this bcm_op remove itself here? */
|
||||||
return;
|
return;
|
||||||
@ -764,7 +763,7 @@ static void bcm_remove_op(struct bcm_op *op)
|
|||||||
static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
|
static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
|
||||||
{
|
{
|
||||||
if (op->rx_reg_dev == dev) {
|
if (op->rx_reg_dev == dev) {
|
||||||
can_rx_unregister(&init_net, dev, op->can_id,
|
can_rx_unregister(dev_net(dev), dev, op->can_id,
|
||||||
REGMASK(op->can_id), bcm_rx_handler, op);
|
REGMASK(op->can_id), bcm_rx_handler, op);
|
||||||
|
|
||||||
/* mark as removed subscription */
|
/* mark as removed subscription */
|
||||||
@ -800,7 +799,7 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
|
|||||||
if (op->rx_reg_dev) {
|
if (op->rx_reg_dev) {
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
|
|
||||||
dev = dev_get_by_index(&init_net,
|
dev = dev_get_by_index(sock_net(op->sk),
|
||||||
op->ifindex);
|
op->ifindex);
|
||||||
if (dev) {
|
if (dev) {
|
||||||
bcm_rx_unreg(dev, op);
|
bcm_rx_unreg(dev, op);
|
||||||
@ -808,7 +807,8 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
can_rx_unregister(&init_net, NULL, op->can_id,
|
can_rx_unregister(sock_net(op->sk), NULL,
|
||||||
|
op->can_id,
|
||||||
REGMASK(op->can_id),
|
REGMASK(op->can_id),
|
||||||
bcm_rx_handler, op);
|
bcm_rx_handler, op);
|
||||||
|
|
||||||
@ -1220,9 +1220,9 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
|
|||||||
if (ifindex) {
|
if (ifindex) {
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
|
|
||||||
dev = dev_get_by_index(&init_net, ifindex);
|
dev = dev_get_by_index(sock_net(sk), ifindex);
|
||||||
if (dev) {
|
if (dev) {
|
||||||
err = can_rx_register(&init_net, dev,
|
err = can_rx_register(sock_net(sk), dev,
|
||||||
op->can_id,
|
op->can_id,
|
||||||
REGMASK(op->can_id),
|
REGMASK(op->can_id),
|
||||||
bcm_rx_handler, op,
|
bcm_rx_handler, op,
|
||||||
@ -1233,7 +1233,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else
|
} else
|
||||||
err = can_rx_register(&init_net, NULL, op->can_id,
|
err = can_rx_register(sock_net(sk), NULL, op->can_id,
|
||||||
REGMASK(op->can_id),
|
REGMASK(op->can_id),
|
||||||
bcm_rx_handler, op, "bcm", sk);
|
bcm_rx_handler, op, "bcm", sk);
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -1273,7 +1273,7 @@ static int bcm_tx_send(struct msghdr *msg, int ifindex, struct sock *sk,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev = dev_get_by_index(&init_net, ifindex);
|
dev = dev_get_by_index(sock_net(sk), ifindex);
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
@ -1338,7 +1338,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
|
|||||||
if (ifindex) {
|
if (ifindex) {
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
|
|
||||||
dev = dev_get_by_index(&init_net, ifindex);
|
dev = dev_get_by_index(sock_net(sk), ifindex);
|
||||||
if (!dev)
|
if (!dev)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
@ -1419,7 +1419,7 @@ static int bcm_notifier(struct notifier_block *nb, unsigned long msg,
|
|||||||
struct bcm_op *op;
|
struct bcm_op *op;
|
||||||
int notify_enodev = 0;
|
int notify_enodev = 0;
|
||||||
|
|
||||||
if (!net_eq(dev_net(dev), &init_net))
|
if (!net_eq(dev_net(dev), sock_net(sk)))
|
||||||
return NOTIFY_DONE;
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
if (dev->type != ARPHRD_CAN)
|
if (dev->type != ARPHRD_CAN)
|
||||||
@ -1491,6 +1491,7 @@ static int bcm_init(struct sock *sk)
|
|||||||
static int bcm_release(struct socket *sock)
|
static int bcm_release(struct socket *sock)
|
||||||
{
|
{
|
||||||
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
||||||
|
struct net *net = sock_net(sk);
|
||||||
struct bcm_sock *bo;
|
struct bcm_sock *bo;
|
||||||
struct bcm_op *op, *next;
|
struct bcm_op *op, *next;
|
||||||
|
|
||||||
@ -1522,14 +1523,14 @@ static int bcm_release(struct socket *sock)
|
|||||||
if (op->rx_reg_dev) {
|
if (op->rx_reg_dev) {
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
|
|
||||||
dev = dev_get_by_index(&init_net, op->ifindex);
|
dev = dev_get_by_index(net, op->ifindex);
|
||||||
if (dev) {
|
if (dev) {
|
||||||
bcm_rx_unreg(dev, op);
|
bcm_rx_unreg(dev, op);
|
||||||
dev_put(dev);
|
dev_put(dev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
can_rx_unregister(&init_net, NULL, op->can_id,
|
can_rx_unregister(net, NULL, op->can_id,
|
||||||
REGMASK(op->can_id),
|
REGMASK(op->can_id),
|
||||||
bcm_rx_handler, op);
|
bcm_rx_handler, op);
|
||||||
|
|
||||||
@ -1537,8 +1538,8 @@ static int bcm_release(struct socket *sock)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* remove procfs entry */
|
/* remove procfs entry */
|
||||||
if (proc_dir && bo->bcm_proc_read)
|
if (net->can.bcmproc_dir && bo->bcm_proc_read)
|
||||||
remove_proc_entry(bo->procname, proc_dir);
|
remove_proc_entry(bo->procname, net->can.bcmproc_dir);
|
||||||
|
|
||||||
/* remove device reference */
|
/* remove device reference */
|
||||||
if (bo->bound) {
|
if (bo->bound) {
|
||||||
@ -1561,6 +1562,7 @@ static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len,
|
|||||||
struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
|
struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
|
||||||
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
||||||
struct bcm_sock *bo = bcm_sk(sk);
|
struct bcm_sock *bo = bcm_sk(sk);
|
||||||
|
struct net *net = sock_net(sk);
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if (len < sizeof(*addr))
|
if (len < sizeof(*addr))
|
||||||
@ -1577,7 +1579,7 @@ static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len,
|
|||||||
if (addr->can_ifindex) {
|
if (addr->can_ifindex) {
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
|
|
||||||
dev = dev_get_by_index(&init_net, addr->can_ifindex);
|
dev = dev_get_by_index(net, addr->can_ifindex);
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
ret = -ENODEV;
|
ret = -ENODEV;
|
||||||
goto fail;
|
goto fail;
|
||||||
@ -1596,11 +1598,11 @@ static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len,
|
|||||||
bo->ifindex = 0;
|
bo->ifindex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proc_dir) {
|
if (net->can.bcmproc_dir) {
|
||||||
/* unique socket address as filename */
|
/* unique socket address as filename */
|
||||||
sprintf(bo->procname, "%lu", sock_i_ino(sk));
|
sprintf(bo->procname, "%lu", sock_i_ino(sk));
|
||||||
bo->bcm_proc_read = proc_create_data(bo->procname, 0644,
|
bo->bcm_proc_read = proc_create_data(bo->procname, 0644,
|
||||||
proc_dir,
|
net->can.bcmproc_dir,
|
||||||
&bcm_proc_fops, sk);
|
&bcm_proc_fops, sk);
|
||||||
if (!bo->bcm_proc_read) {
|
if (!bo->bcm_proc_read) {
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
@ -1687,6 +1689,31 @@ static const struct can_proto bcm_can_proto = {
|
|||||||
.prot = &bcm_proto,
|
.prot = &bcm_proto,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int canbcm_pernet_init(struct net *net)
|
||||||
|
{
|
||||||
|
/* create /proc/net/can-bcm directory */
|
||||||
|
if (IS_ENABLED(CONFIG_PROC_FS)) {
|
||||||
|
net->can.bcmproc_dir =
|
||||||
|
proc_net_mkdir(net, "can-bcm", net->proc_net);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canbcm_pernet_exit(struct net *net)
|
||||||
|
{
|
||||||
|
/* remove /proc/net/can-bcm directory */
|
||||||
|
if (IS_ENABLED(CONFIG_PROC_FS)) {
|
||||||
|
if (net->can.bcmproc_dir)
|
||||||
|
remove_proc_entry("can-bcm", net->proc_net);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct pernet_operations canbcm_pernet_ops __read_mostly = {
|
||||||
|
.init = canbcm_pernet_init,
|
||||||
|
.exit = canbcm_pernet_exit,
|
||||||
|
};
|
||||||
|
|
||||||
static int __init bcm_module_init(void)
|
static int __init bcm_module_init(void)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@ -1699,17 +1726,14 @@ static int __init bcm_module_init(void)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* create /proc/net/can-bcm directory */
|
register_pernet_subsys(&canbcm_pernet_ops);
|
||||||
proc_dir = proc_mkdir("can-bcm", init_net.proc_net);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __exit bcm_module_exit(void)
|
static void __exit bcm_module_exit(void)
|
||||||
{
|
{
|
||||||
can_proto_unregister(&bcm_can_proto);
|
can_proto_unregister(&bcm_can_proto);
|
||||||
|
unregister_pernet_subsys(&canbcm_pernet_ops);
|
||||||
if (proc_dir)
|
|
||||||
remove_proc_entry("can-bcm", init_net.proc_net);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(bcm_module_init);
|
module_init(bcm_module_init);
|
||||||
|
72
net/can/gw.c
72
net/can/gw.c
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* gw.c - CAN frame Gateway/Router/Bridge with netlink interface
|
* gw.c - CAN frame Gateway/Router/Bridge with netlink interface
|
||||||
*
|
*
|
||||||
* Copyright (c) 2011 Volkswagen Group Electronic Research
|
* Copyright (c) 2017 Volkswagen Group Electronic Research
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@ -59,7 +59,7 @@
|
|||||||
#include <net/net_namespace.h>
|
#include <net/net_namespace.h>
|
||||||
#include <net/sock.h>
|
#include <net/sock.h>
|
||||||
|
|
||||||
#define CAN_GW_VERSION "20130117"
|
#define CAN_GW_VERSION "20170425"
|
||||||
#define CAN_GW_NAME "can-gw"
|
#define CAN_GW_NAME "can-gw"
|
||||||
|
|
||||||
MODULE_DESCRIPTION("PF_CAN netlink gateway");
|
MODULE_DESCRIPTION("PF_CAN netlink gateway");
|
||||||
@ -79,9 +79,7 @@ MODULE_PARM_DESC(max_hops,
|
|||||||
__stringify(CGW_MAX_HOPS) " hops, "
|
__stringify(CGW_MAX_HOPS) " hops, "
|
||||||
"default: " __stringify(CGW_DEFAULT_HOPS) ")");
|
"default: " __stringify(CGW_DEFAULT_HOPS) ")");
|
||||||
|
|
||||||
static HLIST_HEAD(cgw_list);
|
|
||||||
static struct notifier_block notifier;
|
static struct notifier_block notifier;
|
||||||
|
|
||||||
static struct kmem_cache *cgw_cache __read_mostly;
|
static struct kmem_cache *cgw_cache __read_mostly;
|
||||||
|
|
||||||
/* structure that contains the (on-the-fly) CAN frame modifications */
|
/* structure that contains the (on-the-fly) CAN frame modifications */
|
||||||
@ -438,16 +436,16 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data)
|
|||||||
gwj->handled_frames++;
|
gwj->handled_frames++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int cgw_register_filter(struct cgw_job *gwj)
|
static inline int cgw_register_filter(struct net *net, struct cgw_job *gwj)
|
||||||
{
|
{
|
||||||
return can_rx_register(&init_net, gwj->src.dev, gwj->ccgw.filter.can_id,
|
return can_rx_register(net, gwj->src.dev, gwj->ccgw.filter.can_id,
|
||||||
gwj->ccgw.filter.can_mask, can_can_gw_rcv,
|
gwj->ccgw.filter.can_mask, can_can_gw_rcv,
|
||||||
gwj, "gw", NULL);
|
gwj, "gw", NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void cgw_unregister_filter(struct cgw_job *gwj)
|
static inline void cgw_unregister_filter(struct net *net, struct cgw_job *gwj)
|
||||||
{
|
{
|
||||||
can_rx_unregister(&init_net, gwj->src.dev, gwj->ccgw.filter.can_id,
|
can_rx_unregister(net, gwj->src.dev, gwj->ccgw.filter.can_id,
|
||||||
gwj->ccgw.filter.can_mask, can_can_gw_rcv, gwj);
|
gwj->ccgw.filter.can_mask, can_can_gw_rcv, gwj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,9 +453,8 @@ static int cgw_notifier(struct notifier_block *nb,
|
|||||||
unsigned long msg, void *ptr)
|
unsigned long msg, void *ptr)
|
||||||
{
|
{
|
||||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||||
|
struct net *net = dev_net(dev);
|
||||||
|
|
||||||
if (!net_eq(dev_net(dev), &init_net))
|
|
||||||
return NOTIFY_DONE;
|
|
||||||
if (dev->type != ARPHRD_CAN)
|
if (dev->type != ARPHRD_CAN)
|
||||||
return NOTIFY_DONE;
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
@ -468,11 +465,11 @@ static int cgw_notifier(struct notifier_block *nb,
|
|||||||
|
|
||||||
ASSERT_RTNL();
|
ASSERT_RTNL();
|
||||||
|
|
||||||
hlist_for_each_entry_safe(gwj, nx, &cgw_list, list) {
|
hlist_for_each_entry_safe(gwj, nx, &net->can.cgw_list, list) {
|
||||||
|
|
||||||
if (gwj->src.dev == dev || gwj->dst.dev == dev) {
|
if (gwj->src.dev == dev || gwj->dst.dev == dev) {
|
||||||
hlist_del(&gwj->list);
|
hlist_del(&gwj->list);
|
||||||
cgw_unregister_filter(gwj);
|
cgw_unregister_filter(net, gwj);
|
||||||
kmem_cache_free(cgw_cache, gwj);
|
kmem_cache_free(cgw_cache, gwj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,12 +589,13 @@ cancel:
|
|||||||
/* Dump information about all CAN gateway jobs, in response to RTM_GETROUTE */
|
/* Dump information about all CAN gateway jobs, in response to RTM_GETROUTE */
|
||||||
static int cgw_dump_jobs(struct sk_buff *skb, struct netlink_callback *cb)
|
static int cgw_dump_jobs(struct sk_buff *skb, struct netlink_callback *cb)
|
||||||
{
|
{
|
||||||
|
struct net *net = sock_net(skb->sk);
|
||||||
struct cgw_job *gwj = NULL;
|
struct cgw_job *gwj = NULL;
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
int s_idx = cb->args[0];
|
int s_idx = cb->args[0];
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
hlist_for_each_entry_rcu(gwj, &cgw_list, list) {
|
hlist_for_each_entry_rcu(gwj, &net->can.cgw_list, list) {
|
||||||
if (idx < s_idx)
|
if (idx < s_idx)
|
||||||
goto cont;
|
goto cont;
|
||||||
|
|
||||||
@ -812,6 +810,7 @@ static int cgw_parse_attr(struct nlmsghdr *nlh, struct cf_mod *mod,
|
|||||||
static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
{
|
{
|
||||||
|
struct net *net = sock_net(skb->sk);
|
||||||
struct rtcanmsg *r;
|
struct rtcanmsg *r;
|
||||||
struct cgw_job *gwj;
|
struct cgw_job *gwj;
|
||||||
struct cf_mod mod;
|
struct cf_mod mod;
|
||||||
@ -842,7 +841,7 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
ASSERT_RTNL();
|
ASSERT_RTNL();
|
||||||
|
|
||||||
/* check for updating an existing job with identical uid */
|
/* check for updating an existing job with identical uid */
|
||||||
hlist_for_each_entry(gwj, &cgw_list, list) {
|
hlist_for_each_entry(gwj, &net->can.cgw_list, list) {
|
||||||
|
|
||||||
if (gwj->mod.uid != mod.uid)
|
if (gwj->mod.uid != mod.uid)
|
||||||
continue;
|
continue;
|
||||||
@ -880,7 +879,7 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
|
|
||||||
err = -ENODEV;
|
err = -ENODEV;
|
||||||
|
|
||||||
gwj->src.dev = __dev_get_by_index(&init_net, gwj->ccgw.src_idx);
|
gwj->src.dev = __dev_get_by_index(net, gwj->ccgw.src_idx);
|
||||||
|
|
||||||
if (!gwj->src.dev)
|
if (!gwj->src.dev)
|
||||||
goto out;
|
goto out;
|
||||||
@ -888,7 +887,7 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
if (gwj->src.dev->type != ARPHRD_CAN)
|
if (gwj->src.dev->type != ARPHRD_CAN)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
gwj->dst.dev = __dev_get_by_index(&init_net, gwj->ccgw.dst_idx);
|
gwj->dst.dev = __dev_get_by_index(net, gwj->ccgw.dst_idx);
|
||||||
|
|
||||||
if (!gwj->dst.dev)
|
if (!gwj->dst.dev)
|
||||||
goto out;
|
goto out;
|
||||||
@ -898,9 +897,9 @@ static int cgw_create_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
|
|
||||||
ASSERT_RTNL();
|
ASSERT_RTNL();
|
||||||
|
|
||||||
err = cgw_register_filter(gwj);
|
err = cgw_register_filter(net, gwj);
|
||||||
if (!err)
|
if (!err)
|
||||||
hlist_add_head_rcu(&gwj->list, &cgw_list);
|
hlist_add_head_rcu(&gwj->list, &net->can.cgw_list);
|
||||||
out:
|
out:
|
||||||
if (err)
|
if (err)
|
||||||
kmem_cache_free(cgw_cache, gwj);
|
kmem_cache_free(cgw_cache, gwj);
|
||||||
@ -908,16 +907,16 @@ out:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cgw_remove_all_jobs(void)
|
static void cgw_remove_all_jobs(struct net *net)
|
||||||
{
|
{
|
||||||
struct cgw_job *gwj = NULL;
|
struct cgw_job *gwj = NULL;
|
||||||
struct hlist_node *nx;
|
struct hlist_node *nx;
|
||||||
|
|
||||||
ASSERT_RTNL();
|
ASSERT_RTNL();
|
||||||
|
|
||||||
hlist_for_each_entry_safe(gwj, nx, &cgw_list, list) {
|
hlist_for_each_entry_safe(gwj, nx, &net->can.cgw_list, list) {
|
||||||
hlist_del(&gwj->list);
|
hlist_del(&gwj->list);
|
||||||
cgw_unregister_filter(gwj);
|
cgw_unregister_filter(net, gwj);
|
||||||
kmem_cache_free(cgw_cache, gwj);
|
kmem_cache_free(cgw_cache, gwj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -925,6 +924,7 @@ static void cgw_remove_all_jobs(void)
|
|||||||
static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||||
struct netlink_ext_ack *extack)
|
struct netlink_ext_ack *extack)
|
||||||
{
|
{
|
||||||
|
struct net *net = sock_net(skb->sk);
|
||||||
struct cgw_job *gwj = NULL;
|
struct cgw_job *gwj = NULL;
|
||||||
struct hlist_node *nx;
|
struct hlist_node *nx;
|
||||||
struct rtcanmsg *r;
|
struct rtcanmsg *r;
|
||||||
@ -953,7 +953,7 @@ static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
|
|
||||||
/* two interface indices both set to 0 => remove all entries */
|
/* two interface indices both set to 0 => remove all entries */
|
||||||
if (!ccgw.src_idx && !ccgw.dst_idx) {
|
if (!ccgw.src_idx && !ccgw.dst_idx) {
|
||||||
cgw_remove_all_jobs();
|
cgw_remove_all_jobs(net);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -962,7 +962,7 @@ static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
ASSERT_RTNL();
|
ASSERT_RTNL();
|
||||||
|
|
||||||
/* remove only the first matching entry */
|
/* remove only the first matching entry */
|
||||||
hlist_for_each_entry_safe(gwj, nx, &cgw_list, list) {
|
hlist_for_each_entry_safe(gwj, nx, &net->can.cgw_list, list) {
|
||||||
|
|
||||||
if (gwj->flags != r->flags)
|
if (gwj->flags != r->flags)
|
||||||
continue;
|
continue;
|
||||||
@ -985,7 +985,7 @@ static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
hlist_del(&gwj->list);
|
hlist_del(&gwj->list);
|
||||||
cgw_unregister_filter(gwj);
|
cgw_unregister_filter(net, gwj);
|
||||||
kmem_cache_free(cgw_cache, gwj);
|
kmem_cache_free(cgw_cache, gwj);
|
||||||
err = 0;
|
err = 0;
|
||||||
break;
|
break;
|
||||||
@ -994,6 +994,24 @@ static int cgw_remove_job(struct sk_buff *skb, struct nlmsghdr *nlh,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int __net_init cangw_pernet_init(struct net *net)
|
||||||
|
{
|
||||||
|
INIT_HLIST_HEAD(&net->can.cgw_list);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __net_exit cangw_pernet_exit(struct net *net)
|
||||||
|
{
|
||||||
|
rtnl_lock();
|
||||||
|
cgw_remove_all_jobs(net);
|
||||||
|
rtnl_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct pernet_operations cangw_pernet_ops = {
|
||||||
|
.init = cangw_pernet_init,
|
||||||
|
.exit = cangw_pernet_exit,
|
||||||
|
};
|
||||||
|
|
||||||
static __init int cgw_module_init(void)
|
static __init int cgw_module_init(void)
|
||||||
{
|
{
|
||||||
/* sanitize given module parameter */
|
/* sanitize given module parameter */
|
||||||
@ -1002,6 +1020,7 @@ static __init int cgw_module_init(void)
|
|||||||
pr_info("can: netlink gateway (rev " CAN_GW_VERSION ") max_hops=%d\n",
|
pr_info("can: netlink gateway (rev " CAN_GW_VERSION ") max_hops=%d\n",
|
||||||
max_hops);
|
max_hops);
|
||||||
|
|
||||||
|
register_pernet_subsys(&cangw_pernet_ops);
|
||||||
cgw_cache = kmem_cache_create("can_gw", sizeof(struct cgw_job),
|
cgw_cache = kmem_cache_create("can_gw", sizeof(struct cgw_job),
|
||||||
0, 0, NULL);
|
0, 0, NULL);
|
||||||
|
|
||||||
@ -1031,10 +1050,7 @@ static __exit void cgw_module_exit(void)
|
|||||||
|
|
||||||
unregister_netdevice_notifier(¬ifier);
|
unregister_netdevice_notifier(¬ifier);
|
||||||
|
|
||||||
rtnl_lock();
|
unregister_pernet_subsys(&cangw_pernet_ops);
|
||||||
cgw_remove_all_jobs();
|
|
||||||
rtnl_unlock();
|
|
||||||
|
|
||||||
rcu_barrier(); /* Wait for completion of call_rcu()'s */
|
rcu_barrier(); /* Wait for completion of call_rcu()'s */
|
||||||
|
|
||||||
kmem_cache_destroy(cgw_cache);
|
kmem_cache_destroy(cgw_cache);
|
||||||
|
141
net/can/proc.c
141
net/can/proc.c
@ -75,21 +75,23 @@ static const char rx_list_name[][8] = {
|
|||||||
* af_can statistics stuff
|
* af_can statistics stuff
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void can_init_stats(void)
|
static void can_init_stats(struct net *net)
|
||||||
{
|
{
|
||||||
|
struct s_stats *can_stats = net->can.can_stats;
|
||||||
|
struct s_pstats *can_pstats = net->can.can_pstats;
|
||||||
/*
|
/*
|
||||||
* This memset function is called from a timer context (when
|
* This memset function is called from a timer context (when
|
||||||
* can_stattimer is active which is the default) OR in a process
|
* can_stattimer is active which is the default) OR in a process
|
||||||
* context (reading the proc_fs when can_stattimer is disabled).
|
* context (reading the proc_fs when can_stattimer is disabled).
|
||||||
*/
|
*/
|
||||||
memset(&can_stats, 0, sizeof(can_stats));
|
memset(can_stats, 0, sizeof(struct s_stats));
|
||||||
can_stats.jiffies_init = jiffies;
|
can_stats->jiffies_init = jiffies;
|
||||||
|
|
||||||
can_pstats.stats_reset++;
|
can_pstats->stats_reset++;
|
||||||
|
|
||||||
if (user_reset) {
|
if (user_reset) {
|
||||||
user_reset = 0;
|
user_reset = 0;
|
||||||
can_pstats.user_reset++;
|
can_pstats->user_reset++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,64 +117,66 @@ static unsigned long calc_rate(unsigned long oldjif, unsigned long newjif,
|
|||||||
|
|
||||||
void can_stat_update(unsigned long data)
|
void can_stat_update(unsigned long data)
|
||||||
{
|
{
|
||||||
|
struct net *net = (struct net *)data;
|
||||||
|
struct s_stats *can_stats = net->can.can_stats;
|
||||||
unsigned long j = jiffies; /* snapshot */
|
unsigned long j = jiffies; /* snapshot */
|
||||||
|
|
||||||
/* restart counting in timer context on user request */
|
/* restart counting in timer context on user request */
|
||||||
if (user_reset)
|
if (user_reset)
|
||||||
can_init_stats();
|
can_init_stats(net);
|
||||||
|
|
||||||
/* restart counting on jiffies overflow */
|
/* restart counting on jiffies overflow */
|
||||||
if (j < can_stats.jiffies_init)
|
if (j < can_stats->jiffies_init)
|
||||||
can_init_stats();
|
can_init_stats(net);
|
||||||
|
|
||||||
/* prevent overflow in calc_rate() */
|
/* prevent overflow in calc_rate() */
|
||||||
if (can_stats.rx_frames > (ULONG_MAX / HZ))
|
if (can_stats->rx_frames > (ULONG_MAX / HZ))
|
||||||
can_init_stats();
|
can_init_stats(net);
|
||||||
|
|
||||||
/* prevent overflow in calc_rate() */
|
/* prevent overflow in calc_rate() */
|
||||||
if (can_stats.tx_frames > (ULONG_MAX / HZ))
|
if (can_stats->tx_frames > (ULONG_MAX / HZ))
|
||||||
can_init_stats();
|
can_init_stats(net);
|
||||||
|
|
||||||
/* matches overflow - very improbable */
|
/* matches overflow - very improbable */
|
||||||
if (can_stats.matches > (ULONG_MAX / 100))
|
if (can_stats->matches > (ULONG_MAX / 100))
|
||||||
can_init_stats();
|
can_init_stats(net);
|
||||||
|
|
||||||
/* calc total values */
|
/* calc total values */
|
||||||
if (can_stats.rx_frames)
|
if (can_stats->rx_frames)
|
||||||
can_stats.total_rx_match_ratio = (can_stats.matches * 100) /
|
can_stats->total_rx_match_ratio = (can_stats->matches * 100) /
|
||||||
can_stats.rx_frames;
|
can_stats->rx_frames;
|
||||||
|
|
||||||
can_stats.total_tx_rate = calc_rate(can_stats.jiffies_init, j,
|
can_stats->total_tx_rate = calc_rate(can_stats->jiffies_init, j,
|
||||||
can_stats.tx_frames);
|
can_stats->tx_frames);
|
||||||
can_stats.total_rx_rate = calc_rate(can_stats.jiffies_init, j,
|
can_stats->total_rx_rate = calc_rate(can_stats->jiffies_init, j,
|
||||||
can_stats.rx_frames);
|
can_stats->rx_frames);
|
||||||
|
|
||||||
/* calc current values */
|
/* calc current values */
|
||||||
if (can_stats.rx_frames_delta)
|
if (can_stats->rx_frames_delta)
|
||||||
can_stats.current_rx_match_ratio =
|
can_stats->current_rx_match_ratio =
|
||||||
(can_stats.matches_delta * 100) /
|
(can_stats->matches_delta * 100) /
|
||||||
can_stats.rx_frames_delta;
|
can_stats->rx_frames_delta;
|
||||||
|
|
||||||
can_stats.current_tx_rate = calc_rate(0, HZ, can_stats.tx_frames_delta);
|
can_stats->current_tx_rate = calc_rate(0, HZ, can_stats->tx_frames_delta);
|
||||||
can_stats.current_rx_rate = calc_rate(0, HZ, can_stats.rx_frames_delta);
|
can_stats->current_rx_rate = calc_rate(0, HZ, can_stats->rx_frames_delta);
|
||||||
|
|
||||||
/* check / update maximum values */
|
/* check / update maximum values */
|
||||||
if (can_stats.max_tx_rate < can_stats.current_tx_rate)
|
if (can_stats->max_tx_rate < can_stats->current_tx_rate)
|
||||||
can_stats.max_tx_rate = can_stats.current_tx_rate;
|
can_stats->max_tx_rate = can_stats->current_tx_rate;
|
||||||
|
|
||||||
if (can_stats.max_rx_rate < can_stats.current_rx_rate)
|
if (can_stats->max_rx_rate < can_stats->current_rx_rate)
|
||||||
can_stats.max_rx_rate = can_stats.current_rx_rate;
|
can_stats->max_rx_rate = can_stats->current_rx_rate;
|
||||||
|
|
||||||
if (can_stats.max_rx_match_ratio < can_stats.current_rx_match_ratio)
|
if (can_stats->max_rx_match_ratio < can_stats->current_rx_match_ratio)
|
||||||
can_stats.max_rx_match_ratio = can_stats.current_rx_match_ratio;
|
can_stats->max_rx_match_ratio = can_stats->current_rx_match_ratio;
|
||||||
|
|
||||||
/* clear values for 'current rate' calculation */
|
/* clear values for 'current rate' calculation */
|
||||||
can_stats.tx_frames_delta = 0;
|
can_stats->tx_frames_delta = 0;
|
||||||
can_stats.rx_frames_delta = 0;
|
can_stats->rx_frames_delta = 0;
|
||||||
can_stats.matches_delta = 0;
|
can_stats->matches_delta = 0;
|
||||||
|
|
||||||
/* restart timer (one second) */
|
/* restart timer (one second) */
|
||||||
mod_timer(&can_stattimer, round_jiffies(jiffies + HZ));
|
mod_timer(&net->can.can_stattimer, round_jiffies(jiffies + HZ));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -206,57 +210,61 @@ static void can_print_recv_banner(struct seq_file *m)
|
|||||||
|
|
||||||
static int can_stats_proc_show(struct seq_file *m, void *v)
|
static int can_stats_proc_show(struct seq_file *m, void *v)
|
||||||
{
|
{
|
||||||
|
struct net *net = m->private;
|
||||||
|
struct s_stats *can_stats = net->can.can_stats;
|
||||||
|
struct s_pstats *can_pstats = net->can.can_pstats;
|
||||||
|
|
||||||
seq_putc(m, '\n');
|
seq_putc(m, '\n');
|
||||||
seq_printf(m, " %8ld transmitted frames (TXF)\n", can_stats.tx_frames);
|
seq_printf(m, " %8ld transmitted frames (TXF)\n", can_stats->tx_frames);
|
||||||
seq_printf(m, " %8ld received frames (RXF)\n", can_stats.rx_frames);
|
seq_printf(m, " %8ld received frames (RXF)\n", can_stats->rx_frames);
|
||||||
seq_printf(m, " %8ld matched frames (RXMF)\n", can_stats.matches);
|
seq_printf(m, " %8ld matched frames (RXMF)\n", can_stats->matches);
|
||||||
|
|
||||||
seq_putc(m, '\n');
|
seq_putc(m, '\n');
|
||||||
|
|
||||||
if (can_stattimer.function == can_stat_update) {
|
if (net->can.can_stattimer.function == can_stat_update) {
|
||||||
seq_printf(m, " %8ld %% total match ratio (RXMR)\n",
|
seq_printf(m, " %8ld %% total match ratio (RXMR)\n",
|
||||||
can_stats.total_rx_match_ratio);
|
can_stats->total_rx_match_ratio);
|
||||||
|
|
||||||
seq_printf(m, " %8ld frames/s total tx rate (TXR)\n",
|
seq_printf(m, " %8ld frames/s total tx rate (TXR)\n",
|
||||||
can_stats.total_tx_rate);
|
can_stats->total_tx_rate);
|
||||||
seq_printf(m, " %8ld frames/s total rx rate (RXR)\n",
|
seq_printf(m, " %8ld frames/s total rx rate (RXR)\n",
|
||||||
can_stats.total_rx_rate);
|
can_stats->total_rx_rate);
|
||||||
|
|
||||||
seq_putc(m, '\n');
|
seq_putc(m, '\n');
|
||||||
|
|
||||||
seq_printf(m, " %8ld %% current match ratio (CRXMR)\n",
|
seq_printf(m, " %8ld %% current match ratio (CRXMR)\n",
|
||||||
can_stats.current_rx_match_ratio);
|
can_stats->current_rx_match_ratio);
|
||||||
|
|
||||||
seq_printf(m, " %8ld frames/s current tx rate (CTXR)\n",
|
seq_printf(m, " %8ld frames/s current tx rate (CTXR)\n",
|
||||||
can_stats.current_tx_rate);
|
can_stats->current_tx_rate);
|
||||||
seq_printf(m, " %8ld frames/s current rx rate (CRXR)\n",
|
seq_printf(m, " %8ld frames/s current rx rate (CRXR)\n",
|
||||||
can_stats.current_rx_rate);
|
can_stats->current_rx_rate);
|
||||||
|
|
||||||
seq_putc(m, '\n');
|
seq_putc(m, '\n');
|
||||||
|
|
||||||
seq_printf(m, " %8ld %% max match ratio (MRXMR)\n",
|
seq_printf(m, " %8ld %% max match ratio (MRXMR)\n",
|
||||||
can_stats.max_rx_match_ratio);
|
can_stats->max_rx_match_ratio);
|
||||||
|
|
||||||
seq_printf(m, " %8ld frames/s max tx rate (MTXR)\n",
|
seq_printf(m, " %8ld frames/s max tx rate (MTXR)\n",
|
||||||
can_stats.max_tx_rate);
|
can_stats->max_tx_rate);
|
||||||
seq_printf(m, " %8ld frames/s max rx rate (MRXR)\n",
|
seq_printf(m, " %8ld frames/s max rx rate (MRXR)\n",
|
||||||
can_stats.max_rx_rate);
|
can_stats->max_rx_rate);
|
||||||
|
|
||||||
seq_putc(m, '\n');
|
seq_putc(m, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
seq_printf(m, " %8ld current receive list entries (CRCV)\n",
|
seq_printf(m, " %8ld current receive list entries (CRCV)\n",
|
||||||
can_pstats.rcv_entries);
|
can_pstats->rcv_entries);
|
||||||
seq_printf(m, " %8ld maximum receive list entries (MRCV)\n",
|
seq_printf(m, " %8ld maximum receive list entries (MRCV)\n",
|
||||||
can_pstats.rcv_entries_max);
|
can_pstats->rcv_entries_max);
|
||||||
|
|
||||||
if (can_pstats.stats_reset)
|
if (can_pstats->stats_reset)
|
||||||
seq_printf(m, "\n %8ld statistic resets (STR)\n",
|
seq_printf(m, "\n %8ld statistic resets (STR)\n",
|
||||||
can_pstats.stats_reset);
|
can_pstats->stats_reset);
|
||||||
|
|
||||||
if (can_pstats.user_reset)
|
if (can_pstats->user_reset)
|
||||||
seq_printf(m, " %8ld user statistic resets (USTR)\n",
|
seq_printf(m, " %8ld user statistic resets (USTR)\n",
|
||||||
can_pstats.user_reset);
|
can_pstats->user_reset);
|
||||||
|
|
||||||
seq_putc(m, '\n');
|
seq_putc(m, '\n');
|
||||||
return 0;
|
return 0;
|
||||||
@ -264,7 +272,7 @@ static int can_stats_proc_show(struct seq_file *m, void *v)
|
|||||||
|
|
||||||
static int can_stats_proc_open(struct inode *inode, struct file *file)
|
static int can_stats_proc_open(struct inode *inode, struct file *file)
|
||||||
{
|
{
|
||||||
return single_open(file, can_stats_proc_show, NULL);
|
return single_open_net(inode, file, can_stats_proc_show);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct file_operations can_stats_proc_fops = {
|
static const struct file_operations can_stats_proc_fops = {
|
||||||
@ -277,25 +285,28 @@ static const struct file_operations can_stats_proc_fops = {
|
|||||||
|
|
||||||
static int can_reset_stats_proc_show(struct seq_file *m, void *v)
|
static int can_reset_stats_proc_show(struct seq_file *m, void *v)
|
||||||
{
|
{
|
||||||
|
struct net *net = m->private;
|
||||||
|
struct s_pstats *can_pstats = net->can.can_pstats;
|
||||||
|
struct s_stats *can_stats = net->can.can_stats;
|
||||||
|
|
||||||
user_reset = 1;
|
user_reset = 1;
|
||||||
|
|
||||||
if (can_stattimer.function == can_stat_update) {
|
if (net->can.can_stattimer.function == can_stat_update) {
|
||||||
seq_printf(m, "Scheduled statistic reset #%ld.\n",
|
seq_printf(m, "Scheduled statistic reset #%ld.\n",
|
||||||
can_pstats.stats_reset + 1);
|
can_pstats->stats_reset + 1);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (can_stats.jiffies_init != jiffies)
|
if (can_stats->jiffies_init != jiffies)
|
||||||
can_init_stats();
|
can_init_stats(net);
|
||||||
|
|
||||||
seq_printf(m, "Performed statistic reset #%ld.\n",
|
seq_printf(m, "Performed statistic reset #%ld.\n",
|
||||||
can_pstats.stats_reset);
|
can_pstats->stats_reset);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int can_reset_stats_proc_open(struct inode *inode, struct file *file)
|
static int can_reset_stats_proc_open(struct inode *inode, struct file *file)
|
||||||
{
|
{
|
||||||
return single_open(file, can_reset_stats_proc_show, NULL);
|
return single_open_net(inode, file, can_reset_stats_proc_show);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct file_operations can_reset_stats_proc_fops = {
|
static const struct file_operations can_reset_stats_proc_fops = {
|
||||||
@ -314,7 +325,7 @@ static int can_version_proc_show(struct seq_file *m, void *v)
|
|||||||
|
|
||||||
static int can_version_proc_open(struct inode *inode, struct file *file)
|
static int can_version_proc_open(struct inode *inode, struct file *file)
|
||||||
{
|
{
|
||||||
return single_open(file, can_version_proc_show, NULL);
|
return single_open_net(inode, file, can_version_proc_show);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct file_operations can_version_proc_fops = {
|
static const struct file_operations can_version_proc_fops = {
|
||||||
|
Loading…
Reference in New Issue
Block a user