bsp开发学习2平台设备驱动(代码片段)

与光同程 与光同程     2022-12-08     281

关键词:

文章目录

Linux设备驱动模型

Linux 中的设备驱动模型组成

(1)类class、总线bus、设备device、驱动driver
(2)kobject和对象生命周期
(3)sysfs
(4)udev

为什么要使用设备驱动模型

我对于Linux 引入设备驱动模型的理解是就在于将一份驱动代码分成两份,一份代码是通用的也就是驱动driver ,令一份代码不是通用的会随着板子CPU的不同,发生改变。

设备驱动模型的底层架构

sysfs 目录

通过sys文件系统下面的目录和文件可以清楚的了解到Linux系统中的设备情况和组织关系。

sysfs提供一种可以显式描述内核对象,对象属性以及对象关系的方法。

sysfs在内核空间的组成要素在用户空间的体现
kobject目录
attribute文件
relationship链接

kobject

树形结构中每一个目录与一个kobject对象相对应,其包含了目录的组织结构和名字等信息。在Linux系统中, kobject结构体是组成设备驱动模型的基本结构。

(1)kobject提供了最基本的设备对象管理能力,每一个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录,而不是文件。

(2)各种对象最基本单元,提供一些公用型服务如:对象引用计数、维护对象链表、对象上锁、对用户空间的表示
(3)设备驱动模型中的各种对象其内部都会包含一个kobject
(4)地位相当于面向对象体系架构中的总基类

struct kobject 
 
const char		*name;//kobject的名字,且作为一个目录的名字
struct list_head	entry;//连接下一个kobject结构
struct kobject		*parent;//指向父亲kobject结构体,如果父设备存在
struct kset		*kset;  //指向kset集合
struct kobj_type	*ktype;  //指向kobject的属性描述符
struct sysfs_dirent	*sd;     //对应sysfs的文件目录
struct kref		kref;   //kobject的引用计数
unsigned int state_initialized:1; //表示该kobject是否初始化
unsigned int state_in_sysfs:1;   //表示是否加入sysfs中
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
;

kobj_type

使用该kobject设备的共同属性
(1)很多书中简称为ktype,每一个kobject都需要绑定一个ktype来提供相应功能
(2)关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store)
(2)关键点2:attribute,提供在sysfs中以文件形式存在的属性,其实就是应用接口


struct kobj_type 
	void (*release)(struct kobject *kobj);//释放kobject和其占用的函数
	const struct sysfs_ops *sysfs_ops;  //操作一个属性数组的方法
	struct attribute **default_attrs;  //属性数组的方法
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	const void *(*namespace)(struct kobject *kobj);
;

kset

kset是具有相同类型的kobject的集合

kset包含了一个kobject,其实它相当于一个链表节点,虽然Kobject包含了kset元素
(1)kset的主要作用是做顶层kobject的容器类
(2)kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构
(3)可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起

struct kset 
	struct list_head list;  //连接链表
	spinlock_t list_lock;  //链表的自旋锁
	struct kobject kobj;  //内嵌的kobject结构体,说明kset本身也是一个目录
	const struct kset_uevent_ops *uevent_ops;  //热插拔事件
;

设备驱动模型三大组件

不管是平台总线还是IIC总线都都有这样的调用路线:

当系统发现了新设备或者新驱动就会掉用相应总线的Match()进行匹配,当找到后就会掉用相对应的总线的Probe函数,最后Probe函数再调用驱动自己的Probe函数

总线

struct bus_type 
 
const char		*name;  //总线类型名
struct bus_attribute	*bus_attrs;  //总线属性和导出到sysfs中的方法
struct device_attribute	*dev_attrs;  //设备属性和导出到sysfs中的方法
struct driver_attribute	*drv_attrs;  //驱动程序属性和导出到sysfs中的方法
 
//匹配函数,检验参数2中的驱动是否支持参数1中的设备
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);  //探测设备
int (*remove)(struct device *dev); //移除设备
void (*shutdown)(struct device *dev); //关闭函数
 
int (*suspend)(struct device *dev, pm_message_t state);//改变设备供电状态,使其节能
int (*resume)(struct device *dev);  //恢复供电状态,使其正常工作
 
const struct dev_pm_ops *pm;  //关于电源管理的操作符
 
struct bus_type_private *p;  //总线的私有数据
;

设备

在Linux设备驱动模型中,每一个设备都由一个device结构体来描述。device结构体包含了设备所具有的一些通用信息。对于驱动开发人员来说,当遇到新设备时,需要定义一个新的设备结构体,将device作为新结构体的成员。这样就可以在新结构体中定义新设备的一些信息,而设备通用的信息就使用device结构体来表示。使用device结构体的另一个好处是,可以通过device轻松地将新设备加入设备驱动模型的管理中。

device中的大多数函数被内核使用,驱动开发人员不用关注

(1)struct device是硬件设备在内核驱动框架中的抽象
(2)device_register用于向内核驱动框架注册一个设备
(3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device

struct device 
 
struct klist_klist children;/*连接子设备的链表*/
struct device *parent;/*指向父设备的指针*/
struct kobject kobj;/*内嵌的kobject结构体*/
char bus_id[BUS ID SIZE];/*连接到总线上的位置*/ 
unsigned uevent suppress:1;/*是否支持热插拔事件*/
const char init_name;/*设备的初始化名字*/
struct device_type *type;/*设备相关的特殊处理函数*/
struct bus_type *bus;/*指向连接的总线指针*/
struct device_driver *driver;/*指向该设备的驱动程序*/
void *driver data;/*指向驱动程序私有数据的指针*/
struct dev_pm info power;/*电源管理信息*/ 
dev t deyt;/*设备号*/
struct class *class;/*指向设备所属类*/ 
struct attribute_group **groups;/*设备的组属性*/ 
void (*release) (struct device *dev);/*释放设备描述符的回调函数*/

驱动

(1)struct device_driver是驱动程序在内核驱动框架中的抽象
(2)关键元素1:name,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
(3)关键元素2:probe,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理

struct device_driver 
	const char		*name;//设备驱动程序的名字
	struct bus_type		*bus;//指向驱动属于的总线
 
	struct module		*owner;//设备驱动自身模块
	const char		*mod_name;	/* used for built-in modules */
 
	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
 
#if defined(CONFIG_OF)
	const struct of_device_id	*of_match_table;
#endif
 
	int (*probe) (struct device *dev);//探测设备的方法,并检测设备驱动可以控制哪些设备
	int (*remove) (struct device *dev);//移除设备调用的方法
	void (*shutdown) (struct device *dev);//关闭设备的方法
	int (*suspend) (struct device *dev, pm_message_t state);//设备处于低功耗的方法
	int (*resume) (struct device *dev);//恢复正常的方法
	const struct attribute_group **groups;//属性组
 
	const struct dev_pm_ops *pm;//电源管理
 
	struct driver_private *p;//设备驱动私有数据
;

平台设备驱动

概述

并不所有设备都会有实体总线,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。

系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下

struct bus_type platform_bus_type = 
 .name = "platform",
 .dev_groups = platform_dev_groups,
 .match = platform_match,
 .uevent = platform_uevent,
 .pm = &platform_dev_pm_ops,
;

match()成员函数,正是此成员函数确定了platform_device和platform_driver之间是如何进行匹配
有4种可能性,
一是基于设备树风格的匹配;
二是基于ACPI风格的匹配;
三是匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内);
第四种是匹配platform_device设备名和驱动的名字。

由以上分析可知,在设备驱动中引入platform的概念至少有如下好处。
1)使得设备被挂接在一个总线上,符合Linux 2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能。
2)隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
3)让一个驱动支持多个设备实例。譬如DM9000的驱动只有一份,但是我们可以在板级添加多份DM9000的platform_device,它们都可以与唯一的驱动匹配

平台设备驱动工作原理

核心变量与函数

(1)platform工作体系都定义在drivers/base/platform.c中
(2)两个结构体:platform_device和platform_driver
(3)两个接口函数:platform_device_register和platform_driver_register

struct platform_device 
    const char    * name;            // 平台总线下设备的名字
    int        id;    //设备名加ID名就得到了设备文件文件名
    struct device    dev;        // 所有设备通用的属性部分
    u32        num_resources;        // 设备使用到的resource的个数
    struct resource    * resource;    // 设备使用到的资源数组的首地址

    const struct platform_device_id    *id_entry;    // 设备ID表

    /* arch specific additions */
    struct pdev_archdata    archdata;            // 自留地,用来提供扩展性的
;

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;    // 设备ID表
;

在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

工作流程

(1)第一步:系统启动时在bus系统中注册platform
(2)第二步:内核移植的人负责提供platform_device
(3)第三步:写驱动的人负责提供platform_driver
(4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了

(1)platdata其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称····)
(2)这些数据在设备和驱动match之后,会由设备方转给驱动方。驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。
(3)这样做的好处是:驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。

不使用设备树的平台设备驱动

使用平台设备API只需要引用下面的头文件
include/linux/platform_device.h

platform_driver

1 struct platform_driver 
2 int (*probe)(struct platform_device *);
3 int (*remove)(struct platform_device *);
4 void (*shutdown)(struct platform_device *);
5 int (*suspend)(struct platform_device *, pm_message_t state);
6 int (*resume)(struct platform_device *);
7 struct device_driver driver;
8 const struct platform_device_id *id_table;
9 bool prevent_deferred_probe;
10 ;

当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用
platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,platform_driver_register 函数
原型如下所示:

int platform_driver_register (struct platform_driver *driver)
函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。

还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,
platform_driver_unregister 函数原型如下:

void platform_driver_unregister(struct platform_driver *drv)
函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。

platform_device

22 struct platform_device 
23 const char *name;
24 int id;
25 bool id_auto;
26 struct device dev;
27 u32 num_resources;
28 struct resource *resource;
29
30 const struct platform_device_id *id_entry;
31 char *driver_override; /* Driver name to force a match */
32
33 /* MFD cell pointer */
34 struct mfd_cell *mfd_cell;
35
36 /* arch specific additions */
37 struct pdev_archdata archdata;
38 ;

include/linux/ioport.h
18 struct resource 
19 resource_size_t start;
20 resource_size_t end;
21 const char *name;
22 unsigned long flags;
23 struct resource *parent, *sibling, *child;
24 ;

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,
然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)
函数参数和返回值含义如下:
pdev:要注册的 platform 设备。
返回值:负数,失败;0,成功。
如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform
设备,platform_device_unregister 函数原型如下:
void platform_device_unregister(struct platform_device *pdev)
函数参数和返回值含义如下:
pdev:要注销的 platform 设备。
返回值:无。
platform 设备信息框架如下所示:

平台设备驱动代码编写

该代码直接在globalmem驱动程序上修改 其实就上将之前在init 函数中做的工作转移到probe 函数 ,exit 函数中所作的工作转移到remove函数中,同时添加从设备驱动中获取参数的代码。

/*
 * @Copyright: 
 * @FileNames: 
 * @Description: 申请全局内存4096 并使用该内存进行 用户和内核之间的数据交换
 * @Author: 
 * @Date: 2022-07-29 09:03:02
 * @Version: V1.0
 * @LastEditTime: 2022-08-01 15:05:34
 */
#include <linux/module.h>//MOUDLE_XXX moudle_xxx
#include <linux/fs.h>    //file_operations register_chrdev_region alloc_chrdev_region
#include <linux/init.h>  //__init __exit
#include <linux/cdev.h>  //cdev
#include <linux/uaccess.h>//copy_to_user copy_from_user 
#include <linux/device.h> // device class 
#include <linux/platform_device.h>

#define GLOBAL_MEM_SIZE 0X1000
#define GLOBAL_MEM_DEBUG 1

#define GLOBAL_MEM_CLASS_NAME "global_mem" //创建的类名称
#define GLOBAL_MEM_NODE_NAME  "global_mem_0" //创建的节点名称


#define GLOBAL_MEM_MAJOR 250

//CMD
#define GLOBAL_MEM_CLEAR 0X01 //清理内存 命令


typedef struct 
    //设备驱动变量
    struct cdev cdev;
    //设备号变量
    dev_t devid;
    int major;
    int minor;      
    //设备节点相关变量
    struct class *class; 
    struct device *device;
    //用户变量
    unsigned char mem[GLOBAL_MEM_SIZE];

global_mem_t;

global_mem_t global_mem;

/**
 * @function: global_mem_open
 * @description: 打开设备 并将内存清零
 * @input: 
 * @output: 
 * @return *
 * @param inode *inode
 * @param file *flip
 */
static int global_mem_open(struct inode *inode ,struct file *flip)

    printk("OPEN GLOBAL MEM\\r\\n");
    flip->private_data=(void *)(&global_mem);
    return 0;

/**
 * @function: global_mem_release
 * @description: 释放设备节点
 * @input: 
 * @output: 
 * @return *
 * @param inode *inode
 * @param file *flip
 */
static int global_mem_release(struct inode *inode ,struct file *flip)

    printk("RELEASE GLOBAL MEM\\r\\n");
    return 0;


/**
 * @function: global_mem_read
 * @description: 拷贝数据到用户区
 * @input: 
 * @output: 
 * @return *
 * @param file* flip  文件结构体
 * @param char __user *buf 从用户区拷贝出来的数据
 * @param size_t size      传入数据大小
 * @param loff_t *ppos     当前数据位置
 */
static ssize_t global_mem_read(struct file* flip, char __user *buf,size_t size ,loff_t *ppos)

    int ret=0;
    printk("READ GLOBAL MEM\\r\\n");
    
    global_mem_t* dev=(global_mem_t*) flip->private_data;
    //当前文件指针所处于的位置
    unsigned long p=*ppos;
    //需要读出的个数
    unsigned int count=(unsigned int)size;
    //检查读取位置合法性
    if(p>=GLOBAL_MEM_SIZE)return 0;
    if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
    //拷贝数据到用户区
    ret=copy_to_user(buf,dev->mem+p,count);
    if (ret<0)
        ret=-EFAULT;
    else
        *ppos+=count;
        ret=count;
        if(GLOBAL_MEM_DEBUG)printk("READ %d BYTES FROM KERNEL At %d\\n",count,p);
    
    return ret;


/**
 * @function: global_mem_write
 * @description: 
 * @input: 
 * @output: 
 * @return *
 * @param file* flip
 * @param char __user *buf
 * @param size_t size
 * @param loff_t *ppos
 */
static ssize_t global_mem_write(struct file* flip, const char __user *buf,size_t size ,loff_t *ppos)

    int ret=0;
    printk("WRITE GLOBAL MEM\\r\\n");
    
    global_mem_t* dev=(global_mem_t*) flip->private_data;
    //当前文件指针所处于的位置
    unsigned long p=*ppos;
    //需要读出的个数
    unsigned int count=(unsigned int)size;
    //检查读取位置合法性
    if(p>=GLOBAL_MEM_SIZE)return 0;
    if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
    ret=copy_from_user(dev->mem+p,buf,count);
    if(ret<0)ret=-EFAULT;
    else
        *ppos+=count;
        ret=count;
        if(GLOBAL_MEM_DEBUG)printk("WRITE %d BYTES TO KERNEL AT %d\\n",(int

bsp开发学习1通用字符设备开发(代码片段)

Linux字符驱动设备API设备号申请API动态申请:intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name)静态申请:intregister_chrdev_region(dev_tfrom,unsignedcount,constchar*name)参数from是要申 查看详情

(linux)bsp板级支持包开发理解(代码片段)

...系统在不同的硬件平台上有效地运行,是嵌入式系统开发中需要解决的关键问题。解决的方法是在硬件平台和操作系统之间提供硬件相关层来屏蔽这些硬件的差异,给操作系统提供统一的运行环境,这种硬件相关层就... 查看详情

bsp开发学习4linux内核时间管理(代码片段)

Linux内核时间管理概述Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的... 查看详情

bsp开发学习5gpio子系统(代码片段)

文章目录基于GPIO子系统的IO口控制gpio子系统API函数1、gpio_request函数2、gpio_free函数3、gpio_direction_input函数4、gpio_direction_output函数5、gpio_get_value函数6、gpio_set_value函数GPIO子系统示例读取按键注意基于GPIO子系统的IO口控制gpio子系统... 查看详情

bsp开发学习5gpio子系统(代码片段)

文章目录基于GPIO子系统的IO口控制gpio子系统API函数1、gpio_request函数2、gpio_free函数3、gpio_direction_input函数4、gpio_direction_output函数5、gpio_get_value函数6、gpio_set_value函数GPIO子系统示例读取按键注意基于GPIO子系统的IO口控制gpio子系统... 查看详情

bsp开发之驱动开发

...系统仅仅有通过驱动程序才干訪问硬件。针对windowsce开发设备驱动。就是通过platformbuilder创建一个新的平台,然后依据硬件平台的须要插入或者移除驱动,须要改动的文件有platform.bib,platform.reg。按载入方式和接口分类,基于win... 查看详情

201892172018-2019-2《移动平台开发实践》第10周学习总结(代码片段)

教材学习内容总结第39章要点要点1:SharedPreference在Android中我们通常使用一个轻量级的存储类——SharedPreferences来保存用户偏好的参数。android.content.SharedPreferences接口提供了用于排序和读取应用程序设置的方法。通过调用PreferenceM... 查看详情

(linux)bsp板级支持包开发理解(代码片段)

...系统在不同的硬件平台上有效地运行,是嵌入式系统开发中需要解决的关键问题。解决的方法是在硬件平台和操作系统之间提供硬件相关层来屏蔽这些硬件的差异࿰ 查看详情

中国电信天翼物联网平台ctwing学习笔记——设备接入(tcp协议)(代码片段)

...xff08;AIoT)是中国电信倾力打造的智能终端汇聚、应用开发运行服务和轻量级应用提供的物联网平台,旨在降低物联网应用开发的准入门槛,降低智能硬件的接入门槛,提供端到端的解决方案,服务于终端开发... 查看详情

hi3861学习笔记(25)——接入华为云物联网平台iot(代码片段)

...创建产品模型产品模型用于描述设备具备的能力和特性。开发者通过定义产品模型,在物联网平台构建一款设备的抽象模型,使平台理解该款设备支持的服务、属性、命令等信息,如颜色、开关等。当定义完一款产品... 查看详情

bsp开发学习5gpio子系统(代码片段)

文章目录基于GPIO子系统的IO口控制gpio子系统API函数1、gpio_request函数2、gpio_free函数3、gpio_direction_input函数4、gpio_direction_output函数5、gpio_get_value函数6、gpio_set_value函数GPIO子系统示例读取按键注意基于GPIO子系统的IO口控制gpio子系统... 查看详情

rk3588平台开发系列讲解(spi篇)spi内核配置及驱动使用(代码片段)

...RK芯片作Master端2.3、SPI设备配置——RK芯片作Slave端2.4、SPI设备驱动介绍三、常见问题3.1、SPI无信号3.2、延迟采样时钟配置方案沉淀、分享、成长,让自己和他人都能有所收获! 查看详情

《移动平台开发实践》第三周学习任务(代码片段)

目录20189230杨静怡2018-2019-2《移动平台开发实践》第3周学习总结学习《Java和Android开发学习指南(第二版)》第5、6、8、9章——教材学习中的问题和解决过程代码调试中的问题和解决过程[代码托管]statistics.sh脚本运行结果的截图... 查看详情

bsp开发之ubootuboot常用命令以及代码分析(代码片段)

文章目录uboot使用uboot命令通用UBOOT命令信息查看命令环境变量操作命令内存操作命令网络操作命令磁盘操作命令boot操作命令其他操作命令RTL8197定制UBOOT命令UBOOT代码分析(通用UBOOT2016.01代码学习)顶层Makefile分析UBOOT启动过程代码分... 查看详情

bsp开发学习4linux内核时间管理(代码片段)

文章目录Linux内核时间管理概述内核延时长延时短延时睡眠延时内核定时器定时器驱动示例高精度定时器高精度定时器API初始化定时器设定超时回调函数。使用hrtimer_start激活该定时器取消定时器再次启动驱动示例Linux内核时间管... 查看详情

bsp开发学习4linux内核时间管理(代码片段)

文章目录Linux内核时间管理概述内核延时长延时短延时睡眠延时内核定时器定时器驱动示例高精度定时器高精度定时器API初始化定时器设定超时回调函数。使用hrtimer_start激活该定时器取消定时器再次启动驱动示例Linux内核时间管... 查看详情

rk3588平台开发系列讲解(audio篇)codec移植(代码片段)

平台内核版本安卓版本RK3588Linux5.10Android12文章目录一、原理图分析二、驱动移植1、驱动代码2、修改dconfig文件3、修改设备树3.1、添加codec3.2、添加声卡三、验证沉淀、分享、成长,让自己和他人都能有所收获! 查看详情

itop4412设备树学习一

...楚平台文件的方式注册设备和驱动。即之前的设备和驱动学习的内容。3.设备树源码的编译环境   1)安装设备树编译器:$sudoapt-getinstalldevice-tree-compiler   2)下载源码包:视频资料同目录的文件:itop4412_kerne... 查看详情