Linux(一)LED驱动程序框架(应用层和驱动层分析)

一、应用层测试c文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>


// 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 <dev> <on|off>\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、主函数剖析:

关于 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

b:

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

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,... */
   

2、 open函数解析

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

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

(1)函数原型

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

(2)函数说明

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

(3)头文件

#include <sys/types.h>//这里提供类型pid_t和size_t的定义
#include <sys/stat.h>
#include <fcntl.h>

(4)标签

Flags: 
O_RDONLY 只读打开  
O_WRONLY 只写打开 
O_RDWR  可读可写打开

(5)返回值

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

3、write 函数解析

(1)功能:

向文件中写入数据

(2)头文件:

#include  <unistd.h>

(3)原型:

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

(4)参数:

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

(5)返回值:

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

二、LED 驱动程序

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

#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、驱动程序解析

(1)定义功能函数

led_drv_read
led_drv_write
led_drv_open
led_drv_close

我们写的程序针对硬件部分抽象出 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-灭 */
};

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

Linux 中#include <linux/fs.h> 中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);
};       
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,
};

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

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

创建设备节点

module_init(led_init);
module_exit(led_exit);

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

应用层序 的 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)copy_from_user,用来将数据从用户空间复制到内核空间  (write)
(2)copy_to_user,用来将数据从内核空间复制到用户空间    (read)
(3)字符设备驱动程序抽象出一个 file_operations 结构体;(应用层 write 和驱动层对接的抽象结构体)
(4)我们写的程序针对硬件部分抽象出 led_operations 结构体(驱动层和硬件层对接的抽象结构体)

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

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

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;
}

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

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

// GPIO5_GDIR 地址:0x020AC004
GPIO5_GDIR = ioremap(0x020AC004, 4);

//GPIO5_DR 地址:0x020AC000
GPIO5_DR  = ioremap(0x020AC000, 4);

(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);     //操作硬件接口函数
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值