版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
概述
- linux驱动可分为三类:字符设备、块设备、网络接口。
- 字符设备驱动程序其实就是为具体硬件的file_operations结构体编写各个函数,实现open、read、write、ioctl等系统调用。
- 实现步骤:编写驱动程序xxx.c文件,编写Makefile、编译链接生成xxx.ko文件、insmod生成节点、编写应用测试程序、执行测试程序查看驱动效果。
基础知识
(一)杂项设备
可以理解为的特殊字符设备,在嵌入式中应用比较多。使用杂项设备原因:
① 节省主设备号
普通字符设备都会消耗一个主设备号,使用杂项设备,主设备号固定10,通过子设备号区分不同驱动。
② 使用简单
普通字符设备需注册字符驱动、创建字符class等操作,杂项设备只需把 struct miscdevice交给misc_register(),则成功创建设备驱动。
(二)MMU
MMU:(Memory ManagementUnit)内存管理单元。
功能:负责物理地址到虚拟地址的映射,并提供内存访问权限检查。
作用:
① 内存扩充:为用户提供更大的地址空间(虚拟出来的)
② 内存保护:确保每个进程分配的内存区域互不干扰。
(三)ioremap、iounmap函数介绍
在linux系统中,开启mmu后,我们不能直接使用寄存器的硬件地址(或者说我们不知道,寄存器硬件地址被映射到那块内存了),所以我们只能使用虚拟地址来操纵寄存器。而目前我们不知道虚拟地址,只知道物理地址。
所以系统给我们提供了一个接口函数,用来通过寄存器的物理地址得到寄存器的虚拟地址。
void __iomem *ioremap(phys_addr_t offset,unsigned long size)
它的第一个输入参数是物理地址,第二个输入参数是请求的寄存器的长度。输出参数是虚拟地址。
其功能是建立新的一个页表,把输入的物理地址映射到内核空间的一块虚拟地址。(记住是映射一页)
所以使用完一定要释放掉,否则块内核的内存就不能再被使用了。
static inline void iounmap(void __iomem*addr)
取消映射,使用的是要释放的虚拟地址。
驱动程序
直接上代码,详细见注释
#include <linux/fs.h> /*包含file_operation结构体*/
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <linux/miscdevice.h> /*包含miscdevice结构体*/
#include <linux/io.h> /*包含ioremap等操作函数*/
#include <linux/kernel.h> /*包含printk等操作函数*/
/**************宏定义***************/
#define PIO_SODR (*(volatile unsigned long *)(virt_addr +0x0030))/*虚拟寄存器地址*/
#define PIO_CODR (*(volatile unsigned long *)(virt_addr +0x0034))
#define PIO_PER (*(volatile unsigned long *)(virt_addr +0x0000))
#define PIO_MDDR (*(volatile unsigned long *)(virt_addr +0x0054))
#define PIO_OER (*(volatile unsigned long *)(virt_addr +0x0010))
#define PIO_OWDR (*(volatile unsigned long *)(virt_addr +0x00A4))
#define LED2 1 << 29
#define LED1 1 << 26
#define LED_ON 1
#define LED_OFF 0
#define LED1_CTL 5
#define LED2_CTL 6
/**************内部变量***************/
unsigned long virt_addr;
/* 定义一个打开设备的,open函数 */
static int led_open(struct inode *inode,struct file *file)
{
return 0;
}
/* 定义一个打开设备的,open函数 */
static int led_open(struct inode *inode,struct file *file)
{
return 0;
}
/* 定义一个打开设备的,ioctl函数 */
static long led_ioctl(struct file *file,unsigned int data,unsigned long arg)
{
switch(data)
{
case LED1_CTL:
if(arg == LED_ON){
PIO_SODR |= LED1;/*对寄存器IO操作*/
}
else{
PIO_CODR |= LED1;
}
break;
case LED2_CTL:
if(arg == LED_ON){
PIO_SODR |= LED2;
}
else{
PIO_CODR |= LED2;
}
break;
default:
break;
}
return 0;
}
/*字符设备驱动程序就是为具体硬件的file_operations结构编写各个函数*/
static const struct file_operations led_ctl={
.owner = THIS_MODULE,
.unlocked_ioctl = led_ioctl,
.open = led_open,
};
/*杂项设备,主设备号为10的字符设备,相对普通字符设备,使用更简单*/
static struct miscdevice led_miscdev = {
.minor = 255,
.name = "led_ctl",
.fops = &led_ctl,
};
static int __init led_init(void)
{
char res;
/*注册杂项设备驱动*/
res = misc_register(&led_miscdev);
printk(KERN_ALERT"led_init %d\n",res);
/*通过物理地址,得到寄存器的虚拟地址*/
virt_addr =(volatile unsigned long )ioremap(0xfffff200,0x200);
/*对芯片寄存器操作,输出IO使能*/
PIO_PER |= LED1 | LED2;
PIO_MDDR |= LED1 | LED2;
PIO_OER |= LED1 | LED2;
PIO_OWDR |= LED1 | LED2;
PIO_CODR |= LED1 | LED2;
return res;
}
static void __exit led_exit(void)
{
/*释放杂项设备*/
misc_deregister(&led_miscdev);
/*取消虚拟地址映射*/
iounmap((unsigned long *)virt_addr);
printk(KERN_ALERT"led_exit\r\n");
}
/*驱动模块的加载和卸载入口*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("boyee");
MODULE_DESCRIPTION("control output led");
Makefile
最简单的makefile如下:
KERNEL_DIR:=/home/boyee/at91/linux-at91-V3.18.0-git
CROSS=arm-linux-
obj-m +=ledCtrl.o
all:
make -C $(KERNEL_DIR) M=`pwd` modules
clean:
rm -rf *.o *.ko
应用测试程序
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
char *leds = "/dev/led_ctl";
int fd,i;
if((fd = open(leds,O_RDWR))<0)
{
printf("leds open err\r\n");
return -1;
}
for(i=0;i<5;i++)
{
ioctl(fd,5,1);
ioctl(fd,6,1);
printf("led on\r\n");
sleep(1);
ioctl(fd,5,0);
ioctl(fd,6,0);
printf("led off\r\n");
sleep(1);
}
close(fd);
return 0;
}
执行测试
编译驱动,应用测试程序
上传xxx.ko和应用程序到板子,并更改权限,insmod,执行测试程序