Linux设备模型(9)_device resource management作者:蜗蜗 发布于:2014-9-24 23:28 1. 前言蜗蜗建议,每一个Linux驱动工程师,都能瞄一眼本文。 之所以用“瞄”,因此它很简单,几乎不需要花费心思就能理解。之所有这建议,是因为它非常实用,可以解答一些困惑,可以使我们的代码变得简单、简洁。先看一个例子: 1: /* drivers/media/platform/soc_camera/mx1_camera.c, line 695 */ 2: static int __init mx1_camera_probe(struct platform_device *pdev) 3: { 4: ... 5: 6: res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 7: irq = platform_get_irq(pdev, 0); 8: if (!res || (int)irq <= 0) { 9: err = -ENODEV; 10: goto exit; 11: } 12: 13: clk = clk_get(&pdev->dev, "csi_clk"); 14: if (IS_ERR(clk)) { 15: err = PTR_ERR(clk); 16: goto exit; 17: } 18: 19: pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL); 20: if (!pcdev) { 21: dev_err(&pdev->dev, "Could not allocate pcdev\n"); 22: err = -ENOMEM; 23: goto exit_put_clk; 24: } 25: 26: ... 27: 28: /* 29: * Request the regions. 30: */ 31: if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) { 32: err = -EBUSY; 33: goto exit_kfree; 34: } 35: 36: base = ioremap(res->start, resource_size(res)); 37: if (!base) { 38: err = -ENOMEM; 39: goto exit_release; 40: } 41: ... 42: 43: /* request dma */ 44: pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH); 45: if (pcdev->dma_chan < 0) { 46: dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n"); 47: err = -EBUSY; 48: goto exit_iounmap; 49: } 50: ... 51: 52: /* request irq */ 53: err = claim_fiq(&fh); 54: if (err) { 55: dev_err(&pdev->dev, "Camera interrupt register failed\n"); 56: goto exit_free_dma; 57: } 58: 59: ... 60: err = soc_camera_host_register(&pcdev->soc_host); 61: if (err) 62: goto exit_free_irq; 63: 64: dev_info(&pdev->dev, "MX1 Camera driver loaded\n"); 65: 66: return 0; 67: 68: exit_free_irq: 69: disable_fiq(irq); 70: mxc_set_irq_fiq(irq, 0); 71: release_fiq(&fh); 72: exit_free_dma: 73: imx_dma_free(pcdev->dma_chan); 74: exit_iounmap: 75: iounmap(base); 76: exit_release: 77: release_mem_region(res->start, resource_size(res)); 78: exit_kfree: 79: kfree(pcdev); 80: exit_put_clk: 81: clk_put(clk); 82: exit: 83: return err; 84: } 相信每一个写过Linux driver的工程师,都在probe函数中遇到过上面的困惑:要顺序申请多种资源(IRQ、Clock、memory、regions、ioremap、dma、等等),只要任意一种资源申请失败,就要回滚释放之前申请的所有资源。于是函数的最后,一定会出现很多的goto标签(如上面的exit_free_irq、exit_free_dma、等等),并在申请资源出错时,小心翼翼的goto到正确的标签上,以便释放已申请资源。 正像上面代码一样,整个函数被大段的、重复的“if (condition) { err = xxx; goto xxx; }”充斥,浪费精力,容易出错,不美观。有困惑,就有改善的余地,最终,Linux设备模型借助device resource management(设备资源管理),帮我们解决了这个问题。就是:driver你只管申请就行了,不用考虑释放,我设备模型帮你释放。最终,我们的driver可以这样写: 1: static int __init mx1_camera_probe(struct platform_device *pdev) 2: { 3: ... 4: 5: res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 6: irq = platform_get_irq(pdev, 0); 7: if (!res || (int)irq <= 0) { 8: return -ENODEV; 9: } 10: 11: clk = devm_clk_get(&pdev->dev, "csi_clk"); 12: if (IS_ERR(clk)) { 13: return PTR_ERR(clk); 14: } 15: 16: pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); 17: if (!pcdev) { 18: dev_err(&pdev->dev, "Could not allocate pcdev\n"); 19: return -ENOMEM; 20: } 21: 22: ... 23: 24: /* 25: * Request the regions. 26: */ 27: if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), DRIVER_NAME)) { 28: return -EBUSY; 29: } 30: 31: base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); 32: if (!base) { 33: return -ENOMEM; 34: } 35: ... 36: 37: /* request dma */ 38: pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH); 39: if (pcdev->dma_chan < 0) { 40: dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n"); 41: return -EBUSY; 42: } 43: ... 44: 45: /* request irq */ 46: err = claim_fiq(&fh); 47: if (err) { 48: dev_err(&pdev->dev, "Camera interrupt register failed\n"); 49: return err; 50: } 51: 52: ... 53: err = soc_camera_host_register(&pcdev->soc_host); 54: if (err) 55: return err; 56: 57: dev_info(&pdev->dev, "MX1 Camera driver loaded\n"); 58: 59: return 0; 60: } 怎么做到呢?注意上面“devm_”开头的接口,答案就在那里。不要再使用那些常规的资源申请接口,用devm_xxx的接口代替。为了保持兼容,这些新接口和旧接口的参数保持一致,只是名字前加了“devm_”,并多加一个struct device指针。 2. devm_xxx下面列举一些常用的资源申请接口,它们由各个framework(如clock、regulator、gpio、等等)基于device resource management实现。使用时,直接忽略“devm_”的前缀,后面剩下的部分,driver工程师都很熟悉。只需记住一点,driver可以只申请,不释放,设备模型会帮忙释放。不过如果为了严谨,在driver remove时,可以主动释放(也有相应的接口,这里没有列出)。 1: extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp); 2: 3: void __iomem *devm_ioremap_resource(struct device *dev, 4: struct resource *res); 5: void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, 6: unsigned long size); 7: 8: struct clk *devm_clk_get(struct device *dev, const char *id); 9: 10: int devm_gpio_request(struct device *dev, unsigned gpio, 11: const char *label); 12: 13: static inline struct pinctrl * devm_pinctrl_get_select( 14: struct device *dev, const char *name) 15: 16: static inline struct pwm_device *devm_pwm_get(struct device *dev, 17: const char *consumer); 18: 19: struct regulator *devm_regulator_get(struct device *dev, const char *id); 20: 21: static inline int devm_request_irq(struct device *dev, unsigned int irq, 22: irq_handler_t handler, unsigned long irqflags, 23: const char *devname, void *dev_id); 24: 25: struct reset_control *devm_reset_control_get(struct device *dev, 26: const char *id); 3. 什么是“设备资源”一个设备能工作,需要依赖很多的外部条件,如供电、时钟等等,这些外部条件称作设备资源(device resouce)。对于现代计算机的体系结构,可能的资源包括:
而在Linux kernel的眼中,“资源”的定义更为广义,比如PWM、RTC、Reset,都可以抽象为资源,供driver使用。 在较早的kernel中,系统还不是特别复杂,且各个framework还没有成型,因此大多的资源都由driver自行维护。但随着系统复杂度的增加,driver之间共用资源的情况越来越多,同时电源管理的需求也越来越迫切。于是kernel就将各个resource的管理权收回,基于“device resource management”的框架,由各个framework统一管理,包括分配和回收。 4. device resource management的软件框架
而更为具体的事情,如怎么抽象某一种设备,则由上层的framework负责。这些framework包括:regulator framework(管理power资源),clock framework(管理clock资源),interrupt framework(管理中断资源)、gpio framework(管理gpio资源),pwm framework(管理PWM),等等。 其它的driver,位于这些framework之上,使用它们提供的机制和接口,开发起来就非常方便了。 5. 代码分析5.1 数据结构先从struct device开始吧!该结构中有一个名称为“devres_head”的链表头,用于保存该设备申请的所有资源,如下: 1: struct device { 2: ... 3: spinlock_t devres_lock; 4: struct list_head devres_head; 5: ... 6: } 7: 那资源的数据结构呢?在“drivers/base/devres.c”中,名称为struct devres,如下: 1: struct devres { 2: struct devres_node node; 3: /* -- 3 pointers */ 4: unsigned long long data[]; /* guarantee ull alignment */ 5: }; 咋一看非常简单,一个struct devres_node的变量node,一个零长度数组data,但其中有无穷奥妙,让我们继续分析。 node用于将devres组织起来,方便插入到device结构的devres_head链表中,因此一定也有一个list_head(见下面的entry)。另外,资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源,如下: 1: struct devres_node { 2: struct list_head entry; 3: dr_release_t release; 4: #ifdef CONFIG_DEBUG_DEVRES 5: const char *name; 6: size_t size; 7: #endif 8: }; 抛开用于debug的变量不说,也很简单,一个entry list_head,一个release回调函数。看不出怎么抽象资源啊!别急,奥妙都在data这个零长度数组上面呢。 注1:不知道您是否注意到,devres有关的数据结构,是在devres.c中定义的(是C文件哦!)。换句话说,是对其它模块透明的。这真是优雅的设计(尽量屏蔽细节)! 5.2 一个无关话题:零长度数组零长度数组的英文原名为Arrays of Length Zero,是GNU C的规范,主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。什么意思呢? 以struct devres为例,node变量的长度为3个指针的长度,而struct devres的长度也是3个指针的长度。而data只是一个标记,当有人分配了大于3个指针长度的空间并把它转换为struct devres类型的变量后,我们就可以通过data来访问多出来的memory。也就是说,有了零长度数组data,struct devres结构的长度可以不定,完全依赖于你分配的空间的大小。有什么用呢? 以本文的应用场景为例,多出来的、可通过data访问的空间,正是具体的device resource所占的空间。资源的类型不同,占用的空间的多少也不同,但devres模块的主要功能又是释放资源所占的资源。这是就是零长度数组的功能之一,因为整个memory空间是连续的,因此可以通过释devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。 零长度数组(data[0]),在不同的C版本中,有不同的实现方案,包括1长度数组(data[1])和不定长度数组(data[],本文所描述就是这一种),具体可参考GCC的规范: https://gcc./onlinedocs/gcc/Zero-Length.html 5.3 向上层framework提供的接口:devres_alloc/devres_free、devres_add/devres_remove先看一个使用device resource management的例子(IRQ模块): 1: /* include/linux/interrupt.h */ 2: static inline int __must_check 3: devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, 4: unsigned long irqflags, const char *devname, void *dev_id) 5: { 6: return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, 7: devname, dev_id); 8: } 9: 10: 11: /* kernel/irq/devres.c */ 12: int devm_request_threaded_irq(struct device *dev, unsigned int irq, 13: irq_handler_t handler, irq_handler_t thread_fn, 14: unsigned long irqflags, const char *devname, 15: void *dev_id) 16: { 17: struct irq_devres *dr; 18: int rc; 19: 20: dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres), 21: GFP_KERNEL); 22: if (!dr) 23: return -ENOMEM; 24: 25: rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname, 26: dev_id); 27: if (rc) { 28: devres_free(dr); 29: return rc; 30: } 31: 32: dr->irq = irq; 33: dr->dev_id = dev_id; 34: devres_add(dev, dr); 35: 36: return 0; 37: } 38: EXPORT_SYMBOL(devm_request_threaded_irq); 39: 40: void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id) 41: { 42: struct irq_devres match_data = { irq, dev_id }; 43: 44: WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match, 45: &match_data)); 46: free_irq(irq, dev_id); 47: } 48: EXPORT_SYMBOL(devm_free_irq); 前面我们提过,上层的IRQ framework,会提供两个和request_irq/free_irq基本兼容的接口,这两个接口的实现非常简单,就是在原有的实现之上,封装一层devres的操作,如要包括: 1)一个自定义的数据结构(struct irq_devres),用于保存和resource有关的信息(对中断来说,就是IRQ num),如下: 1: /* 2: * Device resource management aware IRQ request/free implementation. 3: */ 4: struct irq_devres { 5: unsigned int irq; 6: void *dev_id; 7: }; 2)一个用于release resource的回调函数(这里的release,和memory无关,例如free IRQ),如下: 1: static void devm_irq_release(struct device *dev, void *res) 2: { 3: struct irq_devres *this = res; 4: 5: free_irq(this->irq, this->dev_id); 6: }
3)以回调函数、resource的size为参数,调用devres_alloc接口,为resource分配空间。该接口的定义如下: 1: void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp) 2: { 3: struct devres *dr; 4: 5: dr = alloc_dr(release, size, gfp); 6: if (unlikely(!dr)) 7: return NULL; 8: return dr->data; 9: } 调用alloc_dr,分配一个struct devres类型的变量,并返回其中的data指针(5.2小节讲过了,data变量实际上是资源的代表)。alloc_dr的定义如下: 1: static __always_inline struct devres * alloc_dr(dr_release_t release, 2: size_t size, gfp_t gfp) 3: { 4: size_t tot_size = sizeof(struct devres) + size; 5: struct devres *dr; 6: 7: dr = kmalloc_track_caller(tot_size, gfp); 8: if (unlikely(!dr)) 9: return NULL; 10: 11: memset(dr, 0, tot_size); 12: INIT_LIST_HEAD(&dr->node.entry); 13: dr->node.release = release; 14: return dr; 15: } 看第一句就可以了,在资源size之前,加一个struct devres的size,就是total分配的空间。除去struct devres的,就是资源的(由data指针访问)。之后是初始化struct devres变量的node。 4)调用原来的中断注册接口(这里是request_threaded_irq),注册中断。该步骤和device resource management无关。 5)注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add,将资源添加到设备的资源链表头(devres_head)中,该接口定义如下: 1: void devres_add(struct device *dev, void *res) 2: { 3: struct devres *dr = container_of(res, struct devres, data); 4: unsigned long flags; 5: 6: spin_lock_irqsave(&dev->devres_lock, flags); 7: add_dr(dev, &dr->node); 8: spin_unlock_irqrestore(&dev->devres_lock, flags); 9: } 从资源指针中,取出完整的struct devres指针,调用add_dr接口。add_dr也很简单,把struct devres指针挂到设备的devres_head中即可: 1: static void add_dr(struct device *dev, struct devres_node *node) 2: { 3: devres_log(dev, node, "ADD"); 4: BUG_ON(!list_empty(&node->entry)); 5: list_add_tail(&node->entry, &dev->devres_head); 6: } 6)如果失败,可以通过devres_free接口释放资源占用的空间,devm_free_irq接口中,会调用devres_destroy接口,将devres从devres_head中移除,并释放资源。这里就不详细描述了。 5.4 向设备模型提供的接口:devres_release_all这里是重点,用于自动释放资源。 先回忆一下设备模型中probe的流程(可参考“Linux设备模型(5)_device和device driver”),devres_release_all接口被调用的时机有两个: 1)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 devres_release_all的实现如下: 1: int devres_release_all(struct device *dev) 2: { 3: unsigned long flags; 4: 5: /* Looks like an uninitialized device structure */ 6: if (WARN_ON(dev->devres_head.next == NULL)) 7: return -ENODEV; 8: spin_lock_irqsave(&dev->devres_lock, flags); 9: return release_nodes(dev, dev->devres_head.next, &dev->devres_head, 10: flags); 11: } 以设备指针为参数,直接调用release_nodes: 1: static int release_nodes(struct device *dev, struct list_head *first, 2: struct list_head *end, unsigned long flags) 3: __releases(&dev->devres_lock) 4: { 5: LIST_HEAD(todo); 6: int cnt; 7: struct devres *dr, *tmp; 8: 9: cnt = remove_nodes(dev, first, end, &todo); 10: 11: spin_unlock_irqrestore(&dev->devres_lock, flags); 12: 13: /* Release. Note that both devres and devres_group are 14: * handled as devres in the following loop. This is safe. 15: */ 16: list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) { 17: devres_log(dev, &dr->node, "REL"); 18: dr->node.release(dev, dr->data); 19: kfree(dr); 20: } 21: 22: return cnt; 23: } release_nodes会先调用remove_nodes,将设备所有的struct devres指针从设备的devres_head中移除。然后,调用所有资源的release回调函数(如5.3小节描述的devm_irq_release),回调函数会回收具体的资源(如free_irq)。最后,调用free,释放devres以及资源所占的空间。
原创文章,转发请注明出处。蜗窝科技,www.。 评论: Issues 2015-02-05 08:54 谢谢楼主的解答,可这些头文件,在安装内核树之前应该已经存在了啊(/usr/src/ 下本来就有内核的头文件)。内核树构建的的是source文件和编译后的vmlinux.o镜像文件。如果编译新内核时这些文件没用,哪这些文件是干什么的。而且只有头文件,编译新model的时候引用的内核符号在哪?? Issues 2015-02-05 16:28 @wowo:按楼主的意思,如果我编译和的module是和主机kernel相同版本的话,那不用构建内核源码树应该也能编译成功。可这样是会失败的啊,编译时会提示要求安装源码树 wowo 2015-02-05 21:14 @Issues:先确认是否有header文件。 再确认是否有模块编译目录:/lib/modules/$(uanem -r)/build 以及header的搜索路径是否正确。 等等。 Issues 2015-02-07 08:12 @wowo:最后还是在ldd 3rd上找到答案了,2.6内核的模块要和内核源码树中的目标文件连接,这样可以得到更加健壮的模块加载器,因此就需要这些目标文件存在于内核目录树中。以前版本的内核是只需要一套头文件的。第一遍看书的时候根本没理解是啥意思。现在发现,这句话是解释的最明白的。 谢谢楼主的耐心解答。网站的内容非常不错,有很多我需要学习的文章。希望以后窝窝可以变成linux爱好者的乐园。 Issues 2015-01-28 15:41 刚转入linux驱动开发,从楼主的系列文章学到了很多。不过鉴于个人还是内核菜鸟,有几个困扰多时,网上也没有找到确切答案的问题,想请楼主百忙之中拨冗解答一下。 1.在已安装linux系统的pc上进行驱动开发(面向本机,如给嵌入式开发,还比较好理解),为何还要安装内核源码树。已安装的linux系统没有内核么,两者有何区别。个人理解,是不是因为操作系统的内核是一个完整的可执行程序,因此,新建module是无法链接其中的目标文件,因此需要安装源码树。源码树安装并make后,形成目标文件,是不是只生成了一个vmlinux.o(ubuntu),之后新建的module文件就可以通过/linux/*.h链接到此目标文件完成编译。 2.新建的moudle编译完成后,insmod加载模块,新module是加载到当前的linux操作系统中,还是新安装的内核源码树上。 希望楼主能详细解说一下,非常感谢。 wowo 2015-01-29 10:37 @Issues:我想你问的是有关”编译linux kernel module“的问题。 首先,编译kernel module(无论target machine是Host还是嵌入式本机),kernel source tree(从kernel.org下载的)不是必须的。而 linux kernel header才是必须的。 那么,什么是 linux kernel header呢?打个比方,我们使用一个第三方库(静态库或者动态库)的时候,除了库文件,往往还需要一些头文件,以便知道这些库文件中符号(其实这些也不是必须的,如果我们知道所有的符号的话,但这对linux kernel是不可能的)。 module编译也类似,我们编写驱动,需要用到很多很多kernel已有的机制(如各种framework,其它driver等等,这些表现出来的就是一个一个符号,位于各个模块的头文件中)。这也是为什么需要linux kernel header。 最后,没有安装在”内核源码树“上这一说法。module文件就像一个可执行文件(windows下的EXE,Linux的EFL,等等),需要由操作系统,加载到内存,解析并执行之。 linuxer 2014-09-29 12:49 @forion:的确,我和蜗窝性格还是有些不一样的,我比较温和,他比较犀利,如果是一个人的话的确是有人格分裂的嫌疑,呵呵~~~ 工作不忙的时候别忘了也写写文章哦~~~蜗窝是大家的蜗窝,呵呵~~~ linuxer 2014-09-29 12:46 @perr:我们曾经在一个公司,有相同的理念,对linux kernel有相同的热情,自然也就很容易成为朋友,一起做事情。 我也是从蜗蜗那里学到很多,我很少见到有一个年轻人能象蜗蜗那么有大局观。 我们经常通话,确定短期内的方向,当然不会写重复的,呵呵~~~ linuxer 2014-09-29 12:59 @perr:clocksource是linux时间子系统的一部分,我搞完中断子系统这部分,下一个专题就是时间子系统了。 clk是和clock distribution相关的内容,属于内核common clock framework,可以归入电源管理子系统,蜗蜗同学应该会写这部分内容的 蜗蜗 2014-09-29 22:35 @perr:device tree的设备节点,准确的说,是用来描述资源,例如device的I/O memory、IRQ number等。系统启动后,device tree会把这些节点统统挂到struct device的of_node指针上。 另外,对于一些标准资源(IO、IRQ、DMA、GPIO、等等),device tree会帮助解析(需要和对应的framework交互),并以platform resource(struct resource)的形式保存下来,因此driver可以通过platform_get_xxx系列接口获取。 而对于一些非标准节点(如自定义的配置参数),则需要调用of_xxx系列接口,自行解析。 要完全分析清楚,估计也得一篇不短的文章啊,Linuxer写了三篇DTS的文章,把DTS的原理分析的很透彻了。如果能从这个角度,再写一篇,阐述从Linux驱动开发者的角度,怎么使用,就perfect了。哈哈。 |
|
来自: 老匹夫 > 《Device Driver》