从上次hello world程序中,我们已经搭建好了驱动学习相关的环境搭建,为接下来的设备驱动做好了准备。同时通过最简单的hello world程序,学习了模块的初始化和退出,知道了如何编写***_init和***_exit函数,知道了如何通过内核打印函数printk输出相关信息。
Linux中的设备驱动分三大类:字符设备、块设备、网络设备。本篇文章讨论字符型设备程序如何编写,通过简单的LED驱动程序介绍相关知识。下篇文章介绍杂项设备(misc)驱动程序的编写,这在实际项目中很常用,相当于字符设备的简化版。
首先附上完整程序:
</pre><pre name="code" class="plain">/*********************************************************************************************
* File: led_driver.c
* Author: Fawen Xu
* Desc: led driver code
* History: May 9th 2015
*********************************************************************************************/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
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<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<linux/ioctl.h>
#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;
}
欢迎大家将遇到的问题和我分享。