从上次hello world程序中,我们已经搭建好了驱动学习相关的环境搭建,为接下来的设备驱动做好了准备。同时通过最简单的hello world程序,学习了模块的初始化和退出,知道了如何编写***_init和***_exit函数,知道了如何通过内核打印函数printk输出相关信息。
Linux中的设备驱动分三大类:字符设备、块设备、网络设备。本篇文章讨论字符型设备程序如何编写,通过简单的LED驱动程序介绍相关知识。下篇文章介绍杂项设备(misc)驱动程序的编写,这在实际项目中很常用,相当于字符设备的简化版。
首先附上完整程序:
/********************************************************************************************* * File: led_driver.c * Author: Fawen Xu * Desc: led driver code * History: May 9th 2015 *********************************************************************************************/ #include#include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("Fawen Xu"); MODULE_LICENSE("Dual BSD/GPL"); static unsigned int led_major=0; static unsigned int bcm2835_gpio_baseaddr; module_param(led_major,int,0); #define LED_MAGIC 'k' #define LED_ON_CMD _IO(LED_MAGIC,1) #define LED_OFF_CMD _IO(LED_MAGIC,2) //#define BCM2835_PERI_BASE 0x20000000 //#define BCM2835_PERI_BASE 0x7e000000 #define BCM2835_GPIO_BASE 0x20200000 #define BCM2835_GPIOReg_GPFSEL0 0x00000000 #define BCM2835_GPIOReg_GPSET0 0x0000001c #define BCM2835_GPIOReg_GPCLR0 0x00000028 #define BCM2835_GPIO_FSEL_INP 0x00000000 #define BCM2835_GPIO_FSEL_OUTP 0x00000001 #define RPI_BPLUS_GPIO_J8_12 18 int bcm2835_gpio_fsel(int pin, int mode) { volatile int shift,value; volatile int *bcm2835_gpio_fsel_addrp = bcm2835_gpio_baseaddr + (pin/10)*4; //printk("In bcm2835_gpio_fsel function:\n"); //printk(" pin = %d, (pin/10)*4= %d",pin,(pin/10)*4); //printk(" bcm2835_gpio_baseaddr = 0x%x\n", bcm2835_gpio_baseaddr); //printk(" bcm2835_gpio_fsel_addrp = 0x%p\n", bcm2835_gpio_fsel_addrp); shift = (pin % 10) * 3; value = mode << shift; //printk(" shift=%d,value=0x%x\n",shift,value); //printk(" bcm2835_gpio_fsel addr 0x%p: 0x%x\n", bcm2835_gpio_fsel_addrp, *bcm2835_gpio_fsel_addrp); (*bcm2835_gpio_fsel_addrp) = (*bcm2835_gpio_fsel_addrp) | value; //printk(" bcm2835_gpio_fsel addr 0x%p: 0x%x\n", bcm2835_gpio_fsel_addrp, *bcm2835_gpio_fsel_addrp); return 0; } int bcm2835_gpio_set(int pin) { volatile int shift,value; volatile int *bcm2835_gpio_set_addrp = bcm2835_gpio_baseaddr + BCM2835_GPIOReg_GPSET0 + (pin/32)*4; //printk("In bcm2835_gpio_set function:\n"); //printk(" bcm2835_gpio_baseaddr = 0x%x\n", bcm2835_gpio_baseaddr); //printk(" bcm2835_gpio_set_addrp = 0x%p\n", bcm2835_gpio_set_addrp); shift = pin % 32; value = 1 << shift; //printk(" shift=%d,value=0x%x\n",shift,value); //printk(" bcm2835_gpio_set addr 0x%p: 0x%x\n", bcm2835_gpio_set_addrp, *bcm2835_gpio_set_addrp); (*bcm2835_gpio_set_addrp) = (*bcm2835_gpio_set_addrp) | value; //printk(" bcm2835_gpio_set addr 0x%p: 0x%x\n", bcm2835_gpio_set_addrp, *bcm2835_gpio_set_addrp); return 0; } int bcm2835_gpio_set_release(int pin) { volatile int shift,value; volatile int *bcm2835_gpio_set_release_addrp = bcm2835_gpio_baseaddr + BCM2835_GPIOReg_GPSET0 + (pin/32)*4; //printk("In bcm2835_gpio_set_release function:\n"); //printk(" bcm2835_gpio_baseaddr = 0x%x\n", bcm2835_gpio_baseaddr); //printk(" bcm2835_gpio_set_release_addrp = 0x%p\n", bcm2835_gpio_set_release_addrp); shift = pin % 32; value =~(1 << shift); //printk(" shift=%d,value=0x%x\n",shift,value); //printk(" bcm2835_gpio_set_release addr 0x%p: 0x%x\n", bcm2835_gpio_set_release_addrp, *bcm2835_gpio_set_release_addrp); (*bcm2835_gpio_set_release_addrp) = (*bcm2835_gpio_set_release_addrp) & value; //printk(" bcm2835_gpio_set_release addr 0x%p: 0x%x\n", bcm2835_gpio_set_release_addrp, *bcm2835_gpio_set_release_addrp); return 0; } int bcm2835_gpio_clr(int pin) { volatile int shift,value; volatile int *bcm2835_gpio_clr_addrp = bcm2835_gpio_baseaddr + BCM2835_GPIOReg_GPCLR0 + (pin/32)*4; //printk("In bcm2835_gpio_clr function:\n"); //printk(" bcm2835_gpio_baseaddr = 0x%x\n", bcm2835_gpio_baseaddr); //printk(" bcm2835_gpio_clr_addrp = 0x%p\n", bcm2835_gpio_clr_addrp); shift = pin % 32; value = 1 << shift; //printk(" shift=%d,value=0x%x\n",shift,value); //printk(" bcm2835_gpio_clr addr 0x%p: 0x%x\n", bcm2835_gpio_clr_addrp, *bcm2835_gpio_clr_addrp); (*bcm2835_gpio_clr_addrp) = (*bcm2835_gpio_clr_addrp) | value; //printk(" bcm2835_gpio_clr addr 0x%p: 0x%x\n", bcm2835_gpio_clr_addrp, *bcm2835_gpio_clr_addrp); return 0; } int bcm2835_gpio_clr_release(int pin) { volatile int shift,value; volatile int *bcm2835_gpio_clr_release_addrp = bcm2835_gpio_baseaddr + BCM2835_GPIOReg_GPCLR0 + (pin/32)*4; //printk("In bcm2835_gpio_clr_release function:\n"); //printk(" bcm2835_gpio_baseaddr = 0x%x\n", bcm2835_gpio_baseaddr); //printk(" bcm2835_gpio_clr_release_addrp = 0x%p\n", bcm2835_gpio_clr_release_addrp); shift = pin % 32; value =~( 1 << shift); //printk(" shift=%d,value=0x%x\n",shift,value); //printk(" bcm2835_gpio_clr_release addr 0x%p: 0x%x\n", bcm2835_gpio_clr_release_addrp, *bcm2835_gpio_clr_release_addrp); (*bcm2835_gpio_clr_release_addrp) = (*bcm2835_gpio_clr_release_addrp) & value; //printk(" bcm2835_gpio_clr_release addr 0x%p: 0x%x\n", bcm2835_gpio_clr_release_addrp, *bcm2835_gpio_clr_release_addrp); return 0; } void led_on(int pin) { bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP); bcm2835_gpio_set(pin); } void led_off(int pin) { bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP); bcm2835_gpio_clr(pin); } int led_open(struct inode *inode, struct file *filp) { return 0; } size_t led_release(struct inode *inode, struct file *flip) { bcm2835_gpio_fsel(RPI_BPLUS_GPIO_J8_12, BCM2835_GPIO_FSEL_INP); bcm2835_gpio_set_release(RPI_BPLUS_GPIO_J8_12); bcm2835_gpio_clr_release(RPI_BPLUS_GPIO_J8_12); return 0; } ssize_t led_read(struct file *flip, char __user *buff, size_t count, loff_t *offp) { return 0; } ssize_t led_write(struct file *flip, const char __user *buff, size_t count, loff_t *offp) { return 0; } static int led_ioctl(struct file *flip, unsigned int cmd, unsigned long arg) { switch(cmd){ case LED_ON_CMD:{ led_on(RPI_BPLUS_GPIO_J8_12); break; } case LED_OFF_CMD:{ led_off(RPI_BPLUS_GPIO_J8_12); break; } default:{ break; } } return 0; } static void led_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops) { int err,devno = MKDEV(led_major, minor); cdev_init(dev, fops); dev->owner = THIS_MODULE; dev->ops = fops; err = cdev_add(dev, devno, 1); if (err) printk(KERN_WARNING "Error %d adding beep%d",err,minor); } static struct cdev led_cdev; static struct file_operations led_fops = { .owner = THIS_MODULE, .read = led_read, .write = led_write, .unlocked_ioctl = led_ioctl, .open = led_open, .release = led_release, }; static int __init led_init(void) { int result; dev_t dev = MKDEV(led_major,0); char dev_name[]="led"; bcm2835_gpio_baseaddr=ioremap(0x20200000,0x100000); if(!bcm2835_gpio_baseaddr){ return -EIO; } if (led_major) result = register_chrdev_region(dev,1,dev_name); else{ result = alloc_chrdev_region(&dev,0,1,dev_name); led_major=MAJOR(dev); } if (result < 0){ printk(KERN_WARNING "led: unable to get major %d\n",led_major); return result; } led_setup_cdev(&led_cdev,0,&led_fops); printk("led device installed, with major %d\n", led_major); printk("The device name is: %s\n", dev_name); return 0; } static void __exit led_exit(void) { cdev_del(&led_cdev); unregister_chrdev_region(MKDEV(led_major,0),1); iounmap(bcm2835_gpio_baseaddr); printk("led device uninstalled\n"); } module_init(led_init); module_exit(led_exit); EXPORT_SYMBOL(led_major);
建议大家下载Source insight并安装,可以方便的进行源码的阅读和编辑。安装完后,请建好Linux源码的工程,注意工程存放路径不要包含中文。如图所示。
硬件连接:本次通过树莓派B+的GPIO18(引脚12)控制LED灯亮灭。当GPIO18输出高电平时,LED灯亮,输出低电平时LED灯灭。
下面开始进行字符设备驱动程序的编写。
1.模块初始化和卸载函数:
首先是初始化函数:我们需要完成字符设备的注册和初始化。这里面涉及到两个十分重要的结构体,struct cdev和struct file-operations。我们知道,驱动介于内核和用户程序之间,保护了内核。用户程序只能调用驱动实现的机制完成相关策略。沟通用户程序和驱动的就是struct file-operations。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
..................................................................
};
值得注意的是:旧版本file-operations结构体的成员函数ioctl已经删除,作为替代,内核开发者编写了unlocked_ioctl和compat_ioctl函数可供调用。可参考:http://www.cnblogs.com/jack204/archive/2012/03/20/2407422.html。
当我们实现了:
int led_open(struct inode *inode, struct file *filp);
size_t led_release(struct inode *inode, struct file *flip);
ssize_t led_read(struct file *flip, char __user *buff, size_t count, loff_t *offp);
ssize_t led_write(struct file *flip, const char __user *buff, size_t count, loff_t *offp);
static int led_ioctl(struct file *flip, unsigned int cmd, unsigned long arg);
我们就可以绑定这些函数到文件操作结构体中。
下面我们来实现初始化函数:
① 首先是分配设备号:包括手工分配和动态分配。
int register_chrdev_region(dev_t, unsigned, const char *);
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
② 接着是字符设备初始化:void cdev_init(struct cdev *, const struct file_operations *);绑定文件操作结构体到字符设备结构体中。
③ 添加字符设备:int cdev_add(struct cdev *, dev_t, unsigned);
④ 相关外设初始化操作:这里映射GPIO口操作的虚拟地址,通过ioremap这个函数。至于物理地址怎么知道,可以参考该文章:http://blog.csdn.net/hcx25909/article/details/16860725。
下面是模块卸载函数:
销毁字符设备,释放设备号,释放相关资源。
将模块下到树莓派中,通过:sudo insmod led_driver.ko安装好模块。此时查看内核日志:dmesg | tail -5或cat /var/log/kern.log | tail -5,知道内核分配的主设备号246。创建设备文件节点:sudo mknod /dev/led c 246 0。现在我们可以执行编译好的led_test程序。经过漫长的纠错,终于LED灯按照预期的亮灭了。这是测试程序:
/********************************************************************************************
* File: led_test.c
* Author: Fawen Xu
* Desc: led test code
* History: May 11th 2014
*********************************************************************************************/
#include
#include
#include
#include
#include
#define LED_MAGIC 'k'
#define LED_ON_CMD _IO(LED_MAGIC,1)
#define LED_OFF_CMD _IO(LED_MAGIC,2)
int main()
{
int dev_fd;
dev_fd = open("/dev/led",O_RDWR | O_NONBLOCK);
if( dev_fd == -1){
printf("Cann't open file /dev/led\n");
}
printf("Start led\n");
ioctl(dev_fd,LED_ON_CMD);
sleep(10);
printf("Stop led\n");
ioctl(dev_fd,LED_OFF_CMD);
close(dev_fd);
return 0;
}
欢迎大家将遇到的问题和我分享。