从上次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 cdevstruct 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_ioctlcompat_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 -5cat /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;
}
欢迎大家将遇到的问题和我分享。

Logo

更多推荐