当前位置: 首页 > news >正文

linux驱动开发:linux设备模型

目录

1.基本概念

2.platform_device

3.platform_driver

4.platform_bus_type

5.平台驱动及相关函数

linux设备树

设备树的基本语法

节点名命名规范

默认意义的属性


1.基本概念

驱动模型 :由于计算机的外设越来越丰富,linux 内核中的驱动程序也越来越多,为了便于管理, linux 内核2 . 6 版本采用设备模型来进行管理。在设备模型中包括总线,驱动,设备,类。
device:负责提供硬件资源
driver:负责初始化设备以及提供一些操作方式
bus:负责管理挂载对应总线的设备以及驱动,匹配设备和驱动,它维护着两个链表,里面记录着各个已 经注册的设备和驱动,可以物理存在,也可以虚拟。
linux设备中设备和驱动通常都需要挂接到一种总线上,例如 usbpcii2c 。但是嵌入式系统中 SoC 集 成了外设控制器或挂接到SoC 内存空间,基于这种背景,发明了一种虚拟总线称为 platform 总线用来连接那些没有物理总线的设备或一些不支持热插拔的设备DM9000网卡设备就是挂接在这条总线上的,相应的设备 称为platform_device , 相应的驱动 platform_driver
class : 具有相同属性或特征的一类事物,例如将一些设备归为一类进行管理 ( / sys / class )
总线的工作原理:
总线管理着两个链表:设备链表 和 驱动链表。
当我们向内核注册一个驱动时,便插入到总线的驱动链表。
当我们向内核注册一个设备时,便插入到总线的设备链表。
在插入的同时,总线会执行一个 bus_type 结构体中的 match 方法对新插入的 设备 / 驱动 进行匹配
,匹配成功后,会调用驱动device_driver 结构体中的 probe 方法。 在移除设备或驱动时,会调用 device_driver 结构体中的 remove 方法。

Linux设备模型为这三种对象各自定义了对应的类:struct bus_type代表总线,struct device代表设备,struct device_driver代表驱动,这三者都内嵌了strcut kobject或struct

kset,于是会生成对应的总线,设备,驱动的目录。

设备专门用来描述设备所占有的资源信息,而驱动和设备绑定成功后,驱动负责从设备中动态获取这些资源信息,当设备的资源改变后,只是设备改变而已,驱动的代码可以不做任何修改,这就大大提高了驱动代码的通用性。总线就是联系两者的桥梁。

2.platform_device

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 ;
        struct mfd_cell * mfd_cell ;
        struct pdev_archdata archdata ;
};

3.platform_driver

struct platform_driver {
        int ( * probe )( struct platform_device * ); // 匹配成功执行 probe
        int ( * remove )( struct platform_device * ); // 卸载时执行 remove
        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 ;
};

4.platform_bus_type

struct of_device_id led_of_matches [] = {
        {. compatible = "fs" , "myled" }, // 通过设备树匹配
};
struct platform_driver mydriver = {
        . probe = xxx_probe ,
        . remove = xxx_remove ,
        . driver = {
                . name = "mytest" , // 直接通过 name 匹配
                . of_match_table = of_match_ptr ( led_of_matches ), // 通过设备树进行匹配
        },
};
struct platform_device mydev = {
        . name = "mytest" , // 需要与驱动中的名字匹配
        . num_resources = ARRAY_SIZE ( myresource ),
        . resource = myresource ,
};

5.平台驱动及相关函数

platform_driver_register(drv); //注册平台驱动

//drv:platform_driver对象

//返回值:成功返回0

void platform_driver_unregister(struct platform_driver *pdev); //注销平台驱动 //pdev:platform_driver对象

module_platform_driver( platform_driver); //(注销+注册)
//platform_driver:platform_driver对象

int platform_device_register(struct platform_device *); //注册平台设备 //platform_device:platform_device对象

//返回值:成功返回0

void platform_device_unregister(struct platform_device *); //注销平台设备 //platform_device:platform_device对象

// 硬件资源描述
struct resource xxxresouce [] = {
        [ 0 ] = {
        . start = 0x11000c40 ,
        . end = 0x11000c40 + 3 ,
        . flags = IORESOURCE_MEM ,
       },
        [ 1 ] = {
        . start = 168 ,
        . flags = IORESOURCE_IRQ ,
        }
};

获取硬件资源

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

//platform_device:platform_device对象

//type:资源类型

//num: resource index

//返回值:成功返回资源,失败返回NULL

创建设备文件

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char fmt, ...);

//class:该设备依附的类

//parent:父设备

//devt:设备号(此处的设备号为主次设备号)

//drvdata:私有数据

//fmt:设备名。 (自动创建在 /dev目录下) //返回值:成功返回struct device, 失败返回 ERR_PTR() on error

创建class

class_create(owner, name)

//owner通常为THIS_MODULE

//name为类名 (自动创建在sys/class目录下)

//返回值:成功返回struct class*, 失败返回 ERR_PTR() on error

void device_destroy(struct class *dev, dev_t devt); //销毁device

//dev: calss对象

//devt:设备号

extern void class_destroy(struct class *cls); //销毁class

//dev: calss对象

通过这个模型我们可以对之前的字符设备驱动框架进行修改

平台驱动代码:

...........................
//定义platform_driver对象
static struct platform_driver mydriver ={
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "mytest",     //通过名字匹配
    },
};  

int my_probe(struct platform_device *pdev)
{
    u32 val;
    int ret;
    //获取硬件资源
    rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
 
    //字符设备注册
    ret =  alloc_chrdev_region(&devnum,0,1,name);//1.申请设备号
    if(ret!=0){
        goto failed_alloc;
    }
    cdev_init(&mycdev,&myops); //2.cdev初始化
    
    ret = cdev_add(&mycdev,devnum,1); //3.cdev添加到内核
    if(ret!=0){
        goto failed_add;
    }
    printk("register success %d,%d\n",MAJOR(devnum),MINOR(devnum));
    
    myclass = class_create(THIS_MODULE,"myclass");
    if(IS_ERR(myclass)){
        goto failed_class;
    }
    mydevice = device_create(myclass,NULL,devnum,NULL,"led2");
    if(IS_ERR(mydevice)){
        goto failed_device;
    }
    //硬件操作


}
int my_remove(struct platform_device *pdev)
{
    ........
    return 0;

}

...........

static int mod_init(void)
{
    return  platform_driver_register(&mydriver);  //平台驱动注册
}
static void mod_exit(void)
{
    platform_driver_unregister(&mydriver);        //平台驱动注销
}

module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");

平台设备代码:

//描述硬件资源
struct resource	 myresource[]={
    [0] = {
        .start = 0x11000C20,
        .end   = 0x11000C20+3,
        .flags = IORESOURCE_MEM
    },
    [1] = {
        .start = 0x11000C24,
        .end   = 0x11000C24+3,
        .flags = IORESOURCE_MEM
    },

};
void my_release(struct device *dev)
{
    printk("device release\n");
    return ;
}
//定义platform_device对象
struct platform_device mydev = {
        .name = "mytest",                      //通过名字匹配
        .num_resources = ARRAY_SIZE(myresource),
        .resource = myresource,
        .dev = {
            .release = my_release,
        },
};


static int mod_init(void)
{
    return platform_device_register(&mydev); //平台设备注册
}
static void mod_exit(void)
{
    platform_device_unregister(&mydev);      //平台设备注销
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");

在后面引入了设备树的概念后我们平台设备代码这块就可以不再使用了。

linux设备树

在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。

Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。

设备树的基本语法

设备树的基本单元是节点(node),这些node被组织成树状结构,除了root node,每个node都只有一个parent node,一个device tree文件中只能有一个root node。每个node中包含了若干的键值对(property/value)来描述该node的一些特性。每个node用节点名字标识。(类似于linux文件系统)

/{                                           //根节点

Property = value;                           //描述信息

<name>[@<unit-address>]{                  //子节点

Property = value                       //描述信息

};

……

};

节点名命名规范

节点名字的格式是[@]。如果该node没有reg属性,那么该节点名字中不能包括@unit-address。unit-address的具体格式是和设备挂在哪个bus上相关。例如对于CPU,其unit-address就是从0开始编址,以此加1,而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。根节点的节点名是确定的,必须是“/”。

默认意义的属性

设备树语法中已经定义好的,具有通用规范意义的属性如果是设备信息和驱动分离框架的设备节点,则能够在内核初始化找到节点时候,自动解析生成相应的设备信息。

常见属性的有: compatible、地址address、中断interrupt

简单举例:

compatible属性:用于匹配设备节点和设备驱动的属性,规则是驱动设备ID表中的compatible域的值(字符串),和设备树中设备节点中的compatible属性值完全一致。 compatible=“厂商名,设备名” ;

设备树键值对相关语法

[1]. 字符串信息

compatible = "随风,飘落";

[2]. 32位无符号整形数组

word-array = <32 45 67 89>;

reg = <0x10001000 0x24 0x20001000 0x24>;

[3]. 二进制数组

bi-array = [0c 20 11 24];

mac = [FE 02 11 CB 40 58];

[4]. 字符数组

string-list = "aaa" , "bbb" , "ccc";

在引入设备树的概念后,我们可以对前面的代码进行优化了,不再需要编写平台设备的代码。

那接下来我展示一段从设备树获取资源,来操控led灯的代码,和之前接口编程类似,我们需要先通过芯片手册找到控制led灯的管脚和操纵2该管脚的寄存器地址,然后修改对应的设备树内容:

 然后我们开始编写我们的平台驱动代码框架:

int my_probe(struct platform_device *pdev);
int my_remove(struct platform_device *pdev);

static struct resource *rescon;//接收状态寄存器的资源
static struct resource *resdata;//接收数据寄存器的资源

unsigned int *gpx1con;
unsigned int *gpx1data;

//定义platform_driver对象
struct of_device_id of_matches[]={
    {.compatible="fs,myled"}, //名字要与设备树中的一样           
    {}, 
};
static struct platform_driver mydriver ={
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "myled",               
        .of_match_table =  of_matches,  //通过设备树匹配
    },
    
};  

int my_probe(struct platform_device *pdev)
{
    u32 val;
    int ret;
    //通过设备树获取状态寄存器资源
    rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
    if(rescon==NULL){
        goto failed_getcon;
    }
    printk("%#x\n",rescon->start);
    gpx1con = ioremap(rescon->start,4);//映射
    //通过设备树获取数据寄存器资源
    resdata = platform_get_resource(pdev,IORESOURCE_MEM,1);
    if(resdata==NULL){
        goto failed_getdata;
    }
    printk("%#x\n",resdata->start);
    gpx1data = ioremap(resdata->start,4);
    
    //字符设备注册

    //硬件操作


}
int my_remove(struct platform_device *pdev)
{
    printk("driver remove\n");
    //解除映射
    iounmap(gpx1con);
    iounmap(gpx1data);
    .................
    //注销字符设备
    //。。。。。。
    return 0;
}
static int mod_init(void)
{
    return  platform_driver_register(&mydriver);  //平台驱动注册
}
static void mod_exit(void)
{
    platform_driver_unregister(&mydriver);        //平台驱动注销
}

module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");

相关文章:

  • 数据结构笔记01
  • 一种基于双MCU协同的多功能押解脚环
  • 算法系列十:十大经典排序算法之——堆排序
  • Docker获取镜像报错docker Error response from daemon
  • 考虑人机协同的智能工厂多AGV物流调度仿真研究
  • 安装mkimage工具,解决报错“Invalid CPU Type - valid names are:”
  • 卡卷平台接口2
  • 尚硅谷Vue系列教程学习笔记(11)
  • win11任务栏时间显示到秒的操作方法
  • 【Saras算法】TD Learning的一种
  • 可裂解试剂142439-92-7,Biotin-bisamido-SS-NHS ester 性质特点有哪些?
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • 神经网络架构图讲解教程,神经网络架构图讲解图
  • 【分享】使用 PXE + Kickstart 无人值守安装 Linux
  • 我赢助手之爆款内容创作:爆款内容的底层逻辑,检查下自己的内容是否符合呢?
  • [笔记] php常见简单功能及函数
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • AHK 中 = 和 == 等比较运算符的用法
  • const let
  • gcc介绍及安装
  • Go 语言编译器的 //go: 详解
  • Java知识点总结(JavaIO-打印流)
  • js写一个简单的选项卡
  • nginx 配置多 域名 + 多 https
  • oldjun 检测网站的经验
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • 初探 Vue 生命周期和钩子函数
  • 多线程 start 和 run 方法到底有什么区别?
  • 讲清楚之javascript作用域
  • 力扣(LeetCode)56
  • 使用docker-compose进行多节点部署
  • 使用parted解决大于2T的磁盘分区
  • 说说动画卡顿的解决方案
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • 正则表达式小结
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • ![CDATA[ ]] 是什么东东
  • #laravel 通过手动安装依赖PHPExcel#
  • (10)ATF MMU转换表
  • (C语言)逆序输出字符串
  • (pojstep1.1.2)2654(直叙式模拟)
  • (ZT)一个美国文科博士的YardLife
  • (二)PySpark3:SparkSQL编程
  • (翻译)Quartz官方教程——第一课:Quartz入门
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (转载)虚函数剖析
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • ... 是什么 ?... 有什么用处?
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .Mobi域名介绍