Linux驱动学习第五天----基于总线驱动模型修改LED驱动

目录

一.总线驱动框架模型介绍

1.1介绍

1.2总线的匹配规则

1.2.1最先比较

1.2.2然后比较

1.2.3最后比较

1.2.4函数从上层到下层的调用关系

1.2.5常用函数

二.程序编写思路

2.1分配/设置/注册 platform_device 结构体(board_A_led.c)

2.2分配/设置/注册 platform_driver 结构体(chip_demo_gpio.c)

三.具体程序

应用程序:ledtest.c

上层驱动代码:(leddrv.c)

上层驱动头文件:(leddrv.h)

下层驱动资源文件(board_A_led.c)

led_resource.h

led_opr.h

四.总结与踩坑


一.总线驱动框架模型介绍

1.1介绍

        如果CPU和外设或者外设想要进行通讯或操作,需要将通信双方挂载到同一条总线上,这里的总线可以使具体的总线,如IIC,CAN总线等也可以是虚拟的总线,platform平台总线。

         总线设备驱动可分为总线,设备和驱动三个部分当将一个设备加入到总线上时,内核会在这条总线上寻找该设备对应的驱动当将一个驱动加入到一条总线上时,内核会在该总线上寻找与该驱动对应的设备。匹配的规则根据不同类型的总线及设备特征进行定义。

1.2总线的匹配规则

总线驱动结构体:

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};

总线设备结构体:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

1.2.1最先比较

        platform_device.driver_override 和 platform_driver.driver.name可以设置 platform_device 的 driver_override,强制选择某个 platform_driver。若定义了driver_override ,最先使用它去比较

1.2.2然后比较

        platform_device. name 和 platform_driver.id_table[i].name   Platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的{.name, .driver_data},其中的“name”表示该drv 支持的设备的名字,driver_data 是些提供给该 device 的私有数据

1.2.3最后比较

platform_device.name 和 platform_driver.driver.name   platform_driver.id_table 可能为空,这时可以根据 platform_driver.driver.name 来寻找同名的 platform_device。

1.2.4函数从上层到下层的调用关系

1.2.5常用函数

这些函数可查看内核源码:drivers/base/platform.c,根据函数名即可知道其含义。
下面摘取常用的几个函数。

注册/反注册

platform_device_register/ platform_device_unregister
platform_driver_register/ platform_driver_unregister
platform_add_devices // 注册多个 device

platform_device_unregister//注销
platform_driver_unregister

获得资源:

返回该dev中某类型(type)中的第几个(num),拿到资源结构体

struct resource *platform_get_resource(struct platform_device *dev, unsigned int
type,unsigned int num)

返回该 dev 所用的第几个(num)中断:

int platform_get_irq(struct platform_device *dev, unsigned int num)

通过名字(name)返回该 dev 的某类型(type)资源:

struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,const char *name)

通过名字(name)返回该 dev 的中断号:

int platform_get_irq_byname(struct platform_device *dev, const char *name)

二.程序编写思路

2.1分配/设置/注册 platform_device 结构体(board_A_led.c)

        在里面定义资源(引脚,中断), 指定设备的名字。

2.2分配/设置/注册 platform_driver 结构体(chip_demo_gpio.c)

             里面probe函数在总线上的设备和驱动匹配成功之后会执行,注册设备,得到资源那边的存储资源的结构体。

        注册/配置platform_driver结构体,注册总线。

三.具体程序

应用程序:ledtest.c


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

/*
 * ./ledtest /dev/100ask_led0 on
 * ./ledtest /dev/100ask_led0 off
 */
int main(int argc, char **argv)
{
	int fd;
	char status;
	
	/* 1. 判断参数 */
	if (argc != 3) 
	{
		printf("Usage: %s <dev> <on | off>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 写文件 */
	if (0 == strcmp(argv[2], "on"))
	{
		status = 1;
		write(fd, &status, 1);
	}
	else
	{
		status = 0;
		write(fd, &status, 1);
	}
	
	close(fd);
	
	return 0;
}


上层驱动代码:(leddrv.c)

#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 "leddrv.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)

/*个人理解这里为什么要在这里单独再写一个API去注册设备
  major和led_class设备类是在上层驱动定义的,要在下层用的话太麻烦
*/


void led_class_create_device(int minor)
{
	
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */

}

void led_class_destroy_device(int minor)
{

	device_destroy(led_class, MKDEV(major, minor));

}

void register_led_operations(struct led_operations *opr)
{
	p_led_opr = opr;
}


EXPORT_SYMBOL(led_class_create_device);//导出到内核的全局符号表中,让其它的内核模块也能使用这个函数
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);

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

 	
// 	p_led_opr = get_board_led_opr();
	
	return 0;
}








/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);



	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");






上层驱动头文件:(leddrv.h)


#ifndef _LEDDRV_H
#define _LEDDRV_H

#include "led_opr.h"

void led_class_create_device(int minor);
void led_class_destroy_device(int minor);
void register_led_operations(struct led_operations *opr);
//void led_device_create(int minor);
//void led_device_destory(int minor);

#endif /* _LEDDRV_H */


下层驱动资源文件(board_A_led.c)


#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 <linux/platform_device.h>

#include "led_resource.h"

static struct led_resource board_A_led = {
	.pin = GROUP_PIN(5,3),
};

struct led_resource *get_led_resource(void)
{
	return &board_A_led;
}

static struct resource resources[] = {
	{
		.start = GROUP_PIN(3, 1),
		.flags = IORESOURCE_IRQ,
	},
	{
		.start = GROUP_PIN(5,8),
		.flags = IORESOURCE_IRQ,
	}


};



static struct platform_device board_A_dev = {
	.name = "100ask_led",
	.num_resources = ARRAY_SIZE(resources),
	.resource = resources,

};

static int led_dev_init(void)
{
	int err;

	err = platform_device_register(&board_A_dev);

	return 0;
}

static void led_dev_exit(void)
{
	platform_device_unregister(&board_A_dev);

	
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

下层驱动,驱动操作文件(chip_demo_gpio.c)

#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 <linux/platform_device.h>
#include "leddrv.h"
#include "led_opr.h"
#include "led_resource.h"
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/module.h>
#include <asm/io.h>




//static struct led_resource *led_rsc = NULL;
static int g_ledpins[100];
static int g_ledcnt = 0;

static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */	   
{


	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, GROUP(g_ledpins[which]));



	switch (GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("set pin of group 0 ....\n");
			break;
		}



		case 1:
		{
			printk("set pin of group 1 ....\n");
			break;
		}



		case 2:
		{
			printk("set pin of group 2 ....\n");
			break;
		}

		case 3 :
		{
			printk("set pin of group 3 ....\n");
			break;
		}

		case 5:
		{
			printk("set pin of group 5 ...\n");
			break;
		}

	}
	
	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__, g_ledpins[which], status ? "on" : "off");


	switch (GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("set pin of group 0 ....\n");
			break;
		}



		case 1:
		{
			printk("set pin of group 1 ....\n");
			break;
		}



		case 2:
		{
			printk("set pin of group 2 ....\n");
			break;
		}

		case 3 :
		{
			printk("set pin of group 3 ....\n");
			break;
		}

		case 5:
		{
			printk("set pin of group 5 ...\n");
			break;
		}
		case 8:
		{
			printk("set pin 8\n");
			break;
		}

	}
	return 0;
}

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

struct led_operations *get_board_led_opr(void)
{
	return &board_demo_led_opr;
}

/*系统检测到驱动和设备都注册了之后会调用这个函数*/
static int chip_demo_gpio_led_probe(struct platform_device *dev)
{
	int i = 0;
	struct resource *res;

	while(1)
	{
		/*
		
		platform_get_resource() 函数的定义位于 linux/platform_device.h 头文件中。其函数原型如下:
		c
		struct resource *platform_get_resource(struct platform_device *dev, unsigned int type,
							unsigned int num);	
		该函数的参数依次为:
		
			dev: 指向要获取资源的设备对象。
			type: 要获取的资源的类型,通常可以使用宏定义 IORESOURCE_MEM 或 IORESOURCE_IRQ。
			num: 获取指定类型资源的索引,从 0 开始。
		
		其函数实现具体流程如下:
		
			首先通过调用 dev->resource 获取该设备的第一个资源(如果有的话)。
			然后循环查找 dev->resource 链表,找到所需的资源节点。
			最后返回找到的资源节点的指针。
		
		*/

	
		res = platform_get_resource(dev, IORESOURCE_IRQ, i++);//相当于就是获得board_A_led.c那边的resource结构体

		if(!res)
			break;

		/*记录引脚*/
		g_ledpins[g_ledcnt] = res->start;//对应boar_A_led.c中的结构体,得到引脚

		/*device_creat*/
		led_class_create_device(g_ledcnt);//创建设备

		g_ledcnt++;

	}


	return 0;
}

/*删除模块时会自动执行这个函数,内核自动调用*/
static int chip_demo_gpio_led_remove(struct platform_device *dev)
{
    struct resource *res;
    int i = 0;

    while (1)
    {
        res = platform_get_resource(dev, IORESOURCE_IRQ, i);//获得board_A_led.c那边的resource结构体
        if (!res)
            break;
        
        led_class_destroy_device(i);//注销设备
        i++;
        g_ledcnt--;
    }
    return 0;

}

static struct platform_driver chip_demo_gpio_driver = {
	.probe = chip_demo_gpio_led_probe,
	.remove = chip_demo_gpio_led_remove,
	.driver = {
		.name = "100ask_led",
	}
};


static int chip_demo_gpio_drv_init(void)
{
	int err;

	err=  platform_driver_register(&chip_demo_gpio_driver);

	register_led_operations(&board_demo_led_opr);
	

	return 0;

}

static void chip_demo_gpio_dev_exit(void)
{
	platform_driver_unregister(&chip_demo_gpio_driver);//注销设备

}


module_init(chip_demo_gpio_drv_init);
module_exit(chip_demo_gpio_dev_exit);
MODULE_LICENSE("GPL");


led_resource.h

#ifndef _LED_RESOURCE_H
#define _LED_RESOURCE_H



/*GPIO3_0*/
/* Ibit[31:16] = group*/
/*bit[15:0]  = which pin*/
#define GROUP(x) (x >> 16)
#define PIN(x) (x & 0XFFFF)
#define GROUP_PIN(g,p) ((g<<16) | (p))//这里将引脚转换成16进制数通过上面式子运算可以得出想要结果

struct led_resource {
	int pin;

};


struct led_resource *get_led_resource(void);//要进行外部调用所以声明一下


#endif

led_opr.h

#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

四.总结与踩坑

4.1符号声明外部函数调用以及交叉依赖

EXPORT_SYMBOL(led_class_create_device);//导出到内核的全局符号表中,让其它的内核模块也能使用这个函数
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);

相当于让其它文件也能够使用这几个函数,但是也需要在头文件里面进行声明才能够使用

避免交叉依赖,我需要调用你的API才能行,这里面的交叉依赖指的是,我加载模块的时候需要你的函数,所以需要你先加载,而你又需要我的模块这样导致两个模块都没有办法加载

4.2驱动与设备树的关系

        4.2.1设备树的由来:

        随着 ARM 芯片的流行,内核中针对这些 ARM 板保存有大量的、没有技术含量的文件。

Linus(linux研发设计者) 大发雷霆:"this whole ARM thing is a f*cking pain in theass"。

        于是,Linux 内核开始引入设备树。设备树并不是重新发明出来的,在 Linux 内核中其他平台如 PowerPC,早就使用设备树来描述硬件了。Linus 发火之后,内核开始全面使用设备树来改造,神人就神人。

        4.2.2设备树现在理解前期介绍:

        有一种错误的观点,说“新驱动都是用设备树来写了”。设备树不可能用来写
驱动

        想要操作硬件就需要操作复杂的寄存器,如果设备树可以操作寄存器,那么它就是驱动了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值