对Linux CAN驱动的理解

在Ti的AM335X系列Cortext-A8芯片中,CAN模块采用D_CAN结构,实质即两路CAN接口。

在此分享一下对基于AM335X的Linux CAN驱动源码的理解。下面来分析它的驱动源码及其工作方式。

在Linux内核源码中,CAN设备驱动文件如下:

drivers/net/can/d_can/d_can_platform.c

drivers/net/can/d_can/d_can.c

drivers/net/can/d_can/d_can.h

 

首先分析d_can_platform.c文件,驱动运行时,也是先从这里开始。首先是驱动入口函数:

module_init(d_can_plat_init);

static int __init d_can_plat_init(void)

{

printk(KERN_INFO D_CAN_DRV_DESC "\n");

return platform_driver_register(&d_can_plat_driver);

}

在驱动入口函数d_can_plat_init()中,使用platform_driver_register(&d_can_plat_driver)将结构体变量d_can_plat_driver注册为平台驱动。

static struct platform_driver d_can_plat_driver = {

.driver = {

.name = D_CAN_DRV_NAME,

.owner = THIS_MODULE,

},

.probe = d_can_plat_probe,

.remove = __devexit_p(d_can_plat_remove),

};

平台驱动中,最重要的是探测函数d_can_plat_probe。探测函数主要的工作是获取平台设备传递过来的资源及初始化硬件。下面来看看d_can_plat_probe() 函数都做了些什么工作。

static int __devinit d_can_plat_probe(struct platform_device *pdev)

{

int ret = 0;

void __iomem *addr;

struct net_device *ndev;

struct d_can_priv *priv;

struct resource *mem;

/* 定义d_can_platform_data结构体变量指针pdata,d_can_platform_data结构体类型与板级文件中的平台设备使用的结构体类型是一致的 */

struct d_can_platform_data *pdata;

/*获取平台设备数据*/

pdata = pdev->dev.platform_data;

if (!pdata) {

dev_err(&pdev->dev, "No platform data\n");

goto exit;

}

/* allocate the d_can device */

/*分配d_can设备,如can0、can1、…等*/

ndev = alloc_d_can_dev(pdata->num_of_msg_objs);

if (!ndev) {

ret = -ENOMEM;

dev_err(&pdev->dev, "alloc_d_can_dev failed\n");

goto exit;

}

/*获取设备私有数据*/

priv = netdev_priv(ndev);

/*获取时钟并使能*/

priv->fck = clk_get(&pdev->dev, pdata->fck_name);

if (IS_ERR(priv->fck)) {

dev_err(&pdev->dev, "%s is not found\n", pdata->fck_name);

ret = -ENODEV;

goto exit_free_ndev;

}

clk_enable(priv->fck);

 

/*获取时钟并使能*/

priv->ick = clk_get(&pdev->dev, pdata->ick_name);

if (IS_ERR(priv->ick)) {

dev_err(&pdev->dev, "%s is not found\n", pdata->ick_name);

ret = -ENODEV;

goto exit_free_fck;

}

clk_enable(priv->ick);

 

/* get the platform data */

/*获取平台内存资源*/

mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);

if (!mem) {

ret = -ENODEV;

dev_err(&pdev->dev, "No mem resource\n");

goto exit_free_clks;

}

/*申请I/O内存*/

if (!request_mem_region(mem->start, resource_size(mem),

D_CAN_DRV_NAME)) {

dev_err(&pdev->dev, "resource unavailable\n");

ret = -EBUSY;

goto exit_free_clks;

}

/*在内核中访问 I/O 内存之前,需首先使用 ioremap()函数将设备所处的物理地址映

射到虚拟地址*/

addr = ioremap(mem->start, resource_size(mem));

if (!addr) {

dev_err(&pdev->dev, "ioremap failed\n");

ret = -ENOMEM;

goto exit_release_mem;

}

 

/* IRQ specific to Error and status & can be used for Message Object */

ndev->irq = platform_get_irq_byname(pdev, "int0");

if (!ndev->irq) {

dev_err(&pdev->dev, "No irq0 resource\n");

goto exit_iounmap;

}

 

/* IRQ specific for Message Object */

priv->irq_obj = platform_get_irq_byname(pdev, "int1");

if (!priv->irq_obj) {

dev_err(&pdev->dev, "No irq1 resource\n");

goto exit_iounmap;

}

 

priv->base = addr;

priv->can.clock.freq = clk_get_rate(priv->fck);

priv->test_mode = pdata->test_mode_enable;

 

platform_set_drvdata(pdev, ndev);

SET_NETDEV_DEV(ndev, &pdev->dev);

/*注册CAN网络设备*/

ret = register_d_can_dev(ndev);

if (ret) {

dev_err(&pdev->dev, "registering %s failed (err=%d)\n",

D_CAN_DRV_NAME, ret);

goto exit_free_device;

}

 

dev_info(&pdev->dev, "%s device registered (irq=%d, irq_obj=%d)\n",

D_CAN_DRV_NAME, ndev->irq, priv->irq_obj);

 

return 0;

 

exit_free_device:

platform_set_drvdata(pdev, NULL);

exit_iounmap:

iounmap(addr);

exit_release_mem:

release_mem_region(mem->start, resource_size(mem));

exit_free_clks:

clk_disable(priv->ick);

clk_put(priv->ick);

exit_free_fck:

clk_disable(priv->fck);

clk_put(priv->fck);

exit_free_ndev:

free_d_can_dev(ndev);

exit:

dev_err(&pdev->dev, "probe failed\n");

 

return ret;

}

 

在d_can_plat_probe()函数中调用register_d_can_dev()注册CAN为网络设备。函数register_d_can_dev()在文件drivers/net/can/d_can/d_can.c中。通过EXPORT_SYMBOL_GPL宏导出。

int register_d_can_dev(struct net_device *dev)

{

/* we support local echo */

dev->flags |= IFF_ECHO;

dev->netdev_ops = &d_can_netdev_ops;

 

return register_candev(dev);

}

EXPORT_SYMBOL_GPL(register_d_can_dev);

 

在register_d_can_dev()函数中填充其网络设备操作函数成员dev->netdev_ops= &d_can_netdev_ops。

static const struct net_device_ops d_can_netdev_ops = {

.ndo_open = d_can_open,

.ndo_stop = d_can_close,

.ndo_start_xmit = d_can_start_xmit,

};

 

由于Linux的CAN驱动是写成了socket can的架构,即将其模拟成网络设备。因此我们可以借鉴操作网络设备的方法,进行socket can的应用编程。

下面我们借用一个开源的socket can工具:canconfig将CAN设备打开。相应的在内核驱动层会相应调用d_can_open()函数。

static int d_can_open(struct net_device *ndev)

{

int err;

struct d_can_priv *priv = netdev_priv(ndev);

 

/* Open common can device */

err = open_candev(ndev);

if (err) {

netdev_err(ndev, "open_candev() failed %d\n", err);

return err;

}

/* register interrupt handler for Message Object (MO) and Error + status change (ES) */

err = request_irq(ndev->irq, &d_can_isr, IRQF_SHARED, ndev->name,

ndev);

if (err) {

netdev_err(ndev, "failed to request MO_ES interrupt\n");

goto exit_close_candev;

}

 

/* register interrupt handler for only Message Object */

err = request_irq(priv->irq_obj, &d_can_isr, IRQF_SHARED, ndev->name,

ndev);

if (err) {

netdev_err(ndev, "failed to request MO interrupt\n");

goto exit_free_irq;

}

 

/* start the d_can controller */

// d_can_start(ndev);

napi_enable(&priv->napi);

netif_start_queue(ndev);

 

d_can_start(ndev); //embest

 

return 0;

exit_free_irq:

free_irq(ndev->irq, ndev);

exit_close_candev:

close_candev(ndev);

return err;

}


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux内核是一个开源的操作系统内核,它的驱动源码详解主要是指对Linux内核中的设备驱动模块进行解析和分析。 Linux内核中的驱动源码通常分为两类:字符设备驱动和块设备驱动。字符设备驱动主要用于处理串行通信设备、音频设备、输入设备等,而块设备驱动则用于处理硬盘、闪存等块设备。驱动源码的详解主要涉及对设备驱动的初始化、注册、操作和中断处理等方面的内容。 在驱动源码详解中,我们可以了解到驱动模块的初始化过程,包括对设备资源的分配、设备的注册和初始化函数的调用。通过对设备驱动的分析,我们能够理解驱动模块与硬件设备之间的交互方式,包括读写操作、中断处理和设备的状态管理等。 驱动源码详解还涉及对设备的操作方法,包括打开设备、关闭设备、读写设备等。这部分内容主要关注设备驱动的各个函数的作用和调用顺序,以及数据的传递和处理方式。 此外,在驱动源码详解中还可以了解到设备的中断处理,包括中断的注册、中断处理函数的编写和中断处理的调用流程。中断处理是设备驱动中非常重要的一部分,了解中断处理的过程和方法对于理解设备驱动的工作原理和性能优化是很有帮助的。 综上所述,Linux内核的驱动源码详解内容丰富,主要涵盖了设备驱动的初始化、注册、操作和中断处理等方面的内容。通过对驱动源码的详细解析,我们可以更加深入地理解Linux内核的工作原理和设备驱动的工作方式,为后续的驱动开发和性能优化提供有力的支持。 ### 回答2: Linux驱动源码详解是指对Linux操作系统中的驱动程序源代码进行深入解析和讲解的过程。Linux操作系统采用开放源代码模型,其内核和驱动程序的源代码可以被开发者自由获取并进行修改和学习。 在Linux系统中,硬件设备的操作和管理主要通过驱动程序来实现。驱动程序的作用是将操作系统与硬件设备之间进行通讯和交互,实现设备的初始化、数据传输等功能。而Linux驱动源码详解的目的就是帮助开发者深入了解驱动程序的实现原理和工作机制。 在Linux驱动源码详解中,开发者可以学习到以下内容: 1. 驱动程序的基本结构和框架:了解驱动程序的基本组成部分,如设备注册、设备初始化、设备打开、设备关闭等流程。 2. 设备操作和中断处理:学习如何通过驱动程序向设备发送命令、读取数据以及接收设备中断信号等。 3. 数据传输和缓冲区管理:了解数据在驱动程序中的传输方式、缓冲区的管理和操作。 4. 设备的状态管理和错误处理:学习如何管理设备的状态,处理设备异常和错误情况。 5. 设备驱动的注册和注销:掌握如何将驱动程序注册到系统中并进行设备绑定,以及如何在不使用设备时进行注销。 通过对Linux驱动源码的详解,开发者可以深入了解驱动程序的实现细节,提高对驱动开发的理解和能力。同时,也可以通过学习和参考已有的驱动源码,为特定硬件设备开发定制化的驱动程序。 ### 回答3: Linux驱动源码是指运行在Linux操作系统上的设备驱动程序的代码。驱动程序负责与硬件设备进行通信,使操作系统能够识别设备并与之交互。 Linux驱动源码详解可以从以下几个方面展开: 1. 设备驱动模型:Linux使用一种称为设备树(Device Tree)的数据结构来描述系统中的硬件设备,这是一种独立于架构的设备描述方法。驱动源码中包含了设备树的解析和操作代码。 2. 设备注册和初始化:驱动源码会包含设备的注册和初始化代码,通过这些代码,驱动程序向操作系统注册设备并准备好与设备进行通信所需的资源。 3. 设备访问和控制:驱动源码中会包含设备的读写和控制操作的代码,以实现对设备的访问和控制。这些代码负责与设备进行数据传输和状态控制。 4. 中断和异常处理:驱动源码还包含中断和异常处理的代码。当硬件设备发生中断或异常时,驱动程序会相应地进行处理,以确保系统的稳定运行。 5. 资源管理:驱动源码会包含设备资源的管理代码,用于管理设备的内存、寄存器和中断等资源,以确保设备能够正常运行。 6. 驱动开发接口:Linux提供了一系列的API和接口,用于驱动开发。驱动源码中会使用这些接口来实现设备的注册、读写、内存分配等功能。 通过对Linux驱动源码进行详细解析,可以深入理解设备驱动程序的工作原理和实现方式,从而能够编写高效、可靠的设备驱动程序,提高系统的性能和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值