Netdev List
 help / color / mirror / Atom feed
From: Cheng Liu <chengliu480@gmail.com>
To: Marc Kleine-Budde <mkl@pengutronix.de>,
	Wolfgang Grandegger <wg@grandegger.com>
Cc: linux-can@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	Elaine Zhang <zhangqing@rock-chips.com>,
	Cheng Liu <chengliu480@gmail.com>
Subject: Re: [PATCH can] can: rockchip: rk3576: fix rtnl_lock deadlock during interface down under bus traffic
Date: Mon, 17 Aug 2026 10:31:08 +0800	[thread overview]
Message-ID: <20260817023108.1854647-1-chengliu480@gmail.com> (raw)
In-Reply-To: <20260817013750.1836064-1-chengliu480@gmail.com>

Hi Marc, Elaine, and linux-can community,

To help anyone easily reproduce and verify this deadlock scenario on
RV1126B / RK3576, below is the full standalone Python SocketCAN test
script used to trigger the race condition:

```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
reproduce_can_deadlock.py

Constructs a concurrent kernel-level deadlock scenario:
1. Blocking sender threads: deep sleep wait in can_send() waiting for TX FIFO.
2. Error frame receiver: setsockopt(CAN_RAW_ERR_FILTER) to receive all error frames.
3. Socket churn: rapid open/bind/send/close.
4. Down/up interrupt loop: calls `ip link set can0 down` during active traffic.
"""

import os
import sys
import time
import socket
import struct
import threading
import subprocess

AF_CAN = getattr(socket, "AF_CAN", 29)
PF_CAN = AF_CAN
CAN_RAW = getattr(socket, "CAN_RAW", 1)
SOL_CAN_BASE = 100
CAN_RAW_FILTER = 1
CAN_RAW_ERR_FILTER = 2
CAN_RAW_LOOPBACK = 3
CAN_RAW_RECV_OWN_MSGS = 4
CAN_RAW_FD_FRAMES = 5

CAN_ERR_MASK = 0x1FFFFFFF

INTERFACE = "can0"
STOP_EVENT = threading.Event()
DEADLOCK_DETECTED = threading.Event()

def build_can_frame(can_id, data):
    can_dlc = len(data)
    data = data.ljust(8, b'\x00')
    return struct.pack("=IB3x8s", can_id, can_dlc, data)

def build_canfd_frame(can_id, data, flags=0):
    length = len(data)
    data = data.ljust(64, b'\x00')
    return struct.pack("=IBB2x64s", can_id, length, flags, data)

def sender_thread(thread_id, is_fd=False):
    sock = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW)
    try:
        if is_fd:
            try:
                sock.setsockopt(SOL_CAN_BASE, CAN_RAW_FD_FRAMES, 1)
            except Exception:
                pass
        sock.bind((INTERFACE,))
        sock.setblocking(True)
    except Exception:
        sock.close()
        return

    frame_data = b'\x11\x22\x33\x44\x55\x66\x77\x88'
    can_id = 0x100 + thread_id
    frame = build_can_frame(can_id, frame_data)

    while not STOP_EVENT.is_set():
        try:
            sock.send(frame)
        except socket.error:
            time.sleep(0.001)
        except Exception:
            break
    sock.close()

def receiver_thread():
    try:
        sock = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW)
        sock.setsockopt(SOL_CAN_BASE, CAN_RAW_ERR_FILTER, CAN_ERR_MASK)
        sock.bind((INTERFACE,))
        sock.settimeout(0.1)
    except Exception:
        return

    while not STOP_EVENT.is_set():
        try:
            _ = sock.recv(72)
        except socket.timeout:
            continue
        except Exception:
            time.sleep(0.01)
    sock.close()

def socket_churn_thread():
    while not STOP_EVENT.is_set():
        try:
            s = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW)
            s.bind((INTERFACE,))
            s.setblocking(False)
            frame = build_can_frame(0x200, b'\xaa\xbb\xcc\xdd')
            try:
                s.send(frame)
            except Exception:
                pass
            s.close()
        except Exception:
            pass
        time.sleep(0.002)

def main():
    if os.geteuid() != 0:
        print("[-] Please run as root: sudo python3 reproduce_can_deadlock.py")
        sys.exit(1)

    print("=" * 65)
    print("Starting SocketCAN deadlock reproduction tool")
    print("=" * 65)

    subprocess.run(["ip", "link", "set", INTERFACE, "down"], stderr=subprocess.DEVNULL)
    time.sleep(0.3)
    subprocess.run(["ip", "link", "set", INTERFACE, "txqueuelen", "1"], check=False)
    subprocess.run([
        "ip", "link", "set", INTERFACE, "up", "type", "can",
        "bitrate", "500000", "dbitrate", "500000", "fd", "on", "restart-ms", "100"
    ], check=False)

    threads = []
    for i in range(3):
        t = threading.Thread(target=sender_thread, args=(i, i % 2 == 0), daemon=True)
        t.start()
        threads.append(t)

    t_recv = threading.Thread(target=receiver_thread, daemon=True)
    t_recv.start()
    threads.append(t_recv)

    t_churn = threading.Thread(target=socket_churn_thread, daemon=True)
    t_churn.start()
    threads.append(t_churn)

    loop_count = 0
    try:
        while True:
            loop_count += 1
            now_str = time.strftime("%H:%M:%S")
            sys.stdout.write(f"[{now_str}] Loop {loop_count:5d}: Bringing can0 down... ")
            sys.stdout.flush()

            t0 = time.time()
            p_down = subprocess.Popen(["ip", "link", "set", INTERFACE, "down"],
                                      stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            try:
                p_down.communicate(timeout=4.0)
                down_cost = time.time() - t0
                sys.stdout.write(f"Done ({down_cost*1000:.1f}ms) -> Bringing can0 up... ")
                sys.stdout.flush()
            except subprocess.TimeoutExpired:
                print("\n" + "!" * 65)
                print("Captured deadlock! `ip link set can0 down` hung > 4s!")
                print("Kernel rtnl_lock is deadlocked!")
                print("!" * 65)
                DEADLOCK_DETECTED.set()
                break

            t1 = time.time()
            p_up = subprocess.Popen(["ip", "link", "set", INTERFACE, "up"],
                                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            try:
                p_up.communicate(timeout=4.0)
                up_cost = time.time() - t1
                sys.stdout.write(f"Done ({up_cost*1000:.1f}ms)\n")
                sys.stdout.flush()
            except subprocess.TimeoutExpired:
                print("\n" + "!" * 65)
                print("Captured deadlock! `ip link set can0 up` hung > 4s!")
                print("!" * 65)
                DEADLOCK_DETECTED.set()
                break

            time.sleep(0.005)

    except KeyboardInterrupt:
        print("\n[*] Exiting test...")
    finally:
        STOP_EVENT.set()
        if not DEADLOCK_DETECTED.is_set():
            subprocess.run(["ip", "link", "set", INTERFACE, "down"], stderr=subprocess.DEVNULL)
        print(f"[*] Completed {loop_count} loops.")

if __name__ == "__main__":
    main()
```

Also, if maintainers prefer this fix to be split into separate smaller
atomic patches (e.g. 1. close deadlock fix, 2. NAPI budget handling, 3. error
handling NULL check), please let me know and I will gladly send a v2 patch series!

Best regards,
Cheng Liu

      reply	other threads:[~2026-08-17  2:31 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-17  1:37 [PATCH can] can: rockchip: rk3576: fix rtnl_lock deadlock during interface down under bus traffic Cheng Liu
2026-08-17  2:31 ` Cheng Liu [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260817023108.1854647-1-chengliu480@gmail.com \
    --to=chengliu480@gmail.com \
    --cc=linux-can@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mkl@pengutronix.de \
    --cc=netdev@vger.kernel.org \
    --cc=wg@grandegger.com \
    --cc=zhangqing@rock-chips.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox