Linux Character Device Driver and RTC驱动来龙去脉浅析

通常LED Device Driver是一个比较简单的例子,但是麻雀虽小、五脏俱全。我们就从一个最简单的LED驱动的例子来认识如何设计一个Character Device Driver(以后我们简写为CDD),并真正搞清楚CDD其所以然。本文基于的Linux 版本为2.6.23

LED Device Driver的例子分析

限于篇幅,我将一些Hardware本身的处理、出错处理等全部删除了,这些也不是我们讨论的重点了。

static int s3c2451_led_probe(struct platform_device *dev)

{

    if ( led_major ) {

        led_devno = MKDEV(led_major, led_minor);

        result = register_chrdev_region(led_devno, 1, "led");

          } else {

        result = alloc_chrdev_region(&led_devno, led_minor, 1, "led");

        led_major=MAJOR(led_devno);

        led_minor=MINOR(led_devno);

          }

          cdev_init(&led_dev, &led_fops);

          led_dev.owner=THIS_MODULE;

          result = cdev_add(&led_dev, led_devno, 1);

}

 

static struct platform_driver s3c2451_led_driver = {

    .probe        = s3c2451_led_probe,

    .remove       = s3c2451_led_remove,

#ifdef CONFIG_PM

    .suspend      = s3c2451_led_suspend,

    .resume       = s3c2451_led_resume,

#endif

    .driver       = {

        .name     = "s3c2451-led",

    },

};

static int __init s3c2451_led_init(void)

{

    int ret;

    ret = platform_driver_register(&s3c2451_led_driver);

    return ret;

}

static void  __exit s3c2451_led_exit(void)

{

    platform_driver_unregister(&s3c2451_led_driver);

}

 

module_init(s3c2451_led_init);

module_exit(s3c2451_led_exit);

 

首先这里要说明一下,该driverLED device当作platform virtual bus(这是Linux kernel自己提供的)下的一个device来实现。我们可以不用platform bus,仅仅用cdev_init, cdev_add也可以实现一个CDD,但是这样做是Linux 2.4的做法,这样的做的结果就是我们的CDD游离在Linux Device Driver Module之外,你在sysfs下也看不到你的driver

目前系统支持哪些bus,可以ls /sys/bus查看。关于busdevicedriver的概念以及platform是如何基于busdevicedriver来实现的我们会在以后详细分析。这里大家首先只要知道,当Linux Kernel initializing的时候会调用platform_driver 下的probe function,即s3c2451_led_probe

s3c2451_led_probe实现的操作分析如下:

1.             如果led_major0,就是说我们指定静态的major device no。那么register_chrdev_region注册该dev no就可以了。

2.             如果led_major0alloc_chrdev_region动态分配一个dev no,并register之。这两个function interface我们在前面一章中已经介绍过了。

3.             cdev_init用来initializestruct cdev。这里我们指定了file operation,该fp实现了对device的所有operations。这些往往是device driver的核心所在。

4.             cdev_add() - add a char device to the systemCdev_add的作用就是:当我们openreadwritedevice file的时候,能够根据device no找到该device driver,也就可以access cdev_init中的file operation了。

 

几乎所有的character device driver这部分都是类似的,不同的是对file operation中个function的实现不同而已。对LED device driver来说,主要需实现openrelease,和ioctl三个function。如下:

static struct file_operations led_fops = {

    .owner =       THIS_MODULE,

    .open =        led_open,

    .release =      led_released,

    .ioctl =         led_ioctl,

};

通常openrelease可以进行一些资源的分配和释放,如memory;另外往往也可以处理access control,如是否可以同时被多个process进行open。本例中直接return 0Ioctl主要实现了如:turn on ledturn off ledtoggle led等。

下面我们会一一分析这些file operation中的functions。在分析这些之前,我们先要知道:当App调用了某个C Library Functionopen function,该function又会调用了Linux system callsys_opensys_open最后就会调用我们在Device Driver中实现的file operation中的open function。其它如closeioctlreadwritepollselectmmap也基本类似,只是close对应release。今后为了便于描述,我们会简单的说:某个App调用了sys_open,而sys_open最后调用了该file_operationopen。我们以后会进一步分析之,但是现在请大家先记住就好。(注:C libsystem call的来龙去脉我们已经在之前分析过了。)

Device file operation functions

Open release接口

Openrelease function proto type如下:

int (*open) (struct inode *, struct file *);

int (*release) (struct inode *, struct file *);

参数struct inodestruct file都是Linux kernel传进来的,struct inodefile index node,它是file system管理一个file所需要大的information,不同的process如果访问同一个文件,用的也是同一个inodeStruct fileprocess操作file的一个概念,一个process open多次同一个file(或调用),但在Linux Kernel中都是同一个struct file,但是对应多个file descriptor

release其实就是执行open相反的操作,比如:如果open分配了一些memory,那么release就要释放它。我们这里重点介绍openrelease只要反过来做就行的。一般而言,App调用系统调用sys_open打开该device时,最后会调用到我们driver中自己定义的open function。而当App调用系统调用sys_close关闭该device时,最后会调用到我们driver中自己定义的release function。(但有例外,这些例外往往就会引起一些比较难处理的bug,后面会分析之。)

之前的例子比较简单,open什么也没有做,直接return 0。其实际工作中确实有部分device driver中的open都是这样的。但是在很多时候open又是很重要的,首先要明确:

1.       openApp能够使用该device driver的第一道关隘,如果open return failure,那么App根本无法使用该device。这里我们就可以做很多文章了。

2.       Appopendevice的时候,一定是前面例子中init probe function已经被执行了。否者,我们在device driver中自己定义的open function根本就不会被执行。并且return error to App

open中,我们通常实现:

1.       hardware相关的操作,如初始化,power on等。因为我们一般不会在init中就power on了,这不利于节电。

2.       access control,比如多个process同时open如何处理?为了简单处理,通常我们会限制只有一个process可以open,除非有特别的requirement。这个我们可以通过semaphoreatomic operations(如:atomic_inc_and_testatomic_add等,详见include/asm-arm/atomic.h)等来实现。

3.       修改f_opfile operation。这样我们可以根据不同的情况,选择使用不同的file operations去操作device。这一点我们平时用的很少。一个典型的例子是Linux Kernel本身在实现Character Device Driver lookup的时候会改变f_op

下面我们讨论一下之前所提到的“例外”有哪些情况,又可能引起怎样的bug

1.       如果打开某个device file(比说fd=open(“/dev/watchdog”,…))的parent processfork()或者clone(~CLONE_FILES,…)创建了child process,此时虽然在child process中无论有没有再做:fd=open(“/dev/watchdog”,…),如果parent process close(fd)device,那么release function不一定会被调用。这是因为:

1)       创建child process时:sys_fork()àdo_fork()àcopy_process()àcopy_files()àdup_fd()àget_file中会增加f_countf_count代表了打开该device fileprocess的数目。此时f_count 2

2)       sys_close() à filp_close() àfput()中会检查f_count是否为1,为1才会调用release。由于f_count 2release自然不会被调用。  

2.       如果该parent process 调用C lib中的dup(fd),同样会f_count++,所以也有同样的结果。

讨论:

1.       module_init中定义的init fuctionopen主要各负何职呢?hotplugalbe accessories device

2.       上述例子中,如果child process又调用了sys_execxx,那么就不存在这个问题。

3.       我曾经碰到过一个bug 简述如下:

QPE main process 打开了(sys_openwatchdog,同时创建了很多child process(如voice call等等),然后由于App某种需要想关闭(sys_close)watchdog,却无法关闭。由于watchdog不能关闭,但是QPE又没有去feed dog,这样会导致设备重新启动。

ReadWrite接口

Openrelease function proto type如下:

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

__user只是标示该buffer位于User Space,也就是说它是App传给Linux Kernel的。通常Linux Kernel认为AppLinux Kernel的东西都是不可靠的,该address可能不是user spaceKernel space(如hacker program),该address也有可能不存在(NULL,或不在该process address spacevma中)。所以device driver需要检查。而如果我们用copy_to_user 和 copy_from_user进行User SpaceKernel Space之间的数据交互的话,我们就不用检查了,因为copy_to_user 和 copy_from_user已经帮忙做。

至于它们是如何实现的,由于不关乎对device driver designing的整体性的理解,且时间关系,不做讨论。

Producer and Consumer model

学过Operating System都应该知道Producer and Consumer model。简单来说,就是作为Producer的一方产生data,而Consumer将数据取走。以USB Device UART Device为例子我们这里要实现的readConsumer,而Hardware data FIFO fullinterrupt handler就是ProducerProducer产生data的速度和read读取的速度之间是不一致的,并且由于Operating Systemprocess schedule的不确定性read何时能开始读也是不确定的,这个问题的解决很简单:建立circular buffer。这个circular buffer data structure 通常有head pointertail pointer。我们这里不讨论circular buffer的算法,本身这个也很简单,Linux Kernel也有简单的实现,见:include\linux\Circ_buf.h

我们以read为例讨论一下由于这些不一致而要做的处理:

1.       如果该circular buffer中有data,则读取min(要读的长度,buffer中现有的数据)长度的数据。

2.       如果没有data,此时有两种处理:一是直接return -EAGAIN,二是进入sleep直到circular buffer中有新的数据产生。前者我们叫non blocking reading,后者叫blocking reading

下面一节就着重讨论情形2的处理细节以及其背后的深层次的缘由。

Non blocking and blocking read ------ For Consumer

Appopen一个device的时候的如果指定是non blocking reading即指定O_NONBLOCKor O_NDELAY,这个连个macroARM platform上是完全一样的) flag,那么device driver应该直接return –EAGAINEAGAIN的含义就是:App进行non blocking reading时无data可读。这是约定俗成的东西。

如果App没有指定O_NONBLOCK flag,那么device driver通常应该进入sleep,除非我们自己的requirement规定不用这样做。进入sleep的方法最基本的方法和步骤简单来说:

1.       建立一个wait queueLinux Kernel中的data structurewait_queue_head_t

2.       将当前process(也就是当前正在调用readprocess,注:当前processLinux kernel中已经定义为current macro,我们可以直接使用之)挂到该wait queueLinux Kerneldata structurewait_queue_t,其定义了该wait queueelements

3.       设定当前processprocess stateTASK_INTERRUPTIBLETASK_INTERRUPTIBLE意味着它可以被signal唤醒。通常我们不要设为TASK_UNINTERRUPTIBLE,否者一旦出了问题,该process就再也没有办法唤醒,而且用kill也没有办法杀死的。Kill的本质就是向某个process发一个SIGKILL signal而已。

4.       调用schedule进入sleep

5.       如果从schedule中返回,就是被wake up了,此时需要判定是否是被signal wakeup的,如果是需要return – ERESTARTSYSERESTARTSYS就是重新启动被signal wake upsystem call

讨论:

4.       ERESTARTSYS为什么能restart system call,这里简单说明一下,如果我们将来有机会进行深入Linux Kernel机制的交流,我们可以详细分析其来龙去脉,但是暂时不用去管,还是先有个整体的概念为好。为了便于有兴趣的朋友自己去分析,简单列出可以去查看的文件和function

Linux/arch/arm/kernel/entry-common.S中的ret_fast_syscall à do_notify_resume àdo_signalàhandle_signalàrestart_syscall。这些代码比较难以理解。

其实,上述我只是为了更好说明清楚进入sleep的最基本的行为,其实Linux Kernel已经提供了很多interfaces给我们使用,如:

wait_event(queue, condition)

wait_event_interruptible(queue, condition)

wait_event_timeout(queue, condition, timeout)

wait_event_interruptible_timeout(queue, condition, timeout)

这些全部定义在 include/linux/wait.h中。有时间我们可以分析一下这些源代码。关于这些的使用的细节和例子请参看LDD[1] Section6.2

从上面的分析,可以看出在read中进行blocking reading的处理还是比较麻烦的,实际中,我们往往并不这样做,而是采用下面将详述的更为typical的做法:select。即由单独的select来处理blocking,当有data可读时通过select 通知App,之后App再去read。此时保证有data可读。异常情况下就算没有data可读,直接return –EAGAIN也不会有任何问题。

 

讨论:

5.       reentrance问题。

Linux Kernel本身是可以reentrance的。否则就极其不合理了。

如果多个processP1P2)需要read,虽然我们不支持pre-emptive,但如果P1 readblock了,P2当然有机会去read。这样如果设计不当就会有很大问题。其实一种解决方法就是semaphore如:

if (down_interruptible(&sem))

             return -ERESTARTSYS;

但是最简单的方法莫过于read中即不支持blocking,该device driver也不支持多个process。在Embedded development中很多devices都可以这样做。

6.       这些设计跟Apprequirement或实现也是密切相关的。如果App的设计者无视Driver的设计,是很可能导致问题的,反之亦然。比如read不做任何block的处理,而交由select做,此时如果App没有使用select,直接如:

Do{

        Ret = read();

} while (ret <= 0)

那么CPU资源就会被这个program占完了。当然这里例子中如果read做了block的处理的话,就不会有任何问题。我们知道QPE对部分deviceaccess做了encapsulation,我们是需要清楚它们是怎么做的,否者就有可能有问题,而且这些问题不一定很容易发现。

 

Wake up ------ For Producer

但是无论在read中还是select中进入sleep,都需要在有新的data到来时,要wake up它。很多device,如USBUART等当haradware FIFO中有新的data时,都可以通过Interrupt来通知。此时我们device driver中都会在该interrupt handler中读出这些data,并保存到circular buffer中,并且wake up前面进入sleepprocess

Linux Kernel也同样提供了大量wake upinterface,如:

wake_up(wait_queue_head_t *queue);

wake_up_interruptible(wait_queue_head_t *queue);

Pollselect)接口

Pollfunction proto type为:

unsigned int (*poll) (struct file *, struct poll_table_struct *);

这两个参数都是Linux Kernel传进来的。App调用poll, select, or epoll,最后都会调用到该function pointer

我们将以sys_select为例来分析:(C Libselectsys_select几乎一致。)

asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,

                     fd_set __user *exp, struct timeval __user *tvp)

一个简单的例子

先看一个例子,看看一般如何实现之:

static unsigned int usb_char_poll(struct file *file, poll_table * wait)

{

      struct usb_char_dev*dev = file->private_data;

      struct circ_buf *rx_circ_buf=&(dev->rx_circ_buf);

      unsigned int mask = 0;

     

      poll_wait( file, &(dev->wq_poll), wait );

 

      if (dev->usb_device_exception!=USB_DEVICE_NO_EXCEPTION) {

             mask |= POLLPRI;

      }     

      if( usb_circ_count( rx_circ_buf ) )

             mask |= POLLIN | POLLRDNORM;//readable;   

      mask |= POLLOUT | POLLWRNORM;// can write at any time.   

      return mask;

}

poll_wait的本质

这里首先调用poll_wait( file, &(dev->wq_poll), wait ); 表面上来看是将dev->wq_poll加入wait。(LDD也是这样说的,其实不然,而恰恰相反)。我们不妨来分析一下poll_wait的源代码就知道一切了:

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

{

      if (p && wait_address)

             p->qproc(filp, wait_address, p);

}

App调用sys_select的时候:sys_selectàcore_sys_selectàdo_selectàpoll_initwaitàinit_poll_funcptrset p->qproc = __pollwait

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,

                           poll_table *p)

{

   struct poll_table_entry *entry = poll_get_entry(p);

   if (!entry)

          return;

   get_file(filp);

   entry->filp = filp;

   entry->wait_address = wait_address;

   init_waitqueue_entry(&entry->wait, current);

   add_wait_queue(wait_address, &entry->wait);

}

最后一行add_wait_queue(wait_address, &entry->wait);wait_address就是dev->wq_poll。所以它其实是把poll_table中对应poll_table_entry->wait_queue_t加到我们自己device driver中的wq_poll。这才是真相所在。

poll_get_entry也有一番故事,有兴趣的不妨读读,这样可以更好的理解前面一段文字,这里时间关系,不再赘述。

所以总结一下:poll_wait( file, &(dev->wq_poll), wait );就是将当前processcurrent)加到wq_poll中而已,注意此时没有进入sleep

sys_select的来龙去脉

那么它何时进入sleep呢?

sys_selectàcore_sys_selectàdo_select中遍历三个fd_setinp, outp, exp对应readablewritableexceptional中的所有file structure,且逐个调用f_op->poll,只要有一个返回值有一个在POLLIN_SET | POLLOUT_SET | POLLEX_SET中,就不会进入sleep。反之schedule_timeout进入sleep。这个恰恰就是selectC lib API的定义

#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)

#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)

#define POLLEX_SET (POLLPRI)

注:LDD中的对POLLPRI描述也会让人不好理解,这里或许有一些历史的原因,但从上面的分析它就是exception,我们不用管什么in band还是out of band了。由上也可以知道如果我们返回POLLERR,那么既是readable fd setwritable fd set都会得到返回的。

这样我想前面的例子之来龙去脉应该很好理解了。我们也可以做到知其然且知其所以然了。

Ioctl接口

Ioctlfunction proto type

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

这四个参数都是Linux kernel传进来的,前两个大家应该都很清楚,第三个参数就是我们ioctl command,第四个是该command的参数,第四个参数可以当作32 bit int,或是当作一个pointer address,如果当作pointer addressdevice driver都不要直接使用,而是通过copy_to_user 和 copy_from_user后再使用。

通常command都是用#define来定义,我们不要直接定义,象:#define LED_TURNON 1000。一律使用include/asm-arm/ioctl.h中的MACRO来定义之。且不要使用“T”为magic number,否则就极可能和Linux Kernel predefined command冲突。一旦冲突我们定义的command就没哟办法得到执行了。

llseek接口

通常我们不实现该接口,但是一旦llseek没有定义,如果App调用了C Lib中的lseek,那么Linux kernel就会修改file structurefilp->f_pos,这样我们的readwrite就不能依赖filp->f_pos来进行读写了。

有兴趣的话可以看看LDDSection 6.5,其实很简单的。

Mmap接口

Mmapfunction proto type是:

int (*mmap) (struct file *, struct vm_area_struct *);

mmap就是将一段physical memory mapProcess User Space中,这个physical memory可以是common memory(如SDRAM,并且已经被Linux Kernel Buddy system管理起来的memory)或是device memory(如LCDgraphic memory)。从而App可以direct access这些memory。通常Devicememoryaccess有两种方式:一是IO方式,二是memory方式,即可以象访问SDRAMSRAM一样直接access device memory。前一种即不能直接象访问SDRAMSRAM一样直接access device memory,而是通过某几个registers来进行access。对于后者memory方式我们直接将这部分memory mapUser Space。而对前者,我们往往是先分配一段physical memory,然后再这段physical memory mapUser Space。而Driver实现将App写进这段physical memory中的data通过IO接口同步到Device memory

vm_area_struct

简单介绍一下,我们通常所说的一个process4GB Virtual Memory并不是全部使用,所有被使用到的virtual memory被分成了一段段的连续area,这些area的组织信息存储在vm_area_struct中。所有被使用到的virtual memory就由一个vm_area_struct list来统一管理。

Linux Kernel提供的几种memory mappingUser Space的方法以及其差别

      remap_pfn_range

prototype为:

/**

 * remap_pfn_range - remap kernel memory to userspace

 * @vma: user vma to map to

 * @addr: target user address to start at

 * @pfn: physical address of kernel memory

 * @size: size of map area

 * @prot: page protection flags for this mapping

 

 *  Note: this is only safe if the mm semaphore is held when called.

 */

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,

                 unsigned long pfn, unsigned long size, pgprot_t prot)

vma

使用这个function的时候第一个参数直接使用int (*mmap) (struct file *, struct vm_area_struct *);中的struct vm_area_struct *,这是Linux Kernel传进来的(由sys_mmap2àdo_mmap2àdo_mmap_pgoffàmmap_region创建)。

addr

set as vma->vm_start

pfn

我们要mapUser Space那段physical memoryphysical address

size

set size as vma->vm_end - vma->vm_start

prot

通常 set prot as vma->vm_page_prot

 

简单分析一个例子:其中__pa(logic_addr)就是我们要mapUser Space那段physical memoryphysical address

static int fb_mmap(struct file *file, struct vm_area_struct *vma)

{

    size_t size = vma->vm_end - vma->vm_start;

    /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */

    if (remap_pfn_range(vma, vma->vm_start, __pa(logic_addr) >> PAGE_SHIFT,

                size, vma->vm_page_prot)) {

        printk("LCD : remap_pfn_range failed!\n");

        return -EAGAIN;

    }

    return 0;

}

 

但是我们需要注意的是:remap_pfn_range建立的MMU map entry中的cacheon的。也就是说AppUser Spaceaccess这段memory时,cache是起作用的。

讨论:

7.       如果我们用DMA将该memory copyDevice memory中会有怎样的问题?如果我们发现LCD screen的显示偶尔会有有一些黑线或花屏,可能原因就在于此了。

8.       如果两个不同的process都进行mmapreadwrite,结果会如何呢?这里要提一下ARM Cache的机制,我们ARM9中的cache的对象是经过MMUVirtual Memory。有些是Physical Address。在ARM MMUpage table entry中会有一个bit表明Cache on or off(独立控制到每个page)。请参考AAM Part B Section 5.4

9.       使用remap_pfn_range同样可以关闭cache,只要将上面的例子中最后一个参数由vma->vm_page_prot改为pgprot_noncached(vma->vm_page_prot)即可以了。

 

LDD一书提到了io_XXXX应该使用在I/O memory,表明了和XXXX的差别,其实LDD没有讲到问题的本质,应该是由于cache的存在,写入的数据可能只是在cache中,而并没有真正写入到IO memory里面。所以io_XXXXXXXX相比,io_XXXX会关闭cache而已。

 

      dma_mmap_coherent

 

prototype为:

int dma_mmap_coherent(struct device *dev, struct vm_area_struct *vma,

                   void *cpu_addr, dma_addr_t handle, size_t size);

remap_pfn_range类似,实际上dma_mmap_coherent 最后也由remap_pfn_range来实现的。但是它和remap_pfn_range比其中一大差别在于:

1.       它关闭了cache

2.       它只能mapdma_alloc_coherent分配的memory。而remap_pfn_range要灵活的多。

int dma_mmap_coherent(struct device *dev, struct vm_area_struct *vma,

                   void *cpu_addr, dma_addr_t dma_addr, size_t size)

{

         vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

         return dma_mmap(dev, vma, cpu_addr, dma_addr, size);

}

dev

Linux 2.6.23中没有任何意义。可能将来会扩展,set as NULL

Vma

remap_pfn_range

cpu_addr

dma_alloc_coherent返回的address

dma_addr

Linux 2.6.23中没有任何意义。可能将来会扩展。考虑将来的扩展性,可以设置为dma_alloc_coherent返回的handle pointer

size

vma->vm_end - vma->vm_start

 

讨论:

10.   思考一下:我们确实需要用DMA来进行data synchronizing to Device memory中,但其内容发了很多read or write以后才需要做一次DMA,那么如果我们关闭了cache,岂不是降低了效率。LCD device Graphic memoryrefresh通常是50ms一次。此时我们可以用flush_cache_all。(flush_cache_all的来龙去脉以后分析)

sys_mmap2的来龙去脉

calls.S文件我们可以知道C Lib中的mmap对应的system callsys_mmap2。而sys_mmap2ARM platform实现在arch/arm/kernel/entry-common.S中:

sys_mmap2:

#if PAGE_SHIFT > 12

             tst   r5, #PGOFF_MASK

             moveq     r5, r5, lsr #PAGE_SHIFT - 12

             streq       r5, [sp, #4]

             beq  do_mmap2

             mov r0, #-EINVAL

             mov pc, lr

#else

             str   r5, [sp, #4]

             b     do_mmap2

#endif

 

sys_mmap2àdo_mmap2àdo_mmap_pgoffàmmap_regionàfile->f_op->mmap

 

Character Device Driver的完整的概念以及来龙去脉

到目前为止,我们应该已经很清楚如何做一个Character Device Driver了,且从之前的分析我们知道App只要知道一个Device Driver所对应的Device no就可以操作整个device了,但是为什么可以?Linux Kernel又是如何从一个device no找到其对应的device driver的呢?

下面我们以open为例,分析从App调用C library 中的opendevice driverfile operationopen function,从而真正分析清楚Character Device Driver的完整的概念以及来龙去脉

App首先会:open(“/dev/rtc”, … )打开rtc device。我们直接从sys_open开始:

sys_openàdo_sys_openàdo_filp_open àopen_namei àpath_lookup_open à__path_lookup_intent_open àdo_path_lookup àpath_walk àlink_path_walk à__link_path_walk àdo_lookup àreal_lookupàresult = dir->i_op->lookup(dir, dentry, nd);

Embedded Device经常使用Yaffs2 file system,就是说/dev/rtc是存储在Flash Yaffs2上的。此处的loopup function pointer却是在Yaffs2 file system初始化的时候就已经设置了(注:file system的初始化全过程分析,如果以后有机会可以进一步交流,VFSYaffs2还是很值得我们去研究,但是我们暂时放下,因为我们还是先着力于对Device Driver开发的整体理解上面,有了这些基础后我们回头再来研究也会轻松很多)

Yaffs2 file system初始化的时候首先就是读取super block,并进行相应的处理,详见:yaffs_internal_read_super。与我们今天交流的主题比较相关的部分如下:

      /* Create root inode */

      if (err == YAFFS_OK)

             inode = yaffs_get_inode(sb, S_IFDIR | 0755, 0,

                                  yaffs_Root(dev));

      if (!inode)

             return NULL;

      inode->i_op = &yaffs_dir_inode_operations;

      inode->i_fop = &yaffs_dir_operations;

它其实就是创建整个Yaffs2 file systemroot inode,并设定 inode->i_op inode->i_fop

 

当然这里需要明确的是,sys_open是从该当前进程(current)的root directory作为开始lookup的出发点的:sys_openàdo_sys_openàdo_filp_open àopen_namei àpath_lookup_open à__path_lookup_intent_open àdo_path_lookup

。。。。。。

             nd->mnt = mntget(fs->rootmnt);

             nd->dentry = dget(fs->root);

。。。。。。

 

yaffs_dir_inode_operations定义如下:

static struct inode_operations yaffs_dir_inode_operations = {

      .create = yaffs_create,

      .lookup = yaffs_lookup,

      .link = yaffs_link,

      .unlink = yaffs_unlink,

      .symlink = yaffs_symlink,

      .mkdir = yaffs_mkdir,

      .rmdir = yaffs_unlink,

      .mknod = yaffs_mknod,

      .rename = yaffs_rename,

      .setattr = yaffs_setattr,

};

 

所以下面我们就要从yaffs_lookup开始分析了,之前都是属于VFS部分,现在到了Yaffs2 file system了:

yaffs_lookupàyaffs_get_inodeàigetàsb->s_op->read_inode(inode);(即:yaffs_read_inode) àyaffs_FillInodeFromObject

到此我们知道当前从Flash Yaffs2中要读出的file不是一个普通的文件,而是device file,这样下面就到了:init_special_inode,我们可以看看其source code

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)

{

      inode->i_mode = mode;

      if (S_ISCHR(mode)) {

             inode->i_fop = &def_chr_fops;

             inode->i_rdev = rdev;

      } else if (S_ISBLK(mode)) {

             inode->i_fop = &def_blk_fops;

             inode->i_rdev = rdev;

      } else if (S_ISFIFO(mode))

             inode->i_fop = &def_fifo_fops;

      else if (S_ISSOCK(mode))

             inode->i_fop = &bad_sock_fops;

      else

             printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",

                    mode);

}

此时设定了inode->i_fop = &def_chr_fops; 它仅仅定义了open function

const struct file_operations def_chr_fops = {

      .open = chrdev_open,

};

 

现在我们再回到sys_openàdo_sys_openàdo_filp_open如果open_namei成功返回,那么就开始:

nameidata_to_filp à__dentry_open: 中我们关心的部分是:

f->f_mapping = inode->i_mapping;

f->f_path.dentry = dentry;

f->f_path.mnt = mnt;

f->f_pos = 0;

f->f_op = fops_get(inode->i_fop);

file_move(f, &inode->i_sb->s_files);    

if (!open && f->f_op)

             open = f->f_op->open;

      if (open) {

             error = open(inode, f);

             if (error)

                    goto cleanup_all;

      }

f->f_op->open其实就是chrdev_open,这个function就真正开始了Character Device Driver的历程了。我们经历长途跋涉,到这里才发现才刚刚开始。而chrdev_open

1.       obj = kobj_lookup(cdev_map, inode->i_rdev, &idx); 根据device nocdev_map中找到我们实现device driver,从而找到我们前面花了大量的时间进行分析的file operation了。cdev_mapLinux Kernel character device driver中的重要的global data structure,此种所存储的信息都是我们在4.1中分析到的cdev_add()添加的。现在应该明白cdev_add()真正的作用了。

2.       filp->f_op = fops_get(p->ops); 将找到的file operation 写到如filp->f_op。此处filp对应正是我们前面所讨论的如openreadwrite等中struct file *参数了。

3.       ret = filp->f_op->open(inode,filp);,此时的open就是我们自己的device driver中的open了。

注:还记得我们在1.2.1中提过我们可能修改f_opfile operation,这里可以看作是一个例子了。当然也和chrdev_open一样调用一下新的file operationopen

 

至此以后,sys_read等最后调用到的file operation都是我们在device driver中定义的。这部分code应该不难理解,我们也没有时间去仔细的分析,但是我已经把主要的线索都整理出来了,有兴趣的朋友可以自行分析之。但是始终我认为先不要太多关注其中的细节,先尽量做到理清楚这个整体的过程,这才是目前最重要的东西。有了整体的认识,细节也就简单了。

 

讨论:

11.   结合之前讨论的system call的来龙去脉,以及本section所述,我们是否可以从最上层的C Lib API到最底层的Hardware整个逻辑过程分析清楚了?整个过程简单做个总结,前后我们经历了:AP调用C library "open" functionàswi启动Linux system callà Linux VFS àYaffs2 File SystemàLinux Character Device Driverà具体的Device Driverà对应的Hardware

12.   到现在为止,我们Linux VFS àYaffs2 File System的分析仅仅停留在跟我Character device driver的一小部分而已,但是有了这些已经不影响我们Linux Character Device Driver的整体性的理解。而Linux File system architecture(又包括VFS和众多的physical FSYaffs2EXTn)也是非常值得研究讨论的,以后有机会我们可以再一起分析。

Linux KernelStandard RTC驱动程序的分析

下面我们以RTC为例子,来看一个完整的CDDLinux RTC Device Driver相关的代码在drivers\rtc下。其结构大致如下图所示:

 

      User ApplicationUser application可以通过device fileprocsysfsaccess RTC CDD

      RTC Character Device LayerLinux Kernel自身提供了,是platform independent

      RTC Core Interface layerLinux Kernel自身提供了,是platform independent

      platform相关的的code,这部分才是我们自己依据不同的platformSocHardware)来实现的。

 

首先Linux Kernel初始化的时候会执行RTC Character Device Layer中的rtc_dev_init,其目的就是动态分配device no

void __init rtc_dev_init(void)

{

      int err;

 

      err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");

      if (err < 0)

             printk(KERN_ERR "%s: failed to allocate char dev region\n",

                    __FILE__);

      else

             printk("major = %d, minor = %d\n", MAJOR(rtc_devt ), MINOR(rtc_devt ));

}

 

之后就调用我们自己设计的RTC  Hardware related Driver中的xxx_rtc_probe,其中我们使用RTC Core Inferface Layer中的rtc_device_register来注册一个rtc device,它实际就是:

1.       首先分配一个rtc_deviceinstance。其pointerrtc

2.       rtc->ops = ops;设定本RTC driver的跟我们使用platformhardware相关的rtc_class_ops。在具体项目中对应的是:xxx_rtcops

static const struct rtc_class_ops xxx _rtcops = {

      .open             = xxx _rtc_open,

      .release   = xxx _rtc_release,

      .ioctl              = xxx _rtc_ioctl,

      .read_time      = xxx _rtc_gettime,

      .set_time = xxx _rtc_settime,

      .read_alarm    = xxx _rtc_getalarm,

      .set_alarm      = xxx _rtc_setalarm,

      .proc               = xxx _rtc_proc,

};

3.       rtc_device_registeràrtc_dev_prepareàcdev_init初始化RTC character device,并设定file operationrtc_dev_fopsdevice no为之前分配的device no即:rtc_devtrtc_dev_fops定义如下:

static const struct file_operations rtc_dev_fops = {

       .owner            = THIS_MODULE,

       .llseek            = no_llseek,

       .read              = rtc_dev_read,

       .poll        = rtc_dev_poll,

       .ioctl              = rtc_dev_ioctl,

       .open             = rtc_dev_open,

       .release   = rtc_dev_release,

       .fasync           = rtc_dev_fasync,

};

4.       rtc_device_register àrtc_dev_add_deviceàcdev_add真正将RTC character driver加入Linux system

根据之前的分析,我们知道当App调用ioctl的时候,最后实际会调用rtc_dev_ioctl而其又调用RTC Core Interface layer中的相关interface来完成RTC operation,比如我们要get rtc timeioctl command为:RTC_RD_TIME),它就调用rtc_read_time就会调用xxx _rtcops中的xxx _rtc_gettimexxx _rtc_gettime中我们要实现各自project中相关硬件操作。

那么App如何知道RTC alarm来了呢?首先App会调用select,实际上最后调用rtc_dev_poll它会sleep在一个wait queue,当有RTC alarm来时会trigger RTC Alarm interrupt,该interrupt handler中会通过rtc_update_irq() wake up它。

static irqreturn_t xxx_rtc_alarmirq(int irq, void *id)

{

      struct rtc_device *rdev = id;

 

      //clear the alarm event. 

      xxx_rtc_clear_status(XXX_RTC_ALARM_EVENT );

 

      //wake up the rtc->irq_queue

      rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);

      return IRQ_HANDLED;

}

讨论:

13.   RTC中的这种结构是Linux Kernel中广泛使用的一种结构,Linux Kernel自己帮你实现了主要的部分,不用的用户只要在其结构下实现Architecture或说platform下的hardware相关的操作。(对与ARM linux而言其实就是不同SOCdevelopment board上具体的硬件实现。)。类似的还有TTYUSBframebuffer等。通常我们要求尽量都要基于Linux Kernel的现有结构来实现。但是前提是要求我们的engineer要很清楚这些结构,否则还不如抛开这些,从零来实现,类似4.1中的LED例子。

14.   RTC alarmRTC_ALM_SET设定alarm只能在24hour之内,超过24hours,请用RTC_WKALM_SET

15.   设定RTC time的时候要不要同时设定linux system time呢?如果RTC driver中没有去同时设定linux system time,那App程序就要设定了。

16.   RTC_ALM_SET的时候,如果我们要支持关机alarm,那么我们就需要enable_irq_wake。关于此我会在在以后详细分析。

到目前为止,我想对于RTC Device Driver大体上都已经将其来龙去脉分析清楚(其它的CDD也同理),但是还有两点我们还没有分析:

1.       RTC Hardware related Driverxxx_rtc_initplatform bus是什么?前面分析的xxx_rtc_probe又是何时被Linux Kernel调用的?这些我们将在6中在分析。

static int __init xxx_rtc_init(void)

{

       printk(banner);

       return platform_driver_register(&xxx_rtcdrv);

}

2.       RTC alarm interrupt。中断子系统的来龙去脉我们将在Linux kernel中断处理的来龙去脉浅析中详述。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值