基于树莓派Raspberry: 字符设备内核驱动程序框架编写

        之前写了一篇移植2.4寸TFT驱动到树莓派的文章,那篇博文中的驱动代码是国外大牛写的,看了一下,还是有很多地方没理解,是得好好再学习一下内核驱动的编写,这里就从字符设备驱动开始,采用最简单的LED驱动来建立内核驱动移植的驱动框架.

       个人原创,转载请注明原文出处:      

                http://blog.csdn.net/embbnux/article/details/17712547

      参考文章:

                http://blog.csdn.net/hcx25909/article/details/16860725

      内核驱动与普通单片机模块驱动的差别就是在于,写内核驱动的时候,要提供内核调用的接口,使内核能找到相应的驱动入口,用户通过告诉内核要干嘛,内核再去调用那个驱动,驱动的最底层和单片机模块是一样的,同样是对GPIO的操作,配置输入输出,以及某些特殊的寄存器. LED的点亮就是对GPIO的操作 .

       对于ARM的GPIO调用需要通过IO映射的方法,要操作内存上对应的地址.

       对于bcm2708上的io对应关系,可以查看bcm2835的手册,和stm32基本上是一样的,因为同为ARM体系:

              

     我参考的那博客讲这个比较清楚,可以参考下,由于树莓派的内核以及很好的提供了GPIO调用的接口,即把内存操作封装了很好,这里就不像那篇博客那样再自己写函数通过内存操作来进行GPIO操作,觉得有点麻烦,但是对于学好底层很有用.

  一  首先上个驱动程序


        这里直接把该程序添加到内核的源码目录里面,也可在自己的目录下,但是要写Makefile.

        在/drivers/char/新建rasp_led.c,内核中的kconfig文件和makefile文件,参照前一篇文章

 led.c:

/********************************************************************/
/***************Rasp led 驱动程序************************************/
/***************作者: Embbnux Ji*************************************/
/***************博客: http://blog.csdn.net/embbnux/ *****************/
/********************************************************************/

#include <linux/kernel.h>  
#include <linux/module.h>
#include <linux/device.h> 
#include <mach/platform.h>       
#include <linux/platform_device.h>
#include <linux/types.h>  
#include <linux/fs.h>   
#include <linux/ioctl.h>  
#include <linux/cdev.h>  
#include <linux/delay.h>  
#include <linux/uaccess.h>
#include <linux/init.h> 
#include <linux/gpio.h>

#define DEVICE_NAME "Pi_Led"
#define DRIVER_NAME "pi_led"

//class声明内核模块驱动信息,是UDEV能够自动生成/dev下相应文件
static dev_t pi_led_devno; //设备号
static struct class *pi_led_class;
static struct cdev pi_led_class_dev;

struct gpio_chip *gpiochip;

#define led_pin 4  //gpio 4

//这部分函数为内核调用后open的设备IO操作,和裸板程序一样
int open_flag=0;
static int pi_led_open(struct inode *inode, struct file *filp)  
{   
    printk("Open led ing!\n");  
    if(open_flag ==0){
       open_flag =1;
       printk("Open led success!\n");
       return 0;
    }
    else{
       printk("Led has opened!\n");     
    }
    return 0;  
} 
//这部分函数为内核调用后ioctl的设备IO操作,和裸板程序一样
static long pi_led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 
{  
    switch(cmd){
      case 0: 
          gpiochip->set(gpiochip, led_pin, 0);
            printk("Led  up!\n");
           break;
      case 1:
            gpiochip->set(gpiochip, led_pin, 1);
            printk("Led  down!\n");
           break;
    }
   
   
    return 0;
} 

static int pi_led_release(struct inode *inode,struct file *file){
     printk("Led has release!\n");
     return 0;
}

//file_operations使系统的open,ioctl等函数指针指向我们所写的led_open等函数,
//这样系统才能够调用
static struct file_operations pi_led_dev_fops = {  
   .owner          =THIS_MODULE,  
   .open	   =pi_led_open, 
   .unlocked_ioctl = pi_led_ioctl, 
   .release       = pi_led_release,
}; 

static int is_right_chip(struct gpio_chip *chip, void *data)
{

	if (strcmp(data, chip->label) == 0)
		return 1;
	return 0;
}

//内核加载后的初始化函数.
static int __init pi_led_init(void)
{
   struct device *dev;
   int major; //自动分配主设备号
   major = alloc_chrdev_region(&pi_led_devno,0,1,DRIVER_NAME);
   //register_chrdev 注册字符设备使系统知道有LED这个模块在.
   
   cdev_init(&pi_led_class_dev, &pi_led_dev_fops);
   major = cdev_add(&pi_led_class_dev,pi_led_devno,1);
   //注册class
   pi_led_class = class_create(THIS_MODULE,DRIVER_NAME);
   
   dev = device_create(pi_led_class ,NULL,pi_led_devno,NULL,DRIVER_NAME);
   
   gpiochip = gpiochip_find("bcm2708_gpio", is_right_chip);
   gpiochip->direction_output(gpiochip, led_pin, 1);
   gpiochip->set(gpiochip, led_pin, 0);
   printk("pi led init ok!\n");
   return 0;
}
//内核卸载后的销毁函数.
void pi_led_exit(void)
{
   gpio_free(led_pin);
   device_destroy(pi_led_class,pi_led_devno);
   class_destroy(pi_led_class);
   cdev_del(&pi_led_class_dev);
   unregister_chrdev_region(pi_led_devno, 1);
   printk("pi led exit ok!\n");
   
}

module_init(pi_led_init);
module_exit(pi_led_exit);

MODULE_DESCRIPTION("Rasp Led Driver");
MODULE_AUTHOR("Embbnux Ji < http://blog.csdn.net/embbnux >");
MODULE_LICENSE("GPL");


二  源码框架分析


    我们首先从内核模块的入口,module_init(pi_led_init)这个函数看起,可以看出初始化后调用pi_led_init这个函数.

   

//内核加载后的初始化函数.
static int __init pi_led_init(void)
{
   struct device *dev;
   int major; //自动分配主设备号
   major = alloc_chrdev_region(&pi_led_devno,0,1,DRIVER_NAME);
   //register_chrdev 注册字符设备使系统知道有LED这个模块在.
   
   cdev_init(&pi_led_class_dev, &pi_led_dev_fops);
   major = cdev_add(&pi_led_class_dev,pi_led_devno,1);
   //注册class
   pi_led_class = class_create(THIS_MODULE,DRIVER_NAME);
   
   dev = device_create(pi_led_class ,NULL,pi_led_devno,NULL,DRIVER_NAME);
   
   gpiochip = gpiochip_find("bcm2708_gpio", is_right_chip);
   gpiochip->direction_output(gpiochip, led_pin, 1);
   gpiochip->set(gpiochip, led_pin, 0);
   printk("pi led init ok!\n");
   return 0;
}
   初始化时首先分配给这个函数设备号,注册该设备,通过class注册使能够在/dev/目录下自动生成相应的设备文件,用户通过操作这个文件,来告诉内核怎么做.

   由于是字符设备,所以对该文件的操作通过open,write,ioctl等函数,所以要把这个函数和底层的操作函数对应起来,这就要用到file_operation这个结构体,来声明:

  

//file_operations使系统的open,ioctl等函数指针指向我们所写的led_open等函数,
//这样系统才能够调用
static struct file_operations pi_led_dev_fops = {  
   .owner          =THIS_MODULE,  
   .open	   =pi_led_open, 
   .unlocked_ioctl = pi_led_ioctl, 
   .release       = pi_led_release,
};

    这里就让open函数对应到pi_led_open函数,ioctl函数对应到pi_led_ioctl函数;

    然后我们就只需要编写相应的pi_led_open以及pi_led_ioctl;这些函数里面的操作就是最底层的GPIO操作,和单片机是一样的.


三  GPIO操作

     内核里面的GPIO操作函数,被定义在#include <linux/gpio.h>,这个头文件里面,树莓派官方做好了树莓派的GPIO在内核里面的注册,所以调用gpio.h里面的函数即可进行树莓派的GPIO操作.

    

gpiochip = gpiochip_find("bcm2708_gpio", is_right_chip);
    通过上面这个函数把内核的GPIO操作和BCM2708的GPIO操作关联起来;

     bcm2708的操作可以查看/arch/arm/mach-bcm2708/bcm2708_gpio.c文件,具体也是对内存地址的操作:

 

#define GPIOFSEL(x)  (0x00+(x)*4)
#define GPIOSET(x)   (0x1c+(x)*4)
#define GPIOCLR(x)   (0x28+(x)*4)
#define GPIOLEV(x)   (0x34+(x)*4)
#define GPIOEDS(x)   (0x40+(x)*4)
#define GPIOREN(x)   (0x4c+(x)*4)
#define GPIOFEN(x)   (0x58+(x)*4)
#define GPIOHEN(x)   (0x64+(x)*4)
#define GPIOLEN(x)   (0x70+(x)*4)
#define GPIOAREN(x)  (0x7c+(x)*4)
#define GPIOAFEN(x)  (0x88+(x)*4)
#define GPIOUD(x)    (0x94+(x)*4)
#define GPIOUDCLK(x) (0x98+(x)*4)

    这里定义的就是相应的GPIO寄存器的地址.

四  测试程序

    ssh进入树莓派,在主目录下新建led.c

    

 #include<stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>
 int main(int argc, char **argv)
 {
    int on;
    int led_no;
    int fd;
    int i;

    fd = open("/dev/pi_led", 0);
    if (fd < 0) {
       fd = open("/dev/pi_led", 0);
    }
    if (fd < 0) {
      perror("open device led");
      exit(1);
    }
    for(i=0;i<=20;i++){
      on = i%2;
      ioctl(fd, on, led_no);
      sleep(1);
    }
    close(fd);
    return 0;
 }





郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。