平台总线式驱动开发(lv12-day2)

本文介绍了Linux平台总线式驱动开发,包括为了解决硬编码驱动问题而引入的struct device和struct device_driver,以及platform总线的核心数据类型。讨论了platform的三种设备与驱动匹配方式:名称匹配、id匹配和设备树匹配,并通过led驱动名称匹配版举例说明。

1 平台总线式驱动开发引入。

硬编码式的驱动开发带来的问题:

  1. 垃圾代码太多
  2. 结构不清晰
  3. 一些统一设备功能难以支持
  4. 开发效率低下

所以要解决这些问题需要将设备和驱动分离
struct device来表示一个具体设备,主要提供具体设备相关的资源(如寄存器地址、GPIO、中断等等)
​ struct device_driver来表示一个设备驱动,一个驱动可以支持多个操作逻辑相同的设备
​ 带来的问题-------怎样将二者进行关联(匹配)?
​ 硬件上同一总线上的设备遵循一致的时序通信,在其基础上增加管理设备和驱动的软件功能 于是引入总线(bus),各种总线的核心框架由内核来实现,通信时序一般由SOC供应商支持内核中用struct bus_type来表示一种总线,总线可以是实际存在的总线,也可以是虚拟总线:

  1. 实际总线:提供时序通信方式 + 管理设备和驱动
  2. 虚拟总线:仅用来管理设备和驱动(最核心的作用之一就是完成设备和驱动的匹配)

其次根据设备树,在系统启动时自动产生每个节点对应的设备。

2 基本数据类型
2.1 struct device
struct device
{
struct bus_type *bus; //总线类型
dev_t devt; //设备号
struct device_driver *driver; //设备驱动
struct device_node *of_node;//设备树中的节点,重要
void (*release)(struct device *dev);//删除设备,重要
 //.......
}
2.2 struct device_driver
struct device_driver
{
const char *name; //驱动名称,匹配device用,重要
struct bus_type *bus; //总线类型
struct module *owner; //模块THIS_MODULE
const struct of_device_id *of_match_table;//用于设备树匹配 of_match_ptr(某struct of_device_id对象地址) 重要
//......
};
struct of_device_id
{
char name[32];//设备名
char type[32];//设备类型
char compatible[128]; //用于device和driver的match,重点
};
3 platform总线驱动

platform是一种虚拟总线,主要用来管理那些不需要时序通信的设备

3.1 核心数据类型之platform_device
struct platform_device
{
    const char    *name;   //匹配用的名字
    int        id;//设备id,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1
    struct device    dev;  //设备模块必须包含该结构体
    struct resource    *resource;//资源结构体 指向资源数组
u32        num_resources;//资源的数量 资源数组的元素个数
    const struct platform_device_id    *id_entry;//设备八字
};
struct platform_device_id
{
char name[20];//匹配用名称
kernel_ulong_t driver_data;//需要向驱动传输的其它数据
};
struct resource
{
resource_size_t start; //资源起始位置
 resource_size_t end; //资源结束位置
const char *name;
unsigned long flags; //区分资源是什么类型的
};
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_IRQ 0x00000400
/*
flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ  这两种。start 和 end 的含义会随着 flags而变更,如
a -- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;注意不同MEM的地址值不能重叠
b -- flags为 IORESOURCE_IRQ  时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值
*/
/**
*注册:把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
*/
int platform_device_register(struct platform_device *);
/**
*注销:把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release;
*/
void platform_device_unregister(struct platform_device *);
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
/*
 功能:获取设备资源
参数:dev:平台驱动
type:获取的资源类型
num:对应类型资源的序号(如第0个MEM、第2个IRQ等,不是数组下标)
返回值:成功:资源结构体首地址,失败:NULL
*/
3.2 核心数据类型之platform_driver
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;  //能够支持的设备八字数组,用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束
};
int platform_driver_register(struct platform_driver*pdrv);
/*
功能:注册平台设备驱动
参数:pdrv:平台设备驱动结构体
 返回值:成功:0
失败:错误码
*/
void platform_driver_unregister(struct platform_driver*pdrv);
4 platform的三种匹配方式
4.1 名称匹配

一个驱动只对应一个设备 ----- 优先级最低

4.1.2名称匹配驱动基础框架

hello_device.c (设备)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
//做清理工作
void hello_dev_release(struct device *pdev)
{
    printk("hello_dev_release is called\n");
}
//资源
struct resource hello_dev_res[]=
{
	[0]={.start=0x1000,.end=0x1003,.name="reg1",.flags=IORESOURCE_MEM},
    [1]={.start=0x2000,.end=0x2003,.name="reg2",.flags=IORESOURCE_MEM},
    [2]={.start=10,.end=10,.name="irq1",.flags=IORESOURCE_IRQ},
	[3]={.start=0x3000,.end=0x3003,.name="reg3",.flags=IORESOURCE_MEM},
    [4]={.start=100,.end=100,.name="irq2",.flags=IORESOURCE_IRQ},
	[5]={.start=62,.end=62,.name="irq3",.flags=IORESOURCE_IRQ},
};
struct platform_device hello_device=
{
	//用于名称匹配
	.name="hello",
	//设备回收时会被调用
	.dev.release=hello_dev_release,
	.resource=hello_dev_res,
	//计算资源个数
	.num_resources=ARRAY_SIZE(hello_dev_res),
};
//初始化
int __init hello_device_init(void)
{
   
    platform_device_register(&hello_device);
	return 0;
}
//退出
void __exit hello_device_exit(void)
{

    platform_device_unregister(&hello_device);
}

MODULE_LICENSE("GPL");
module_init(hello_device_init);
module_exit(hello_device_exit);

hello_driver.c(驱动代码)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
//匹配成功将会被调用
int hello_driver_probe(struct platform_device *p_pltdev)
{
	struct resource *pres =NULL;
	printk("hello_driver_probe is called\n");
	//取第3个IORESOURCE_MEM资源
	pres=platform_get_resource(p_pltdev,IORESOURCE_MEM,2);
	printk("res.start=0x%d\n",(unsigned int)pres->start);
	//取第2个IORESOURCE_IRQ资源
	pres=platform_get_resource(p_pltdev,IORESOURCE_IRQ,1);
	printk("res.start=0x%d\n",(unsigned int)pres->start);

	return 0;

}
//匹配失败会被调用
int hello_driver_remove(struct platform_device *p_pltdev)
{

	printk("hello_driver_remove is called\n");
	return 0;
}
struct platform_driver hello_driver=
{
    .driver.name="hello",
	.probe=hello_driver_probe,
	.remove=hello_driver_remove,
};
int __init hello_driver_init(void)
{
	platform_driver_register(&hello_driver);
	return 0;
}
void __exit hello_driver_exit(void)
{

	platform_driver_unregister(&hello_driver);
}
MODULE_LICENSE("GPL");
module_init(hello_driver_init);
module_exit(hello_driver_exit);
4.2 id匹配(可想象成八字匹配)

一个驱动可以对应多个设备 ------优先级次低 ​ device模块中,id的name成员必须与struct platform_device中的name成员内容一致 ​ 因此device模块中,struct platform_device中的name成员必须指定 ​ driver模块中,struct platform_driver成员driver的name成员必须指定,但与device模块中name可以不相同。

4.3 设备树匹配

内核启动时根据设备树自动产生的设备 ------ 优先级最高 使用compatible属性进行匹配,注意设备树中compatible属性值不要包含空白字符 ​ id_table可不设置,但struct platform_driver成员driver的name成员必须设置。

5 led驱动名称匹配版

fs4412_led.h

#ifndef MY_CHAR_H
#define MY_CHAR_H
#include <asm/ioctl.h>
#define LED_DEV_MAGIC 'g'
//#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
//#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON  _IO(LED_DEV_MAGIC,1)
// led2
#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44
// led3
#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24
//led4,5
#define GPF3CON 0x114001E0
#define GPF3DAT 0x114001E4

#endif

// 这个文件编写的是设备信息(资源等等)
fs4412leds_device.c(设备)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include "fs4412_led.h"
//做清理工作,设备回收时被调用
void fs4412led_dev_release(struct device *pdev)
{
    printk("fs4412led_dev_release is called\n");
}
//资源,这里是各个led的各个GPIO的控制寄存器的地址和读写寄存器的地址
struct resource fs4412led_dev_res[]=
{
	[0]={.start=GPX1CON,.end=GPX1CON+3,.name="GPX1CON",.flags=IORESOURCE_MEM},
    [1]={.start=GPX1DAT,.end=GPX1DAT+3,.name="GPX1DAT",.flags=IORESOURCE_MEM},

    [2]={.start=GPX2CON,.end=GPX2CON+3,.name="GPX2CON",.flags=IORESOURCE_MEM},
    [3]={.start=GPX2DAT,.end=GPX2DAT+3,.name="GPX2DAT",.flags=IORESOURCE_MEM},

    [4]={.start=GPF3CON,.end=GPF3CON+3,.name="GPF3CON",.flags=IORESOURCE_MEM},
    [5]={.start=GPF3DAT,.end=GPF3DAT+3,.name="GPF3DAT",.flags=IORESOURCE_MEM},
};
struct platform_device fs4412led_device=
{
	//用于名称匹配
	.name="fs4412leds",
	//设备回收时会被调用
	.dev.release=fs4412led_dev_release,
	.resource=fs4412led_dev_res,
	//计算资源个数
	.num_resources=ARRAY_SIZE(fs4412led_dev_res),
};
int __init fs4412led_device_init(void)
{
    platform_device_register(&fs4412led_device);
	return 0;
}
void __exit fs4412led_device_exit(void)
{

    platform_device_unregister(&fs4412led_device);
}

MODULE_LICENSE("GPL");
module_init(fs4412led_device_init);
module_exit(fs4412led_device_exit);

//这个是驱动代码
fs4412leds_driver.c(驱动)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/atomic.h>

#include <linux/mm.h>
#include <linux/slab.h>

#include <linux/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>



#include "fs4412_led.h"
#define fs4412leds_driver_DEV_CNT 1
int major =11;//主设备号
int minor = 0;//次设备号
int fs4412leds_driver_num =fs4412leds_driver_DEV_CNT;//设备数量
struct fs4412leds_driver_dev
{

	struct cdev mydev;//建立一个设备对象
	//volatile为不做优化
	volatile unsigned long * led2con;
	volatile unsigned long * led2dat;
	volatile unsigned long * led3con;
	volatile unsigned long * led3dat;
	volatile unsigned long * led4con;
	volatile unsigned long * led4dat;
	volatile unsigned long * led5con;
	volatile unsigned long * led5dat;
	struct class *pcls;
	struct device *pdev;
};
struct fs4412leds_driver_dev *gmydev_arr[fs4412leds_driver_DEV_CNT];
//打开
int fs4412leds_driver_open(struct inode *pnode,struct file *pfile)
{
	/*为避免使用全局变量*/
	/*
	 *container_of是一个宏,可以用来求全局结构体变量的地址
	 *已知其成员的地址和成员名字,就可以求出其地址
	 *pnode->i_cdev:指向插入系统的设备对象(这里也是gmydev的成员mydev的地址)
	 *pnode->private_data:本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
	 */

	pfile->private_data=(void *)(container_of(pnode->i_cdev,struct fs4412leds_driver_dev,mydev));
	return 0;
}
//关闭
int fs4412leds_driver_close(struct inode *pnode,struct file *pfile)
{


	return 0;
}

void led_on(struct fs4412leds_driver_dev * pmydev,int ledno)
{
     switch(ledno)
	 {
	   case 2:
		    printk("2\n");
	        writel(readl(pmydev->led2dat) | (0x1<<7),pmydev->led2dat);//关闭led2
		    break;
	   case 3:
		    printk("3\n");
	        writel(readl(pmydev->led3dat) | (0x1<<0),pmydev->led3dat);//关闭led2
		    break;
	   case 4:
		    printk("4\n");
	        writel(readl(pmydev->led4dat) | (0x1<<4),pmydev->led4dat);//关闭led2
		    break;
       case 5:
		    printk("5\n");
	        writel(readl(pmydev->led5dat) | (0x1<<5),pmydev->led5dat);//关闭led2
		    break;
	 }
}
void led_off(struct fs4412leds_driver_dev * pmydev,int ledno)
{
     switch(ledno)
	 {
	   case 2:
			writel(readl(pmydev->led2dat) & (~(1<<7)),pmydev->led2dat);//关闭led2
		    break;
	   case 3:
			writel(readl(pmydev->led3dat) & (~(1<<0)),pmydev->led3dat);//关闭led3
		    break;
	   case 4:
			writel(readl(pmydev->led4dat) & (~(1<<4)),pmydev->led4dat);//关闭led4
		    break;
       case 5:
			writel(readl(pmydev->led5dat) & (~(1<<5)),pmydev->led5dat);//关闭led5
		    break;
	 }

}
long fs4412leds_driver_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
  struct fs4412leds_driver_dev * pmydev = (struct fs4412leds_driver_dev*)pfile->private_data;
  if(arg<2||arg>5)
  {
	  return -1;
  }
  switch(cmd)
  {
    case MY_LED_ON:
		led_on(pmydev,arg);
		break;
    case MY_LED_OFF:
		led_off(pmydev,arg);
		break;
    default:
		return -1;
  }
  return 0;
}
struct file_operations myops={
	.owner=THIS_MODULE,
	.open=fs4412leds_driver_open,
	.unlocked_ioctl=fs4412leds_driver_ioctl,
	.release =fs4412leds_driver_close,
};
/**
 *  @brief           将寄存器物理地址映射为虚拟地址
 *  @param[in]       pmydev:设备对象结构体                                       
 *  @retval          NULL
 *
 */
void ioremap_ledreg(struct fs4412leds_driver_dev *pmydev,struct platform_device *p_pltdev)
{
	/*得到虚拟内存地址*/
	struct resource *pres=NULL;
    //获取相应资源
    pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,2);
	pmydev->led2con=ioremap(pres->start,4);	
    pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,3);
	pmydev->led2dat=ioremap(pres->start,4);	

    pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,0);
	pmydev->led3con=ioremap(pres->start,4);	
    pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,1);
	pmydev->led3dat=ioremap(pres->start,4);	

    pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,4);
	pmydev->led4con=ioremap(pres->start,4);	
    pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,5);
	pmydev->led4dat=ioremap(pres->start,4);	

	pmydev->led5con=pmydev->led4con;	
	pmydev->led5dat=pmydev->led4dat;	

}

/**
 *  @brief           将寄存器物理地址映射为虚拟地址
 *  @param[in]       pmydev:设备对象结构体                                       
 *  @retval          NULL
 *
 */
void set_output_ledconreg(struct fs4412leds_driver_dev *pmydev)
{
	writel((readl(pmydev->led2con) & (~(0xF<<28))) | (0x1<<28),pmydev->led2con);//设置GPX2_7为输出模式
	writel((readl(pmydev->led3con) & (~(0xF<<0)))  | (0x1<<0),pmydev->led3con);//设置GPX1_0为输出模式
	writel((readl(pmydev->led4con) & (~(0xF<<16))) | (0x1<<16),pmydev->led4con);//设置GPF3_4为输出模式
	writel((readl(pmydev->led5con) & (~(0xF<<20))) | (0x1<<20),pmydev->led5con);//设置GPF3_5为输出模式

	writel(readl(pmydev->led2dat) & (~(1<<7)),pmydev->led2dat);//关闭led2
	writel(readl(pmydev->led3dat) & (~(1<<0)),pmydev->led3dat);//关闭led3
	writel(readl(pmydev->led4dat) & (~(1<<4)),pmydev->led4dat);//关闭led4
	writel(readl(pmydev->led5dat) & (~(1<<5)),pmydev->led5dat);//关闭led5


}
/**
 *  @brief           解除寄存器物理地址和虚拟地址映射
 *  @param[in]       pmydev:设备对象结构体                                       
 *  @retval          NULL
 *
 */
void iounremap_ledreg(struct fs4412leds_driver_dev *pmydev)
{
	iounmap(pmydev->led2con);
	pmydev->led2con=NULL;
	iounmap(pmydev->led2dat);
	pmydev->led2dat=NULL;

	iounmap(pmydev->led3con);
	pmydev->led3con=NULL;
	iounmap(pmydev->led3dat);
	pmydev->led3dat=NULL;

	iounmap(pmydev->led4con);
	pmydev->led4con=NULL;
	iounmap(pmydev->led4dat);
	pmydev->led4dat=NULL;

	pmydev->led5con=NULL;
	pmydev->led5dat=NULL;

}
//初始化
int fs4412leds_driver_probe(struct platform_device *p_pltdev)
{
	int ret = 0;
	int i = 0;
	dev_t devno =MKDEV(major,minor);//组合成一个完整的设备号
	/*
	 * 手动申请设备号
	 *int register_chrdev_region(dev_t from, unsigned count, const char *name)
	 *参数分别为 设备号,设备数量,设备名字 
	 */
	ret=register_chrdev_region(devno,fs4412leds_driver_num,"fs4412leds");
	if(ret)
	{
		/*
		 *自动申请设备号
		 *int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
		 * dev:分配设备号成功后用来存放分配到的设备号
		 * baseminior:起始的次设备号,一般为0
		 * count:申请的设备数量
		 * name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
		 */
		ret = alloc_chrdev_region(&devno,minor,fs4412leds_driver_num,"fs4412leds");
		if(ret)
		{
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//分离主设备号(手动申请失败需要)
	}
	for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
	{
		gmydev_arr[i]=(struct fs4412leds_driver_dev*)kmalloc(sizeof(struct fs4412leds_driver_dev),GFP_KERNEL);
		if(NULL==gmydev_arr[i])
		{
			printk("kmalloc failed\n");
			unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
			return -1;

		}
		memset(gmydev_arr[i],0,sizeof(struct fs4412leds_driver_dev));//初始化
	}

	/*指定操作函数集*/
	for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
	{	
		devno =MKDEV(major,minor+i);
		cdev_init(&gmydev_arr[i]->mydev,&myops);//初始化设备对象
		/*将struct_cdev对象添加到内核对应的数据结构里*/
		gmydev_arr[i]->mydev.owner=THIS_MODULE;
		cdev_add(&gmydev_arr[i]->mydev,devno,1);//添加设备
		/*ioremap*/
		ioremap_ledreg(gmydev_arr[i],p_pltdev);//映射地址
		set_output_ledconreg(gmydev_arr[i]);//led初始化
		/*con_register set output*/
		gmydev_arr[i]->pcls=class_create(THIS_MODULE,"fs4412led");
		if(IS_ERR(gmydev_arr[i]->pcls))
		{
			printk("class_create failed\n");
			cdev_del(&gmydev_arr[i]->mydev);
		    iounremap_ledreg(gmydev_arr[i]);//取消映射
		    kfree(gmydev_arr[i]);
	        unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
		    gmydev_arr[i]=NULL;
				
			return -1;
		}
		gmydev_arr[i]->pdev=device_create(gmydev_arr[i]->pcls,NULL,devno,NULL,"fs4412led");
		if(NULL==gmydev_arr[i]->pdev)
		{
			printk("device_create failed\n");
			cdev_del(&gmydev_arr[i]->mydev);
		    iounremap_ledreg(gmydev_arr[i]);//取消映射
		    kfree(gmydev_arr[i]);
	        unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
		    gmydev_arr[i]=NULL;
			class_destroy(gmydev_arr[i]->pcls);
				
			return -1;

		}
	}

	return 0;
}
int fs4412leds_driver_remove(struct platform_device *p_pltdev)
{
	dev_t devno = MKDEV(major,minor);
	int i=0;
	/*iounremap*/
	for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
	{
		iounremap_ledreg(gmydev_arr[i]);//取消映射
		device_destroy(gmydev_arr[i]->pcls,devno);
		class_destroy(gmydev_arr[i]->pcls);
		cdev_del(&gmydev_arr[i]->mydev);
	}
	for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
	{

		kfree(gmydev_arr[i]);
		gmydev_arr[i]=NULL;
	}
	unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
	return 0;
}

struct platform_driver fs4412leds_driver =
{
	.driver.name="fs4412leds",
	.probe=fs4412leds_driver_probe,
	.remove=fs4412leds_driver_remove,
};
int __init fs4412leds_driver_init(void)
{
	platform_driver_register(&fs4412leds_driver);
	return 0;
}

void __exit fs4412leds_driver_exit(void)
{
	platform_driver_unregister(&fs4412leds_driver);
}
MODULE_LICENSE("GPL");

module_init(fs4412leds_driver_init);
module_exit(fs4412leds_driver_exit);

注意事项:.name=“fs4412leds” 和.driver.name=“fs4412leds”,需要的值要相同不然设备不能被成功建立。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值