韦东山嵌入式linux系列-LED 模板驱动程序的改造:总线设备驱动模型

1 原来的框架

应用程序调用open/write等函数最简单的方法时驱动层也提供对应的drv_open/drv_write,应用程序调用read/write,驱动层也提供对应的drv_open/drv_write等等,需要写出驱动层的函数。

在上一层的leddrv.c文件中构造了file_operations结构体,在这个结构体中有open/write等成员函数,并且指向对应的drv_open/drv_write驱动函数。drv_open函数把LED引脚配置成输出引脚,drv_write函数把LED引脚配置成输出高/低电平;它们需要调用底层提供的p_led_opr->init/p_led_opr->ctl成员。p_led_opr这个指针是由底层芯片相关的(chip_demo_gpio.c)代码提供的,其中chip_demo_gpio.c这里面有led_operations结构体,结构体中有init/ctl函数。那么到底操作哪一个引脚呢?

由board_A_led.c里面指定(这里和具体的板子打交道)

这三个文件(leddrv.c、chip_demo_gpio.c、board_A_led.c)会被编译成一个ko文件

分离、分层、面向对象

2 要实现的框架

结合上一节【韦东山嵌入式linux系列-驱动进化之路:总线设备驱动模型-CSDN博客】的理论,新的框架如下:

首先用通用的platform_device结构体定义资源(引脚)代替之前的board_A_led.c;

再用platform_driver(里面有probe函数)结构体代替之前的chip_demo_gpio.c,当左右两边匹配的时候,probe函数就会被调用。

匹配之后具体做什么呢?

(1)记录资源;(2)对于每一个引脚要调用device_create(辅助作用)

这三个文件会被编译成三个ko文件。

入口函数注册platform_driver(在leddrv.c中)

3 代码

3.1 board_A_led.c

先实现board_A_led.c

参看有resource属性的

flag表示是哪一类资源, 有一堆资源

代码

#include "led_resource.h"

// gpio3_1
// 3(11)左移16位 -- 11 0000 0000 0000 0000									 1
static struct led_resource board_A_led = {
	.pin = GROUP_PIN(3, 1),
};

// 资源数组
// flag表示是哪一类资源,暂时认定IORESOURCE_IRQ表示引脚(IO不就是引脚吗)
// end先不管
static struct resource resource[] = {
	{
		.start = GROUP_PIN(3, 1),
		.flags = IORESOURCE_IRQ
	},
	{
		.start = GROUP_PIN(5, 8),
		.flags = IORESOURCE_IRQ
	},
};

// platform_device结构体
static struct platform_device board_A_led_drv = {
	.name = "winter_led",
	.num_resources = ARRAY_SIZE(resource),
	.recource = &resource,
};

// 入口函数,注册用
static int led_dev_init(void)
{
	int err;
	err = platform_device_register(&board_A_led_drv);
	return 0;
}

// 出口函数,注销用
static void led_dev_exit(void)
{
	platform_device_unregister(&board_A_led_drv);
}

// 修饰为入口函数和出口函数
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

3.2 chip_demo_gpio.c

在另外一边需要提供匹配的platform_driver。

分配、设置、注册某一个platform_driver结构体

platform_driver有name,和platform_device匹配;也有probe函数、remove函数

参考别人的

#include <linux/gfp.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include "led_resource.h"
#include "led_operations.h"

static int g_ledpins[100];					// 记录引脚
static int g_ledcount = 0;					// 计数器


// init函数
static int board_demo_led_init(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	printk("init gpio: group: %d, pin: %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}

// ctl函数
static int board_demo_led_ctl(int which, char status)
{
	printk("set led: %s: group: %d, pin: %d\n", status ? "on" : "off",
		GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}

static struct led_operations board_demo_led_operations = {
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,
};

// probe函数
// (1)记录引脚;(2)对于每一个引脚要调用device_create(辅助作用)
static int chip_demo_gpio_probe(struct platform_device *device)
{
	int i = 0;
	struct resource* res;
	while (1)
	{
		// 获取资源,IORESOURCE_IRQ类型的资源,第i个资源
		res = platform_get_resource(device, IORESOURCE_IRQ, i++);
		if (!res)
		{
			break;
		}
		// 记录引脚
		g_ledpins[g_ledcount] = res->start;
		// 同时创建device
		led_device_create(g_ledcount);
		g_ledcount++;
	}

	return 0;
}


// remove函数
static int chip_demo_gpio_remove(struct platform_device *device)
{
	int i;
	// device_destroy
	for (i = 0; i < g_ledcount; i++)
	{
		led_device_destroy(i);
	}
	// 全局变量清为0
	g_ledcount = 0;
	return 0;
}


// platform_driver结构体
// name一致
static struct platform_driver chip_demo_gpio_drv = {
	.driver = {
		.name  = "winter_led",
	},
	.probe = chip_demo_gpio_probe,
	.remove = chip_demo_gpio_remove,
};


// 注册这个结构体,入口出口
static int chip_demo_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_demo_gpio_drv);
	register_led_operations(&board_demo_led_operations);
	return 0;
}

// 出口函数,注销用
static void chip_demo_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_demo_gpio_drv);
}

// 修饰为入口函数和出口函数
module_init(chip_demo_gpio_drv_init);
module_exit(chip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");


3.3 led_drv.c
/*************************************************************************
 > File Name: led.drv.c
 > Author: Winter
 > Created Time: Sun 07 Jul 2024 12:35:19 AM EDT
 ************************************************************************/

#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_operations.h"

#define LED_NUM 2


// 1确定主设备号,也可以让内核分配
static int major = 0;				// 让内核分配
static struct class *led_class;
struct led_operations* p_led_operations;


#define MIN(a, b) (a < b ? a : b)

// 3 实现对应的 drv_open/drv_read/drv_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* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据
	err = copy_from_user(&status, buf, 1);
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl(minor, status);

	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 得到次设备号
	minor = iminor(node);
	
	// 根据次设备号初始化LED
	p_led_operations->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 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{
	int err, i;	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 注册led_drv,返回主设备号
	major = register_chrdev(0, "winter_led", &led_drv);  /* /dev/led */
	// 创建class
	led_class = class_create(THIS_MODULE, "led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led_class");
		return -1;
	}
	// 创建device
	// 根据次设备号访问多个LED
//	device_create(led_class, NULL, MKDEV(major, 0), NULL, "winter_led0"); /* /dev/winter_led0 */
//	device_create(led_class, NULL, MKDEV(major, 1), NULL, "winter_led1"); /* /dev/winter_led1 */

	// 入口函数获得结构体指针,存在交叉依赖
	// p_led_operations = get_board_led_operations();
	return 0;
}


// 封装device_create函数,创建第minor个
// 供其他文件调用
void led_device_create(int minor)
{
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "winter_led%d", minor);
}

void led_device_destroy(int minor)
{
	device_destroy(led_class, MKDEV(major, minor));
}

// 底层向上册注册函数,底层在入口函数调用
void register_led_operations(struct led_operations* operations)
{
	// 由底层提供
	p_led_operations = operations;
}


// 在驱动程序中,这个文件作为ko加载,别的驱动文件想要引用这个函数,必须把它导出来
// 就得先加载这个ko文件,再加载引用它的ko文件
EXPORT_SYMBOL(led_device_create);
EXPORT_SYMBOL(led_device_destroy);
EXPORT_SYMBOL(register_led_operations);



// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{
	int i;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	class_destroy(led_class);
	// 卸载
	unregister_chrdev(major, "winter_led");
}


// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

demo程序,不会操作硬件。

增加灯,修改这里,增加资源

在之前【韦东山嵌入式linux系列-驱动设计的思想(面向对象/分层/分离)-CSDN博客】的基础上写

编译,有三个ko文件

4 编译

在开发板挂载 Ubuntu 的NFS目录

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs/ /mnt

将ko文件和测试代码拷贝到挂载目录,安装驱动

insmod

注意这里有先后顺序(先加载board_A_led.ko、led_drv.ko都可以,最后加载chip_demo_gpio.ko)

执行测试程序

./led_drv_test /dev/winter_led0 on
./led_drv_test /dev/winter_led0 off

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值