首页 > 其他分享 >devres_add添加设备资源

devres_add添加设备资源

时间:2023-10-11 16:01:52浏览次数:30  
标签:struct err driver dev add 添加 device devres

参考:https://zhuanlan.zhihu.com/p/517974143

1.背景

 每当driver probe一个具体的device实例的时候,都需要建立一些私有的数据结构来保存该device的一些具体的硬件信息。

 以往都是通过kmalloc或者kzalloc来分配内存,但这会引入一些潜在的问题。如:在初始化过程中,会有各种可能的失败情况,需要释放之前分配的内存。

 在初始化过程中,除了memory,driver会为probe的device分配各种资源,例如IRQ号,io memory map,DMA等。当初始化需要管理这么多的资源分配和释放的时候,很多驱动程序都会出现资源管理的issue。

 并且这个issue问题不容易测试出来。

 内核解决这个问题的模式(所谓解决一类问题的设计方法就叫做设计模式)是Devres,即device resource management模块。

// drivers/media/platform/soc_camera/mx1_camera.c
static int __init mx1_camera_probe(struct platform_device *pdev)
{
    // ...

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    irq = platform_get_irq(pdev, 0);
    if (!res || (int)irq <= 0) {
        err = -ENODEV;
        goto exit;
    }

    clk = clk_get(&pdev->dev, "csi_clk");
    if (IS_ERR(clk)) {
        err = PTR_ERR(clk);
        goto exit;
    }

    pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
    if (!pcdev) {
        dev_err(&pdev->dev, "Could not allocate pcdev\n");
        err = -ENOMEM;
        goto exit_put_clk;
    }

    // ...

    /*
     * Request the regions.
     */
    if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) {
        err = -EBUSY;
        goto exit_kfree;
    }

    base = ioremap(res->start, resource_size(res));
    if (!base) {
        err = -ENOMEM;
        goto exit_release;
    }
    // ...

    /* request dma */
    pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
    if (pcdev->dma_chan < 0) {
        dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n");
        err = -EBUSY;
        goto exit_iounmap;
    }
    // ...

    /* request irq */
    err = claim_fiq(&fh);
    if (err) {
        dev_err(&pdev->dev, "Camera interrupt register failed\n");
        goto exit_free_dma;
    }

    // ...
    err = soc_camera_host_register(&pcdev->soc_host);
    if (err)
        goto exit_free_irq;

    dev_info(&pdev->dev, "MX1 Camera driver loaded\n");

    return 0;

exit_free_irq:
    disable_fiq(irq);
    mxc_set_irq_fiq(irq, 0);
    release_fiq(&fh);
exit_free_dma:
    imx_dma_free(pcdev->dma_chan);
exit_iounmap:
    iounmap(base);
exit_release:
    release_mem_region(res->start, resource_size(res));
exit_kfree:
    kfree(pcdev);
exit_put_clk:
    clk_put(clk);
exit:
    return err;
}

在probe函数中

  • 要顺序申请多种资源(IRQ、CLOCK、memory、regions、ioremap、dma、),只要任意一种资源申请失败,会要回滚释放之前已经申请的所有资源。

  • 函数最后面会有很多goto标签,并且在申请资源出错时,需要goto到正确的标签上,以便释放已经申请的资源。

 就像上面的代码一样,整个函数被大段的,重复的"if (condition) { err = xxx; goto xxx;}"充斥,容易出错。

Linux设备模型借助device resource management(设备资源管理),帮忙解决了这个问题。 driver只管申请,不用考虑释放,设备模型会帮忙释放资源。既然驱动需要用的资源都是设备的资源,那么资源的管理归于device,就是说不需要dirver过多的参与。当device与driver detach的时候,deivce会自动释放所有的资源。

static int __init mx1_camera_probe(struct platform_device *pdev)
{
    // ...

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    irq = platform_get_irq(pdev, 0);
    if (!res || (int)irq <= 0) {
        return -ENODEV;
    }

    clk = devm_clk_get(&pdev->dev, "csi_clk");
    if (IS_ERR(clk)) {
        return PTR_ERR(clk);
    }

    pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL);
    if (!pcdev) {
        dev_err(&pdev->dev, "Could not allocate pcdev\n");
        return -ENOMEM;
    }

    // ...

    /*
     * Request the regions.
     */
    if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), DRIVER_NAME)) {
        return -EBUSY;
    }

    base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
    if (!base) {
        return -ENOMEM;
    }
    // ...

    /* request dma */
    pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
    if (pcdev->dma_chan < 0) {
        dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n");
        return -EBUSY;
    }
    // ...

    /* request irq */
    err = claim_fiq(&fh);
    if (err) {
        dev_err(&pdev->dev, "Camera interrupt register failed\n");
        return err;
    }

    // ...
    err = soc_camera_host_register(&pcdev->soc_host);
    if (err)
        return err;

    dev_info(&pdev->dev, "MX1 Camera driver loaded\n");

    return 0;
}

不要再使用那些常规的资源申请接口,用devm_xxx接口来代替申请资源。

为了保持兼容,这些新接口和旧接口的参数保持一致,只是名字前面加了"devm_",并且多加了一个struct device指针。

2.devm_xxx接口

 需要记住:driver可以只申请,不释放,设备模型会帮忙释放。
 为了严谨,在driver remove时,可以主动释放(也有相应的接口)

extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
 
void __iomem *devm_ioremap_resource(struct device *dev, 
  struct resource *res);
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
  unsigned long size);
 
struct clk *devm_clk_get(struct device *dev, const char *id);
 
int devm_gpio_request(struct device *dev, unsigned gpio,
  const char *label);
 
static inline struct pinctrl * devm_pinctrl_get_select(
  struct device *dev, const char *name)
 
static inline struct pwm_device *devm_pwm_get(struct device *dev,
  const char *consumer);
 
struct regulator *devm_regulator_get(struct device *dev, const char *id);
 
static inline int devm_request_irq(struct device *dev, unsigned int irq, 
  irq_handler_t handler, unsigned long irqflags, 
  const char *devname, void *dev_id);
 
struct reset_control *devm_reset_control_get(struct device *dev, 
  const char *id);

3.设备资源

 一个设备要能工作依赖很多的外部条件,如供电,时钟等,这些外部条件称作设备资源(device resource)。

  • power,供电。
  • clock,时钟。
  • memory,内存,在kernel中一般使用kzalloc分配。
  • GPIO,用户和CPU交换简单控制、状态等信息。
  • IRQ,触发中断。
  • DMA,无CPU参与情况下进行数据传输。
  • 虚拟地址空间,一般使用ioremap、request_region等分配。

 而在linux kernel中,资源的定义更为广义,比如PWM,RTC,Reset,都可以抽象为资源,供driver使用。

 在较早的kernel中,系统还不是特别复杂,且各个framework还没有成型,因此大多的资源都是由driver自行维护。随着系统复杂度的增加,driver之间共用资源的情况越来越多,同时电源管理的需求也越来越多。

 于是kernel就将各个resource的管理权回收,基于"device resource management"的框架,由各个framework统一管理,包括分配和回收。

device resource management的软件框架

 位于"driver/base/devres.c"中,它的实现非常简单,因为资源的种类有很多,表现形式也多种多样,而devres不可能都知情,也不能进行具体的分配和回收。

因此,devres唯一的功能:

提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach的时候,自动释放。

 如果抽象出某一种设备,则由上层的framework负责。这些franmework包括:regulator(管理power资源) ,clock framework(管理clock资源),interrupt framework(管理中断资源),gpio framework(管理gpio资源)。

其他的driver,位于这些framework之上,使用它们提供的机制和接口。

device原型的devres_head
先从struct device开始吧!该结构中有一个名称为“devres_head”的链表头,用于保存该设备申请的所有资源。

struct device {
    // ...
    spinlock_t    devres_lock;
    struct list_head   devres_head;
    //...
} 

devres原型
devres代表了资源的数据结构。

// drivers/base/devres.c
struct devres {
    struct devres_node    node;
    /* -- 3 pointers */
    unsigned long long    data[]; /* guarantee ull alignment */
};
  • data是一个零长数组,用于存放所申请的不定长内存;因为整个memory空间是连续的,因此可以通过释放devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。

  • 而node用于将devres组织起来,方便插入到device结构的devres_head链表中

devres_node原型

// base/devres.c
struct devres_node {
    struct list_head        entry;
    dr_release_t            release;
#ifdef CONFIG_DEBUG_DEVRES
    const char          *name;
    size_t              size;
#endif
};

向设备模型提供的接口

向设备模型提供的接口:devres_release_all

这里是重点,用于自动释放资源。

int devres_release_all(struct device *dev)
{
    unsigned long flags;

    /* Looks like an uninitialized device structure */
    if (WARN_ON(dev->devres_head.next == NULL))
        return -ENODEV;
    spin_lock_irqsave(&dev->devres_lock, flags);
    return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
                         flags);
}

以设备指针为参数,直接调用release_nodes:

static int release_nodes(struct device *dev, struct list_head *first,
                         struct list_head *end, unsigned long flags)
    __releases(&dev->devres_lock)
{
    LIST_HEAD(todo);
    int cnt;
    struct devres *dr, *tmp;

    // 将设备所有的`devres`从设备的`devres_head`中移除
    cnt = remove_nodes(dev, first, end, &todo);

    spin_unlock_irqrestore(&dev->devres_lock, flags);

    /* Release.  Note that both devres and devres_group are
    * handled as devres in the following loop.  This is safe.
    */
    
    list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
        devres_log(dev, &dr->node, "REL");
        // 调用所有资源的release回调函数(例如上面`devm_irq_release`),
        // 回调函数会回收具体的资源(如`free_irq`)。
        dr->node.release(dev, dr->data);
        // 最后,调用free,释放devres以及资源所占的空间
        kfree(dr);
    }

    return cnt;
}

调用时机

先回忆一下设备模型中probe的流程,devres_release_all接口被调用的时机有两个:

  • really_probe失败
  • 设备与驱动分离时:deriver dettach时(就是driver remove时)

1.really_probe失败

probe调用过程为(就不详细的贴代码了):__driver_attach/__device_attach-->driver_probe_device—>really_probe。

really_probe调用driver或者bus的probe接口,如果失败(返回值非零,可参考本文开头的例子),则会调用devres_release_all.

2.设备与驱动分离时

另外一个时机是在,deriver dettach时(就是driver remove时):driver_detach/bus_remove_device-->__device_release_driver-->devres_release_all

标签:struct,err,driver,dev,add,添加,device,devres
From: https://www.cnblogs.com/caseyzq/p/17757388.html

相关文章

  • 日期格式转换异常:Java 8 date/time type `java.time.LocalDateTime` not supported by
    异常信息:"unexpectederror:Typedefinitionerror:[simpletype,classjava.time.LocalDateTime];nestedexceptioniscom.fasterxml.jackson.databind.exc.InvalidDefinitionException:Java8date/timetype`java.time.LocalDateTime`notsupportedbydefault:......
  • 直播平台源代码,bmob_gudongStpeAdd
    直播平台源代码,bmob_gudongStpeAddfunctiononRequest(request,response,modules){ varaccess_token;varstpe=0; if(request.body.stpe){access_token=request.body.access_token;stpe=request.body.stpe;}  if(request.query.stpe){access_token=request.query.a......
  • What causes "Invalid Address specified to RtlValidateHeap"?
    ForumVisualC++&C++ProgrammingVisualC++ProgrammingWhatcauses"InvalidAddressspecifiedtoRtlValidateHeap"?Ifthisisyourfirstvisit,besuretocheckoutthe FAQ byclickingthelinkabove.Youmayhaveto register or Login ......
  • .NET6 startup.cs 注入 本地缓存,AddTransient ,AddScoped ,AddSingleton生命周期
    .NET6startup.cs注入本地缓存//使用缓存usingMicrosoft.Extensions.Caching.Memory;services.AddMemoryCache();//自定义缓存类,类继承接口services.AddScoped<IMemoryCacheHelper,MemoryCacheHelper>();service.cs里使用构造函数注入生成对象方法里调用对象的写,获取......
  • Git-添加SSH秘钥后还是git clone 失败
    可能是Git版本过高,需要降低Git版本。本来Git版本为2.41,降低为2.33,gitclone就可以了......
  • FreeRTOS添加计时器
    最近需要将在Linux上的代码移植到FreeRTOS上,许多系统函数运行不了,其中就包括gettimeofday,以及使用定时器的不同。FreeRTOS的时间管理首先,FreeRTOS的系统时钟节拍可以在配置文件FreeRTOSConfig.h里面设置:#defineconfigTICK_RATE_HZ((TickType_t)1000)//配置系统时......
  • centos8添加永久路由
    linux配置静态路由,一般使用iprouteadd添加临时路由,重启会丢失配置永久配置路由方法:touch/etc/sysconfig/network-scripts/route-ens192nmcliconnectionmodifyens192+ipv4.routes"10.0.3.0/24192.168.2.254"nmclicreloadnmclicupens192编辑脚本封装多个命令,一......
  • logger.add() 方法的所有参数及其用法说明:
    Loguru是一个强大而易于使用的日志记录库,logger.add()方法用于向Logurulogger添加处理程序。下面是logger.add()方法的所有参数及其用法说明:logger.add(sink,*,level=None,format=None,filter=None,colorize=None,backtrace=None,diagnose=None,serialize=False,......
  • 论文阅读(一)—— Adding Conditional Control to Text-to-Image Diffusion Models
    ......
  • 使用LVM给Centos根分区扩容(删除其他分区,将空余空间添加到根分区)
    使用LVM给Centos根分区扩容(删除其他分区,将空余空间添加到根分区)新建的虚拟机发现根分区空间只分配了一半,另外一半分给了/home,和想要的效果不一致,于是需要删除/dev/mapper/centos-home分区,将空间添加到根分区1.查看磁盘发现/dev/mapper/centos-home分区占了一半空间.[root@te......