分类: LINUX
2009-10-19 17:56:04
本例是冯国进的 《嵌入式Linux 驱动程序设计从入门到精通》的第一个例子
感觉真是好书 强烈推荐 注释是deep_pro加的 转载请注明!我的特点是文不加点! 这个驱动是在内存中分配一个256字节的空间,供用户态应用程序读写。 先是头文件 demo.h #ifndef _DEMO_H_ #define _DEMO_H_ #include /******************************************************** * Macros to help debugging ********************************************************/ #undef PDEBUG /* undef it, just in case */ #ifdef DEMO_DEBUG #ifdef __KERNEL__ # define PDEBUG(fmt, args...) printk( KERN_DEBUG "DEMO: " fmt, ## args) #else//usr space # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) #endif #else # define PDEBUG(fmt, args...) /* not debugging: nothing */ #endif #undef PDEBUGG #define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ //设备号 #define DEMO_MAJOR 224 #define DEMO_MINOR 0 #define COMMAND1 1 #define COMMAND2 2 //自己定义的设备结构 struct DEMO_dev { struct cdev cdev; /* Char device structure */ }; //函数申明 原来Linux驱动程序设计这么简单 只需要实现这么几个函数就可以了 ssize_t DEMO_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t DEMO_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); loff_t DEMO_llseek(struct file *filp, loff_t off, int whence); int DEMO_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); #endif /* _DEMO_H_ */ 然后是demo.c /* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2007, 2010 fengGuojin(fgjnew@163.com) */ #include #include #include #include #include #include #include #include #include #include #include #include "demo.h" MODULE_AUTHOR("fgj"); MODULE_LICENSE("Dual BSD/GPL"); //很郁闷的是就算这样 移植到开发板上仍然说这个驱动污染了内核 struct DEMO_dev *DEMO_devices; //申明自己定义的设备结构的指针 static unsigned char demo_inc=0; //计数 记录此设备被打开的次数 static u8 demoBuffer[256]; //内核里的数据结构和数据类型还真是诡异啊 这个u8还真费解 原来是无符号8位的数据类型 // int DEMO_open(struct inode *inode, struct file *filp) { struct DEMO_dev *dev; //只允许打开设备一次 if(demo_inc>0)return -ERESTARTSYS; demo_inc++; /*container_of 宏 通过结构中的某个变量获取结构本身的指针 %CE%DA%D1%BB%C3%F7/blog/item/b805ae1975f02e4443a9ade3.html 真是太高级了 我看不懂 */ dev = container_of(inode->i_cdev, struct DEMO_dev, cdev); filp->private_data = dev; return 0; } int DEMO_release(struct inode *inode, struct file *filp) { demo_inc--; return 0; } ssize_t DEMO_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos) { int result; loff_t pos= *f_pos; /* 文件的读写位置 */ if(pos>=256) { result=0; goto out; } if(count>(256-pos)) { count=256-pos; } pos += count; if (copy_to_user(buf,demoBuffer+*f_pos,count)) { count=-EFAULT; /* 把数据写到应用程序空间 */ goto out; } *f_pos = pos; /* 改变文件的读写位置 */ out: return count; } ssize_t DEMO_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos) { ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ loff_t pos= *f_pos; if(pos>=256) { goto out; } //如果要写入的输入大于剩下的内存空间 就只写入剩下的空间数量 以防溢出 if(count>(256-pos)) { count=256-pos; } //剩下的代码 如果c语言扎实的话 ,不难看懂 因为我的表述能力不好 见谅 pos += count; //copy_from_user 原文说将数据复制到用户空间 ,我觉得应该是将数据复制到内核空间,应该是作者笔误,没什么大不了的 if (copy_from_user(demoBuffer+*f_pos, buf, count)) { retval = -EFAULT; goto out; } *f_pos = pos; return count; out: return retval; } /*我是这几天才大致知道ioctl的作用 ioctl是用来控制设备的 ,unsigned int cmd就是发给设备的命令 ioctl()或许是Linux下最庞杂的函数*/ int DEMO_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg) { if(cmd==COMMAND1) { printk("ioctl command1 successfully\n"); return 0; } if(cmd==COMMAND2) { printk("ioctl command2 successfully\n"); return 0; } printk("ioctl error\n"); return -EFAULT; } //llseek 实现随即存取 ,loff_t应该是一个无符号整型 记录文件指针偏移量的 这段代码参考用户态的seek()就不难理解 loff_t DEMO_llseek(struct file *filp, loff_t off, int whence) { loff_t pos; pos = filp->f_pos; switch (whence) { case 0: pos = off; break; case 1: pos += off; break; case 2: default: return -EINVAL; } if ((pos>256) || (pos<0)) { return -EINVAL; } return filp->f_pos=pos; } //file_operations这个结构体真是相当重要 需要搞清楚它的作用 struct file_operations DEMO_fops = { .owner = THIS_MODULE, .llseek = DEMO_llseek, .read = DEMO_read, .write = DEMO_write, .ioctl = DEMO_ioctl, .open = DEMO_open, .release = DEMO_release, }; /******************************************************* MODULE ROUTINE *******************************************************/ void DEMO_cleanup_module(void) { //在下面的入口函数有讲MKDEV dev_t devno = MKDEV(DEMO_MAJOR, DEMO_MINOR); if (DEMO_devices) { cdev_del(&DEMO_devices->cdev); //内核下的内存操作函数还真怪异 习惯就好 kfree(DEMO_devices); } //调用unregister_chrdev_region()函数释放分配的一系列设备号 unregister_chrdev_region(devno,1); } int DEMO_init_module(void) { int result; // 在内核中,dev_t类型(在 dev_t dev = 0; /*内核中定义了三个宏来处理主、次设备号:MAJOR和MINOR宏可以从16位数中提取出主、次设备号,而MKDEV宏可以把主、此号合并为一个16位数。 高8位用于主设备号,低8位用于次设备号。 %E7%AC%AC%E5%8D%81%E4%B8%80%E7%AB%A0%20%20%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E7%A8%8B%E5%BA%8F/11.2.3.htm*/ dev = MKDEV(DEMO_MAJOR, DEMO_MINOR); /*获取一个或多个设备编号来使用 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 你不能存取请求的区域. */ result = register_chrdev_region(dev, 1, "DEMO"); if (result < 0) { printk(KERN_WARNING "DEMO: can't get major %d\n", DEMO_MAJOR); return result; } //为自定义的设备结构申请空间 DEMO_devices = kmalloc(sizeof(struct DEMO_dev), GFP_KERNEL); if (!DEMO_devices) { result = -ENOMEM; goto fail; } //为新申请的空间清零 我的水平也只能看懂这些简单的函数了 memset(DEMO_devices, 0, sizeof(struct DEMO_dev)); //初始化一个字符驱动 这里我就不了解cdev_init了 只能大概猜个意思 //还有就是 struct file_operations , 这个结构体的作用搞清楚了 就明白为什么Linux 驱动只要实现很少的驱动就可以了 cdev_init(&DEMO_devices->cdev, &DEMO_fops); DEMO_devices->cdev.owner = THIS_MODULE; DEMO_devices->cdev.ops = &DEMO_fops; //在内核中添加字符驱动 result = cdev_add (&DEMO_devices->cdev, dev, 1); if(result) { printk(KERN_NOTICE "Error %d adding DEMO\n", result); goto fail; } return 0; fail: //失败了 就调用出口函数擦pp走人了 DEMO_cleanup_module(); return result; } //这两个是内核驱动必备的 指明程序入口和出口 module_init(DEMO_init_module); module_exit(DEMO_cleanup_module); demo.c文件结束 make文件我还不会写 我使用kdevelop建立内核工程来编译这个驱动 下面这个makefile是编译移植到arm9 开发板(s3c2440) 的ko文件用的 AR= ar ARCH = arm CC = arm-linux-gcc CFLAGS += $(DEBFLAGS) -Wall CFLAGS += -I$(LDDINC) LDFLAGS = -Xlinker -rpath-link /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1 ifneq ($(KERNELRELEASE),) obj-m:= demo-driver.o #模块名称 else KDIR:= /root/linux-2.6.12 PWD:= $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules endif 加载驱动 # insmod demo.ko 然后使用lsmod 或 cat /proc/modules查看驱动是否加载 # mknod /dev/fgj c 224 0 创建设备节点 然后就可以使用下面代码来测试驱动 /* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2007, 2010 fengGuojin(fgjnew@163.com) */ #include #include #include #include #include #include #include #define COMMAND1 1 #define COMMAND2 2 main() { int fd; int i; char data[256]; int retval; fd=open("/dev/fgj",O_RDWR); if(fd==-1) { perror("error open\n"); exit(-1); } printf("open /dev/smbus successfully\n"); retval=ioctl(fd,COMMAND1,0); if(retval==-1) { perror("ioctl error\n"); exit(-1); } printf("send command1 successfully\n"); retval=write(fd,"fgj",3); if(retval==-1) { perror("write error\n"); exit(-1); } retval=lseek(fd,0,0); if(retval==-1) { perror("lseek error\n"); exit(-1); } retval=read(fd,data,3); if(retval==-1) { perror("read error\n"); exit(-1); } printf("read successfully:%s\n",data); close(fd); } 好了 对源程序的认真阅读 ,初步了解了字符驱动程序的编写 |