* [PATCH v3 0/2] Add Loongson CAN-FD controller driver
@ 2026-06-30 3:01 Binbin Zhou
2026-06-30 3:01 ` [PATCH v3 1/2] can: " Binbin Zhou
2026-06-30 3:01 ` [PATCH v3 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
0 siblings, 2 replies; 6+ messages in thread
From: Binbin Zhou @ 2026-06-30 3:01 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
Binbin Zhou
Hi all:
This patchset adds support for the CAN-FD controller found on Loongson
CPUs.
Patch 1 introduces the basic CAN-FD controller driver with support
for classic CAN and CAN FD, including bit timing, error handling,
NAPI-based RX, and multiple TX buffers.
Patch 2 adds optional DMA support for RX path using the Loongson APB
CMC DMA engine, which significantly reduces CPU load under high
receive throughput.
The driver has been tested on Loongson-2K3000 platforms with various
CAN/CAN FD traffic patterns.
Finally, I'd like to thank Bingxiong, the original author of this driver,
for his efforts in working on the patch.
Thanks.
Binbin
=========
V3:
Patch (1/2):
- Replace the switch-case statement with a calculated mask in
`loongson_canfd_get_txbuf_sts()`;
- Change `loongson_canfd_reset()` return type to `void`;
- Drop old code in `loongson_canfd_set_btr()`;
- Rename `loongson_canfd_set_mode()` to `loongson_canfd_set_conf_mode()`;
- Drop `cf->__res*` usage;
- Drop unnecessary macro definitions;
- Add `netif_napi_del()` in probe error route path;
- Replace ternary operator with `if-else`;
- Drop stats->rx_packets and stats->rx_bytes count in CAN error frames;
- Add ethtool_ops;
- Add CAN_CTRLMODE_CC_LEN8_DLC;
Patch (2/2):
- Fix W=2 warning;
- Rename get_rx_frc() -> get_rxbuf_empty();
- Put `loongson_canfd_rxdma_remove()` before free_candev();
- Adjust the RXDMA probe process.
Link to V2:
https://lore.kernel.org/all/cover.1780908445.git.zhoubinbin@loongson.cn/
V2:
Patch (1/2):
- Put all code into one file;
- Add COMPILE_TEST Kconfig option;
- Rewrite Kconfig description;
- Use `regmap_test_bits()` to simplify bit field checks;
- Drop odd FIELD_GET() usage;
- Don't use FIELD_GET() for a single bit;
- Use an if/else instead of the ternary operator;
- Use the CAN TDC framework to get the SSP value;
- Use guard(spinlock)/scoped_guard(spinlock);
- Use netdev_debug() to be less verboss;
- Check for memory allocation failure;
- Add CAN_ERR_CNT flag;
- Drop unused REG_DATA_xx_yy_W_DATA_yy definition;
- Add more function comment;
Link to V1:
https://lore.kernel.org/all/cover.1777273055.git.zhoubinbin@loongson.cn/
Binbin Zhou (2):
can: Add Loongson CAN-FD controller driver
can: loongson_canfd: Add RXDMA support
MAINTAINERS | 7 +
drivers/net/can/Kconfig | 11 +
drivers/net/can/Makefile | 1 +
drivers/net/can/loongson_canfd.c | 1859 ++++++++++++++++++++++++++++++
4 files changed, 1878 insertions(+)
create mode 100644 drivers/net/can/loongson_canfd.c
base-commit: b266bacba796ff5c4dcd2ae2fc08aacf7ab39153
--
2.52.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 1/2] can: Add Loongson CAN-FD controller driver
2026-06-30 3:01 [PATCH v3 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
@ 2026-06-30 3:01 ` Binbin Zhou
2026-06-30 3:16 ` sashiko-bot
2026-06-30 3:01 ` [PATCH v3 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
1 sibling, 1 reply; 6+ messages in thread
From: Binbin Zhou @ 2026-06-30 3:01 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
Binbin Zhou
Add support for the CAN-FD controller integrated into Loongson-2K series
SoCs. The controller supports both Classic CAN and CAN FD, with up to 8
transmit buffers, hardware timestamping, error counters, and various
control modes (loopback, listen-only, one-shot, non-ISO FD, etc.).
The driver implements:
- NAPI for RX path
- TX buffer management with echo skb support
- Bus error reporting and fault confinement state handling
- Bit timing configuration for nominal and data phase
- Secondary Sample Point (SSP) configuration for high bitrates
Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
MAINTAINERS | 7 +
drivers/net/can/Kconfig | 11 +
drivers/net/can/Makefile | 1 +
drivers/net/can/loongson_canfd.c | 1693 ++++++++++++++++++++++++++++++
4 files changed, 1712 insertions(+)
create mode 100644 drivers/net/can/loongson_canfd.c
diff --git a/MAINTAINERS b/MAINTAINERS
index ec8661b446fb..9bd016da08bc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15042,6 +15042,13 @@ F: arch/loongarch/
F: drivers/*/*loongarch*
F: drivers/cpufreq/loongson3_cpufreq.c
+LOONGSON CAN FD DRIVER
+M: Bingxiong Li <libingxiong@loongson.cn>
+M: Binbin Zhou <zhoubinbin@loongson.cn>
+L: linux-can@vger.kernel.org
+S: Maintained
+F: drivers/net/can/loongson_canfd/
+
LOONGSON GPIO DRIVER
M: Yinbo Zhu <zhuyinbo@loongson.cn>
L: linux-gpio@vger.kernel.org
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index e15e320db476..55c40b0654fb 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -189,6 +189,17 @@ config CAN_KVASER_PCIEFD
Kvaser M.2 PCIe 4xCAN
Kvaser PCIe 8xCAN
+config CAN_LOONGSON_CANFD
+ tristate "Loongson CAN-FD controller"
+ depends on HAS_IOMEM
+ select REGMAP_MMIO
+ help
+ This is a canfd driver switch for the Loongson platform,
+ integrated with the Loongson-2K series SoCs.
+
+ To compile as a module, choose M here: the module will be
+ called loongson_canfd.
+
config CAN_SLCAN
tristate "Serial / USB serial CAN Adaptors (slcan)"
depends on TTY
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index d7bc10a6b8ea..1ce78443d710 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_CAN_GRCAN) += grcan.o
obj-$(CONFIG_CAN_IFI_CANFD) += ifi_canfd/
obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o
obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd/
+obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd.o
obj-$(CONFIG_CAN_MSCAN) += mscan/
obj-$(CONFIG_CAN_M_CAN) += m_can/
obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/
diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c
new file mode 100644
index 000000000000..1c7381129c2b
--- /dev/null
+++ b/drivers/net/can/loongson_canfd.c
@@ -0,0 +1,1693 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LOONGSON CANFD controller
+ *
+ * Copyright (C) 2024-2026 Loongson Technology Corporation Limited
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#define LOONGSON_CANFD_DEVICE_ID 0x0 /* CANFD controller ID Register */
+#define LOONGSON_CANFD_MODE 0x4 /* Mode Configuration Register */
+#define LOONGSON_CANFD_CONF 0x8 /* Configure Register */
+#define LOONGSON_CANFD_STAT 0xc /* Status Register */
+#define LOONGSON_CANFD_CMD 0x10 /* Command Register */
+#define LOONGSON_CANFD_INT_STAT 0x14 /* Interrupt Status Register */
+#define LOONGSON_CANFD_INT_ENA 0x18 /* Interrupt Enable Register */
+#define LOONGSON_CANFD_INT_MASK 0x1c /* Interrupt Mask Register */
+#define LOONGSON_CANFD_BTR_NORM 0x20 /* Normal Rate Configuration Register */
+#define LOONGSON_CANFD_BTR_FD 0x24 /* FD Data Rate Configuration Register */
+#define LOONGSON_CANFD_ERL 0x28 /* Error Threshold Configuration Register */
+#define LOONGSON_CANFD_FSTAT 0x2c /* Error Status Register */
+#define LOONGSON_CANFD_ERC 0x30 /* Error Count Register */
+#define LOONGSON_CANFD_BRE 0x34 /* Rate Error Count Register */
+#define LOONGSON_CANFD_CTR_PRES 0x38 /* Error Count Debug Register */
+#define LOONGSON_CANFD_ERR_CAPT 0x3c /* Error Capture Status Register */
+#define LOONGSON_CANFD_RETX_CNT 0x40 /* Retransmission Count Register */
+#define LOONGSON_CANFD_ALC 0x44 /* Lost Arbitration Capture Register */
+#define LOONGSON_CANFD_TRV_DLY 0x48 /* Transmission Delay Measurement Register */
+#define LOONGSON_CANFD_SSP_CFG 0x4c /* Second Sampling Point Configuration Register */
+#define LOONGSON_CANFD_RX_FR_CNT 0x50 /* Receive Message Count Register */
+#define LOONGSON_CANFD_TX_FR_CNT 0x54 /* Transmit Message Count Register */
+#define LOONGSON_CANFD_DEBUG 0x58 /* Debug Register */
+#define LOONGSON_CANFD_TS 0x5c /* Timestamp Register */
+#define LOONGSON_CANFD_TX_FRM_TST 0x60 /* Transmit Message Debug Register */
+#define LOONGSON_CANFD_FRC_DIV 0x64 /* Fractional Divider Ratio Register */
+#define LOONGSON_CANFD_FLT_A_MASK 0x68 /* Filter A Mask Register */
+#define LOONGSON_CANFD_FLT_A_VAL 0x6c /* Filter A value Register */
+#define LOONGSON_CANFD_FLT_B_MASK 0x70 /* Filter B Mask Register */
+#define LOONGSON_CANFD_FLT_B_VAL 0x74 /* Filter B value Register */
+#define LOONGSON_CANFD_FLT_C_MASK 0x78 /* Filter C Mask Register */
+#define LOONGSON_CANFD_FLT_C_VAL 0x7c /* Filter C value Register */
+#define LOONGSON_CANFD_FLT_R_LOW 0x80 /* Range Filter Low Threshold Register */
+#define LOONGSON_CANFD_FLT_R_HI 0x84 /* Range Filter High Threshold Register */
+#define LOONGSON_CANFD_FLT_CTRL 0x88 /* Filter Control Register */
+#define LOONGSON_CANFD_RX_MEM_INFO 0x8c /* Receive Buffer Information Register */
+#define LOONGSON_CANFD_RX_PRT 0x90 /* Receive Buffer Pointer Register */
+#define LOONGSON_CANFD_RX_STAT 0x94 /* Receive Buffer Status Register */
+#define LOONGSON_CANFD_RX_DATA 0x98 /* Receive Data Register */
+#define LOONGSON_CANFD_TX_STAT 0x9c /* Transmit Buffer Status Register */
+#define LOONGSON_CANFD_TX_CMD 0xa0 /* Transmit Command Register */
+#define LOONGSON_CANFD_TX_SEL 0xa4 /* Transmit Buffer Selection Register */
+#define LOONGSON_CANFD_TX_DATA_1 0xb0
+#define LOONGSON_CANFD_TX_DATA_18 0xf4
+
+/* Bitfields of CANFD controller ID register */
+#define REG_ID_MASK GENMASK(15, 0)
+#define REG_ID_VER_MIN GENMASK(23, 16)
+#define REG_ID_VER_MAJ GENMASK(31, 24)
+
+/* Bitfields of Mode Configuration register */
+#define REG_MODE_RST BIT(0) /* Soft Reset */
+#define REG_MODE_BMM BIT(1) /* Bus Monitoring Mode */
+#define REG_MODE_STM BIT(2) /* Self-Test Mode */
+#define REG_MODE_AFM BIT(3) /* Receive Filter Mode */
+#define REG_MODE_FDE BIT(4) /* CANFD Enabled */
+#define REG_MODE_TTTM BIT(5) /* Timed Transmission Mode */
+#define REG_MODE_ROM BIT(6) /* Restricted Operation Mode */
+#define REG_MODE_ACF BIT(7) /* No ACK Mode*/
+#define REG_MODE_TSTM BIT(8) /* Test Mode */
+#define REG_MODE_RXBAM BIT(9) /* Receive Buffer Auto Mode */
+#define REG_MODE_ITSM BIT(10) /* Internal Timestamp Mode */
+#define REG_MODE_RTSOP BIT(12) /* Receive Buffer Timestamp Addition Mode */
+#define REG_MODE_BUFM BIT(13) /* Transmit Buffer Mode */
+
+/* Bitfields of Configure register */
+#define REG_CONF_RTRLE BIT(0) /* Retransmission Threshold Enable */
+#define REG_CONF_RTRTH GENMASK(4, 1) /* Retransmission Threshold */
+#define REG_CONF_ILBP BIT(5) /* Internal Loopback Mode */
+#define REG_CONF_ENA BIT(6) /* Controller Enable */
+#define REG_CONF_NISOFD BIT(7) /* NON-ISO CANFD Protocol Enable */
+#define REG_CONF_PEX BIT(8) /* Protocol Exception Handling */
+#define REG_CONF_FDRF BIT(10) /* Ignore Remote Frame Enable */
+
+/* Bitfields of Status register */
+#define REG_STAT_RXNE BIT(0) /* Receive Buffer Not Empty Status */
+#define REG_STAT_DOR BIT(1) /* Receive Data Overflow Status */
+#define REG_STAT_EFT BIT(3) /* Error Frame Flag */
+#define REG_STAT_RXS BIT(4) /* Receive Status Flag */
+#define REG_STAT_TXS BIT(5) /* Transmission Status Flag */
+#define REG_STAT_EWL BIT(6) /* Transmission Error Threshold Flag */
+#define REG_STAT_IDLE BIT(7) /* Bus Idle Flag */
+#define REG_STAT_PEXS BIT(8) /* Protocol Exception Status */
+#define REG_STAT_STCNT BIT(16) /* Transmission Count Support */
+
+/* Bitfields of Command register */
+#define REG_CMD_RXRPMV BIT(1) /* Receive Buffer Read-to-Move */
+#define REG_CMD_RRB BIT(2) /* Receive Buffer Release */
+#define REG_CMD_CDO BIT(3) /* Received Data Overflow Status Clear */
+#define REG_CMD_ERCRST BIT(4) /* Error Count Clear */
+#define REG_CMD_RXFCRST BIT(5) /* Receive Frame Count Clear */
+#define REG_CMD_TXFCRST BIT(6) /* Transmit Frame Count Clear */
+#define REG_CMD_CPEXS BIT(7) /* Exception Status Clear */
+
+/* Bitfields of Interrupt Status register */
+#define REG_INT_STAT_RXI BIT(0)
+#define REG_INT_STAT_TXI BIT(1)
+#define REG_INT_STAT_EWLI BIT(2)
+#define REG_INT_STAT_DOI BIT(3)
+#define REG_INT_STAT_FCSI BIT(4)
+#define REG_INT_STAT_ALI BIT(5)
+#define REG_INT_STAT_BEI BIT(6)
+#define REG_INT_STAT_RXFI BIT(7)
+#define REG_INT_STAT_BSI BIT(8)
+#define REG_INT_STAT_RBNEI BIT(9)
+#define REG_INT_STAT_TXBHCI BIT(10)
+#define REG_INT_STAT_OFI BIT(11)
+#define REG_INT_STAT_DMADI BIT(12)
+
+#define REG_INT_STAT_ERRORI (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI |\
+ REG_INT_STAT_ALI | REG_INT_STAT_BEI)
+
+/* Bitfields of Interrupt Enable register */
+#define REG_INT_ENA_CLR GENMASK(28, 16)
+#define REG_INT_ENA_SET GENMASK(12, 0)
+
+/* Bitfields of Interrupt Mask register */
+#define REG_INT_MASK_SET GENMASK(12, 0)
+#define REG_INT_MASK_CLR GENMASK(28, 16)
+
+/* Bitfields of Normal Rate Configuration register */
+#define REG_BTR_PROP GENMASK(6, 0)
+#define REG_BTR_PH1 GENMASK(12, 7)
+#define REG_BTR_PH2 GENMASK(18, 13)
+#define REG_BTR_BRP GENMASK(22, 19)
+#define REG_BTR_SJW GENMASK(31, 27)
+
+/* Bitfields of FD Data Rate Configuration register */
+#define REG_BTR_FD_PROP GENMASK(6, 0)
+#define REG_BTR_FD_PH1 GENMASK(11, 7)
+#define REG_BTR_FD_PH2 GENMASK(17, 13)
+#define REG_BTR_FD_BRP GENMASK(26, 19)
+#define REG_BTR_FD_SJW GENMASK(31, 27)
+
+/* Bitfields of Error Threshold Configuration register */
+#define REG_ERL_ERP GENMASK(7, 0)
+#define REG_ERL_EW GENMASK(23, 16)
+
+/* Bitfields of Error Status register */
+#define REG_FSTAT_ERA BIT(0)
+#define REG_FSTAT_ERP BIT(1)
+#define REG_FSTAT_BOF BIT(2)
+
+#define REG_FSTAT_MASK GENMASK(2, 0)
+
+/* Bitfields of Error Count register */
+#define REG_ERC_TEC GENMASK(8, 0)
+#define REG_ERC_REC GENMASK(24, 16)
+
+/* Bitfields of Rate Error Count register */
+#define REG_BRE_NORM GENMASK(15, 0)
+#define REG_BRE_FD_DATA GENMASK(31, 16)
+
+/* Bitfields of Error Count Debug register */
+#define REG_CTR_PRES_CTPV GENMASK(8, 0)
+#define REG_CTR_PRES_PTX BIT(9)
+#define REG_CTR_PRES_PRX BIT(10)
+
+/* Bitfields of Error Capture Status register */
+#define REG_ERR_CAPT_POS GENMASK(4, 0)
+#define REG_ERR_CAPT_TYPE GENMASK(7, 5)
+
+/* Bitfields of Retransmission Count register */
+#define REG_RETX_CNT_VAL GENMASK(3, 0)
+
+/* Bitfields of Lost Arbitration Capture register */
+#define REG_ALC_BIT_POS GENMASK(4, 0)
+#define REG_ALC_ID_FIELD GENMASK(7, 5)
+
+/* Bitfields of Transmission Delay Measurement register */
+#define REG_TRV_DLY_VAL GENMASK(6, 0)
+
+/* Bitfields of Second Sampling Point Configuration register */
+#define REG_SSP_CFG_OFF GENMASK(7, 0)
+#define REG_SSP_CFG_SRC GENMASK(9, 8)
+#define REG_SSP_CFG_SAT BIT(10)
+
+/* Bitfields of Receive Message Count register */
+#define REG_RX_FR_CNT_VAL GENMASK(31, 0)
+
+/* Bitfields of Transmit Message Count register */
+#define REG_TX_FR_CNT_VAL GENMASK(31, 0)
+
+/* Bitfields of Debug register */
+#define REG_DEBUG_STF_CNT GENMASK(2, 0)
+#define REG_DEBUG_DSTF_CNT GENMASK(5, 3)
+#define REG_DEBUG_PC_ARB BIT(6)
+#define REG_DEBUG_PC_CON BIT(7)
+#define REG_DEBUG_PC_DAT BIT(8)
+#define REG_DEBUG_PC_STC BIT(9)
+#define REG_DEBUG_PC_CRC BIT(10)
+#define REG_DEBUG_PC_CRCD BIT(11)
+#define REG_DEBUG_PC_ACK BIT(12)
+#define REG_DEBUG_PC_ACKD BIT(13)
+#define REG_DEBUG_PC_EOF BIT(14)
+#define REG_DEBUG_PC_INT BIT(15)
+#define REG_DEBUG_PC_SUSP BIT(16)
+#define REG_DEBUG_PC_OVR BIT(17)
+#define REG_DEBUG_PC_SOF BIT(18)
+
+/* Bitfields of Timestamp register */
+#define REG_TS_TIMESTAMP GENMASK(15, 0)
+#define REG_TS_PSC GENMASK(24, 16)
+
+/* Bitfields of Fractional Divider Ratio register */
+#define REG_FRC_FRC_DBT GENMASK(15, 8)
+#define REG_FRC_FRC_NBT GENMASK(7, 0)
+
+/* Bitfields of Filter A Mask register */
+#define REG_FIL_A_MASK GENMASK(28, 0)
+
+/* Bitfields of Filter A value register */
+#define REG_FIL_A_VAL GENMASK(28, 0)
+
+/* Bitfields of Filter B Mask register */
+#define REG_FIL_B_MASK GENMASK(28, 0)
+
+/* Bitfields of Filter B value register */
+#define REG_FIL_B_VAL GENMASK(28, 0)
+
+/* Bitfields of Filter C Mask register */
+#define REG_FIL_C_MASK GENMASK(28, 0)
+
+/* Bitfields of Filter C value register */
+#define REG_FIL_C_VAL GENMASK(28, 0)
+
+/* Bitfields of Range Filter Low Threshold register */
+#define REG_FIL_R_LOW_VAL GENMASK(28, 0)
+
+/* Bitfields of Range Filter High Threshold register */
+#define REG_FIL_R_HI_VAL GENMASK(28, 0)
+
+/* Bitfields of Filter Control register */
+#define REG_FIL_CTRL_FANB BIT(0)
+#define REG_FIL_CTRL_FANE BIT(1)
+#define REG_FIL_CTRL_FAFB BIT(2)
+#define REG_FIL_CTRL_FAFE BIT(3)
+#define REG_FIL_CTRL_FBNB BIT(4)
+#define REG_FIL_CTRL_FBNE BIT(5)
+#define REG_FIL_CTRL_FBFB BIT(6)
+#define REG_FIL_CTRL_FBFE BIT(7)
+#define REG_FIL_CTRL_FCNB BIT(8)
+#define REG_FIL_CTRL_FCNE BIT(9)
+#define REG_FIL_CTRL_FCFB BIT(10)
+#define REG_FIL_CTRL_FCFE BIT(11)
+#define REG_FIL_CTRL_FRNB BIT(12)
+#define REG_FIL_CTRL_FRNE BIT(13)
+#define REG_FIL_CTRL_FRFB BIT(14)
+#define REG_FIL_CTRL_FRFE BIT(15)
+#define REG_FIL_CTRL_SFA BIT(16)
+#define REG_FIL_CTRL_SFB BIT(17)
+#define REG_FIL_CTRL_SFC BIT(18)
+#define REG_FIL_CTRL_SFR BIT(19)
+
+/* Bitfields of Receive Buffer Information register */
+#define REG_RX_MEM_INFO_BUFF_SIZE GENMASK(12, 0)
+#define REG_RX_MEM_INFO_MEM_FREE GENMASK(28, 16)
+
+/* Bitfields of Receive Buffer Pointer register */
+#define REG_RX_PTR_WPP GENMASK(11, 0)
+#define REG_RX_PTR_RPP GENMASK(27, 16)
+
+/* Bitfields of Receive Buffer Status register */
+#define REG_RX_STAT_RXE BIT(0)
+#define REG_RX_STAT_RXF BIT(1)
+#define REG_RX_STAT_RXMOF BIT(2)
+#define REG_RX_STAT_RXFRC GENMASK(14, 4)
+#define REG_RX_STAT_RTSOP BIT(16)
+
+/* Bitfields of Receive Data register */
+#define REG_RX_DATA_VAL GENMASK(31, 0)
+
+/* Bitfields of Transmit Buffer Status register */
+#define REG_TX_STAT_BRP GENMASK(7, 0)
+#define REG_TX_STAT_TXS GENMASK(10, 8)
+#define REG_TX_STAT_BS GENMASK(31, 16)
+
+#define REG_TX_STAT_BS_TX_MASK_BASE GENMASK(17, 16)
+
+/* Bitfields of Transmit Command register */
+#define REG_TX_CMD_BAR GENMASK(7, 0)
+#define REG_TX_CMD_BCR GENMASK(15, 8)
+#define REG_TX_CMD_BSC GENMASK(23, 16)
+
+/* Bitfields of Transmit Buffer Selection register */
+#define REG_TX_SEL_BUF_SEL GENMASK(3, 0)
+#define REG_TX_SEL_BUF_CNT GENMASK(7, 4)
+
+/* Loongson CANFD Frame format */
+#define LOONGSON_CANFD_FRAME_META0 0x0
+#define LOONGSON_CANFD_FRAME_META1 0x4
+#define LOONGSON_CANFD_FRAME_DB_1 0x8
+
+/* Bitfields of FRAME META0 */
+#define REG_FRAME_META0_ID_EXT GENMASK(17, 0)
+#define REG_FRAME_META0_ID_BASE GENMASK(28, 18)
+#define REG_FRAME_META0_RTR BIT(29) /* Remote Transmission Request, only in CAN2.0 */
+/* Extended Identifier Type. 1: 29 bits ID, 0: 11 bits ID. */
+#define REG_FRAME_META0_XDT BIT(30)
+#define REG_FRAME_META0_ESI BIT(31) /* Error State Indicator, only in CAN-FD */
+
+/* Bitfields of FRAME META1 */
+#define REG_FRAME_META1_TIMESTAMP GENMASK(15, 0)
+#define REG_FRAME_META1_DLC GENMASK(19, 16) /* Data Length Code */
+#define REG_FRAME_META1_BRS BIT(20) /* Bit Rate Shift, Only in CANFD */
+/* Flexible Data-rate Format. 1: CANFD, 0: CAN2.0 */
+#define REG_FRAME_META1_FDF BIT(21)
+#define REG_FRAME_META1_RWCNT GENMASK(28, 24) /* Read Word Counter */
+
+/* Bitfields of FRAME TEST */
+#define REG_FRAME_TEST_FSTC BIT(0)
+#define REG_FRAME_TEST_FCRC BIT(1)
+#define REG_FRAME_TEST_SDLC BIT(2)
+#define REG_FRAME_TEST_TPRM GENMASK(12, 8)
+
+#define DEV_NAME "loongson_canfd"
+#define LOONGSON_CANFD_ID 0xBABE
+#define LOONGSON_CANFD_DW_BYTE 4
+#define LOONGSON_CANFD_TXBUF_NUM 8
+#define LOONGSON_CANFD_MAX_RTXTH 0xf
+
+/**
+ * struct loongson_canfd_priv - This definition define CAN driver instance
+ * @can: CAN private data structure.
+ * @napi: NAPI structure
+ * @regmap: regmap of the CAN device
+ * @res: Pointer to the CAN device respurce
+ * @tx_lock: Lock for synchronizing TX interrupt handling
+ */
+struct loongson_canfd_priv {
+ struct can_priv can; /* must be first member! */
+ struct napi_struct napi;
+ struct regmap *regmap;
+ struct resource *res;
+ spinlock_t tx_lock; /* protect the sending queue */
+};
+
+/**
+ * enum loongson_canfd_txbuf_sts - status of TX buffer
+ * @TX_BS_IDLE: Status processed or not sent.
+ * @TX_BS_VALID: Sending successful.
+ * @TX_BS_FAIL: Sending failed.
+ * @TX_BS_CANCEL: Sending cancelled.
+ */
+enum loongson_canfd_txbuf_sts {
+ TX_BS_IDLE = 0x0,
+ TX_BS_VALID = 0x1,
+ TX_BS_FAIL = 0x2,
+ TX_BS_CANCEL = 0x3
+};
+
+/**
+ * enum loongson_canfd_txbuf_cmd - command of TX buffer
+ * @TXT_CMD_ADD: Buffer add request.
+ * @TXT_CMD_CANCEL: Buffer cancel request.
+ * @TXT_CMD_SR_CLEAN: Buffer send record clear.
+ * 1: to clear the send completion record (LOONGSON_CANFD_TX_STAT[BS]).
+ */
+enum loongson_canfd_txbuf_cmd {
+ TXT_CMD_ADD = 0x01,
+ TXT_CMD_CANCEL = 0x02,
+ TXT_CMD_SR_CLEAN = 0x03
+};
+
+static const struct can_bittiming_const loongson_canfd_bit_timing = {
+ .name = DEV_NAME,
+ .tseg1_min = 2,
+ .tseg1_max = 190,
+ .tseg2_min = 2,
+ .tseg2_max = 63,
+ .sjw_max = 31,
+ .brp_min = 1,
+ .brp_max = 15,
+ .brp_inc = 1,
+};
+
+static const struct can_bittiming_const loongson_canfd_bit_timing_data = {
+ .name = DEV_NAME,
+ .tseg1_min = 2,
+ .tseg1_max = 190,
+ .tseg2_min = 2,
+ .tseg2_max = 63,
+ .sjw_max = 31,
+ .brp_min = 1,
+ .brp_max = 255,
+ .brp_inc = 1,
+};
+
+/* CAN FD Transmission Delay Compensation constants */
+static const struct can_tdc_const loongson_canfd_tdc_data = {
+ .tdcv_min = 0,
+ .tdcv_max = 0,
+ .tdco_min = 1,
+ .tdco_max = 127,
+ .tdcf_min = 0, /* Filter window not supported */
+ .tdcf_max = 0,
+};
+
+/**
+ * loongson_canfd_enabled() - Gets the controller is enabled
+ * @priv: Pointer to private data
+ *
+ * Return: True - the controller is enabled.
+ * False - the controller is disabled.
+ */
+static bool loongson_canfd_enabled(struct loongson_canfd_priv *priv)
+{
+ return !!regmap_test_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA);
+}
+
+/**
+ * loongson_canfd_txbuf_freed() - Gets the flag for TX buffer to be sent
+ * @priv: Pointer to private data
+ *
+ * Return: True - TX buffer is empty.
+ * False - TX buffer is processing
+ */
+static bool loongson_canfd_txbuf_freed(struct loongson_canfd_priv *priv)
+{
+ return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT, REG_TX_STAT_BRP);
+}
+
+/**
+ * loongson_canfd_get_txbuf_sts() - Gets status of TX buffer
+ * @priv: Pointer to private data
+ * @buf_id: Buffer index (0-based)
+ *
+ * Return: Status of TX buffer
+ */
+static enum loongson_canfd_txbuf_sts
+loongson_canfd_get_txbuf_sts(struct loongson_canfd_priv *priv, u8 buf_id)
+{
+ u32 sts, mask;
+
+ mask = REG_TX_STAT_BS_TX_MASK_BASE << 2 * buf_id;
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts);
+
+ return field_get(mask, sts);
+}
+
+/**
+ * loongson_canfd_get_txbuf_id() - Gets the index of a non-idle TX buffer
+ * @priv: Pointer to private data
+ * @sts: Status of TXT buffer to outside
+ *
+ * Return: Buffer index (0-based)
+ */
+static unsigned int loongson_canfd_get_txbuf_id(struct loongson_canfd_priv *priv,
+ enum loongson_canfd_txbuf_sts *sts)
+{
+ unsigned int i;
+
+ for (i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
+ if (loongson_canfd_get_txbuf_sts(priv, i)) {
+ *sts = loongson_canfd_get_txbuf_sts(priv, i);
+ break;
+ }
+ }
+
+ if (i == LOONGSON_CANFD_TXBUF_NUM)
+ *sts = TX_BS_IDLE;
+
+ return i;
+}
+
+/**
+ * loongson_canfd_txbuf_is_writable() - Checks if frame can be inserted to TX Buffer
+ * @priv: Pointer to private data
+ * @buf_id: Buffer index (0-based)
+ *
+ * Return: True - Frame can be inserted to TXT Buffer,
+ * False - If attempted, frame will not be inserted to TX Buffer
+ */
+static bool loongson_canfd_txbuf_is_writable(struct loongson_canfd_priv *priv, u8 buf_id)
+{
+ enum loongson_canfd_txbuf_sts bs;
+
+ bs = loongson_canfd_get_txbuf_sts(priv, buf_id);
+ if (bs)
+ return false;
+
+ return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT, BIT(buf_id));
+}
+
+/**
+ * loongson_canfd_set_txbuf_cmd() - Applies command on TX buffer
+ * @ndev: Pointer to net_device structure
+ * @cmd: Command to set
+ * @buf_id: Buffer index (0-based)
+ */
+static void loongson_canfd_set_txbuf_cmd(struct net_device *ndev,
+ enum loongson_canfd_txbuf_cmd cmd, u8 buf_id)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u32 mask;
+
+ switch (cmd) {
+ case TXT_CMD_ADD:
+ mask = REG_TX_CMD_BAR;
+ break;
+ case TXT_CMD_CANCEL:
+ mask = REG_TX_CMD_BCR;
+ break;
+ case TXT_CMD_SR_CLEAN:
+ mask = REG_TX_CMD_BSC;
+ break;
+ default:
+ netdev_err(ndev, "Unknown command id: %x\n", cmd);
+ return;
+ }
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD, field_prep(mask, BIT(buf_id)));
+}
+
+/**
+ * loongson_canfd_rxbuf_empty() - Gets the RX buffer is empty
+ * @priv: Pointer to private data
+ *
+ * Return: True - RX buffer is empty.
+ * False - RX buffer is processing
+ */
+static bool loongson_canfd_rxbuf_empty(struct loongson_canfd_priv *priv)
+{
+ return !!regmap_test_bits(priv->regmap, LOONGSON_CANFD_RX_STAT, REG_RX_STAT_RXE);
+}
+
+/**
+ * loongson_canfd_reset() - Issues software reset request to Loongson CANFD
+ * @ndev: Pointer to net_device structure
+ */
+static void loongson_canfd_reset(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RST);
+ regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RXBAM | REG_MODE_BUFM);
+}
+
+/**
+ * loongson_canfd_set_btr() - Sets CAN bus bit timing in Loongson CANFD
+ * @ndev: Pointer to net_device structure
+ * @bt: Pointer to Bit timing structure
+ * @nominal: True - Nominal bit timing, False - Data bit timing
+ */
+static void loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u32 ph1_max = FIELD_MAX(REG_BTR_PH1);
+ u32 btr = 0;
+
+ /*
+ * The timing calculation functions have only constraints on tseg1,
+ * which is prop_seg + phase1_seg combined.
+ * tseg1 is then split in half and stored into prog_seg and phase_seg1.
+ * In Loongson CAN-FD, PROP is 7 bits wide but PH1 only 6, so we must
+ * re-distribute the values here.
+ */
+ if (bt->phase_seg1 > ph1_max) {
+ bt->prop_seg += bt->phase_seg1 - ph1_max;
+ bt->phase_seg1 = ph1_max;
+ }
+
+ if (nominal) {
+ btr = FIELD_PREP(REG_BTR_PROP, bt->prop_seg) |
+ FIELD_PREP(REG_BTR_PH1, bt->phase_seg1) |
+ FIELD_PREP(REG_BTR_PH2, bt->phase_seg2) |
+ FIELD_PREP(REG_BTR_BRP, bt->brp) |
+ FIELD_PREP(REG_BTR_SJW, bt->sjw);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_BTR_NORM, btr);
+ } else {
+ btr = FIELD_PREP(REG_BTR_FD_PROP, bt->prop_seg) |
+ FIELD_PREP(REG_BTR_FD_PH1, bt->phase_seg1) |
+ FIELD_PREP(REG_BTR_FD_PH2, bt->phase_seg2) |
+ FIELD_PREP(REG_BTR_FD_BRP, bt->brp) |
+ FIELD_PREP(REG_BTR_FD_SJW, bt->sjw);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_BTR_FD, btr);
+ }
+}
+
+/**
+ * loongson_canfd_set_bittiming() - CAN set nominal bit timing routine
+ * @ndev: Pointer to net_device structure
+ *
+ * Return: 0 on success, -%EPERM on error
+ */
+static int loongson_canfd_set_bittiming(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct can_bittiming *bt = &priv->can.bittiming;
+
+ /* Note that bt may be modified here */
+ loongson_canfd_set_btr(ndev, bt, true);
+
+ return 0;
+}
+
+/**
+ * loongson_canfd_set_data_bittiming() - CAN set data bit timing routine
+ * @ndev: Pointer to net_device structure
+ *
+ * Return: 0 on success, -%EPERM on error
+ */
+static int loongson_canfd_set_data_bittiming(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct can_bittiming *dbt = &priv->can.fd.data_bittiming;
+
+ /* Note that dbt may be modified here */
+ loongson_canfd_set_btr(ndev, dbt, false);
+
+ return 0;
+}
+
+/**
+ * loongson_canfd_get_auto_tdcv - Get Transmitter Delay Compensation Value
+ * @ndev: Pointer to net_device structure
+ * @tdcv: Pointer to TDCV value
+ *
+ * Return: 0 on success
+ */
+static int loongson_canfd_get_auto_tdcv(const struct net_device *ndev, u32 *tdcv)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_TRV_DLY, &val);
+ *tdcv = FIELD_GET(REG_TRV_DLY_VAL, val);
+
+ return 0;
+}
+
+/**
+ * loongson_canfd_set_secondary_sample_point() - Set secondary sample point in Loongson CANFD
+ * @ndev: Pointer to net_device structure
+ *
+ * Return: 0 on success, -%EPERM if controller is enabled
+ */
+static int loongson_canfd_set_secondary_sample_point(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u32 ssp_cfg;
+
+ if (loongson_canfd_enabled(priv)) {
+ netdev_err(ndev, "BUG! Cannot set SSP - CAN is enabled\n");
+ return -EPERM;
+ }
+
+ if (can_fd_tdc_is_enabled(&priv->can))
+ ssp_cfg = FIELD_PREP(REG_SSP_CFG_OFF, priv->can.fd.tdc.tdco) |
+ FIELD_PREP(REG_SSP_CFG_SRC, 0x0);
+ else
+ ssp_cfg = FIELD_PREP(REG_SSP_CFG_SRC, 0x1);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_SSP_CFG, ssp_cfg);
+
+ return 0;
+}
+
+/**
+ * loongson_canfd_set_conf_mode() - Sets Loongson CANFD mode and configure registers
+ * @priv: Pointer to private data
+ */
+static void loongson_canfd_set_conf_mode(struct loongson_canfd_priv *priv)
+{
+ u32 ctrlmode = priv->can.ctrlmode;
+ u32 mode, conf;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_MODE, &mode);
+
+ if (ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ mode |= REG_MODE_BMM;
+ else
+ mode &= ~REG_MODE_BMM;
+
+ if (ctrlmode & CAN_CTRLMODE_FD)
+ mode |= REG_MODE_FDE;
+ else
+ mode &= ~REG_MODE_FDE;
+
+ if (ctrlmode & CAN_CTRLMODE_PRESUME_ACK)
+ mode |= REG_MODE_ACF;
+ else
+ mode &= ~REG_MODE_ACF;
+
+ /*
+ * Some bits fixed:
+ * TSTM - Off, User shall not be able to change REC/TEC by hand
+ * during operation
+ */
+ mode &= ~REG_MODE_TSTM;
+ regmap_write(priv->regmap, LOONGSON_CANFD_MODE, mode);
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf);
+
+ if (ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ conf |= REG_CONF_ILBP;
+ else
+ conf &= ~REG_CONF_ILBP;
+
+ if (ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+ conf |= REG_CONF_NISOFD;
+ else
+ conf &= ~REG_CONF_NISOFD;
+
+ /* One shot mode supported indirectly via Retransmit limit */
+ conf &= ~FIELD_PREP(REG_CONF_RTRTH, LOONGSON_CANFD_MAX_RTXTH);
+
+ if (ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+ conf |= REG_CONF_RTRLE;
+ else
+ conf |= REG_CONF_RTRLE | FIELD_PREP(REG_CONF_RTRTH, LOONGSON_CANFD_MAX_RTXTH);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_CONF, conf);
+}
+
+/**
+ * loongson_canfd_chip_start() - This routine starts the driver
+ * @ndev: Pointer to net_device structure
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int loongson_canfd_chip_start(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u16 int_ena;
+ int ret;
+
+ /* Configure bit-rates and ssp */
+ ret = loongson_canfd_set_bittiming(ndev);
+ if (ret < 0)
+ return ret;
+
+ ret = loongson_canfd_set_data_bittiming(ndev);
+ if (ret < 0)
+ return ret;
+
+ ret = loongson_canfd_set_secondary_sample_point(ndev);
+ if (ret < 0)
+ return ret;
+
+ /* Setting mode and configure registers */
+ loongson_canfd_set_conf_mode(priv);
+
+ /* Configure interrupts */
+ int_ena = REG_INT_STAT_RBNEI | REG_INT_STAT_TXBHCI |
+ REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
+
+ /* Bus error reporting */
+ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+ int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
+
+ /* It's after reset, so there is no need to clear anything */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, ~int_ena);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena);
+
+ /* Controller enters ERROR_ACTIVE on initial FCSI */
+ priv->can.state = CAN_STATE_STOPPED;
+
+ /* Enable the controller */
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, REG_CONF_ENA);
+
+ return 0;
+}
+
+/**
+ * loongson_canfd_do_set_mode() - Sets mode of the driver
+ * @ndev: Pointer to net_device structure
+ * @mode: Tells the mode of the driver
+ *
+ * This check the drivers state and calls the corresponding modes to set.
+ *
+ * Return: 0 on success and failure value on error
+ */
+static int loongson_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ int ret;
+
+ switch (mode) {
+ case CAN_MODE_START:
+ loongson_canfd_reset(ndev);
+
+ ret = loongson_canfd_chip_start(ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
+ return ret;
+ }
+
+ netif_wake_queue(ndev);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * loongson_canfd_insert_frame() - Inserts frame to TXT buffer
+ * @priv: Pointer to private data
+ * @cf: Pointer to CAN frame to be inserted
+ * @buf: TXT Buffer index to which frame is inserted (0-based)
+ * @isfdf: True - CAN FD Frame, False - CAN 2.0 Frame
+ *
+ * Return:
+ * * True - Frame inserted successfully
+ * * False - Frame was not inserted due to one of:
+ * 1. TXT Buffer is not writable (it is in wrong state)
+ * 2. Invalid TXT buffer index
+ * 3. Invalid frame length
+ */
+static bool loongson_canfd_insert_frame(struct loongson_canfd_priv *priv,
+ const struct canfd_frame *cf, u8 buf, bool isfdf)
+{
+ u32 meta0, meta1 = 0;
+
+ if (buf >= LOONGSON_CANFD_TXBUF_NUM)
+ return false;
+
+ if (!loongson_canfd_txbuf_is_writable(priv, buf))
+ return false;
+
+ if (cf->len > CANFD_MAX_DLEN)
+ return false;
+
+ /* Prepare identifier */
+ if (cf->can_id & CAN_EFF_FLAG) {
+ meta0 = cf->can_id & CAN_EFF_MASK;
+ meta0 |= REG_FRAME_META0_XDT;
+ } else {
+ meta0 = FIELD_PREP(REG_FRAME_META0_ID_BASE, cf->can_id & CAN_SFF_MASK);
+ }
+
+ /* Prepare Frame format */
+ if (cf->can_id & CAN_RTR_FLAG)
+ meta0 |= REG_FRAME_META0_RTR;
+
+ if (isfdf) {
+ meta1 = REG_FRAME_META1_FDF;
+
+ if (cf->flags & CANFD_BRS)
+ meta1 |= REG_FRAME_META1_BRS;
+ }
+
+ meta1 |= FIELD_PREP(REG_FRAME_META1_DLC, can_fd_len2dlc(cf->len));
+
+ /* TXT buffer select */
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_SEL, buf);
+
+ /* Write ID, Frame format */
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_META0, meta0);
+ regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_META1, meta1);
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ return true;
+
+ /* Write Data payload */
+ for (unsigned int i = 0; i < cf->len; i += LOONGSON_CANFD_DW_BYTE) {
+ regmap_write(priv->regmap,
+ LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_DB_1 + i,
+ *(u32 *)(cf->data + i));
+ }
+
+ return true;
+}
+
+/**
+ * loongson_canfd_start_xmit() - Starts the transmission
+ * @skb: sk_buff pointer that contains data to be Txed
+ * @ndev: Pointer to net_device structure
+ *
+ * Invoked from upper layers to initiate transmission. Uses the next available free TX Buffer and
+ * populates its fields to start the transmission.
+ *
+ * Return: %NETDEV_TX_OK on success,
+ * %NETDEV_TX_BUSY when no free TX buffer is available, negative return values reserved
+ * for error cases.
+ */
+static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct canfd_frame *cf = (struct canfd_frame *)skb->data;
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u32 buf_id = 0, tx_stat, i = 0;
+ unsigned long tx_brp;
+ u16 tx_bs;
+
+ if (can_dropped_invalid_skb(ndev, skb))
+ return NETDEV_TX_OK;
+
+ if (unlikely(!loongson_canfd_txbuf_freed(priv))) {
+ netif_stop_queue(ndev);
+ netdev_err(ndev, "BUG!, no TXB free when queue awake!\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ guard(spinlock_irqsave)(&priv->tx_lock);
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat);
+ tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat);
+ tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat);
+
+ for_each_clear_bit(i, &tx_brp, LOONGSON_CANFD_TXBUF_NUM) {
+ u32 transmission_complete_mask = 0x3 << (i * 2);
+
+ if (!(tx_bs & transmission_complete_mask)) {
+ buf_id = i;
+ break;
+ }
+ }
+
+ if (!loongson_canfd_insert_frame(priv, cf, buf_id, can_is_canfd_skb(skb))) {
+ netdev_err(ndev, "TXNF set but cannot insert frame into TXTB! HW Bug?");
+ kfree_skb(skb);
+ ndev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+ }
+
+ can_put_echo_skb(skb, ndev, buf_id, 0);
+
+ loongson_canfd_set_txbuf_cmd(ndev, TXT_CMD_ADD, buf_id);
+
+ /* Check if all TX buffers are full */
+ if (!loongson_canfd_txbuf_freed(priv))
+ netif_stop_queue(ndev);
+
+ return NETDEV_TX_OK;
+}
+
+/**
+ * loongson_canfd_read_rx_frame() - Reads frame from RX FIFO
+ * @priv: Pointer to private data
+ * @cf: Pointer to CAN frame struct
+ * @meta0: The first `frame format` read previously
+ * @meta1: The second `frame format` read previously
+ */
+static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
+ u32 meta0, u32 meta1)
+{
+ u32 data, i, wc, len;
+
+ /* Extended Identifier Type */
+ if (meta0 & REG_FRAME_META0_XDT)
+ cf->can_id = (meta0 & CAN_EFF_MASK) | CAN_EFF_FLAG;
+ else
+ cf->can_id = FIELD_GET(REG_FRAME_META0_ID_BASE, meta0) & CAN_SFF_MASK;
+
+ /* BRS, ESI, RTR Flags */
+ cf->flags = 0;
+
+ if (meta1 & REG_FRAME_META1_FDF) {
+ if (meta1 & REG_FRAME_META1_BRS)
+ cf->flags |= CANFD_BRS;
+
+ if (meta0 & REG_FRAME_META0_ESI)
+ cf->flags |= CANFD_ESI;
+ } else if (meta0 & REG_FRAME_META0_RTR) {
+ cf->can_id |= CAN_RTR_FLAG;
+ }
+
+ wc = FIELD_GET(REG_FRAME_META1_RWCNT, meta1) - 2;
+
+ /* Data Length Code */
+ len = FIELD_GET(REG_FRAME_META1_DLC, meta1);
+ if (len > 8) {
+ if (meta1 & REG_FRAME_META1_FDF)
+ len = wc << 2;
+ else
+ len = 8;
+ }
+
+ cf->len = len;
+ if (unlikely(len > wc * LOONGSON_CANFD_DW_BYTE))
+ len = wc * LOONGSON_CANFD_DW_BYTE;
+
+ /* Data */
+ for (i = 0; i < len; i += LOONGSON_CANFD_DW_BYTE)
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, (u32 *)(cf->data + i));
+
+ while (unlikely(i < wc * LOONGSON_CANFD_DW_BYTE)) {
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+ i += LOONGSON_CANFD_DW_BYTE;
+ }
+}
+
+/**
+ * loongson_canfd_rx() - Called from CAN ISR to complete the received frame processing
+ * @ndev: Pointer to net_device structure
+ *
+ * This function is invoked from the CAN isr to process the Rx frames. It does minimal
+ * processing and invokes "netif_receive_skb" to complete further processing.
+ * Return: 1 when frame is passed to the network layer, 0 when the first frame word is read but
+ * system is out of free SKBs temporally and left code to resolve SKB allocation later,
+ * -%EAGAIN in a case of empty Rx FIFO.
+ */
+static int loongson_canfd_rx(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct canfd_frame *cf;
+ struct sk_buff *skb;
+ u32 meta0, meta1;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
+
+ /* Number of characters received */
+ if (!FIELD_GET(REG_FRAME_META1_RWCNT, meta1))
+ return -EAGAIN;
+
+ /* Flexible Data-rate Format */
+ if (meta1 & REG_FRAME_META1_FDF)
+ skb = alloc_canfd_skb(ndev, &cf);
+ else
+ skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
+ if (unlikely(!skb))
+ return 0;
+
+ loongson_canfd_read_rx_frame(priv, cf, meta0, meta1);
+
+ if (cf->can_id != CAN_RTR_FLAG)
+ stats->rx_bytes += cf->len;
+ stats->rx_packets++;
+ netif_receive_skb(skb);
+
+ return 1;
+}
+
+/**
+ * loongson_canfd_read_fault_state() - Reads Loongson CANFD fault state.
+ * @ndev: Pointer to net_device structure
+ *
+ * Returns: Fault confinement state of controller
+ */
+static enum can_state loongson_canfd_read_fault_state(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ enum can_state sts = CAN_STATE_ERROR_PASSIVE;
+ u32 fstat;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_FSTAT, &fstat);
+
+ switch (FIELD_GET(REG_FSTAT_MASK, fstat)) {
+ case REG_FSTAT_ERA:
+ u32 ewl, erl, rec_tec, max_tec;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERL, &erl);
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &rec_tec);
+
+ ewl = FIELD_GET(REG_ERL_EW, erl);
+ max_tec = max(FIELD_GET(REG_ERC_REC, rec_tec), FIELD_GET(REG_ERC_TEC, rec_tec));
+
+ if (ewl > max_tec)
+ sts = CAN_STATE_ERROR_ACTIVE;
+ else
+ sts = CAN_STATE_ERROR_WARNING;
+ break;
+ case REG_FSTAT_ERP:
+ sts = CAN_STATE_ERROR_PASSIVE;
+ break;
+ case REG_FSTAT_BOF:
+ sts = CAN_STATE_BUS_OFF;
+ break;
+ default:
+ netdev_err(ndev, "Invalid error state.\n");
+ break;
+ }
+
+ return sts;
+}
+
+/**
+ * loongson_canfd_get_bec() - Reads REC/TEC counter values from controller
+ * @priv: Pointer to private data
+ * @bec: Pointer to Error counter structure
+ */
+static void loongson_canfd_get_bec(struct loongson_canfd_priv *priv, struct can_berr_counter *bec)
+{
+ u32 erc;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &erc);
+ bec->rxerr = FIELD_GET(REG_ERC_REC, erc);
+ bec->txerr = FIELD_GET(REG_ERC_TEC, erc);
+}
+
+/**
+ * loongson_canfd_get_berr_counter() - error counter routine
+ * @ndev: Pointer to net_device structure
+ * @bec: Pointer to can_berr_counter structure
+ *
+ * Return: 0 always
+ */
+static int loongson_canfd_get_berr_counter(const struct net_device *ndev,
+ struct can_berr_counter *bec)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ loongson_canfd_get_bec(priv, bec);
+ return 0;
+}
+
+/**
+ * loongson_canfd_err_interrupt() - Error frame ISR
+ * @ndev: net_device pointer
+ * @isr: interrupt status register value
+ *
+ * This is the CAN error interrupt and it will check the type of error and forward the error
+ * frame to upper layers.
+ */
+static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_berr_counter bec;
+ enum can_state state;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ u32 err_capt, alc;
+
+ loongson_canfd_get_bec(priv, &bec);
+
+ state = loongson_canfd_read_fault_state(ndev);
+ regmap_read(priv->regmap, LOONGSON_CANFD_ERR_CAPT, &err_capt);
+ regmap_read(priv->regmap, LOONGSON_CANFD_ALC, &alc);
+
+ netdev_dbg(ndev, "%s: ISR 0x%08x, rxerr %d, txerr %d, error type %lu, pos %lu, ALC id_field %lu, bit %lu\n",
+ __func__, isr, bec.rxerr, bec.txerr,
+ FIELD_GET(REG_ERR_CAPT_TYPE, err_capt),
+ FIELD_GET(REG_ERR_CAPT_POS, err_capt),
+ FIELD_GET(REG_ALC_ID_FIELD, alc),
+ FIELD_GET(REG_ALC_BIT_POS, alc));
+
+ skb = alloc_can_err_skb(ndev, &cf);
+
+ /*
+ * EWLI: error warning limit condition met
+ * FCSI: fault confinement state changed
+ * ALI: arbitration lost (just informative)
+ * BEI: bus error interrupt
+ */
+ if ((isr & REG_INT_STAT_FCSI) || (isr & REG_INT_STAT_EWLI)) {
+ netdev_info(ndev, "state changes from %s to %s\n",
+ can_get_state_str(priv->can.state), can_get_state_str(state));
+
+ if (priv->can.state == state)
+ netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
+
+ isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
+ priv->can.state = state;
+ switch (state) {
+ case CAN_STATE_BUS_OFF:
+ priv->can.can_stats.bus_off++;
+ if (priv->can.restart_ms)
+ regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
+ REG_CMD_ERCRST);
+
+ can_bus_off(ndev);
+ if (skb)
+ cf->can_id |= CAN_ERR_BUSOFF;
+ break;
+ case CAN_STATE_ERROR_PASSIVE:
+ priv->can.can_stats.error_passive++;
+ if (skb) {
+ cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT;
+ if (bec.rxerr >= CAN_ERROR_PASSIVE_THRESHOLD)
+ cf->data[1] = CAN_ERR_CRTL_RX_PASSIVE;
+ else
+ cf->data[1] = CAN_ERR_CRTL_TX_PASSIVE;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ }
+ break;
+ case CAN_STATE_ERROR_WARNING:
+ priv->can.can_stats.error_warning++;
+ if (skb) {
+ cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT;
+ if (bec.txerr > bec.rxerr)
+ cf->data[1] |= CAN_ERR_CRTL_TX_WARNING;
+ else
+ cf->data[1] |= CAN_ERR_CRTL_RX_WARNING;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ }
+ break;
+ case CAN_STATE_ERROR_ACTIVE:
+ cf->can_id |= CAN_ERR_CNT;
+ cf->data[1] = CAN_ERR_CRTL_ACTIVE;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ break;
+ default:
+ netdev_err(ndev, "Unexpected state: %d, %s!\n", state,
+ can_get_state_str(state));
+ break;
+ }
+ }
+
+ /* Check for Arbitration Lost interrupt */
+ if (isr & REG_INT_STAT_ALI) {
+ isr = REG_INT_STAT_ALI;
+ netdev_err(ndev, "Arbitration Lost interrupt\n");
+ priv->can.can_stats.arbitration_lost++;
+ if (skb) {
+ cf->can_id |= CAN_ERR_LOSTARB;
+ cf->data[0] = CAN_ERR_LOSTARB_UNSPEC;
+ }
+ }
+
+ /* Check for Bus Error interrupt */
+ if (isr & REG_INT_STAT_BEI) {
+ isr = REG_INT_STAT_BEI;
+ netdev_err(ndev, "Bus Error interrupt\n");
+ priv->can.can_stats.bus_error++;
+ stats->rx_errors++;
+ if (skb) {
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+ cf->data[2] = CAN_ERR_PROT_UNSPEC;
+ cf->data[3] = CAN_ERR_PROT_LOC_UNSPEC;
+ }
+ }
+
+ if (skb)
+ netif_rx(skb);
+
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, isr);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, isr << 16);
+}
+
+static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
+{
+ struct net_device *ndev = napi->dev;
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ int work_done = 0, ret = 1;
+ bool rxbuf_is_empty;
+ u32 sts;
+
+ rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv);
+
+ while (!rxbuf_is_empty && work_done < quota && ret > 0) {
+ ret = loongson_canfd_rx(ndev);
+ work_done++;
+ rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv);
+ }
+
+ /* Check for RX FIFO Overflow */
+ regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts);
+ if (sts & REG_STAT_DOR) {
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ netdev_info(ndev, "Loongson canfd RX overflow\n");
+ stats->rx_over_errors++;
+ stats->rx_errors++;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+ if (skb) {
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
+ netif_rx(skb);
+ }
+
+ /* Clear Data Overrun */
+ regmap_write(priv->regmap, LOONGSON_CANFD_CMD, REG_CMD_CDO);
+ }
+
+ if (rxbuf_is_empty && ret != 0) {
+ if (napi_complete_done(napi, work_done)) {
+ /*
+ * Clear and enable RBNEI. It is level-triggered,
+ * so there is no race condition.
+ */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK,
+ (REG_INT_STAT_RBNEI << 16));
+ }
+ }
+
+ return work_done;
+}
+
+static void loongson_canfd_tx_interrupt(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ enum loongson_canfd_txbuf_sts sts;
+ u32 buf_id;
+
+ guard(spinlock_irqsave)(&priv->tx_lock);
+
+ while ((buf_id = loongson_canfd_get_txbuf_id(priv, &sts)) < LOONGSON_CANFD_TXBUF_NUM) {
+ switch (sts) {
+ case TX_BS_VALID:
+ stats->tx_bytes += can_get_echo_skb(ndev, buf_id, NULL);
+ stats->tx_packets++;
+ break;
+ case TX_BS_FAIL:
+ u32 cnt;
+ /*
+ * This indicated that retransmit limit has been reached.
+ * Obviously we should not echo the frame, but also not indicate any
+ * kind of error. If desired, it was already reported (possible
+ * multiple times) on each arbitration lost.
+ */
+ regmap_read(priv->regmap, LOONGSON_CANFD_TX_FR_CNT, &cnt);
+ netdev_warn(ndev, "TXB in FAIL state, TX frame count: %d\n", cnt);
+ can_free_echo_skb(ndev, buf_id, NULL);
+ stats->tx_dropped++;
+ break;
+ case TX_BS_CANCEL:
+ /*
+ * We *could* re-queue the frame, but multiqueue/abort is
+ * not supported yet anyway.
+ */
+ netdev_warn(ndev, "TXB in CANCEL state\n");
+ can_free_echo_skb(ndev, buf_id, NULL);
+ stats->tx_dropped++;
+ break;
+ default:
+ break;
+ }
+
+ loongson_canfd_set_txbuf_cmd(ndev, TXT_CMD_SR_CLEAN, buf_id);
+ }
+
+ /*
+ * Clear the interrupt again. We do not want to receive again interrupt
+ * for the buffer already handled. If it is the last finished one then
+ * it would cause log of spurious interrupt.
+ */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI);
+
+ /* Check if at least one TX buffer is free */
+ if (loongson_canfd_txbuf_freed(priv))
+ netif_wake_queue(ndev);
+}
+
+/**
+ * loongson_canfd_interrupt() - CAN Isr
+ * @irq: irq number
+ * @dev_id: device id pointer
+ *
+ * This is the Loongson CANFD ISR. It checks for the type of interrupt
+ * and invokes the corresponding ISR.
+ *
+ * Return:
+ * IRQ_NONE - If CAN device is in sleep mode, IRQ_HANDLED otherwise
+ */
+static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *)dev_id;
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ u32 isr, imask;
+
+ for (unsigned int irq_loops = 0; irq_loops < 10000; irq_loops++) {
+ /* Get the interrupt status */
+ regmap_read(priv->regmap, LOONGSON_CANFD_INT_STAT, &isr);
+ if (!isr) {
+ if (irq_loops)
+ return IRQ_HANDLED;
+ else
+ return IRQ_NONE;
+ }
+
+ /* Receive Buffer Not Empty Interrupt */
+ if (isr & REG_INT_STAT_RBNEI) {
+ /*
+ * Mask RXBNEI the first, then clear interrupt and schedule NAPI.
+ * Even if another IRQ fires, RBNEI will always be 0 (masked).
+ */
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
+ napi_schedule(&priv->napi);
+ }
+
+ /* TX Buffer HW Command Interrupt */
+ if (isr & REG_INT_STAT_TXBHCI)
+ loongson_canfd_tx_interrupt(ndev);
+
+ /* Error interrupts */
+ imask = isr & REG_INT_STAT_ERRORI;
+ if (imask) {
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, imask);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, imask);
+ loongson_canfd_err_interrupt(ndev, isr);
+ }
+
+ /* Ignore RI, TI, LFI, RFI, BSI */
+ }
+
+ netdev_err(ndev, "Intterupt state: 0x%x.\n", isr);
+
+ if (isr & REG_INT_STAT_TXBHCI) {
+ for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) {
+ u32 sts = loongson_canfd_get_txbuf_sts(priv, i);
+
+ netdev_err(ndev, "TX buf %d status: 0x%x.\n", i, sts);
+ }
+ }
+
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
+ REG_INT_MASK_SET, REG_INT_MASK_SET);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * loongson_canfd_chip_stop() - Driver stop routine
+ * @ndev: Pointer to net_device structure
+ *
+ * This is the drivers stop routine. It will disable the interrupts and
+ * disable the controller.
+ */
+static void loongson_canfd_chip_stop(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ /* Disable interrupts and disable CAN */
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
+ REG_INT_MASK_SET, REG_INT_MASK_SET);
+ regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, 0);
+
+ priv->can.state = CAN_STATE_STOPPED;
+}
+
+/**
+ * loongson_canfd_open() - Driver open routine
+ * @ndev: Pointer to net_device structure
+ *
+ * This is the driver open routine.
+ * Return: 0 on success and failure value on error
+ */
+static int loongson_canfd_open(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ loongson_canfd_reset(ndev);
+
+ /* Common open */
+ ret = open_candev(ndev);
+ if (ret) {
+ netdev_warn(ndev, "open_candev failed!\n");
+ return ret;
+ }
+
+ napi_enable(&priv->napi);
+ ret = request_irq(ndev->irq, loongson_canfd_interrupt, IRQF_SHARED, ndev->name, ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "irq allocation for CAN failed\n");
+ goto err_irq;
+ }
+
+ ret = loongson_canfd_chip_start(ndev);
+ if (ret < 0) {
+ netdev_err(ndev, "loongson_canfd_chip_start failed!\n");
+ goto err_chip_start;
+ }
+
+ netdev_info(ndev, "loongson_canfd_device registered\n");
+ netif_start_queue(ndev);
+
+ return 0;
+
+err_chip_start:
+ free_irq(ndev->irq, ndev);
+err_irq:
+ close_candev(ndev);
+ return ret;
+}
+
+/**
+ * loongson_canfd_close() - Driver close routine
+ * @ndev: Pointer to net_device structure
+ *
+ * Return: 0 always
+ */
+static int loongson_canfd_close(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ napi_disable(&priv->napi);
+ loongson_canfd_chip_stop(ndev);
+ free_irq(ndev->irq, ndev);
+ close_candev(ndev);
+
+ return 0;
+}
+
+static const struct net_device_ops loongson_canfd_netdev_ops = {
+ .ndo_open = loongson_canfd_open,
+ .ndo_stop = loongson_canfd_close,
+ .ndo_start_xmit = loongson_canfd_start_xmit,
+};
+
+static const struct ethtool_ops loongson_canfd_ethtool_ops = {
+ .get_ts_info = ethtool_op_get_ts_info,
+};
+
+static const struct regmap_range loongson_canfd_reg_table_wr_range[] = {
+ regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_CONF),
+ regmap_reg_range(LOONGSON_CANFD_CMD, LOONGSON_CANFD_CMD),
+ regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_ERL),
+ regmap_reg_range(LOONGSON_CANFD_CTR_PRES, LOONGSON_CANFD_CTR_PRES),
+ regmap_reg_range(LOONGSON_CANFD_SSP_CFG, LOONGSON_CANFD_SSP_CFG),
+ regmap_reg_range(LOONGSON_CANFD_TS, LOONGSON_CANFD_FLT_CTRL),
+ regmap_reg_range(LOONGSON_CANFD_TX_CMD, LOONGSON_CANFD_TX_DATA_18),
+};
+
+static const struct regmap_range loongson_canfd_reg_table_rd_range[] = {
+ regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_STAT),
+ regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_BRE),
+ regmap_reg_range(LOONGSON_CANFD_ERR_CAPT, LOONGSON_CANFD_TX_STAT),
+ regmap_reg_range(LOONGSON_CANFD_TX_SEL, LOONGSON_CANFD_TX_DATA_18),
+};
+
+static const struct regmap_access_table loongson_canfd_reg_table_wr = {
+ .yes_ranges = loongson_canfd_reg_table_wr_range,
+ .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_wr_range),
+};
+
+static const struct regmap_access_table loongson_canfd_reg_table_rd = {
+ .yes_ranges = loongson_canfd_reg_table_rd_range,
+ .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_rd_range),
+};
+
+static bool loongson_canfd_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case LOONGSON_CANFD_MODE:
+ case LOONGSON_CANFD_CONF:
+ case LOONGSON_CANFD_STAT:
+ case LOONGSON_CANFD_INT_STAT:
+ case LOONGSON_CANFD_INT_ENA:
+ case LOONGSON_CANFD_INT_MASK:
+ case LOONGSON_CANFD_ERL:
+ case LOONGSON_CANFD_FSTAT:
+ case LOONGSON_CANFD_ERC:
+ case LOONGSON_CANFD_ERR_CAPT:
+ case LOONGSON_CANFD_ALC:
+ case LOONGSON_CANFD_TX_FR_CNT:
+ case LOONGSON_CANFD_RX_STAT:
+ case LOONGSON_CANFD_RX_DATA:
+ case LOONGSON_CANFD_TX_STAT:
+ case LOONGSON_CANFD_TX_SEL:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static const struct regmap_config loongson_cangfd_regmap = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .wr_table = &loongson_canfd_reg_table_wr,
+ .rd_table = &loongson_canfd_reg_table_rd,
+ .volatile_reg = loongson_canfd_volatile_reg,
+ .max_register = LOONGSON_CANFD_TX_DATA_18,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int loongson_canfd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct loongson_canfd_priv *priv;
+ struct net_device *ndev;
+ struct regmap *regmap;
+ struct resource *res;
+ void __iomem *base;
+ u32 clk_rate;
+ int ret = 0, irq;
+
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ device_property_read_u32(dev, "clock-frequency", &clk_rate);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ /* Create a CAN device instance */
+ ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM);
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ spin_lock_init(&priv->tx_lock);
+ priv->regmap = regmap;
+ priv->res = res;
+
+ priv->can.clock.freq = clk_rate;
+ priv->can.bittiming_const = &loongson_canfd_bit_timing;
+ priv->can.fd.data_bittiming_const = &loongson_canfd_bit_timing_data;
+ priv->can.fd.tdc_const = &loongson_canfd_tdc_data;
+
+ priv->can.fd.do_set_data_bittiming = loongson_canfd_set_data_bittiming;
+ priv->can.fd.do_get_auto_tdcv = loongson_canfd_get_auto_tdcv;
+ priv->can.do_set_mode = loongson_canfd_do_set_mode;
+ priv->can.do_set_bittiming = loongson_canfd_set_bittiming;
+ priv->can.do_get_berr_counter = loongson_canfd_get_berr_counter;
+
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY |
+ CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_ONE_SHOT |
+ CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD |
+ CAN_CTRLMODE_PRESUME_ACK | CAN_CTRLMODE_FD_NON_ISO |
+ CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_CC_LEN8_DLC;
+
+ ndev->irq = irq;
+ ndev->flags |= IFF_ECHO; /* We support local echo */
+ platform_set_drvdata(pdev, ndev);
+ ndev->netdev_ops = &loongson_canfd_netdev_ops;
+ ndev->ethtool_ops = &loongson_canfd_ethtool_ops;
+ SET_NETDEV_DEV(ndev, dev);
+
+ netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
+
+ ret = register_candev(ndev);
+ if (ret) {
+ dev_err(dev, "register_candev failed with %d\n", ret);
+ goto err_candev_free;
+ }
+
+ return 0;
+
+err_candev_free:
+ netif_napi_del(&priv->napi);
+ free_candev(ndev);
+ return ret;
+}
+
+static void loongson_canfd_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+
+ unregister_candev(ndev);
+ netif_napi_del(&priv->napi);
+ free_candev(ndev);
+}
+
+static const struct acpi_device_id loongson_canfd_acpi_match[] = {
+ { "LOON0015" },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, loongson_canfd_acpi_match);
+
+static struct platform_driver loongson_canfd_driver = {
+ .probe = loongson_canfd_probe,
+ .remove = loongson_canfd_remove,
+ .driver = {
+ .name = DEV_NAME,
+ .acpi_match_table = loongson_canfd_acpi_match,
+ },
+};
+module_platform_driver(loongson_canfd_driver);
+
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
+MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 2/2] can: loongson_canfd: Add RXDMA support
2026-06-30 3:01 [PATCH v3 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
2026-06-30 3:01 ` [PATCH v3 1/2] can: " Binbin Zhou
@ 2026-06-30 3:01 ` Binbin Zhou
2026-06-30 3:11 ` sashiko-bot
1 sibling, 1 reply; 6+ messages in thread
From: Binbin Zhou @ 2026-06-30 3:01 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Marc Kleine-Budde, Vincent Mailhol,
Bingxiong Li
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-can, jeffbai,
Binbin Zhou
Add optional DMA support for RX path using the Loongson APB CMC DMA
engine. When a DMA channel is successfully requested, the driver:
- Uses DMA cyclic transfers to write incoming CAN frames directly to
a coherent DMA buffer
- Replaces RXBNEI (RX buffer not empty interrupt) with DMADI (DMA
done interrupt)
- Dynamically switches between DMA and PIO modes based on channel
availability
This significantly reduces CPU intervention under high RX load,
especially beneficial for CAN FD at higher data rates.
Co-developed-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Bingxiong Li <libingxiong@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/net/can/Kconfig | 2 +-
drivers/net/can/loongson_canfd.c | 202 ++++++++++++++++++++++++++++---
2 files changed, 185 insertions(+), 19 deletions(-)
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index 55c40b0654fb..f367517ede0d 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -191,7 +191,7 @@ config CAN_KVASER_PCIEFD
config CAN_LOONGSON_CANFD
tristate "Loongson CAN-FD controller"
- depends on HAS_IOMEM
+ depends on HAS_IOMEM && (LOONGSON2_APB_CMC_DMA || COMPILE_TEST)
select REGMAP_MMIO
help
This is a canfd driver switch for the Loongson platform,
diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c
index 1c7381129c2b..a4b5d9e50676 100644
--- a/drivers/net/can/loongson_canfd.c
+++ b/drivers/net/can/loongson_canfd.c
@@ -6,10 +6,14 @@
*/
#include <linux/acpi.h>
+#include <linux/acpi_dma.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/can/dev.h>
#include <linux/can/error.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
#include <linux/ethtool.h>
#include <linux/io.h>
#include <linux/interrupt.h>
@@ -339,8 +343,10 @@
#define DEV_NAME "loongson_canfd"
#define LOONGSON_CANFD_ID 0xBABE
#define LOONGSON_CANFD_DW_BYTE 4
+#define LOONGSON_CANFD_RXBUF_SZ SZ_1K
#define LOONGSON_CANFD_TXBUF_NUM 8
#define LOONGSON_CANFD_MAX_RTXTH 0xf
+#define LOONGSON_CANFD_RXDMA_NUM (LOONGSON_CANFD_RXBUF_SZ / DMA_SLAVE_BUSWIDTH_4_BYTES)
/**
* struct loongson_canfd_priv - This definition define CAN driver instance
@@ -348,14 +354,26 @@
* @napi: NAPI structure
* @regmap: regmap of the CAN device
* @res: Pointer to the CAN device respurce
+ * @rx_ch: CAN DMA rx channel
+ * @rx_dma_buf: CAN DMA rx buffer bus address
+ * @rx_buf: CAN DMA rx buffer cpu address
+ * @last_res: Last rx data in DMA route
* @tx_lock: Lock for synchronizing TX interrupt handling
+ * @get_rx_data: Callback of reading CAN rx data
+ * @get_rxbuf_empty: Callback of gets the RX buffer is empty in dma mode
*/
struct loongson_canfd_priv {
struct can_priv can; /* must be first member! */
struct napi_struct napi;
struct regmap *regmap;
struct resource *res;
+ struct dma_chan *rx_ch;
+ dma_addr_t rx_dma_buf; /* dma rx buffer bus address */
+ unsigned int *rx_buf; /* dma rx buffer cpu address */
+ u16 last_res;
spinlock_t tx_lock; /* protect the sending queue */
+ u32 (*get_rx_data)(struct loongson_canfd_priv *priv);
+ bool (*get_rxbuf_empty)(struct loongson_canfd_priv *priv);
};
/**
@@ -536,13 +554,139 @@ static void loongson_canfd_set_txbuf_cmd(struct net_device *ndev,
}
/**
- * loongson_canfd_rxbuf_empty() - Gets the RX buffer is empty
+ * loongson_canfd_get_rxdata_in_dma() - Reading RX data in DMA mode
+ * @priv: Pointer to private data
+ *
+ * Return: The CANFD RX data.
+ */
+static u32 loongson_canfd_get_rxdata_in_dma(struct loongson_canfd_priv *priv)
+{
+ u32 data = 0;
+
+ data = priv->rx_buf[LOONGSON_CANFD_RXDMA_NUM - priv->last_res--];
+ if (!priv->last_res)
+ priv->last_res = LOONGSON_CANFD_RXDMA_NUM;
+
+ return data;
+}
+
+/**
+ * loongson_canfd_get_rxbuf_empty_in_dma() - Gets the RX buffer is empty in dma mode
* @priv: Pointer to private data
*
* Return: True - RX buffer is empty.
* False - RX buffer is processing
*/
-static bool loongson_canfd_rxbuf_empty(struct loongson_canfd_priv *priv)
+static bool loongson_canfd_get_rxbuf_empty_in_dma(struct loongson_canfd_priv *priv)
+{
+ struct dma_tx_state state;
+ enum dma_status status;
+
+ status = dmaengine_tx_status(priv->rx_ch, priv->rx_ch->cookie, &state);
+ if (status != DMA_IN_PROGRESS)
+ return true;
+
+ return priv->last_res == (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES);
+}
+
+static void loongson_canfd_rxdma_free(struct loongson_canfd_priv *priv, struct device *dev)
+{
+ dma_free_coherent(dev, LOONGSON_CANFD_RXBUF_SZ, priv->rx_buf, priv->rx_dma_buf);
+}
+
+static void loongson_canfd_rxdma_remove(struct loongson_canfd_priv *priv, struct device *dev)
+{
+ if (!priv->rx_ch)
+ return;
+
+ dmaengine_terminate_sync(priv->rx_ch);
+ loongson_canfd_rxdma_free(priv, dev);
+ dma_release_channel(priv->rx_ch);
+}
+
+/**
+ * loongson_canfd_rxdma_init() - Loongson canfd RXDMA initialization
+ * @ndev: Pointer to net_device structure
+ *
+ * Return: The number of messages in the receive buffer
+ */
+static int loongson_canfd_rxdma_init(struct net_device *ndev)
+{
+ struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ struct dma_async_tx_descriptor *desc = NULL;
+ struct device *dev = ndev->dev.parent;
+ struct dma_slave_config config;
+ int ret;
+
+ if (!priv->rx_ch)
+ return -EINVAL;
+
+ priv->rx_buf = dma_alloc_coherent(dev, LOONGSON_CANFD_RXBUF_SZ,
+ &priv->rx_dma_buf, GFP_KERNEL);
+ if (!priv->rx_buf)
+ return -ENOMEM;
+
+ /* Configure DMA channel */
+ memset(&config, 0, sizeof(config));
+ config.src_addr = priv->res->start + LOONGSON_CANFD_RX_DATA;
+ config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ ret = dmaengine_slave_config(priv->rx_ch, &config);
+ if (ret < 0) {
+ netdev_err(ndev, "Loongson canfd rxdma channel config failed\n");
+ goto err_config;
+ }
+
+ /* Prepare a DMA cyclic transaction */
+ desc = dmaengine_prep_dma_cyclic(priv->rx_ch, priv->rx_dma_buf,
+ LOONGSON_CANFD_RXBUF_SZ, LOONGSON_CANFD_RXBUF_SZ,
+ DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
+ if (!desc) {
+ netdev_err(ndev, "Loongson canfd rxdma cyclic transaction failed\n");
+ ret = -EBUSY;
+ goto err_config;
+ }
+
+ /* Push current dma transaction in the pending queue */
+ ret = dma_submit_error(dmaengine_submit(desc));
+ if (ret) {
+ dmaengine_terminate_sync(priv->rx_ch);
+ goto err_config;
+ }
+
+ /* Issue pending DMA requests */
+ dma_async_issue_pending(priv->rx_ch);
+
+ return 0;
+
+err_config:
+ loongson_canfd_rxdma_free(priv, dev);
+ return ret;
+}
+
+/**
+ * loongson_canfd_get_rxdata_in_poll() - Reading RX data in poll mode
+ * @priv: Pointer to private data
+ *
+ * Return: The CANFD RX data.
+ */
+static u32 loongson_canfd_get_rxdata_in_poll(struct loongson_canfd_priv *priv)
+{
+ u32 data;
+
+ regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+
+ return data;
+}
+
+/**
+ * loongson_canfd_get_rxbuf_empty_in_poll() - Gets the RX buffer is empty in poll mode
+ * @priv: Pointer to private data
+ *
+ * Return: True - RX buffer is empty.
+ * False - RX buffer is processing
+ */
+static bool loongson_canfd_get_rxbuf_empty_in_poll(struct loongson_canfd_priv *priv)
{
return !!regmap_test_bits(priv->regmap, LOONGSON_CANFD_RX_STAT, REG_RX_STAT_RXE);
}
@@ -767,8 +911,8 @@ static int loongson_canfd_chip_start(struct net_device *ndev)
loongson_canfd_set_conf_mode(priv);
/* Configure interrupts */
- int_ena = REG_INT_STAT_RBNEI | REG_INT_STAT_TXBHCI |
- REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
+ int_ena = REG_INT_STAT_TXBHCI | REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
+ int_ena |= priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
/* Bus error reporting */
if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
@@ -961,7 +1105,7 @@ static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_dev
static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
u32 meta0, u32 meta1)
{
- u32 data, i, wc, len;
+ u32 i, wc, len;
/* Extended Identifier Type */
if (meta0 & REG_FRAME_META0_XDT)
@@ -999,10 +1143,10 @@ static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struc
/* Data */
for (i = 0; i < len; i += LOONGSON_CANFD_DW_BYTE)
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, (u32 *)(cf->data + i));
+ *(u32 *)(cf->data + i) = priv->get_rx_data(priv);
while (unlikely(i < wc * LOONGSON_CANFD_DW_BYTE)) {
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
+ priv->get_rx_data(priv);
i += LOONGSON_CANFD_DW_BYTE;
}
}
@@ -1025,8 +1169,8 @@ static int loongson_canfd_rx(struct net_device *ndev)
struct sk_buff *skb;
u32 meta0, meta1;
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
- regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
+ meta0 = priv->get_rx_data(priv);
+ meta1 = priv->get_rx_data(priv);
/* Number of characters received */
if (!FIELD_GET(REG_FRAME_META1_RWCNT, meta1))
@@ -1254,16 +1398,17 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
{
struct net_device *ndev = napi->dev;
struct loongson_canfd_priv *priv = netdev_priv(ndev);
+ int int_ena = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
int work_done = 0, ret = 1;
bool rxbuf_is_empty;
u32 sts;
- rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv);
+ rxbuf_is_empty = priv->get_rxbuf_empty(priv);
while (!rxbuf_is_empty && work_done < quota && ret > 0) {
ret = loongson_canfd_rx(ndev);
work_done++;
- rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv);
+ rxbuf_is_empty = priv->get_rxbuf_empty(priv);
}
/* Check for RX FIFO Overflow */
@@ -1291,12 +1436,11 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
if (rxbuf_is_empty && ret != 0) {
if (napi_complete_done(napi, work_done)) {
/*
- * Clear and enable RBNEI. It is level-triggered,
+ * Clear and enable RBNEI/DMADI. It is level-triggered, so
* so there is no race condition.
*/
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK,
- (REG_INT_STAT_RBNEI << 16));
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, int_ena);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, (int_ena << 16));
}
}
@@ -1387,13 +1531,14 @@ static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id)
}
/* Receive Buffer Not Empty Interrupt */
- if (isr & REG_INT_STAT_RBNEI) {
+ imask = priv->rx_ch ? REG_INT_STAT_DMADI : REG_INT_STAT_RBNEI;
+ if (isr & imask) {
/*
* Mask RXBNEI the first, then clear interrupt and schedule NAPI.
* Even if another IRQ fires, RBNEI will always be 0 (masked).
*/
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI);
- regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, imask);
+ regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, imask);
napi_schedule(&priv->napi);
}
@@ -1646,6 +1791,24 @@ static int loongson_canfd_probe(struct platform_device *pdev)
ndev->ethtool_ops = &loongson_canfd_ethtool_ops;
SET_NETDEV_DEV(ndev, dev);
+ priv->get_rx_data = loongson_canfd_get_rxdata_in_poll;
+ priv->get_rxbuf_empty = loongson_canfd_get_rxbuf_empty_in_poll;
+
+ priv->rx_ch = dma_request_chan(dev, "rx");
+ if (PTR_ERR(priv->rx_ch) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (IS_ERR(priv->rx_ch)) {
+ dev_warn(dev, "Fall back in poll mode for any non-deferral error.\n");
+ priv->rx_ch = NULL;
+ }
+
+ ret = loongson_canfd_rxdma_init(ndev);
+ if (!ret) {
+ priv->get_rx_data = loongson_canfd_get_rxdata_in_dma;
+ priv->get_rxbuf_empty = loongson_canfd_get_rxbuf_empty_in_dma;
+ priv->last_res = LOONGSON_CANFD_RXDMA_NUM;
+ }
netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
ret = register_candev(ndev);
@@ -1658,6 +1821,7 @@ static int loongson_canfd_probe(struct platform_device *pdev)
err_candev_free:
netif_napi_del(&priv->napi);
+ loongson_canfd_rxdma_remove(priv, &pdev->dev);
free_candev(ndev);
return ret;
}
@@ -1669,6 +1833,7 @@ static void loongson_canfd_remove(struct platform_device *pdev)
unregister_candev(ndev);
netif_napi_del(&priv->napi);
+ loongson_canfd_rxdma_remove(priv, &pdev->dev);
free_candev(ndev);
}
@@ -1688,6 +1853,7 @@ static struct platform_driver loongson_canfd_driver = {
};
module_platform_driver(loongson_canfd_driver);
+MODULE_SOFTDEP("pre: loongson2-apb-cmc-dma");
MODULE_AUTHOR("Loongson Technology Corporation Limited");
MODULE_DESCRIPTION("Loongson CAN-FD Controller driver");
MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/2] can: loongson_canfd: Add RXDMA support
2026-06-30 3:01 ` [PATCH v3 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
@ 2026-06-30 3:11 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-06-30 3:11 UTC (permalink / raw)
To: Binbin Zhou
Cc: Vincent Mailhol, Marc Kleine-Budde, Oliver Hartkopp,
Oleksij Rempel, linux-can
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] NAPI polling loop reads incomplete CAN frames ahead of the DMA engine, corrupting the ring buffer read pointer.
- [High] DMA initialization failure breaks PIO fallback (hanging RX) and causes double-free on driver removal.
Pre-existing issues:
- [High] Missing bounds check on CAN frame data length allows a heap buffer overflow.
- [High] SKB allocation failure causes permanent NAPI deadlock and RX stream desynchronization.
--
commit 60c5f9dce521c51839cd00b85f3676b903c1d307
Author: Binbin Zhou <zhoubinbin@loongson.cn>
can: loongson_canfd: Add RXDMA support
Add optional DMA support for RX path using the Loongson APB CMC DMA
engine. When a DMA channel is successfully requested, the driver:
- Uses DMA cyclic transfers to write incoming CAN frames directly to
a coherent DMA buffer
> diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c
> index 1c7381129c2b6..a4b5d9e506765 100644
> --- a/drivers/net/can/loongson_canfd.c
> +++ b/drivers/net/can/loongson_canfd.c
[ ... ]
> +static void loongson_canfd_rxdma_remove(struct loongson_canfd_priv *priv, struct device *dev)
> +{
> + if (!priv->rx_ch)
> + return;
> +
> + dmaengine_terminate_sync(priv->rx_ch);
> + loongson_canfd_rxdma_free(priv, dev);
> + dma_release_channel(priv->rx_ch);
> +}
[ ... ]
> +static bool loongson_canfd_get_rxbuf_empty_in_dma(struct loongson_canfd_priv *priv)
> +{
> + struct dma_tx_state state;
> + enum dma_status status;
> +
> + status = dmaengine_tx_status(priv->rx_ch, priv->rx_ch->cookie, &state);
> + if (status != DMA_IN_PROGRESS)
> + return true;
> +
> + return priv->last_res == (state.residue / DMA_SLAVE_BUSWIDTH_4_BYTES);
> +}
[Severity: High]
Could this implementation cause the CPU to read incomplete frames ahead of the
DMA engine?
The cyclic DMA engine writes to memory word-by-word. As soon as the DMA
transfers a single word (like meta0), the residue changes and this function
returns false.
Because the NAPI loop executing on the CPU is faster than the DMA engine, it
appears loongson_canfd_rx() will unconditionally read the entire frame
(including meta1 and data words) before the DMA engine has actually transferred
them.
Does this structurally guarantee stream desynchronization and data corruption
under load?
[ ... ]
> @@ -999,10 +1143,10 @@ static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struc
>
> /* Data */
> for (i = 0; i < len; i += LOONGSON_CANFD_DW_BYTE)
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, (u32 *)(cf->data + i));
> + *(u32 *)(cf->data + i) = priv->get_rx_data(priv);
>
> while (unlikely(i < wc * LOONGSON_CANFD_DW_BYTE)) {
> - regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data);
> + priv->get_rx_data(priv);
> i += LOONGSON_CANFD_DW_BYTE;
> }
> }
[Severity: High]
This is a pre-existing issue, but is it possible for `len` to exceed the size
of `cf->data` and cause a heap buffer overflow?
The `cf->data` buffer is a fixed 64-byte array. `len` is calculated based on
the RWCNT field from meta1, but there appears to be no bounds check verifying
that `len <= CANFD_MAX_DLEN`.
If the hardware provides a malformed RWCNT, or if the CPU reads garbage data
as meta1 (due to the DMA race mentioned above), won't this write past the end
of the allocated SKB and corrupt the `skb_shared_info` and adjacent heap memory?
[ ... ]
> @@ -1291,12 +1436,11 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> if (rxbuf_is_empty && ret != 0) {
> if (napi_complete_done(napi, work_done)) {
[Severity: High]
This isn't a bug introduced by this patch, but what happens if SKB allocation
fails inside loongson_canfd_rx()?
Looking at loongson_canfd_rx(), if alloc_can_skb() or alloc_canfd_skb() fails
due to memory exhaustion, it returns 0 immediately without consuming the rest
of the frame's words:
if (unlikely(!skb))
return 0;
This seems to cause two separate issues:
1. It desynchronizes the data stream, as subsequent polls will misinterpret
remaining payload words as the headers of the next frame.
2. It breaks the NAPI loop because `ret == 0` causes this completion condition
(`rxbuf_is_empty && ret != 0`) to evaluate to false.
By skipping napi_complete_done() and returning a value less than the quota, the
NAPI state bit NAPIF_STATE_SCHED remains permanently set but removed from the
poll list. Doesn't this permanently deadlock the receiver until a module reload?
[ ... ]
> @@ -1646,6 +1791,24 @@ static int loongson_canfd_probe(struct platform_device *pdev)
> ndev->ethtool_ops = &loongson_canfd_ethtool_ops;
> SET_NETDEV_DEV(ndev, dev);
>
> + priv->get_rx_data = loongson_canfd_get_rxdata_in_poll;
> + priv->get_rxbuf_empty = loongson_canfd_get_rxbuf_empty_in_poll;
> +
> + priv->rx_ch = dma_request_chan(dev, "rx");
> + if (PTR_ERR(priv->rx_ch) == -EPROBE_DEFER)
> + return -EPROBE_DEFER;
> +
> + if (IS_ERR(priv->rx_ch)) {
> + dev_warn(dev, "Fall back in poll mode for any non-deferral error.\n");
> + priv->rx_ch = NULL;
> + }
> +
> + ret = loongson_canfd_rxdma_init(ndev);
> + if (!ret) {
> + priv->get_rx_data = loongson_canfd_get_rxdata_in_dma;
> + priv->get_rxbuf_empty = loongson_canfd_get_rxbuf_empty_in_dma;
> + priv->last_res = LOONGSON_CANFD_RXDMA_NUM;
> + }
> netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi);
[Severity: High]
What happens if loongson_canfd_rxdma_init() fails?
If loongson_canfd_rxdma_init() returns an error (e.g., dma_alloc_coherent fails),
it frees the buffer but does not clear priv->rx_ch or release the channel. The
probe function ignores the error and proceeds with the intent to fall back to PIO.
However, since priv->rx_ch remains non-NULL, loongson_canfd_chip_start() will
incorrectly configure the controller to use the DMADI interrupt instead of the
RBNEI PIO interrupt, permanently breaking reception.
Additionally, upon module removal, loongson_canfd_rxdma_remove() checks
priv->rx_ch and calls loongson_canfd_rxdma_free() again. Since the buffer was
already freed during the failed init, won't this trigger a double-free?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782787997.git.zhoubinbin@loongson.cn?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] can: Add Loongson CAN-FD controller driver
2026-06-30 3:01 ` [PATCH v3 1/2] can: " Binbin Zhou
@ 2026-06-30 3:16 ` sashiko-bot
2026-06-30 6:51 ` Vincent Mailhol
0 siblings, 1 reply; 6+ messages in thread
From: sashiko-bot @ 2026-06-30 3:16 UTC (permalink / raw)
To: Binbin Zhou
Cc: Oleksij Rempel, linux-can, Marc Kleine-Budde, Vincent Mailhol,
Oliver Hartkopp
Thank you for your contribution! Sashiko AI review found 18 potential issue(s) to consider:
- [High] Missing `skb` check in `CAN_STATE_ERROR_ACTIVE` case leads to a NULL pointer dereference.
- [High] SKB allocation failure or invalid frame in RX path permanently stalls NAPI and corrupts RX FIFO.
- [High] NAPI poll list corruption due to calling `napi_complete_done` while returning `quota`.
- [High] Missing `napi_disable()` on error paths in `loongson_canfd_open()` leads to a kernel panic on subsequent open attempts.
- [High] Race condition in `loongson_canfd_start_xmit` causes unexpected packet drops due to un-reaped TX buffers.
- [High] Missing bounds check on the hardware-provided word count `REG_FRAME_META1_RWCNT` causes heap buffer overflow.
- [High] Unchecked return value of `device_property_read_u32` leaves `clk_rate` uninitialized.
- [High] Premature hardware restart on Bus-Off when auto-restart is configured.
- [High] Bitwise NOT on a 16-bit variable unintentionally unmasks all interrupts due to integer promotion.
- [High] Overwriting of `isr` variable causes lost error interrupts and permanent interrupt masking.
- [High] Using `regmap_update_bits` on command-style SET/CLR registers causes conflicting hardware commands.
- [High] Calling `kfree_skb()` with hardware interrupts disabled.
- [High] Potential infinite loop in TX interrupt handler due to repeated status reads of posted writes.
- [Medium] The driver advertises 'hardware timestamping' in commit message but does not implement it.
- [Medium] `ndev->stats.rx_errors` is modified concurrently from both hardirq and NAPI softirq contexts without locking.
- [Medium] Incorrect check for RTR frames causes invalid RX byte statistics.
- [Medium] Missing `cf->len8_dlc` assignment for Classic CAN frames.
- [Low] Syntax error due to variable declarations immediately following a switch case label.
--
commit e0d474f047900e47a73eaa207fc07dd8c35084bf
Author: Binbin Zhou <zhoubinbin@loongson.cn>
Subject: can: Add Loongson CAN-FD controller driver
This commit adds support for the CAN-FD controller integrated into
Loongson-2K series SoCs. It implements NAPI for the RX path, TX buffer
management, bus error reporting, and various timing configurations.
[Severity: Medium]
The commit message states the driver supports hardware timestamping, but is
this functionality actually implemented? Looking at the driver code, it sets
ethtool_op_get_ts_info but does not seem to extract the timestamp field from
the hardware or write to skb_hwtstamps in the receive path.
> diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c
> new file mode 100644
> index 0000000000000..1c7381129c2b6
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd.c
[ ... ]
> @@ -891,37 +891,24 @@ static int loongson_canfd_chip_start(struct net_device *ndev)
[ ... ]
> + /* Configure interrupts */
> + int_ena = REG_INT_STAT_RBNEI | REG_INT_STAT_TXBHCI |
> + REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
> +
> + /* Bus error reporting */
> + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> + int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
> +
> + /* It's after reset, so there is no need to clear anything */
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, ~int_ena);
[Severity: High]
Will this bitwise NOT operation unmask all interrupts on the controller?
Because int_ena is a 16-bit unsigned integer, the bitwise NOT causes integer
promotion to a 32-bit signed int, filling the upper 16 bits with 1s. Since
the upper bits of this register clear the mask, does this unintentionally
enable every possible interrupt?
> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena);
[ ... ]
> @@ -1048,46 +1048,22 @@ static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
[ ... ]
> + for_each_clear_bit(i, &tx_brp, LOONGSON_CANFD_TXBUF_NUM) {
> + u32 transmission_complete_mask = 0x3 << (i * 2);
> +
> + if (!(tx_bs & transmission_complete_mask)) {
> + buf_id = i;
> + break;
> + }
> + }
> +
> + if (!loongson_canfd_insert_frame(priv, cf, buf_id, can_is_canfd_skb(skb))) {
> + netdev_err(ndev, "TXNF set but cannot insert frame into TXTB! HW Bug?");
> + kfree_skb(skb);
[Severity: High]
Could a race condition here cause silent packet drops? The hardware clears
tx_brp immediately on completion, but tx_bs is cleared later in the ISR.
If all inactive buffers are awaiting a reap, the loop finishes without
finding a buffer, leaving buf_id at 0. This would cause the insert check to
fail, dropping the packet while incorrectly returning NETDEV_TX_OK.
[Severity: High]
Is it safe to call kfree_skb here? The tx_lock is held with
spinlock_irqsave which disables local interrupts. Calling kfree_skb with
hard interrupts disabled can lead to kernel warnings and potential deadlocks.
Should this use dev_kfree_skb_any instead?
> + ndev->stats.tx_dropped++;
> + return NETDEV_TX_OK;
> + }
[ ... ]
> @@ -1105,37 +1105,25 @@ static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
[ ... ]
> + /* Data Length Code */
> + len = FIELD_GET(REG_FRAME_META1_DLC, meta1);
> + if (len > 8) {
> + if (meta1 & REG_FRAME_META1_FDF)
> + len = wc << 2;
> + else
> + len = 8;
> + }
> +
> + cf->len = len;
[Severity: Medium]
Does this properly support the CAN_CTRLMODE_CC_LEN8_DLC feature? For Classic
CAN frames with a DLC greater than 8, the length is appropriately capped to
8, but the original DLC is not saved to cf->len8_dlc. This might break
userspace protocols that rely on the original DLC data.
> + if (unlikely(len > wc * LOONGSON_CANFD_DW_BYTE))
> + len = wc * LOONGSON_CANFD_DW_BYTE;
> +
> + /* Data */
> + for (i = 0; i < len; i += LOONGSON_CANFD_DW_BYTE)
> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, (u32 *)(cf->data + i));
[Severity: High]
Can a malformed hardware word count cause a heap buffer overflow? The len
variable is checked against the hardware-provided word count, but not against
CANFD_MAX_DLEN. If the hardware yields an invalid read word count, the length
could exceed the 64-byte payload limit, causing out of bounds memory writes
to the skb data.
[ ... ]
> @@ -1164,28 +1164,22 @@ static int loongson_canfd_rx(struct net_device *ndev)
[ ... ]
> + /* Flexible Data-rate Format */
> + if (meta1 & REG_FRAME_META1_FDF)
> + skb = alloc_canfd_skb(ndev, &cf);
> + else
> + skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
> + if (unlikely(!skb))
> + return 0;
[Severity: High]
If the skb allocation fails, does returning 0 early permanently stall the
NAPI loop? Since the rest of the data payload is not read from the hardware
FIFO, the RX stream might desynchronize. Also, returning 0 here causes the
caller to exit its loop without completing NAPI, potentially leaving it
permanently in a scheduled state.
> +
> + loongson_canfd_read_rx_frame(priv, cf, meta0, meta1);
> +
> + if (cf->can_id != CAN_RTR_FLAG)
> + stats->rx_bytes += cf->len;
[Severity: Medium]
Is this RTR check accurate? The can_id field contains both the CAN ID and
the flags, so a direct inequality comparison to CAN_RTR_FLAG might evaluate
true for almost all RTR frames, erroneously inflating the RX byte statistics
for frames with no actual payload.
[ ... ]
> @@ -1203,32 +1203,26 @@ static enum can_state loongson_canfd_read_fault_state(struct net_device *ndev)
[ ... ]
> + switch (FIELD_GET(REG_FSTAT_MASK, fstat)) {
> + case REG_FSTAT_ERA:
> + u32 ewl, erl, rec_tec, max_tec;
[Severity: Low]
Will this compile on strict C11 compilers? The C11 standard does not allow
variable declarations immediately following a switch case label.
[ ... ]
> @@ -1278,82 +1278,28 @@ static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
[ ... ]
> + if ((isr & REG_INT_STAT_FCSI) || (isr & REG_INT_STAT_EWLI)) {
> + netdev_info(ndev, "state changes from %s to %s\n",
> + can_get_state_str(priv->can.state), can_get_state_str(state));
> +
> + if (priv->can.state == state)
> + netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
> +
> + isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
[Severity: High]
Does overwriting the isr variable here permanently disable other interrupts?
If arbitration lost or bus error flags are pending, this assignment destroys
them. Later in the function, isr is shifted to unmask interrupts, meaning the
erased flags will never be unmasked again.
> + priv->can.state = state;
> + switch (state) {
> + case CAN_STATE_BUS_OFF:
> + priv->can.can_stats.bus_off++;
> + if (priv->can.restart_ms)
> + regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
> + REG_CMD_ERCRST);
[Severity: High]
Does this prematurely restart the hardware upon a bus-off event? Instantly
writing the error reset command clears the hardware error state and rejoins
the bus immediately, ignoring the restart delay contract enforced by the core
can_bus_off timer.
[ ... ]
> + case CAN_STATE_ERROR_ACTIVE:
> + cf->can_id |= CAN_ERR_CNT;
> + cf->data[1] = CAN_ERR_CRTL_ACTIVE;
> + cf->data[6] = bec.txerr;
[Severity: High]
Could this lead to a null pointer dereference? The alloc_can_err_skb call
earlier in the function can fail and leave cf uninitialized. While the other
switch cases check if skb is valid before modifying cf data, this case does
not, which would crash if memory pressure prevents allocation.
[ ... ]
> + /* Check for Bus Error interrupt */
> + if (isr & REG_INT_STAT_BEI) {
> + isr = REG_INT_STAT_BEI;
> + netdev_err(ndev, "Bus Error interrupt\n");
> + priv->can.can_stats.bus_error++;
> + stats->rx_errors++;
[Severity: Medium]
Is it safe to increment stats->rx_errors without locking here? This counter
is modified from hardirq context here and concurrently from softirq context
in the NAPI poll routine, which could cause lost increments due to data races
on 32-bit systems.
[ ... ]
> @@ -1391,37 +1391,25 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
[ ... ]
> + /* Check for RX FIFO Overflow */
> + regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts);
> + if (sts & REG_STAT_DOR) {
> + struct net_device_stats *stats = &ndev->stats;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + netdev_info(ndev, "Loongson canfd RX overflow\n");
> + stats->rx_over_errors++;
> + stats->rx_errors++;
[Severity: Medium]
As noted above, stats->rx_errors is updated concurrently in both softirq and
hardirq contexts without locking.
[ ... ]
> + if (rxbuf_is_empty && ret != 0) {
> + if (napi_complete_done(napi, work_done)) {
[Severity: High]
Should this check if work_done is less than quota? If exactly quota frames
are processed and the hardware buffer becomes empty, this logic calls
napi_complete_done and then returns quota to the networking core. Returning
the full quota instructs the core to reschedule the instance, but the state
has already been manipulated, which can corrupt the NAPI poll list.
[ ... ]
> @@ -1425,33 +1425,18 @@ static void loongson_canfd_tx_interrupt(struct net_device *ndev)
[ ... ]
> + while ((buf_id = loongson_canfd_get_txbuf_id(priv, &sts)) < LOONGSON_CANFD_TXBUF_NUM) {
> + switch (sts) {
> + case TX_BS_VALID:
> + stats->tx_bytes += can_get_echo_skb(ndev, buf_id, NULL);
> + stats->tx_packets++;
> + break;
[Severity: High]
Could this result in an inescapable infinite loop? When the driver writes a
clear command to the posted MMIO register at the end of the loop, it
immediately reads the status register back in the while condition. Because
the hardware takes cycles to clear the bit, the old status is read, causing
the driver to repeatedly process the same buffer and spin in the ISR.
> + case TX_BS_FAIL:
> + u32 cnt;
[Severity: Low]
Similar to the fault state read function, this violates C11 syntax by
declaring a variable immediately following a switch case label.
[ ... ]
> @@ -1584,20 +1584,14 @@ static void loongson_canfd_chip_stop(struct net_device *ndev)
> {
> struct loongson_canfd_priv *priv = netdev_priv(ndev);
>
> /* Disable interrupts and disable CAN */
> regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
> regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
> REG_INT_MASK_SET, REG_INT_MASK_SET);
[Severity: High]
Are these regmap_update_bits calls safe for W1S/W1C command registers? This
function reads the current 1s from the lower half of INT_ENA and writes them
back along with the clear mask in the upper half, which commands the hardware
to simultaneously set and clear the exact same interrupts.
[ ... ]
> @@ -1604,45 +1604,22 @@ static int loongson_canfd_open(struct net_device *ndev)
[ ... ]
> +err_chip_start:
> + free_irq(ndev->irq, ndev);
> +err_irq:
> + close_candev(ndev);
> + return ret;
> +}
[Severity: High]
Are we missing a napi_disable call on these error paths? If the driver fails
to open, the candev is closed but NAPI is left enabled. A subsequent attempt
to bring the interface up will call napi_enable on an already enabled
instance, triggering a BUG_ON in the core.
[ ... ]
> @@ -1690,46 +1690,24 @@ static int loongson_canfd_probe(struct platform_device *pdev)
[ ... ]
> + regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap);
> + if (IS_ERR(regmap))
> + return PTR_ERR(regmap);
> +
> + device_property_read_u32(dev, "clock-frequency", &clk_rate);
> +
> + irq = platform_get_irq(pdev, 0);
[Severity: High]
Should the return value of device_property_read_u32 be checked? If the
property is missing, clk_rate retains uninitialized stack garbage which is
then assigned to priv->can.clock.freq, leading to unpredictable hardware
timing.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782787997.git.zhoubinbin@loongson.cn?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] can: Add Loongson CAN-FD controller driver
2026-06-30 3:16 ` sashiko-bot
@ 2026-06-30 6:51 ` Vincent Mailhol
0 siblings, 0 replies; 6+ messages in thread
From: Vincent Mailhol @ 2026-06-30 6:51 UTC (permalink / raw)
To: Binbin Zhou
Cc: Oleksij Rempel, linux-can, Marc Kleine-Budde, Oliver Hartkopp,
sashiko-reviews
Hi Binbin,
Thanks for the v3. Please first have a look at the sashiko bot findings.
Some of these are legit.
I will continue my review after you address the relevant findings.
On 30/06/2026 at 05:16, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 18 potential issue(s) to consider:
> - [High] Missing `skb` check in `CAN_STATE_ERROR_ACTIVE` case leads to a NULL pointer dereference.
> - [High] SKB allocation failure or invalid frame in RX path permanently stalls NAPI and corrupts RX FIFO.
> - [High] NAPI poll list corruption due to calling `napi_complete_done` while returning `quota`.
> - [High] Missing `napi_disable()` on error paths in `loongson_canfd_open()` leads to a kernel panic on subsequent open attempts.
> - [High] Race condition in `loongson_canfd_start_xmit` causes unexpected packet drops due to un-reaped TX buffers.
> - [High] Missing bounds check on the hardware-provided word count `REG_FRAME_META1_RWCNT` causes heap buffer overflow.
> - [High] Unchecked return value of `device_property_read_u32` leaves `clk_rate` uninitialized.
> - [High] Premature hardware restart on Bus-Off when auto-restart is configured.
> - [High] Bitwise NOT on a 16-bit variable unintentionally unmasks all interrupts due to integer promotion.
> - [High] Overwriting of `isr` variable causes lost error interrupts and permanent interrupt masking.
> - [High] Using `regmap_update_bits` on command-style SET/CLR registers causes conflicting hardware commands.
> - [High] Calling `kfree_skb()` with hardware interrupts disabled.
> - [High] Potential infinite loop in TX interrupt handler due to repeated status reads of posted writes.
> - [Medium] The driver advertises 'hardware timestamping' in commit message but does not implement it.
> - [Medium] `ndev->stats.rx_errors` is modified concurrently from both hardirq and NAPI softirq contexts without locking.
> - [Medium] Incorrect check for RTR frames causes invalid RX byte statistics.
> - [Medium] Missing `cf->len8_dlc` assignment for Classic CAN frames.
> - [Low] Syntax error due to variable declarations immediately following a switch case label.
> --
>
> commit e0d474f047900e47a73eaa207fc07dd8c35084bf
> Author: Binbin Zhou <zhoubinbin@loongson.cn>
> Subject: can: Add Loongson CAN-FD controller driver
>
> This commit adds support for the CAN-FD controller integrated into
> Loongson-2K series SoCs. It implements NAPI for the RX path, TX buffer
> management, bus error reporting, and various timing configurations.
>
> [Severity: Medium]
> The commit message states the driver supports hardware timestamping, but is
> this functionality actually implemented? Looking at the driver code, it sets
> ethtool_op_get_ts_info but does not seem to extract the timestamp field from
> the hardware or write to skb_hwtstamps in the receive path.
>
>> diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c
>> new file mode 100644
>> index 0000000000000..1c7381129c2b6
>> --- /dev/null
>> +++ b/drivers/net/can/loongson_canfd.c
> [ ... ]
>> @@ -891,37 +891,24 @@ static int loongson_canfd_chip_start(struct net_device *ndev)
> [ ... ]
>> + /* Configure interrupts */
>> + int_ena = REG_INT_STAT_RBNEI | REG_INT_STAT_TXBHCI |
>> + REG_INT_STAT_EWLI | REG_INT_STAT_FCSI;
>> +
>> + /* Bus error reporting */
>> + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
>> + int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI;
>> +
>> + /* It's after reset, so there is no need to clear anything */
>> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, ~int_ena);
>
> [Severity: High]
> Will this bitwise NOT operation unmask all interrupts on the controller?
> Because int_ena is a 16-bit unsigned integer, the bitwise NOT causes integer
> promotion to a 32-bit signed int, filling the upper 16 bits with 1s. Since
> the upper bits of this register clear the mask, does this unintentionally
> enable every possible interrupt?
>
>> + regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena);
> [ ... ]
>> @@ -1048,46 +1048,22 @@ static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> [ ... ]
>> + for_each_clear_bit(i, &tx_brp, LOONGSON_CANFD_TXBUF_NUM) {
>> + u32 transmission_complete_mask = 0x3 << (i * 2);
>> +
>> + if (!(tx_bs & transmission_complete_mask)) {
>> + buf_id = i;
>> + break;
>> + }
>> + }
>> +
>> + if (!loongson_canfd_insert_frame(priv, cf, buf_id, can_is_canfd_skb(skb))) {
>> + netdev_err(ndev, "TXNF set but cannot insert frame into TXTB! HW Bug?");
>> + kfree_skb(skb);
>
> [Severity: High]
> Could a race condition here cause silent packet drops? The hardware clears
> tx_brp immediately on completion, but tx_bs is cleared later in the ISR.
> If all inactive buffers are awaiting a reap, the loop finishes without
> finding a buffer, leaving buf_id at 0. This would cause the insert check to
> fail, dropping the packet while incorrectly returning NETDEV_TX_OK.
>
> [Severity: High]
> Is it safe to call kfree_skb here? The tx_lock is held with
> spinlock_irqsave which disables local interrupts. Calling kfree_skb with
> hard interrupts disabled can lead to kernel warnings and potential deadlocks.
> Should this use dev_kfree_skb_any instead?
>
>> + ndev->stats.tx_dropped++;
>> + return NETDEV_TX_OK;
>> + }
> [ ... ]
>> @@ -1105,37 +1105,25 @@ static void loongson_canfd_read_rx_frame(struct loongson_canfd_priv *priv, struct canfd_frame *cf,
> [ ... ]
>> + /* Data Length Code */
>> + len = FIELD_GET(REG_FRAME_META1_DLC, meta1);
>> + if (len > 8) {
>> + if (meta1 & REG_FRAME_META1_FDF)
>> + len = wc << 2;
>> + else
>> + len = 8;
>> + }
>> +
>> + cf->len = len;
>
> [Severity: Medium]
> Does this properly support the CAN_CTRLMODE_CC_LEN8_DLC feature? For Classic
> CAN frames with a DLC greater than 8, the length is appropriately capped to
> 8, but the original DLC is not saved to cf->len8_dlc. This might break
> userspace protocols that rely on the original DLC data.
>
>> + if (unlikely(len > wc * LOONGSON_CANFD_DW_BYTE))
>> + len = wc * LOONGSON_CANFD_DW_BYTE;
>> +
>> + /* Data */
>> + for (i = 0; i < len; i += LOONGSON_CANFD_DW_BYTE)
>> + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, (u32 *)(cf->data + i));
>
> [Severity: High]
> Can a malformed hardware word count cause a heap buffer overflow? The len
> variable is checked against the hardware-provided word count, but not against
> CANFD_MAX_DLEN. If the hardware yields an invalid read word count, the length
> could exceed the 64-byte payload limit, causing out of bounds memory writes
> to the skb data.
>
> [ ... ]
>> @@ -1164,28 +1164,22 @@ static int loongson_canfd_rx(struct net_device *ndev)
> [ ... ]
>> + /* Flexible Data-rate Format */
>> + if (meta1 & REG_FRAME_META1_FDF)
>> + skb = alloc_canfd_skb(ndev, &cf);
>> + else
>> + skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
>> + if (unlikely(!skb))
>> + return 0;
>
> [Severity: High]
> If the skb allocation fails, does returning 0 early permanently stall the
> NAPI loop? Since the rest of the data payload is not read from the hardware
> FIFO, the RX stream might desynchronize. Also, returning 0 here causes the
> caller to exit its loop without completing NAPI, potentially leaving it
> permanently in a scheduled state.
>
>> +
>> + loongson_canfd_read_rx_frame(priv, cf, meta0, meta1);
>> +
>> + if (cf->can_id != CAN_RTR_FLAG)
>> + stats->rx_bytes += cf->len;
>
> [Severity: Medium]
> Is this RTR check accurate? The can_id field contains both the CAN ID and
> the flags, so a direct inequality comparison to CAN_RTR_FLAG might evaluate
> true for almost all RTR frames, erroneously inflating the RX byte statistics
> for frames with no actual payload.
>
> [ ... ]
>> @@ -1203,32 +1203,26 @@ static enum can_state loongson_canfd_read_fault_state(struct net_device *ndev)
> [ ... ]
>> + switch (FIELD_GET(REG_FSTAT_MASK, fstat)) {
>> + case REG_FSTAT_ERA:
>> + u32 ewl, erl, rec_tec, max_tec;
>
> [Severity: Low]
> Will this compile on strict C11 compilers? The C11 standard does not allow
> variable declarations immediately following a switch case label.
>
> [ ... ]
>> @@ -1278,82 +1278,28 @@ static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
> [ ... ]
>> + if ((isr & REG_INT_STAT_FCSI) || (isr & REG_INT_STAT_EWLI)) {
>> + netdev_info(ndev, "state changes from %s to %s\n",
>> + can_get_state_str(priv->can.state), can_get_state_str(state));
>> +
>> + if (priv->can.state == state)
>> + netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
>> +
>> + isr = REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
>
> [Severity: High]
> Does overwriting the isr variable here permanently disable other interrupts?
> If arbitration lost or bus error flags are pending, this assignment destroys
> them. Later in the function, isr is shifted to unmask interrupts, meaning the
> erased flags will never be unmasked again.
>
>> + priv->can.state = state;
>> + switch (state) {
>> + case CAN_STATE_BUS_OFF:
>> + priv->can.can_stats.bus_off++;
>> + if (priv->can.restart_ms)
>> + regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
>> + REG_CMD_ERCRST);
>
> [Severity: High]
> Does this prematurely restart the hardware upon a bus-off event? Instantly
> writing the error reset command clears the hardware error state and rejoins
> the bus immediately, ignoring the restart delay contract enforced by the core
> can_bus_off timer.
>
> [ ... ]
>> + case CAN_STATE_ERROR_ACTIVE:
>> + cf->can_id |= CAN_ERR_CNT;
>> + cf->data[1] = CAN_ERR_CRTL_ACTIVE;
>> + cf->data[6] = bec.txerr;
>
> [Severity: High]
> Could this lead to a null pointer dereference? The alloc_can_err_skb call
> earlier in the function can fail and leave cf uninitialized. While the other
> switch cases check if skb is valid before modifying cf data, this case does
> not, which would crash if memory pressure prevents allocation.
>
> [ ... ]
>> + /* Check for Bus Error interrupt */
>> + if (isr & REG_INT_STAT_BEI) {
>> + isr = REG_INT_STAT_BEI;
>> + netdev_err(ndev, "Bus Error interrupt\n");
>> + priv->can.can_stats.bus_error++;
>> + stats->rx_errors++;
>
> [Severity: Medium]
> Is it safe to increment stats->rx_errors without locking here? This counter
> is modified from hardirq context here and concurrently from softirq context
> in the NAPI poll routine, which could cause lost increments due to data races
> on 32-bit systems.
>
> [ ... ]
>> @@ -1391,37 +1391,25 @@ static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> [ ... ]
>> + /* Check for RX FIFO Overflow */
>> + regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts);
>> + if (sts & REG_STAT_DOR) {
>> + struct net_device_stats *stats = &ndev->stats;
>> + struct can_frame *cf;
>> + struct sk_buff *skb;
>> +
>> + netdev_info(ndev, "Loongson canfd RX overflow\n");
>> + stats->rx_over_errors++;
>> + stats->rx_errors++;
>
> [Severity: Medium]
> As noted above, stats->rx_errors is updated concurrently in both softirq and
> hardirq contexts without locking.
>
> [ ... ]
>> + if (rxbuf_is_empty && ret != 0) {
>> + if (napi_complete_done(napi, work_done)) {
>
> [Severity: High]
> Should this check if work_done is less than quota? If exactly quota frames
> are processed and the hardware buffer becomes empty, this logic calls
> napi_complete_done and then returns quota to the networking core. Returning
> the full quota instructs the core to reschedule the instance, but the state
> has already been manipulated, which can corrupt the NAPI poll list.
>
> [ ... ]
>> @@ -1425,33 +1425,18 @@ static void loongson_canfd_tx_interrupt(struct net_device *ndev)
> [ ... ]
>> + while ((buf_id = loongson_canfd_get_txbuf_id(priv, &sts)) < LOONGSON_CANFD_TXBUF_NUM) {
>> + switch (sts) {
>> + case TX_BS_VALID:
>> + stats->tx_bytes += can_get_echo_skb(ndev, buf_id, NULL);
>> + stats->tx_packets++;
>> + break;
>
> [Severity: High]
> Could this result in an inescapable infinite loop? When the driver writes a
> clear command to the posted MMIO register at the end of the loop, it
> immediately reads the status register back in the while condition. Because
> the hardware takes cycles to clear the bit, the old status is read, causing
> the driver to repeatedly process the same buffer and spin in the ISR.
>
>> + case TX_BS_FAIL:
>> + u32 cnt;
>
> [Severity: Low]
> Similar to the fault state read function, this violates C11 syntax by
> declaring a variable immediately following a switch case label.
>
> [ ... ]
>> @@ -1584,20 +1584,14 @@ static void loongson_canfd_chip_stop(struct net_device *ndev)
>> {
>> struct loongson_canfd_priv *priv = netdev_priv(ndev);
>>
>> /* Disable interrupts and disable CAN */
>> regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_ENA, REG_INT_ENA_CLR, REG_INT_ENA_CLR);
>> regmap_update_bits(priv->regmap, LOONGSON_CANFD_INT_MASK,
>> REG_INT_MASK_SET, REG_INT_MASK_SET);
>
> [Severity: High]
> Are these regmap_update_bits calls safe for W1S/W1C command registers? This
> function reads the current 1s from the lower half of INT_ENA and writes them
> back along with the clear mask in the upper half, which commands the hardware
> to simultaneously set and clear the exact same interrupts.
>
> [ ... ]
>> @@ -1604,45 +1604,22 @@ static int loongson_canfd_open(struct net_device *ndev)
> [ ... ]
>> +err_chip_start:
>> + free_irq(ndev->irq, ndev);
>> +err_irq:
>> + close_candev(ndev);
>> + return ret;
>> +}
>
> [Severity: High]
> Are we missing a napi_disable call on these error paths? If the driver fails
> to open, the candev is closed but NAPI is left enabled. A subsequent attempt
> to bring the interface up will call napi_enable on an already enabled
> instance, triggering a BUG_ON in the core.
>
> [ ... ]
>> @@ -1690,46 +1690,24 @@ static int loongson_canfd_probe(struct platform_device *pdev)
> [ ... ]
>> + regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap);
>> + if (IS_ERR(regmap))
>> + return PTR_ERR(regmap);
>> +
>> + device_property_read_u32(dev, "clock-frequency", &clk_rate);
>> +
>> + irq = platform_get_irq(pdev, 0);
>
> [Severity: High]
> Should the return value of device_property_read_u32 be checked? If the
> property is missing, clk_rate retains uninitialized stack garbage which is
> then assigned to priv->can.clock.freq, leading to unpredictable hardware
> timing.
Yours sincerely,
Vincent Mailhol
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-30 6:51 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30 3:01 [PATCH v3 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
2026-06-30 3:01 ` [PATCH v3 1/2] can: " Binbin Zhou
2026-06-30 3:16 ` sashiko-bot
2026-06-30 6:51 ` Vincent Mailhol
2026-06-30 3:01 ` [PATCH v3 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
2026-06-30 3:11 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox