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

- URL: https://wuming.si/blog/1082/
- Author: Aaron
- Published: 2015-11-17
- Language: zh-Hans
- Categories: Raspberry Pi, Technology
- Originally published at: https://blog.wo.ai/archives/1082

之前写了一篇移植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体系:[![SouthEast](https://web.archive.org/web/2//images/2015/11/SouthEast2-300x210.)](https://wuming.si/images/2015/11/SouthEast2)

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

**一 首先上个驱动程序**

****

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

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

led.c:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |  `/********************************************************************/` `/***************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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;` `#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>` `#def``ine DEVICE_NAME ``"Pi_Led"` `#def``ine 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;` `#def``ine 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这个函数.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |  `//内核加载后的初始化函数.` `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这个结构体,来声明:

1 2 3 4 5 6 7 8 |  `//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操作.

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

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

1 2 3 4 5 6 7 8 9 10 11 12 13 |  `#def``ine GPIOFSEL(x) (``0``x``00``+(x)*``4``)` `#def``ine GPIOSET(x) (``0``x``1``c+(x)*``4``)` `#def``ine GPIOCLR(x) (``0``x``28``+(x)*``4``)` `#def``ine GPIOLEV(x) (``0``x``34``+(x)*``4``)` `#def``ine GPIOEDS(x) (``0``x``40``+(x)*``4``)` `#def``ine GPIOREN(x) (``0``x``4``c+(x)*``4``)` `#def``ine GPIOFEN(x) (``0``x``58``+(x)*``4``)` `#def``ine GPIOHEN(x) (``0``x``64``+(x)*``4``)` `#def``ine GPIOLEN(x) (``0``x``70``+(x)*``4``)` `#def``ine GPIOAREN(x) (``0``x``7``c+(x)*``4``)` `#def``ine GPIOAFEN(x) (``0``x``88``+(x)*``4``)` `#def``ine GPIOUD(x) (``0``x``94``+(x)*``4``)` `#def``ine GPIOUDCLK(x) (``0``x``98``+(x)*``4``)`  
---|---  
  
这里定义的就是相应的GPIO寄存器的地址.

**四 测试程序**

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |  `#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``;` `}`  
---|---  
  
[![Center](https://web.archive.org/web/2//images/2015/11/Center1-300x169.)](https://wuming.si/images/2015/11/Center1)
