mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

With clang's kernel control flow integrity (kCFI, CONFIG_CFI_CLANG),
indirect call targets are validated against the expected function
pointer prototype to make sure the call target is valid to help mitigate
ROP attacks. If they are not identical, there is a failure at run time,
which manifests as either a kernel panic or thread getting killed. A
warning in clang aims to catch these at compile time, which reveals:
drivers/net/can/rockchip/rockchip_canfd-core.c:770:20: error: incompatible function pointer types initializing 'netdev_tx_t (*)(struct sk_buff *, struct net_device *)' (aka 'enum netdev_tx (*)(struct sk_buff *, struct net_device *)') with an expression of type 'int (struct sk_buff *, struct net_device *)' [-Werror,-Wincompatible-function-pointer-types-strict]
770 | .ndo_start_xmit = rkcanfd_start_xmit,
| ^~~~~~~~~~~~~~~~~~
->ndo_start_xmit() in 'struct net_device_ops' expects a return type of
'netdev_tx_t', not 'int' (although the types are ABI compatible). Adjust
the return type of rkcanfd_start_xmit() to match the prototype's to
resolve the warning.
Fixes: ff60bfbaf6
("can: rockchip_canfd: add driver for Rockchip CAN-FD controller")
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
Link: https://patch.msgid.link/20240906-rockchip-canfd-wifpts-v1-1-b1398da865b7@kernel.org
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
167 lines
4.4 KiB
C
167 lines
4.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Copyright (c) 2023, 2024 Pengutronix,
|
|
// Marc Kleine-Budde <kernel@pengutronix.de>
|
|
//
|
|
|
|
#include <net/netdev_queues.h>
|
|
|
|
#include "rockchip_canfd.h"
|
|
|
|
static bool rkcanfd_tx_tail_is_eff(const struct rkcanfd_priv *priv)
|
|
{
|
|
const struct canfd_frame *cfd;
|
|
const struct sk_buff *skb;
|
|
unsigned int tx_tail;
|
|
|
|
if (!rkcanfd_get_tx_pending(priv))
|
|
return false;
|
|
|
|
tx_tail = rkcanfd_get_tx_tail(priv);
|
|
skb = priv->can.echo_skb[tx_tail];
|
|
if (!skb) {
|
|
netdev_err(priv->ndev,
|
|
"%s: echo_skb[%u]=NULL tx_head=0x%08x tx_tail=0x%08x\n",
|
|
__func__, tx_tail,
|
|
priv->tx_head, priv->tx_tail);
|
|
|
|
return false;
|
|
}
|
|
|
|
cfd = (struct canfd_frame *)skb->data;
|
|
|
|
return cfd->can_id & CAN_EFF_FLAG;
|
|
}
|
|
|
|
unsigned int rkcanfd_get_effective_tx_free(const struct rkcanfd_priv *priv)
|
|
{
|
|
if (priv->devtype_data.quirks & RKCANFD_QUIRK_RK3568_ERRATUM_6 &&
|
|
rkcanfd_tx_tail_is_eff(priv))
|
|
return 0;
|
|
|
|
return rkcanfd_get_tx_free(priv);
|
|
}
|
|
|
|
static void rkcanfd_start_xmit_write_cmd(const struct rkcanfd_priv *priv,
|
|
const u32 reg_cmd)
|
|
{
|
|
if (priv->devtype_data.quirks & RKCANFD_QUIRK_RK3568_ERRATUM_12)
|
|
rkcanfd_write(priv, RKCANFD_REG_MODE, priv->reg_mode_default |
|
|
RKCANFD_REG_MODE_SPACE_RX_MODE);
|
|
|
|
rkcanfd_write(priv, RKCANFD_REG_CMD, reg_cmd);
|
|
|
|
if (priv->devtype_data.quirks & RKCANFD_QUIRK_RK3568_ERRATUM_12)
|
|
rkcanfd_write(priv, RKCANFD_REG_MODE, priv->reg_mode_default);
|
|
}
|
|
|
|
void rkcanfd_xmit_retry(struct rkcanfd_priv *priv)
|
|
{
|
|
const unsigned int tx_head = rkcanfd_get_tx_head(priv);
|
|
const u32 reg_cmd = RKCANFD_REG_CMD_TX_REQ(tx_head);
|
|
|
|
rkcanfd_start_xmit_write_cmd(priv, reg_cmd);
|
|
}
|
|
|
|
netdev_tx_t rkcanfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
|
|
{
|
|
struct rkcanfd_priv *priv = netdev_priv(ndev);
|
|
u32 reg_frameinfo, reg_id, reg_cmd;
|
|
unsigned int tx_head, frame_len;
|
|
const struct canfd_frame *cfd;
|
|
int err;
|
|
u8 i;
|
|
|
|
if (can_dropped_invalid_skb(ndev, skb))
|
|
return NETDEV_TX_OK;
|
|
|
|
if (!netif_subqueue_maybe_stop(priv->ndev, 0,
|
|
rkcanfd_get_effective_tx_free(priv),
|
|
RKCANFD_TX_STOP_THRESHOLD,
|
|
RKCANFD_TX_START_THRESHOLD)) {
|
|
if (net_ratelimit())
|
|
netdev_info(priv->ndev,
|
|
"Stopping tx-queue (tx_head=0x%08x, tx_tail=0x%08x, tx_pending=%d)\n",
|
|
priv->tx_head, priv->tx_tail,
|
|
rkcanfd_get_tx_pending(priv));
|
|
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
|
|
cfd = (struct canfd_frame *)skb->data;
|
|
|
|
if (cfd->can_id & CAN_EFF_FLAG) {
|
|
reg_frameinfo = RKCANFD_REG_FD_FRAMEINFO_FRAME_FORMAT;
|
|
reg_id = FIELD_PREP(RKCANFD_REG_FD_ID_EFF, cfd->can_id);
|
|
} else {
|
|
reg_frameinfo = 0;
|
|
reg_id = FIELD_PREP(RKCANFD_REG_FD_ID_SFF, cfd->can_id);
|
|
}
|
|
|
|
if (cfd->can_id & CAN_RTR_FLAG)
|
|
reg_frameinfo |= RKCANFD_REG_FD_FRAMEINFO_RTR;
|
|
|
|
if (can_is_canfd_skb(skb)) {
|
|
reg_frameinfo |= RKCANFD_REG_FD_FRAMEINFO_FDF;
|
|
|
|
if (cfd->flags & CANFD_BRS)
|
|
reg_frameinfo |= RKCANFD_REG_FD_FRAMEINFO_BRS;
|
|
|
|
reg_frameinfo |= FIELD_PREP(RKCANFD_REG_FD_FRAMEINFO_DATA_LENGTH,
|
|
can_fd_len2dlc(cfd->len));
|
|
} else {
|
|
reg_frameinfo |= FIELD_PREP(RKCANFD_REG_FD_FRAMEINFO_DATA_LENGTH,
|
|
cfd->len);
|
|
}
|
|
|
|
tx_head = rkcanfd_get_tx_head(priv);
|
|
reg_cmd = RKCANFD_REG_CMD_TX_REQ(tx_head);
|
|
|
|
rkcanfd_write(priv, RKCANFD_REG_FD_TXFRAMEINFO, reg_frameinfo);
|
|
rkcanfd_write(priv, RKCANFD_REG_FD_TXID, reg_id);
|
|
for (i = 0; i < cfd->len; i += 4)
|
|
rkcanfd_write(priv, RKCANFD_REG_FD_TXDATA0 + i,
|
|
*(u32 *)(cfd->data + i));
|
|
|
|
frame_len = can_skb_get_frame_len(skb);
|
|
err = can_put_echo_skb(skb, ndev, tx_head, frame_len);
|
|
if (!err)
|
|
netdev_sent_queue(priv->ndev, frame_len);
|
|
|
|
WRITE_ONCE(priv->tx_head, priv->tx_head + 1);
|
|
|
|
rkcanfd_start_xmit_write_cmd(priv, reg_cmd);
|
|
|
|
netif_subqueue_maybe_stop(priv->ndev, 0,
|
|
rkcanfd_get_effective_tx_free(priv),
|
|
RKCANFD_TX_STOP_THRESHOLD,
|
|
RKCANFD_TX_START_THRESHOLD);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
void rkcanfd_handle_tx_done_one(struct rkcanfd_priv *priv, const u32 ts,
|
|
unsigned int *frame_len_p)
|
|
{
|
|
struct net_device_stats *stats = &priv->ndev->stats;
|
|
unsigned int tx_tail;
|
|
struct sk_buff *skb;
|
|
|
|
tx_tail = rkcanfd_get_tx_tail(priv);
|
|
skb = priv->can.echo_skb[tx_tail];
|
|
|
|
/* Manual handling of CAN Bus Error counters. See
|
|
* rkcanfd_get_corrected_berr_counter() for detailed
|
|
* explanation.
|
|
*/
|
|
if (priv->bec.txerr)
|
|
priv->bec.txerr--;
|
|
|
|
if (skb)
|
|
rkcanfd_skb_set_timestamp(priv, skb, ts);
|
|
stats->tx_bytes +=
|
|
can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload,
|
|
tx_tail, ts,
|
|
frame_len_p);
|
|
stats->tx_packets++;
|
|
}
|