现在,我们来编写自己第一个字符设备驱动 —— 点亮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为例,下面是地址映射及读写:
int *pgpx2con ; int *pgpx2dat; pgpx2con = ioremap( GPX2CON, 4); pgpx2dat = ioremap(GPX2DAT,4); readl(pgpx2con); writel(0x01, pgpx2dat );
#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <asm/io.h> #include <asm/uaccess.h> static int major = 250; static int minor=0; static dev_t devno; static struct class *cls; static struct device *test_device; #define GPX2CON 0x11000c40 #define GPX2DAT 0x11000c44 #define GPX1CON 0x11000c20 #define GPX1DAT 0x11000c24 #define GPF3CON 0x114001e0 #define GPF3DAT 0x114001e4 static int *pgpx2con ; static int *pgpx2dat; static int *pgpx1con ; static int *pgpx1dat; static int *pgpf3con ; static int *pgpf3dat; void fs4412_led_off(int num); void fs4412_led_on(int num) { switch(num) { case 1: writel(readl(pgpx2dat) |(0x1<<7), pgpx2dat); break; case 2: writel(readl(pgpx1dat) |(0x1<<0), pgpx1dat); break; case 3: writel(readl(pgpf3dat) |(0x1<<4), pgpf3dat); break; case 4: 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; } } void fs4412_led_off(int num) { switch(num) { case 1: writel(readl(pgpx2dat) &(~(0x1<<7)), pgpx2dat); break; case 2: writel(readl(pgpx1dat)&(~(0x1<<0)), pgpx1dat); break; case 3: writel(readl(pgpf3dat) &(~(0x1<<4)), pgpf3dat); break; case 4: writel(readl(pgpf3dat) &(~(0x1<<5)), pgpf3dat); break; } } static int led_open (struct inode *inode, struct file *filep) {//open fs4412_led_off(1); fs4412_led_off(2); fs4412_led_off(3); fs4412_led_off(4); return 0; } static int led_release(struct inode *inode, struct file *filep) {//close fs4412_led_off(1); fs4412_led_off(2); fs4412_led_off(3); fs4412_led_off(4); return 0; } static ssize_t led_read(struct file *filep, char __user *buf, size_t len, loff_t *pos) { return 0; } static ssize_t led_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos) { int led_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); return 0; } static struct file_operations hello_ops= { .open = led_open, .release = led_release, .read = led_read, .write = led_write, }; static void fs4412_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) ; } static int led_init(void) { int ret; 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(); return 0; } void fs4412_led_unmap(void) { iounmap(pgpx2con); iounmap(pgpx2dat ); iounmap(pgpx1con); iounmap(pgpx1dat ); iounmap(pgpf3con ); iounmap(pgpf3dat ); } static void led_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);
测试程序:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> main() { int fd,i,lednum; fd = open("/dev/led",O_RDWR); if(fd<0) { perror("open fail \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:
ifneq ($(KERNELRELEASE),) obj-m:=hello.o $(info "2nd") else #KDIR := /lib/modules/$(shell uname -r)/build KDIR := /home/xiaoming/linux-3.14-fs4412 PWD:=$(shell pwd) all: $(info "1st") make -C $(KDIR) M=$(PWD) modules arm-none-linux-gnueabi-gcc test.c sudo cp hello.ko a.out /rootfs/test/ clean: rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order endif
# insmod hello.ko
#mknod /dev/hello c 250 0
#./a.out
会看到跑马灯效果。
后面会对该驱动完善。