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

    目的:

    通过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
    ");
    	return 0;
    }
    
    static int RTC_close(struct inode *inode,struct file *filp)
    {
    	printk("close device
    ");
    	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
    ",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
    ", MAJOR(dev));
    		return ret;
    	}
    	printk("Register device successfully!
    ");
    	release_region(0x70, 0x02);
    	rtc_resource = request_region(0x70,0x02,DEVICE_NAME);
    	if(rtc_resource == NULL)
    	{
    		printk("Unable to register RTC I/O addresses
    ");
    		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!
    ");
    }
    
    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,非常渴望能找到志同道合的朋友,大家有过这方面的研究可以给我留言,非常感谢!

  • 相关阅读:
    Python 写入和读取Excel数据
    postman检查点详解
    禅道安装在不同系统下搭建步骤
    Linux下如何启动和关闭防火墙
    tomcat环境搭建
    Lniux下搭建LNMP环境
    Linux下搭建LAMP环境
    通过XAMPP导入WordPress网站建立个人博客
    在Windows下XAMPP的安装及使用教程
    linux 下安装配置xampp环境
  • 原文地址:https://www.cnblogs.com/james1207/p/3313136.html
Copyright © 2011-2022 走看看