现在,我们来编写自己第一个字符设备驱动 —— 点亮LED。(不完善,后面再完善)
硬件平台:Exynos4412(FS4412)
编写驱动分下面几步:
a -- 查看原理图、数据手册,了解设备的操作方法;
b -- 在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始;
c -- 实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名,内核才能找到相应的驱动程序;
d -- 设计所要实现的操作,比如 open、close、read、write 等函数;
e -- 实现中断服务(中断不是每个设备驱动所必须的);
f -- 编译该驱动程序到内核中,或者用 insmod 命令加载;
g-- 测试驱动程序;
下面是一个点亮LED 的驱动:
第一步,当然是查看手册,查看原理图,找到相应寄存器;
查看手册,四个LED 所用寄存器为:
led2
GPX2CON 0x11000c40
GPX2DAT 0x11000c44
led3
GPX1CON 0x11000c20
GPX1DAT 0x11000c24
led4 3-4 3-5
GPF3CON 0x114001e0
GPF3DAT 0x114001e4
这里要注意:arm体系架构是io内存,必须要映射 ioremap( ); 其作用是物理内存向虚拟内存的映射。 用到writel readl这两个函数,详细解释会在后面不上,先看一下简单用法:
以LED2为例,下面是地址映射及读写:
[cpp]view plaincopyint*pgpx2con; int*pgpx2dat; pgpx2con=ioremap(GPX2CON,4); pgpx2dat=ioremap(GPX2DAT,4); readl(pgpx2con); writel(0x01,pgpx2dat);
下面是驱动程序,后面会更完善[cpp]view plaincopy#include<linux/module.h> #include<linux/fs.h> #include<linux/cdev.h> #include<linux/device.h> #include<asm/io.h> #include<asm/uaccess.h> staticintmajor=250; staticintminor=0; staticdev_tdevno; staticstructclass*cls; staticstructdevice*test_device; #defineGPX2CON0x11000c40 #defineGPX2DAT0x11000c44 #defineGPX1CON0x11000c20 #defineGPX1DAT0x11000c24 #defineGPF3CON0x114001e0 #defineGPF3DAT0x114001e4 staticint*pgpx2con; staticint*pgpx2dat; staticint*pgpx1con; staticint*pgpx1dat; staticint*pgpf3con; staticint*pgpf3dat; voidfs4412_led_off(intnum); voidfs4412_led_on(intnum) { switch(num) { case1: writel(readl(pgpx2dat)|(0x1<<7),pgpx2dat); break; case2: writel(readl(pgpx1dat)|(0x1<<0),pgpx1dat); break; case3: writel(readl(pgpf3dat)|(0x1<<4),pgpf3dat); break; case4: writel(readl(pgpf3dat)|(0x1<<5),pgpf3dat); break; default: fs4412_led_off(1); fs4412_led_off(2); fs4412_led_off(3); fs4412_led_off(4); break; } } voidfs4412_led_off(intnum) { switch(num) { case1: writel(readl(pgpx2dat)&(~(0x1<<7)),pgpx2dat); break; case2: writel(readl(pgpx1dat)&(~(0x1<<0)),pgpx1dat); break; case3: writel(readl(pgpf3dat)&(~(0x1<<4)),pgpf3dat); break; case4: writel(readl(pgpf3dat)&(~(0x1<<5)),pgpf3dat); break; } } staticintled_open(structinode*inode,structfile*filep) {//open fs4412_led_off(1); fs4412_led_off(2); fs4412_led_off(3); fs4412_led_off(4); return0; } staticintled_release(structinode*inode,structfile*filep) {//close fs4412_led_off(1); fs4412_led_off(2); fs4412_led_off(3); fs4412_led_off(4); return0; } staticssize_tled_read(structfile*filep,char__user*buf,size_tlen,loff_t*pos) { return0; } staticssize_tled_write(structfile*filep,constchar__user*buf,size_tlen,loff_t*pos) { intled_num; if(len!=4) { return-EINVAL; } if(copy_from_user(&led_num,buf,len)) { return-EFAULT; } fs4412_led_on(led_num); printk("led_num=%d\n",led_num); return0; } staticstructfile_operationshello_ops= { .open=led_open, .release=led_release, .read=led_read, .write=led_write, }; staticvoidfs4412_led_init(void) { pgpx2con=ioremap(GPX2CON,4); pgpx2dat=ioremap(GPX2DAT,4); pgpx1con=ioremap(GPX1CON,4); pgpx1dat=ioremap(GPX1DAT,4); pgpf3con=ioremap(GPF3CON,4); pgpf3dat=ioremap(GPF3DAT,4); writel((readl(pgpx2con)&~(0xf<<28))|(0x1<<28),pgpx2con); writel((readl(pgpx1con)&~(0xf<<0))|(0x1<<0),pgpx1con); writel((readl(pgpf3con)&~(0xff<<16))|(0x11<<16),pgpf3con); } staticintled_init(void) { intret; devno=MKDEV(major,minor); ret=register_chrdev(major,"led",&hello_ops); cls=class_create(THIS_MODULE,"myclass"); if(IS_ERR(cls)) { unregister_chrdev(major,"led"); return-EBUSY; } test_device=device_create(cls,NULL,devno,NULL,"led");//mknod/dev/hello if(IS_ERR(test_device)) { class_destroy(cls); unregister_chrdev(major,"led"); return-EBUSY; } fs4412_led_init(); return0; } voidfs4412_led_unmap(void) { iounmap(pgpx2con); iounmap(pgpx2dat); iounmap(pgpx1con); iounmap(pgpx1dat); iounmap(pgpf3con); iounmap(pgpf3dat); } staticvoidled_exit(void) { fs4412_led_unmap(); device_destroy(cls,devno); class_destroy(cls); unregister_chrdev(major,"led"); printk("led_exit\n"); } MODULE_LICENSE("GPL"); module_init(led_init); module_exit(led_exit);
测试程序:
[cpp]view plaincopy#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h> main() { intfd,i,lednum; fd=open("/dev/led",O_RDWR); if(fd<0) { perror("openfail\n"); return; } for(i=0;i<100;i++) { lednum=0; write(fd,&lednum,sizeof(int)); lednum=i%4+1; write(fd,&lednum,sizeof(int)); sleep(1); } close(fd); }
makefile:
[cpp]view plaincopyifneq($(KERNELRELEASE),) obj-m:=hello.o $(info"2nd") else #KDIR:=/lib/modules/$(shelluname-r)/build KDIR:=/home/xiaoming/linux-3.14-fs4412 PWD:=$(shellpwd) all: $(info"1st") make-C$(KDIR)M=$(PWD)modules arm-none-linux-gnueabi-gcctest.c sudocphello.koa.out/rootfs/test/ clean: rm-f*.ko*.o*.symvers*.mod.c*.mod.o*.order endif
编译结束后,将a.out 和 hello.ko 拷贝到开发板中:
# insmod hello.ko
#mknod /dev/hello c 250 0
#./a.out
会看到跑马灯效果。
后面会对该驱动完善。
如果觉得《Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动》对你有帮助,请点赞、收藏,并留下你的观点哦!