目的:
通过I/O端口方式访问RTC的秒寄存器;
由于本人从来没看过linux方面的书籍,也只是会在终端用些常用的命令而已,这次老大叫我学着通过I/O端口方式直接去读写寄存器。于是我在google中搜索,得到了一些答案,比如要先申请内存空间,再用ioremap映射到虚拟空间啊之类的。我学着网上的例子,写好了我的第一份代码。编译时竟然找不到头文件,非常头疼,头文件明明在那儿,怎么就找不到呢?在这里非常感谢,linux内核涉及与实现QQ群里面各位师哥师姐的鼎力相助,尽管说什么我都不懂,他们还是非常耐心。在群里大哥的指引下,我有了思路。要么添加系统调用,要么写个驱动。这里我选择了第二种方法。
方案:
引用群里大哥给我画的图片,感谢!
过程:
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) #endif dev_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.o ccflags-y=-I/root/testdxx all: make $(ccflags-y) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
说明:ccflags-y是需要包括的头文件的路径,如果不需要依赖自定义的头文件,可去除。
3.make clean(注:若第一次编译,此步骤可略过)
4.make(ls可以看到在路径下多了一些文件)
5. 将模块Insmod进内核
insmod rtc_port.ko
注释:有同学说,诶怎么一点打印信息都没有,原因是printk本身就不会把信息打印到屏幕上,如果有需要的话,大家可以自己去搜索搜索。
6.执行cat /proc/devices可以看到我们新添加的字符型设备rtcport
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
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,非常渴望能找到志同道合的朋友,大家有过这方面的研究可以给我留言,非常感谢!