• Linux(一)最简单的LED驱动程序(应用层和驱动层分析)


    一、应用层测试c文件

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    // ledtest /dev/myled on
    // ledtest /dev/myled off
    
    int main(int argc, char **argv)
    {
    	int fd;
    	char status = 0;
    	
    	if (argc != 3)
    	{
    		printf("Usage: %s  \n", argv[0]);
    		printf("  eg: %s /dev/myled on\n",   argv[0]);
    		printf("  eg: %s /dev/myled off\n", argv[0]);
    		return -1;
    	}
    	// open
    	fd = open(argv[1], O_RDWR);
    	if (fd < 0)
    	{
    		printf("can not open %s\n", argv[0]);
    		return -1;
    	}
    
    	// write
    	if (strcmp(argv[2], "on") == 0)
    	{
    		status = 1;
    	}
    
    	write(fd, &status, 1);
    	return 0;	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    1、主函数剖析:

    关于 int main(int argc, char argv) 参数argc 的理解
    测试文件为 ledtest.c

    a:

    ./ledtest /dev/100ask_led0 off   //关闭 led0 灯
    
    这条指令 中  argc:3    argv[0] = ./ledtest
                          argv[1] = /dev/100ask_led0
                          argv[2] = off
    
    • 1
    • 2
    • 3
    • 4
    • 5

    b:

    /mnt/ledtest /dev/myled on // 点灯
    这条指令 中  argc:3    argv[0] = /mnt/ledtest
                          argv[1] = /dev/myled
                          argv[2] = on
    
    • 1
    • 2
    • 3
    • 4

    c:

    100ask_led0  和 myled 的理解
    
    这是创建设备节点是名字的不同
    	led_class = class_create(THIS_MODULE, "myled");
    	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */
    
       led_class = class_create(THIS_MODULE, "100ask_led_class");
       device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */
       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、 open函数解析

    关于 fd = open(argv[1], O_RDWR);的理解

    open函数是Linux应用层访问驱动层的系统函数

    (1)函数原型

    int open(const char *path, int oflags,mode_t mode);
    
    • 1

    (2)函数说明

    open建立了一条到文件或设备的访问路径。
    open函数一般用于打开或者创建文件,在打开或创建文件时可以制定文件的属性及用户的权限等各种参数。
    第一个参数path表示:路径名或者文件名。路径名为绝对路径名(如C:/cpp/a.cpp),文件则是在当前工作目录下的。
    第二个参数oflags表示:打开文件所采取的动作。

    (3)头文件

    #include //这里提供类型pid_t和size_t的定义
    #include 
    #include 
    
    • 1
    • 2
    • 3

    (4)标签

    Flags: 
    O_RDONLY 只读打开  
    O_WRONLY 只写打开 
    O_RDWR  可读可写打开
    
    • 1
    • 2
    • 3
    • 4

    (5)返回值

    open函数的返回值如果操作成功,它将返回一个文件描述符,如果操作失败,它将返回-1。

    3、write 函数解析

    (1)功能:

    向文件中写入数据

    (2)头文件:

    #include  
    
    • 1

    (3)原型:

    ssize_t write(int fd, const void *buf, size_t count);
    
    • 1

    (4)参数:

    fd: 文件描述符
    buf: 存放要写入的数据的缓冲区首地址
    count: 想要写入的字节数

    (5)返回值:

    =0:成功写入的字节数,0表示什么都没写入
    -1: 写入失败,并设置全局变量errno

    二、LED 驱动程序

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include "led_opr.h"
    
    #define LED_NUM 2
    
    /* 1. 确定主设备号                                                                 */
    static int major = 0;
    static struct class *led_class;
    struct led_operations *p_led_opr;
    
    
    #define MIN(a, b) (a < b ? a : b)
    
    /* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
    static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
    {
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	return 0;
    }
    
    /* write(fd, &val, 1); */
    static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
    {
    	int err;
    	char status;
    	struct inode *inode = file_inode(file);
    	int minor = iminor(inode);
    	
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	err = copy_from_user(&status, buf, 1);
    
    	/* 根据次设备号和status控制LED */
    	p_led_opr->ctl(minor, status);
    	
    	return 1;
    }
    
    static int led_drv_open (struct inode *node, struct file *file)
    {
    	int minor = iminor(node);
    	
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	/* 根据次设备号初始化LED */
    	p_led_opr->init(minor);
    	
    	return 0;
    }
    
    static int led_drv_close (struct inode *node, struct file *file)
    {
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	return 0;
    }
    
    /* 2. 定义自己的file_operations结构体                                              */
    static struct file_operations led_drv = {
    	.owner	 = THIS_MODULE,
    	.open    = led_drv_open,
    	.read    = led_drv_read,
    	.write   = led_drv_write,
    	.release = led_drv_close,
    };
    
    /* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
    /* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
    static int __init led_init(void)
    {
    	int err;
    	int i;
    	
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
    
    
    	led_class = class_create(THIS_MODULE, "100ask_led_class");
    	err = PTR_ERR(led_class);
    	if (IS_ERR(led_class)) {
    		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    		unregister_chrdev(major, "100ask_led");
    		return -1;
    	}
    
    	for (i = 0; i < LED_NUM; i++)
    		device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */
    
    	p_led_opr = get_board_led_opr();
    	
    	return 0;
    }
    
    /* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
    static void __exit led_exit(void)
    {
    	int i;
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    
    	for (i = 0; i < LED_NUM; i++)
    		device_destroy(led_class, MKDEV(major, i)); /* /dev/100ask_led0,1,... */
    
    	device_destroy(led_class, MKDEV(major, 0));
    	class_destroy(led_class);
    	unregister_chrdev(major, "100ask_led");
    }
    
    
    /* 7. 其他完善:提供设备信息,自动创建设备节点                                     */
    
    module_init(led_init);
    module_exit(led_exit);
    
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    1、驱动程序解析

    (1)定义功能函数

    led_drv_read
    led_drv_write
    led_drv_open
    led_drv_close
    
    • 1
    • 2
    • 3
    • 4

    我们写的程序针对硬件部分抽象出 led_operations 结构体(驱动层和硬件层对接的抽象结构体)

    struct led_operations {
    	int num;
    	int (*init) (int which); /* 初始化LED, which-哪个LED */       
    	int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)定义自己的file_operations结构体 ,并且将功能函数注册到结构体(面向对象方式:通过函数指针进行封装)

    Linux 中#include 中file_operations 原型

       struct file_operations {
    	struct module *owner;
    	loff_t (*llseek) (struct file *, loff_t, int);
    	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    	int (*iterate) (struct file *, struct dir_context *);
    	int (*iterate_shared) (struct file *, struct dir_context *);
    	unsigned int (*poll) (struct file *, struct poll_table_struct *);
    	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    	int (*mmap) (struct file *, struct vm_area_struct *);
    	int (*open) (struct inode *, struct file *);
    	int (*flush) (struct file *, fl_owner_t id);
    	int (*release) (struct inode *, struct file *);
    	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    	int (*fasync) (int, struct file *, int);
    	int (*lock) (struct file *, int, struct file_lock *);
    	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    	int (*check_flags)(int);
    	int (*flock) (struct file *, int, struct file_lock *);
    	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    	int (*setlease)(struct file *, long, struct file_lock **, void **);
    	long (*fallocate)(struct file *file, int mode, loff_t offset,
    			  loff_t len);
    	void (*show_fdinfo)(struct seq_file *m, struct file *f);
    #ifndef CONFIG_MMU
    	unsigned (*mmap_capabilities)(struct file *);
    #endif
    	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
    			loff_t, size_t, unsigned int);
    	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
    			u64);
    	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
    			u64);
    };       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    static struct file_operations led_drv = {
    	.owner	 = THIS_MODULE,
    	.open    = led_drv_open,
    	.read    = led_drv_read,
    	.write   = led_drv_write,
    	.release = led_drv_close,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (3)定义入口函数和出口函数

    入口函数:安装驱动程序时,就会去调用这个入口函数
    卸载驱动程序时,就会去调用这个出口函数

    创建设备节点

    module_init(led_init);
    module_exit(led_exit);
    
    • 1
    • 2

    三、应用程序和驱动程序的相互对接

    应用层序 的 write、 open函数通过函数指针的方式在驱动层分别对接led_drv_write 和led_drv_write函数。也就是驱动层的函数把自己的实体注册到了应用层提供的接口,这样就可以通过应用层访问到驱动层,实现了应用和驱动的分层设计,Linux 处处体现着面向对象的编程方法。

    static struct file_operations led_drv = {
    	.owner	 = THIS_MODULE,
    	.open    = led_drv_open,
    	.read    = led_drv_read,
    	.write   = led_drv_write,
    	.release = led_drv_close,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    (1)copy_from_user,用来将数据从用户空间复制到内核空间  (write)
    (2)copy_to_user,用来将数据从内核空间复制到用户空间    (read)
    (3)字符设备驱动程序抽象出一个 file_operations 结构体;(应用层 write 和驱动层对接的抽象结构体)
    (4)我们写的程序针对硬件部分抽象出 led_operations 结构体(驱动层和硬件层对接的抽象结构体)
    
    • 1
    • 2
    • 3
    • 4

    四、驱动程序和硬件的对接过程

    1、 led_operations结构体对硬件函数的封装

    #ifndef _LED_OPR_H
    #define _LED_OPR_H
    
    struct led_operations {
    	int (*init) (int which); /* 初始化LED, which-哪个LED */       
    	int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
    };
    
    struct led_operations *get_board_led_opr(void);
    
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2、对硬件接口进行注册

    static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */	   
    {
    	
    	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
    	return 0;
    }
    
    static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
    {
    	printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
    	return 0;
    }
    
    static struct led_operations board_demo_led_opr = {
    	.num  = 1,
    	.init = board_demo_led_init,
    	.ctl  = board_demo_led_ctl,
    };
    
    struct led_operations *get_board_led_opr(void)
    {
    	return &board_demo_led_opr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3、驱动程序对硬件的操作

    (1)通过 ioremap 映射寄存器的物理地址得到虚拟地址, 读写虚拟地址

    // GPIO5_GDIR 地址:0x020AC004
    GPIO5_GDIR = ioremap(0x020AC004, 4);
    
    //GPIO5_DR 地址:0x020AC000
    GPIO5_DR  = ioremap(0x020AC000, 4);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)获取APP程序驱动状态,操作硬件接口函数

    struct led_operations *p_led_opr;
    p_led_opr = get_board_led_opr();        //获取底层接口
    err = copy_from_user(&status, buf, 1);  //获取APP程序驱动状态
    /* 根据次设备号和status控制LED */
    	p_led_opr->ctl(minor, status);     //操作硬件接口函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    【洛谷P1351】联合权值【数学】
    14 Go的类型转换
    毛玻璃态卡片悬停效果
    web端生成pdf,前端生成pdf导出并自定义页眉页脚
    解决:Package ‘setuptools‘ requires a different Python: 3.7.16 not in ‘>=3.8‘
    GIT的安装与常见命令
    金仓数据库KingbaseES客户端应用参考手册--14. sys_receivewal
    分布式之计算高性能
    [oeasy]python0019_ 打包和解包_struct_pack_unpack
    Linux系统自有服务
  • 原文地址:https://blog.csdn.net/qq_26972441/article/details/126649723