插件窝 干货文章 Linux 驱动开发——Socket CAN模型

Linux 驱动开发——Socket CAN模型

struct priv frame 套接字 401    来源:    2025-05-13

Linux 驱动开发 - Socket CAN 模型详解

1. Socket CAN 概述

Socket CAN 是 Linux 内核中实现 CAN 总线通信的框架,它提供了基于套接字(Socket)的 API 接口,使 CAN 设备可以像网络设备一样被访问。

主要特点:

  • 基于 BSD Socket 接口
  • 支持多进程/多线程访问
  • 提供原始 CAN 套接字和广播管理套接字
  • 支持 CAN FD (灵活数据率)
  • 提供网络设备接口

2. Socket CAN 核心组件

2.1 CAN 设备驱动

负责与硬件 CAN 控制器通信,需要实现以下功能: - 硬件初始化 - 发送和接收 CAN 帧 - 错误处理 - 比特率设置

2.2 CAN 网络设备

将 CAN 控制器抽象为网络设备(net_device),通过 ifconfig 等工具管理。

2.3 CAN 协议族

实现 PF_CAN 协议族,提供套接字接口。

3. 编写 CAN 设备驱动

3.1 基本驱动框架

#include <linux/can/dev.h>
#include <linux/netdevice.h>

static const struct net_device_ops can_netdev_ops = {
    .ndo_open = can_open,
    .ndo_stop = can_close,
    .ndo_start_xmit = can_start_xmit,
    .ndo_change_mtu = can_change_mtu,
};

static int can_probe(struct platform_device *pdev)
{
    struct net_device *dev;
    struct can_priv *priv;

    // 分配网络设备
    dev = alloc_candev(sizeof(struct can_priv), 1);
    if (!dev)
        return -ENOMEM;

    priv = netdev_priv(dev);

    // 设置设备操作
    dev->netdev_ops = &can_netdev_ops;

    // 设置 CAN 特定参数
    priv->can.clock.freq = 8000000; // 8MHz 时钟
    priv->can.bittiming_const = &can_bittiming_const;
    priv->can.do_set_bittiming = can_set_bittiming;
    priv->can.do_set_mode = can_set_mode;
    priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES;

    // 注册网络设备
    register_candev(dev);

    return 0;
}

3.2 发送和接收处理

// 发送处理函数
static netdev_tx_t can_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct can_priv *priv = netdev_priv(dev);
    struct can_frame *cf = (struct can_frame *)skb->data;

    if (can_dropped_invalid_skb(dev, skb))
        return NETDEV_TX_OK;

    // 硬件发送
    if (can_transmit(priv, cf)) {
        dev_kfree_skb(skb);
        return NETDEV_TX_OK;
    }

    return NETDEV_TX_OK;
}

// 接收处理函数
static void can_rx_handler(struct can_priv *priv, struct can_frame *cf)
{
    struct sk_buff *skb;
    struct net_device_stats *stats = &priv->dev->stats;

    skb = alloc_can_skb(priv->dev, &cf);
    if (!skb) {
        stats->rx_dropped++;
        return;
    }

    netif_rx(skb);
    stats->rx_packets++;
    stats->rx_bytes += cf->can_dlc;
}

4. Socket CAN 用户空间接口

4.1 创建 CAN 套接字

int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);

4.2 绑定到特定接口

struct sockaddr_can addr;
struct ifreq ifr;

strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);

addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;

bind(s, (struct sockaddr *)&addr, sizeof(addr));

4.3 发送和接收 CAN 帧

struct can_frame frame;

// 发送
frame.can_id = 0x123;
frame.can_dlc = 8;
strcpy(frame.data, "12345678");
write(s, &frame, sizeof(frame));

// 接收
int nbytes = read(s, &frame, sizeof(frame));
if (nbytes > 0) {
    printf("Received ID: 0x%X DLC: %d Data: %s\n",
           frame.can_id, frame.can_dlc, frame.data);
}

5. CAN 设备树配置

对于使用设备树的系统,CAN 控制器需要在设备树中配置:

can0: can@1c00000 {
    compatible = "vendor,can-controller";
    reg = <0x1c00000 0x1000>;
    interrupts = <0 45 4>;
    clocks = <&clk 20>;
    clock-names = "can_clk";
    status = "okay";
};

6. 调试与监控

6.1 使用 can-utils 工具包

# 设置比特率
ip link set can0 type can bitrate 500000

# 启动接口
ip link set can0 up

# 监控 CAN 总线
candump can0

# 发送 CAN 帧
cansend can0 123#1122334455667788

6.2 内核调试

# 查看 CAN 统计信息
ip -details -statistics link show can0

# 启用调试输出
echo 8 > /proc/sys/kernel/printk

7. 高级主题

7.1 CAN FD 支持

需要在内核配置中启用 CONFIG_CANFD,并在驱动中设置:

priv->can.ctrlmode_supported = CAN_CTRLMODE_FD;

7.2 错误帧处理

实现错误帧检测和报告:

priv->can.do_get_berr_counter = can_get_berr_counter;

7.3 硬件时间戳

支持硬件时间戳需要在驱动中实现:

priv->can.do_get_berr_counter = can_get_berr_counter;

8. 常见问题解决

  1. CAN 接口无法启动

    • 检查物理连接和终端电阻
    • 验证时钟配置
    • 检查设备树配置
  2. 发送失败

    • 检查总线负载
    • 验证比特率设置
    • 检查硬件 FIFO 状态
  3. 接收丢帧

    • 增加接收缓冲区大小
    • 优化中断处理
    • 检查 CPU 负载

通过以上框架和示例,您可以开发出功能完善的 Linux CAN 设备驱动,并利用 Socket CAN 提供的丰富接口实现各种 CAN 总线应用。