首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 操作系统 > UNIXLINUX >

【从零开始,从内核驱动驱动到用户空间调用】编纂第一个linux驱动,通过端口访问I/O寄存器

2013-10-08 
【从零开始,从内核驱动驱动到用户空间调用】编写第一个linux驱动,通过端口访问I/O寄存器。目的:通过I/O端口方

【从零开始,从内核驱动驱动到用户空间调用】编写第一个linux驱动,通过端口访问I/O寄存器。

目的:

通过I/O端口方式访问RTC的秒寄存器;

 

由于本人从来没看过linux方面的书籍,也只是会在终端用些常用的命令而已,这次老大叫我学着通过I/O端口方式直接去读写寄存器。于是我在google中搜索,得到了一些答案,比如要先申请内存空间,再用ioremap映射到虚拟空间啊之类的。我学着网上的例子,写好了我的第一份代码。编译时竟然找不到头文件,非常头疼,头文件明明在那儿,怎么就找不到呢?在这里非常感谢,linux内核涉及与实现QQ群里面各位师哥师姐的鼎力相助,尽管说什么我都不懂,他们还是非常耐心。在群里大哥的指引下,我有了思路。要么添加系统调用,要么写个驱动。这里我选择了第二种方法。

 

方案:

引用群里大哥给我画的图片,感谢!

【从零开始,从内核驱动驱动到用户空间调用】编纂第一个linux驱动,通过端口访问I/O寄存器

 

过程:

1. 编写驱动

#include <linux/ioport.h>#include <linux/module.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/cdev.h>#include <linux/moduleparam.h>#include <linux/slab.h>       /* kmalloc() */#include <linux/fs.h>     /* everything... */#include <linux/errno.h>  /* error codes */#include <linux/types.h>  /* size_t */#include <linux/proc_fs.h>#include <linux/fcntl.h>  /* O_ACCMODE */#include <linux/seq_file.h>#include <linux/cdev.h>#include <asm/uaccess.h>  /* copy_*_user */#include <asm/io.h>#define DEVICE_NAME "rtcport"#define DEVICE_MAJOR 250#ifndef BCD_TO_BIN#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)?4)*10)#endifdev_t dev = 0;static struct resource *rtc_resource;static struct cdev my_dev;static int RTC_open(struct inode *inode,struct file *filp){printk("open device\n");return 0;}static int RTC_close(struct inode *inode,struct file *filp){printk("close device\n");return 0;}static int RTC_read(struct file *filp, char __user *buf, loff_t *f_pos){outb(0,0x70);int test=inb(0x71);printk(KERN_DEBUG "second is %02X\n",test);return 0;}static int RTC_write(void){return 0;}static struct file_operations fops={.owner=THIS_MODULE,.open=RTC_open,.release=RTC_close,.read=RTC_read,.write=RTC_write,};int RTC_init(void){int ret;//ret=register_chrdev(DEVICE_MAJOR,DEVICE_NAME,&fops);ret=alloc_chrdev_region(&dev,0,1,DEVICE_NAME);if (ret < 0) {printk("RTC: can't get major %d\n", MAJOR(dev));return ret;}printk("Register device successfully!\n");release_region(0x70, 0x02);rtc_resource = request_region(0x70,0x02,DEVICE_NAME);if(rtc_resource == NULL){printk("Unable to register RTC I/O addresses\n");return -1;}cdev_init(&my_dev,&fops);my_dev.owner=THIS_MODULE;my_dev.ops=&fops;ret=cdev_add(&my_dev,MKDEV(MAJOR(dev),0),1);if(ret<0){printk("RTC: can't add device");}return 0;}void RTC_exit(void){//      unregister_chrdev(DEVICE_MAJOR,DEVICE_NAME);//      devfs_remove(DEVICE_NAME);release_region(0x70,0x02);cdev_del(&my_dev);unregister_chrdev_region(dev,1);printk("Device has been unregistered!\n");}MODULE_LICENSE("GPL");MODULE_AUTHOR("HJW");module_init(RTC_init);module_exit(RTC_exit);


 

2. Makefile

obj-m += rtc_port.occflags-y=-I/root/testdxxall:        make $(ccflags-y) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean


说明:ccflags-y是需要包括的头文件的路径,如果不需要依赖自定义的头文件,可去除。

 

3.make clean(注:若第一次编译,此步骤可略过)

【从零开始,从内核驱动驱动到用户空间调用】编纂第一个linux驱动,通过端口访问I/O寄存器

 

4.make(ls可以看到在路径下多了一些文件)

【从零开始,从内核驱动驱动到用户空间调用】编纂第一个linux驱动,通过端口访问I/O寄存器

5. 将模块Insmod进内核

insmod rtc_port.ko

注释:有同学说,诶怎么一点打印信息都没有,原因是printk本身就不会把信息打印到屏幕上,如果有需要的话,大家可以自己去搜索搜索。

 

6.执行cat /proc/devices可以看到我们新添加的字符型设备rtcport

【从零开始,从内核驱动驱动到用户空间调用】编纂第一个linux驱动,通过端口访问I/O寄存器

 

7.将rtcport创建dev节点

mknod /dev/rtcport c 237 0

 

8.应用程序app.cpp

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include  <sys/ioctl.h>#include <unistd.h>int main(void){        int fd;        char buf[20];        fd=open("/dev/rtcport",O_RDWR);        if (fd<0)        {                perror("open");                return -1;        }        for(int i=0;i<60;i++)        {                read(fd,buf,20);                sleep(1);        }        close(fd);        return 0;}


 

9. 输出结果dmesg

【从零开始,从内核驱动驱动到用户空间调用】编纂第一个linux驱动,通过端口访问I/O寄存器

 

10 总结

本文只是介绍了完整的步骤,很多小的知识点都没有提及。下面我将进行概括:

1) 模块的格式

关键函数:Init exit之类的;

2)字符型设备的动态注册(动态注册可以减少设备冲突的概率)

关键函数:

alloc_chrdev_region(&dev,0,1,DEVICE_NAME);

cdev_init;

cdev_add

等等

注:释放的关键函数请参见代码;

3) i/o端口的映射

关键函数:

request_region;

release_region;

小的知识点大家可以边google边对照我的代码,希望这篇文章可以帮助大家少走弯路!

 

11.展望

下一步是研究IPMI source code,非常渴望能找到志同道合的朋友,大家有过这方面的研究可以给我留言,非常感谢!

热点排行