Linux +xenomai 下 CAN 卡中断通信 --WT

本文详细介绍了在Xenomai实时系统下,基于Linux-4.9.38+Xenomai 3.0.5如何进行CAN卡中断通信。主要涉及中断申请和中断类别,包括接收数据、错误、发送数据和唤醒四种中断的处理流程,重点解析了中断服务函数如何识别并处理不同类型的中断事件。

xenomai下can卡中断通信

本文基于 linux-4.9.38+Xenomai 3.0.5 的实时系统
加载用的是xenomai下can模块 以及pci总线驱动rtcan_adv_pci,以及rtcan_sja1000的can控制器驱动。

一、中断申请

中断处理程序不是编译内核时就完全确定的,因此要为开发者留下了编程接口。

int rtdm_irq_request(rtdm_irq_t *irq_handle, unsigned int irq_no,rtdm_irq_handler_t handler, unsigned long flags, const char *device_name, void *arg)

此函数将提供的中断处理程序注册到 IRQ 号上并启用该中断。

  1. [in,out]irq_handle IRQ句柄
  2. [in] irq_no 中断通道号,无符号整数
  3. [in] handler 中断处理函数
  4. [in] flags 注册中断标志
    RTDM_IRQTYPE_SHARED 与其他实时驱动程序一起启用IRQ共享
    RTDM_IRQTYPE_EDGE 将IRQ标记为边缘触发,与正确处理共享的边缘触发的IRQ有关。(Mark IRQ as edge-triggered, relevant for correct handling of shared edge-triggered IRQs).
  5. [in] device_name 显示在实时IRQ列表中的设备名称
  6. [in] arg 调用时将传递给中断处理程序的指针

则可利用设备描述结构体中的pci_dev中的中断号申请并启用中断,等待中断的到来。

/*Register and setup interrupt handling 注册和设置中断处理*/
chip->irq_flags = RTDM_IRQTYPE_SHARED//中断共享
chip->irq_num = pdev->irq;
int rtcan_sja1000_register(struct rtcan_device *dev)
{
   
   
    int                         ret;
    struct rtcan_sja1000 *chip = dev->priv;

    if (chip == NULL)
	return -EINVAL;

    /* Set dummy state for following call 为以下调用设置虚拟状态 */
    dev->state = CAN_STATE_ACTIVE;
    /* Enter reset mode 进入重置模式 */
    rtcan_sja_mode_stop(dev, NULL);

    if ((chip->read_reg(dev, SJA_SR) &
	 (SJA_SR_RBS | SJA_SR_DOS | SJA_SR_TBS)) != SJA_SR_TBS) {
   
   
	printk("ERROR! No SJA1000 device found!\n");
	return -ENODEV;
    }

    dev->ctrl_name = sja_ctrl_name;

    dev->hard_start_xmit = rtcan_sja_start_xmit;
    dev->do_set_mode = rtcan_sja_set_mode;
    dev->do_get_state = rtcan_sja_get_state;
    dev->do_set_bit_time = rtcan_sja_set_bit_time;
    dev->do_enable_bus_err = rtcan_sja_enable_bus_err;
#ifndef CONFIG_XENO_DRIVERS_CAN_CALC_BITTIME_OLD
    dev->bittiming_const = &sja1000_bittiming_const;
#endif

    chip->bus_err_on = 1;
	// rtdm 中断请求 
    ret = rtdm_irq_request(&dev->irq_handle,
			   chip->irq_num, rtcan_sja_interrupt,
			   chip->irq_flags, sja_ctrl_name, dev);
    if (ret) {
   
   
	printk(KERN_ERR "ERROR %d: IRQ %d is %s!\n",
	       ret, chip->irq_num, ret == -EBUSY ?
	       "busy, check shared interrupt support" : "invalid");
	return ret;
    }

    sja1000_chip_config(dev);

    /* Register RTDM device 注册RTDM设备*/
    ret = rtcan_dev_register(dev);
    if (ret) {
   
   
	    printk(KERN_ERR
		   "ERROR %d while trying to register RTCAN device!\n", ret);
	goto out_irq_free;
    }

    rtcan_sja_create_proc(dev);

    return 0;

 out_irq_free:
    rtdm_irq_free(&dev->irq_handle);

    return ret;
}

二、中断类别

当外界中断触发过后将会将会进入设定得中断服务函数,辨识中断的在驱动程序的类别,中断服务函数根据寄存器的配置将中断响应分为了4类情况的中断,进而执行不同的功能:
1、接收数据的中断:SJA1000使用64字节的FIFO来保存接收到的CAN报文,当接收FIFO中包含有效报文且RIE位置1,产生中断
2、发生错误的中断:错误状态或总线出错等状态发生
3、发送数据的中断:当SJA1000发送缓冲区由锁定变为释放且TIE置位时,产生发送中断,SR.3置位表示上一报文成功发送。
4、唤醒中断:处于睡眠模式检测到总线活动

static int rtcan_sja_interrupt(rtdm_irq_t *irq_handle)
{
   
   
    struct rtcan_device *dev;
    struct rtcan_sja1000 *chip;
    struct rtcan_skb skb;
    int recv_lock_free = 1;
    int irq_count = 0;
    int ret = RTDM_IRQ_NONE;//未处理的中断标志
    u8 irq_source;


    /* Get the ID of the device which registered this IRQ. 获取注册此IRQ的设备的ID。 */
    dev = (struct rtcan_device *)rtdm_irq_get_arg(irq_handle, void);
    chip = (struct rtcan_sja1000 *)dev->priv;

    /* Take spinlock protecting HW register access and device structures. 采用自旋锁保护硬件寄存器访问和设备结构。 */
    rtdm_lock_get(&dev->device_lock);

    /* Loop as long as the device reports an event  循环,不停询问是否发生了中断  */
    while ((irq_source = chip->read_reg(dev, SJA_IR))) //获得中断寄存器的值
    {
   
   
	ret = RTDM_IRQ_HANDLED;//表示已处理的中断
	irq_count++;

	/* Now look up which interrupts appeared 看看出现了种中断*/

	/* Wake-up interrupt?  唤醒中断*/
	if (irq_source & SJA_IR_WUI)
	    dev->state = dev->state_before_sleep;

	/* Error Interrupt?  错误中断?*/
	if (irq_source & (SJA_IR_EI | SJA_IR_DOI | SJA_IR_EPI |
			  SJA_IR_ALI | SJA_IR_BEI)) {
   
   

	    /* Check error condition and fill error frame 检查错误条件并填充错误帧*/
	    if (!((irq_source & SJA_IR_BEI) && (chip->bus_err_on-- < 2))) {
   
   
		rtcan_sja_err_interrupt(dev, chip, &skb, irq_source);

		if (recv_lock_free) {
   
   
		    recv_lock_free = 0;
		    rtdm_lock_get(&rtcan_recv_list_lock);
		    rtdm_lock_get(&rtcan_socket_lock);
		}
		/* Pass error frame out to the sockets 将错误帧传递到套接字*/
		rtcan_rcv(dev, &skb);
	    }
	}

	/* Transmit Interrupt? 发送中断? */
	if (irq_source & SJA_IR_TI) {
   
   
	    /* Wake up a sender */
	    rtdm_sem_up(&dev->tx_sem);

	    if (rtcan_loopback_pending(dev)) {
   
   

		if (recv_lock_free) {
   
   
		    recv_lock_free = 0;
		    rtdm_lock_get(&rtcan_recv_list_lock);
		    rtdm_lock_get(&rtcan_socket_lock);
		}

		rtcan_loopback(dev);
	    }
	}

	/* Receive Interrupt? 接受中断?*/
	if (irq_source & SJA_IR_RI) {
   
   

	    /* Read out HW registers 读出硬件寄存器*/
	    rtcan_sja_rx_interrupt(dev, &skb);

	    /* Take more locks. Ensure that they are taken and
	     * released only once in the IRQ handler. */
	    /* WARNING: Nested locks are dangerous! But they are
	     * nested only in this routine so a deadlock should
	     * not be possible. */
	     
	    if (recv_lock_free) {
   
   
		recv_lock_free = 0;
		rtdm_lock_get(&rtcan_recv_list_lock);
		rtdm_lock_get(&rtcan_socket_lock);
	    }

	    /* Pass received frame out to the sockets 将接收到的帧传递到套接字*/
	    rtcan_rcv(dev, &skb);
	}
    }

    if (chip->irq_ack)
	chip->irq_ack(dev);

    /* Release spinlocks 释放旋转锁 */
    if (!recv_lock_free) {
   
   
	rtdm_lock_put(&rtcan_socket_lock);
	rtdm_lock_put(&rtcan_recv_list_lock);
    }
    rtdm_lock_put(&dev->device_lock);

    return ret;
}

当外界中断事件发生后,SJA1000中断寄存器(IR)置1,INT引脚变为低电平,中断输出,此中断函数被激活,中断函数中将会询问SJA1000控制器硬件的中断控制寄存器的值,然后根据寄存器的值进一步判断中断的类型,进而转入相应的工作。

1、接收中断:
读取中断------利用PCI 驱动读出数据经过pci总线进而传递到内核接收缓冲区,将控制器中的CAN帧数据读出,在中断中需要构建一个临时缓冲区套接字结构skd,然后它被传递给接收处理程序。

if (irq_source & SJA_IR_RI) //SJA_IR_RI   接收数据掩码
{
   
   

	    /* Read out HW registers 读出硬件寄存器*/
	    rtcan_sja_rx_interrupt(dev, &skb);
	    
	    if (recv_lock_free) {
   
   
		recv_lock_free = 0;
		rtdm_lock_get(&rtcan_recv_list_lock);
		rtdm_lock_get(&rtcan_socket_lock);
	    }
	    /* Pass received frame out to the sockets 将接收到的帧传递到套接字*/
	    rtcan_rcv(dev, &skb);
	}

                
<think> 嗯,用户想了解编程中"unlikely"的用法和含义。从对话历史来看,用户可能是个有一定编程基础的学习者,正在研究代码优化技术。用户提供的四个引用中,前三个都直接与unlikely相关,但第四个关于Python readalllines的内容明显是干扰项,需要忽略。 用户的核心诉求很明确:需要系统解释unlikely的作用机制和应用场景。引用[1]给出了宏观定义,引用[2]提供了C语言的宏示例,引用[3]则补充了C++20的标准实现。这些材料正好构成完整的技术图谱:从底层原理到具体语法。 注意到引用[1]提到"作用机理"分理论和实践两部分,这个结构值得借鉴。可以把响应分为原理层面(分支预测、流水线优化)和实操层面(语法差异、使用禁忌)。用户可能特别关心性能提升效果,需要准备具体数据对比。 有趣的是用户上次询问过快速排序,或许可以暗示unlikely在算法优化中的价值(比如处理递归边界条件)。不过当前问题更偏向语言特性,暂不需要展开算法案例。 需要警惕的陷阱:用户可能混淆不同语言的实现。必须明确区分C/C++的编译器扩展属性和Python等语言的异常处理机制(后者通常不用unlikely概念)。在注意事项里要特别强调语言依赖性。 最后考虑用户可能想动手测试,准备添加简单的性能对比示例。引用[2]的代码片段稍显单薄,考虑补充带时间统计的完整demo会更直观。同时提示编译器依赖性问题(如GCC/Clang的__builtin_expect)。 </think> 在编程中,`unlikely` 是一种优化技术,用于向编译器提示某个条件分支**极不可能被执行**。其核心原理是通过调整代码布局减少 CPU 流水线中断,从而提升程序性能。以下是详细解析: --- ### **核心原理** 1. **分支预测优化** CPU 采用流水线机制顺序执行指令。当遇到条件分支(如 `if`)时,若预测失败会导致流水线清空,产生性能损耗。`unlikely` 告知编译器:该条件大概率不成立,应优先优化 `else` 分支的指令顺序[^2]。 2. **代码布局调整** 编译器会将标记为 `unlikely` 的代码块放置在内存中较远的位置(如 `.text.unlikely` 段),确保高频执行的代码更紧凑,提升 CPU 缓存命中率[^1]。 --- ### **使用方法** #### C语言(编译器扩展) ```c #include <linux/compiler.h> // GCC/Clang 需包含此头文件 if (unlikely(error_condition)) { // < 1% 概率发生的错误检查 handle_error(); } else { // 主逻辑(高频执行) } ``` #### C++20(标准属性) ```cpp if (error_condition) [[unlikely]] { // C++20 标准语法 handle_error(); } else { // 主逻辑 } ``` --- ### **适用场景** [^2] 1. **错误处理** 如文件打开失败、内存分配异常等罕见情况。 ```c FILE *fp = fopen("data.txt", "r"); if (unlikely(fp == NULL)) { perror("File open failed"); exit(1); } ``` 2. **边界条件检查** 循环中的极端值检测(例如循环次数超过阈值)。 3. **硬件中断处理** 设备驱动中异常信号的处理。 --- ### **关键注意事项** 1. **性能影响需实测** 误用(如对高频分支标记 `unlikely`)可能导致性能下降约 **10~15%**。应通过性能分析工具(如 `perf`)验证优化效果[^1]。 2. **编译器依赖性** - C/C++ 非标准:`unlikely` 是 GCC/Clang 的扩展属性,MSVC 不支持。 - 替代方案:C++20 的 `[[unlikely]]` 具有跨平台性。 3. **避免过度优化** 在实时性要求极高的场景(如内核嵌入式),需谨慎平衡可读性与性能。 --- ### **底层机制示例** 编译前后对比(x86 汇编): ```nasm # 未使用 unlikely(默认分支布局) test %eax, %eax jne .Lerror ; 默认预测 je(条件成立)更可能跳转 # 使用 unlikely 后 test %eax, %eax je .Lmain ; 强制预测 jne(条件不成立)更可能执行主逻辑 .Lerror: ... ``` > 📌 **经验法则**:仅在分支执行概率 **< 5%** 时使用 `unlikely`,且优先优化高频代码路径[^3]。 ---
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值