linux设备驱动_linux设备驱动程 - CSDN
精华内容
参与话题
  • linux设备驱动框架

    万次阅读 多人点赞 2018-05-27 10:49:51
    一.Linux设备分类字符设备: 以字节为单位读写的设备。块设备 : 以块为单位(效率最高)读写的设备。网络设备 : 用于网络通讯设备。字符设备: 字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,...

    转载请注明:https://blog.csdn.net/kai_zone/article/details/80459334

    一.Linux设备分类

    字符设备: 以字节为单位读写的设备。

    块设备    : 以块为单位(效率最高)读写的设备。

    网络设备 : 用于网络通讯设备。


    字符设备:
            字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序
    来实现这种特性。字符设备驱动程序通常至少要实现open、close、read和write的系统调
    用。字符终端(/dev/console)和串口(/dev/ttyS0以及类似设备)就是两个字符设备,
    它们能很好的说明“流”这种抽象概念。字符设备可以通过FS节点来访问,比如/dev/tty1
    和/dev/lp0等。这些设备文件和普通文件之间的唯一差别在于对普通文件的访问可以前后
    移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道。然而,也存在具有数据
    区特性的字符设备,访问它们时可前后移动访问位置。例如framebuffer就是这样的一个设
    备,app可以用mmap或lseek访问抓取的整个图像。
    块设备:
            和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备(例如磁盘)
    上能够容纳filesystem。在大多数的Unix系统中,进行I/O操作时块设备每次只能传输一个
    或多个完整的块,而每块包含512字节(或2的更高次幂字节的数据)。Linux可以让app像
    字符设备一样地读写块设备,允许一次传递任意多字节的数据。因此,块设备和字符设备的
    区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不
    同对用户来讲是透明的。在内核中,和字符驱动程序相比,块驱动程序具有完全不同的接口。
    网络设备:
            任何网络事物都需要经过一个网络接口形成,网络接口是一个能够和其他主机交换数据的设
    备。接口通常是一个硬件设备,但也可能是个纯软件设备,比如回环(loopback)接口。
    网络接口由内核中的网络子系统驱动,负责发送和接收数据包。许多网络连接(尤其是使用
    TCP协议的连接)是面向流的,但网络设备却围绕数据包的传送和接收而设计。网络驱动程
    序不需要知道各个连接的相关信息,它只要处理数据包即可。
    由于不是面向流的设备,因此将网络接口映射到filesystem中的节点(比如/dev/tty1)比
    较困难。Unix访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个
    名字在filesystem中不存在对应的节点。内核和网络设备驱动程序间的通信,完全不同于内
    核和字符以及块驱动程序之间的通信,内核调用一套和数据包相关的函数而不是read、
    write等。


    在linux的世界里一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。我们知道如果应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。Linux内核有那么多驱动程序,应用怎么才能精确的调用到底层的驱动程序呢?

    在这里我们拿字符设备为例,来看一下应用程序如何和底层驱动程序关联起来。

      必须知道的知识:

    (1) 在Linux文件系统中,每个文件都用一个struct inode结构体来描述,这个结构体记录了这个文件的所有信息,例如文件类型,访问权限等。

    (2) 在linux操作系统中,每个驱动程序在应用层的/dev目录或者其他如/sys目录下都会有一个文件与之对应。

    (3) 在linux操作系统中,   每个驱动程序都有一个设备号。

    (4) 在linux操作系统中,每打开一次文件,Linux操作系统会在VFS层分配一个struct file结构体来描述打开的文件。

    注意:常常我们认为,struct inode描述的是文件的静态信息,即这些信息很少会改变,而struct file描述的是动态信息,即对文件的操作的时候,struct file里面的信息经常会发生变化。典型的是struct file结构体里面的f_ops(记录当前文件的位移量),每次读写一个普通文件时f_ops的值都会发生改变。

                       

     

        通过上图我们可以知道,如果想访问底层设备,就必须打开对应的设备文件。也就是在这个打开的过程中,Linux内核将应用层和对应的驱动程序关联起来。

    (1) 当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。

    (2) 根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。这里以字符设备为例。在Linux操作系统中每个字符设备都有一个struct cdev结构体。此结构体描述了字符设备所有信息,其中最重要的一项就是字符设备的操作函数接口。

    (3) 找到struct cdev结构体后,linux内核就会将struct cdev结构体所在的内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中的记录的函数操作接口地址记录在struct file结构体的f_ops成员中。

    (4) 任务完成,VFS层会给应用返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层应用程序就可以通过fd找到struct file,然后在有struct file找到操作字符设备的函数接口了。

    三 .   如何编写字符设备驱动。

                 

    四.  字符驱动相关函数分析。  

               

                

                

    五. 开始写字符设备驱动程序

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/cdev.h>
    #include <linux/fs.h>
    
    #define MAJOR_NUM 168
    struct mycdev
    {
    		unsigned char buffer[50];
    		struct cdev cdev;
    }dev_fifo;
    
    MODULE_LICENSE("GPL");
    
    static int dev_fifo_open(struct inode *inode,struct file *file){
    
    	printk("dev_fifo_open success!")
    	
    	return 0;
    }
    
    static ssize_t dev_fifo_read(struct file *file,char __user *buf,size_t size,loff_t *ppos)
    {
    	printk("dev_fifo_read success");
    	return 0;
    }
    
    static ssize_t dev_fifo_write(struct file *file,const char __user *buf,size_t size,loff_t *ppos)
    {
    	printk("dev_fifo_write success");
    	return 0;
    }
    
    static const struct file_operations fifo_operations = {
    .owner = THIS_MODULE,
    .open = dev_fifo_open,
    .read = dev_fifo_read,
    .write = dev_fifo_write,
    };
    
    int __init dev_fifo_init(void)
    {
    	int ret;
    	dev_t dev_num;
    	
    	//初始化字符设备
    	cdev_init(&dev_fifo.cdev,&fifo_operations);
    	
    	//设备号:主设备号(12Bit)|次设备号(20BIT)
    	dev_num = MKDEV(MAJOR_NUM,0);
    	//注册设备号
    	ret = register_chrdev_region(dev_num,1,"dev_fifo");
    	if(ret < 0)
    	{
    		printk("Fail to register_chrdev_region");
    		return -EIO;
    	}
    	//添加设备到操作系统。
    	
    	ret = cdev_add(&dev_fifo.cdev,dev_num,1);
    	if(ret < 0)
    	{
    		printk("fail to cdev_add");
    		goto unregister_chrdev;
    	}
    	
    	printk("Register dev_fifo to system.ok!\n");
    	
    	return 0;
    	
    	unregister_chrdev:
    			unregister_chrdev_region(MKDEV(MAJOR_NUM,0),1);
    			return -1;	
    }
    
    void __exit dev_fifo_exit(void)
    {
    		//从系统中删除添加的字符设备
    		cdev_del(&dev_fifo.cdev);
    		//释放申请的设备号
    		unregister_chrdev_region(MKDEV(MAJOR_NUM,0),1);
    		printk("Exit dev_fifo ok!");
    		return;
    }
    
    module_init(dev_fifo_init);
    module_exit(dev_fifo_exit);

        

    展开全文
  • 1 Linux设备驱动概述及开发环境构建 1.1 设备驱动的作用 驱使硬件设备行动 1.2 无操作系统时的设备驱动 典型架构:一个无限循环中夹杂着对设备中断的检测或者对设备的轮询 1.3 有操作系统时的设备...

    1 Linux设备驱动概述及开发环境构建

    1.1 设备驱动的作用

    • 驱使硬件设备行动

    1.2 无操作系统时的设备驱动

    • 典型架构:一个无限循环中夹杂着对设备中断的检测或者对设备的轮询
      无标题.png

    1.3 有操作系统时的设备驱动

    • 并发 、内存管理

      无标题.png

    1.4 Linux 设备驱动

    1.4.1 设备的分类及特点

    ● 字符设备。
    ● 块设备。
    ● 网络设备。

    1.4.2 Linux 设备驱动与整个软硬件系统的关系

    捕获.PNG

    1.4.3 Linux 设备驱动的重点、难点

    ● 编写 Linux 设备驱动要求工程师有非常好的硬件基础,懂得 SRAM、 Flash、 SDRAM、磁盘的读写方式,UART、 I2C、 USB 等设备的接口以及轮询、中断、 DMA 的原理,PCI 总线的工作方式以及 CPU 的内存管理单元(MMU)等。
    ● 编写 Linux 设备驱动要求工程师有非常好的 C 语言基础,能灵活地运用 C 语言的结构体、指针、函数指针及内存动态申请和释放等。
    ● 编写 Linux 设备驱动要求工程师有一定的 Linux 内核基础,虽然并不要求工程师对内核各个部分有深入的研究,但至少要明白驱动与内核的接口。尤其是对于块设备、网络设备、 Flash 设备、串口设备等复杂设备,内核定义的驱动体系结构本身就非常复杂。
    ● 编写 Linux 设备驱动要求工程师有非常好的多任务并发控制和同步的基础,因为在驱动中会大量使用自旋锁、互斥、信号量、等待队列等并发与同步机制。

    2 驱动设计的硬件基础

    2.1 处理器

    2.1.1 通用处理器

    2.1.2 数字信号处理器

    捕获.PNG

    2.2 存储器

    捕获.PNG

    捕获.PNG

    2.3 接口与总线

    串口 、I2CI2C 、SPI 、USB、以太网 、PCI 和 PCI-E 、SD 和 SDIO

    捕获.PNG

    捕获.PNG

    2.4 CPLD 和 FPGA

    2.5 原理图分析

    • 符号 、网络 、描述

    2.6 硬件时序分析

    • 时序分析的意思是让芯片之间的访问满足芯片数据手册中时序图信号有效的先后顺序、采样建立时间(Setup Time)和保持时间(Hold Time)的要求

    2.7 芯片数据手册阅读方法

    2.8 仪器仪表使用

    • 万用表 、示波器 、逻辑分析仪

    3 Linux 内核及内核编程

    3.1 Linux 内核的发展与演变

    • 表 3.1 Linux 操作系统版本的历史及特点
    版 本 时 间 特 点
    Linux 0.1 1991 年 10 月 最初的原型
    Linux 1.0 1994 年 3 月 包含了 386 的官方支持,仅支持单 CPU 系统
    Linux 1.2 1995 年 3 月 第一个包含多平台(Alpha、 Sparc、 MIPS 等)支持的官方版本
    Linux 2.0 1996 年 6 月 包含很多新的平台支持,最重要的是,它是第一个支持 SMP(对称多处理器)体系的内核版本
    Linux 2.2 1999 年 1 月 极大提升 SMP 系统上 Linux 的性能,并支持更多的硬件
    Linux 2.4 2001 年 1 月 进一步提升了 SMP 系统的扩展性,同时也集成了很多用于支持桌面系统的特性: USB、 PC 卡(PCMCIA)的支持,内置的即插即用等
    Linux 2.6.0 ~ 2.6.39 2003 年 12 月~2011 年 5 月 无论是对于企业服务器还是对于嵌入式系统, Linux 2.6 都是一个巨大的进步。对高端机器来说,新特性针对的是性能改进、可扩展性、吞吐率,以及对 SMP 机器 NUMA 的支持。对于嵌入式领域,添加了新的体系结构和处理器类型。包括对那些没有硬件控制的内存管理方案的无MMU 系统的支持。同样,为了满足桌面用户群的需要,添加了一整套新的音频和多媒体驱动程序
    Linux 3.0 ~ 3.19、Linux 4.0-rc1 至今 2011 年 7 月至今 性能优化等 开发热点聚焦于虚拟化、新文件系统、 Android、新体系结构支持以及

    3.2 内核组件

    捕获.PNG

    1. 进程调度

    捕获.PNG

    2. 内存管理

    捕获.PNG

    3. 虚拟文件系统

    捕获.PNG

    4. 网络接口

    捕获.PNG

    5. 进程间通信

    • 进程间通信支持进程之间的通信, Linux 支持进程间的多种通信机制,包含信号量、共享内存、消息队列、管道、 UNIX 域套接字等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传递。在实际的 Linux 应用中,人们更多地趋向于使用 UNIX 域套接字,而不是 System V IPC 中的消息队列等机制。 Android 内核则新增了 Binder 进程间通信方式。

    4 内核模块

    4.1 模块简介

    insmod ./hello.ko
    rmmod hello
    
    lsmod
    /proc/modules
    /sys/module

    4.2 模块结构

    4.2.1 加载函数

    static int __init hello_init(void)
    {
        ...
    
        return 0;
    }
    
    module_init(hello_init);

    4.2.2 卸载函数

    static void __exit hello_exit(void)
    {
        ...
    }
    
    module_exit(hello_exit);

    4.2.3 许可声明

    MODULE_AUTHOR("lin");
    MODULE_LICENSE("GPL v2");
    MODULE_DESCRIPTION("A simple param Module");
    MODULE_ALIAS("a simplest module");
    • 模块参数module_param(var, int, S_IRUGO);
    • 导出符号EXPORT_SYMBOL_GPL(func); (proc/kallsyms)

    5 文件系统与设备文件

    捕获.PNG

    捕获.PNG

    6 字符设备驱动

    6.1 驱动结构

    6.1.1 cdev结构体

    捕获.PNG

    //生成dev
    MKDEV(int major, int minor);    //major:0-19 minor:20-31
    //获取设备号
    MAJOR(dev_t dev)
    MINOR(dev_t dev)
    //cdev操作
    void cdev_init(struct cdev *, struct file_operations *);
    struct cdev* cdev_alloc(void);
    void cdev_put(struct cdev *);
    int  cdev_add(struct cdev *, dev_t, unsigned);
    void cdev_del(struct cdev *);

    6.1.2 设备号分配

    int register_chrdev_region(dev_t from, unsigned count, const char *name);
    int    alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
    
    int unregister_chrdev_region(dev_t from, unsigned count);

    6.1.3 file_operations结构体

    捕获.PNG

    7 设备驱动中的并发控制

    7.1 并发与竞态

    • 临界区:访问共享资源的代码段
    • 互斥:中断屏蔽、原子操作、自旋锁、信号量、互斥体

    7.2 编译乱序和执行乱序

    • 表 隔离指令
    指令名 功能描述
    DMB 数据存储器隔离。DMB 指令保证: 仅当所有在它前面的存储器访问操作都执行完毕后,才提交(commit)在它后面的存储器访问操作。
    DSB 数据同步隔离。比 DMB 严格: 仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访 问操作——译者注)
    ISB 指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。

    7.3 中断屏蔽

    local_irq_disable() local_irq_enable() //与自旋锁联合使用
    local_irq_save(flags) local_irq_restore(flags)
    local_bh_disable() local_bh_enable()

    7.4 原子操作

    7.4.1 整型原子操作

    • 设置

      void atomic_set(atomic_t *v, int i);
      atomic_t ATOMIC_INIT(int i);
    • 获取

      int atomic_read(atomic_t *v);
    • 加减

      void atomic_add(int i, atomic_t *v);
      void atomic_sub(int i, atomic_t *v);
      
      void atomic_inc(atomic_t *v);
      void atomic_dec(atomic_t *v);
    • 操作后测试(为0返回true,非0返回false)

      int atomic_inc_and_test(atomic_t *v);
      int atomic_dec_and_test(atomic_t *v);
      int atomic_sub_and_test(int i, atomic_t *v);
    • 操作后返回新值

      int atomic_add_return(int i, atomic_t *v);
      int atomic_sub_return(int i, atomic_t *v);
      
      int atomic_inc_return(atomic_t *v);
      int atomic_dec_return(atomic_t *v);

    7.4.2 位原子操作

    捕获.PNG

    7.5 自旋锁

    7.5.1 自旋锁

    spinlock_t lock;
    spin_lock_init(lock);
    spin_lock(lock);
    spin_trylock(lock);
    spin_unlock(lock);
    
    
    spin_lock_irq(lock); spin_unlock_irq(lock);
    spin_lock__irqsave(lock); spin_unlock_irqrestore(lock);
    spin_lock_bh(lock); spin_unlock_bh(lock);

    无标题.png

    7.5.2 读写锁

    无标题.png

    7.5.3 顺序锁

    • 读执行单元不会被写执行单元阻塞;但写执行单元进行写操作时,其他写执行单元就会自旋。

    无标题.png

    7.5.4 读-复制-更新

    • RCU: Read-Copy-Update

      捕获.PNG

      无标题.png

      7.6 信号量

      无标题.png

      7.7 互斥体

      无标题.png

      7.8 完成量

      无标题.png

    8 阻塞I/O和非阻塞I/O

    8.1 阻塞I/O和非阻塞I/O

    fd= open("/dev/ttyS1", O_RDWR | O_NONBLOCK);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    8.1.1 等待队列

    //定义
    wait_queue_head_t queue_head;
    //初始化
    init_waitqueue_head(&queue_head);
    //定义及初始化
    DECLARE_WAIT_QUEUE_HEAD(name)
    //队列等待元素
    DECLARE_WAITQUEUE(name, tsk)
    //操作
    void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
    void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
    //等待事件
    wait_event(queue, condition)
    wait_event_interruptible(queue, condition)
    wait_event_timeout(queue, condition, timeout)
    wait_event_interruptible_timeout(queue, condition, timeout)
    //唤醒队列
    void wake_up(wait_queue_head_t *q);
    void wake_up_interruptible(wait_queue_head_t *q);
    //睡眠
    sleep_on(wait_queue_head_t *q);
    interruptible_sleep_on(wait_queue_head_t *q);
    static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
    {
        ...
        DECLARE_WAITQUEUE(wait, current);
        add_wait_queue(&xxx_wait, &wait);
    
        /*等待设备缓冲区可写*/
        do {
            avail = device_writable();
            if (avail < 0) {
                if (file->f_flags & O_NONBLOCK) {
                    ret = -EAGAIN;
                    goto out;
                }
                __set_current_state(TASK_INTERRUPTIBLE);
                schedule();
                if (signal_pending(current)) {
                    ret = -ERESTARTSYS;
                    goto out;
                }
            }
        } while (avail < 0);
    
        device_write();
    out:
        remove_wait_queue(&xxx_wait, &wait);
        set_current_state(TASK_RUNNING);
    
        reutrn ret;
    }

    捕获.PNG

    8.1.2 支持等待队列的globalfifo

    无标题.png

    8.2 轮询操作

    8.2.1 轮询的概念与作用

    9.2.3 信号的释放

    1. 异步通知结构体

      struct xxx_dev{
          struct cdev cdev;
          ...
          struct fasync_struct *async_queue;
      }
      1. xxx_fasync
      static int xxx_fasync(int fd, struct file *filp, int mode)
      {
          struct xxx_dev *dev=file->private_data;
          return fasync_helper(fd, filp, mode, &dev->async_queue);
      }
      1. 释放读信号
      //xxx_write
      if(dev->async_queue)
        kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
      1. 从异步通知列表删除filp
      //xxx_release
      xxx_fasync(-1, filp, 0);

    9.4 Linux异步I/O

    9.4.1 AIO

    struct aiocb {
     
      int aio_fildes;               // File Descriptor
      int aio_lio_opcode;           // Valid only for lio_listio (r/w/nop)
      volatile void *aio_buf;       // Data Buffer
      size_t aio_nbytes;            // Number of Bytes in Data Buffer
      struct sigevent aio_sigevent; // Notification Structure
     
      /* Internal fields */
      ...
     
    };
    API 函数 说明
    aio_read int aio_read( struct aiocb *aiocbp ); 请求异步读操作
    aio_error int aio_error( struct aiocb *aiocbp ); 检查异步请求的状态
    aio_return ssize_t aio_return( struct aiocb *aiocbp ); 获得完成的异步请求的返回状态
    aio_write int aio_write( struct aiocb *aiocbp ); 请求异步写操作
    aio_suspend int aio_suspend( const struct aiocb *const cblist[], int n, const struct timespec *timeout ); 挂起调用进程,直到一个或多个异步请求已经完成(或失败)
    aio_cancel int aio_cancel( int fd, struct aiocb *aiocbp ); 取消异步 I/O 请求
    lio_listio int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig ); 发起一系列 I/O 操作

    9.4.2 内核AIO与libaio

    10 中断与时钟

    10.1 中断与定时器

    11 内存与I/O访问

    17 I2C、SPI、USB驱动架构类比

    无标题.png

    18 ARM Linux设备树

    18.1 ARM设备树起源

    • 可描述的信息:
      • CPU的数量和类别
      • 内存基地址和大小
      • 总线和桥
      • 外设连接
      • 中断控制器和中断使用情况
      • GPIO控制器和GPIO使用情况
      • 时钟控制器和时钟使用情况

    18.2 设备树的组成和结构

    18.2.1 DTS、DTC和DTB

    1. .dts:device tree source

      1.1 Soc共用部分:.dtsi (/include/ “s3c24440.dtsi”)

      1.2 模板

      /* root节点 */
      / {
          node1 {
              a-string-property = "A string";
              a-string-list-property = "first string", "second string";
              a-byte-data-property = [0x01 0x23 0x34 0x56];
              child-node1 {
                  first-child-property;
                  second-child-property = <1>;
                  a-string-property = "Hello, world";
              };
              child-node2 {
              };
          };
          node2 {
              an-empty-property;
              a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
              child-node1 {
              };
          };
      };
    2. .dtc:device tree compiler

    3. .dtb:Device Tree Blob

    展开全文
  • Linux设备驱动程序第三版 本书指导你如何编写你自己的驱动, 以及如何利用内核相关的部分. 我们采用一种设备-独立的方法; 编程技术和接口, 在任何可能的时候, 不会捆绑到任何特定的设备. 每一个驱动都是不同的; 作为...
  • 5.linux设备驱动模型

    千次阅读 2019-05-05 16:49:56
    1.linux设备驱动模型简介 1.1、什么是设备驱动模型 (1)类class、总线bus、设备device、驱动driver (2)kobject和对象生命周期 (3)sysfs (4)udev 1.2、为什么需要设备驱动模型 (1)早期内核(2.4之前)没有统一的...

    1.linux设备驱动模型简介

    1.1、什么是设备驱动模型

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


    1.2、为什么需要设备驱动模型

    (1)早期内核(2.4之前)没有统一的设备驱动模型,但照样可以用
    (2)2.6版本中正式引入设备驱动模型,目的是在设备越来越多,功耗要求等新特性要求的情况下让驱动体系更易用、更优秀。
    (3)设备驱动模型负责统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施
    (4)设备驱动模型目的是简化驱动程序编写,但是客观上设备驱动模型本身设计和实现很复杂。
    1.3、驱动开发的2个点
    (1)驱动源码本身编写、调试。重点在于对硬件的了解。
    (2)驱动什么时候被安装、驱动中的函数什么时候被调用。跟硬件无关,完全和设备驱动模型有关。


    2.设备驱动模型的底层架构


    2.1 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;
    };


    2.2 kobj_type  kobject的属性  @@@@@

    使用该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);
    };

    属性数组:

    struct attribute {
    	const char		*name;  //属性的名字
    	struct module		*owner;//指向用于该属性的模块,已经不常使用
    	mode_t			mode;  //属性读写权限
    };

    name:属性的名字,对应某个目录下的文件的名字

    操作属性数组的方法:

    struct sysfs_ops
    {
    ssize t (*show) (struct kobject *, struct attribute *, char *);/*读属性操作函数*/
    ssize t (*store) (struct kobject *,struct attribute *,const char *buf, size_t count);/*写属性操作函数*/
    }

    show()函数用于读取一个属性到用户空间。函数的第1个参数是要读取的kobect的指针,它对应要读的目录;第2个参数是要读的属性;第3个参数是存放读到的属性的缓存区。当函数调用成功后,会返回实际读取的数据长度,这个长度不能超过PAGESIZE个字节大小。

    store()函数将属性写入内核中。函数的第1个参数是与写相关的kobject的指针,它对应要写的目录:第2个参数是要写的属性;第3个参数是要写入的数据;第4个参数是要写入的参数长度。这个长度不能超过PAGE-SIZE个字节大小。只有当拥有属性有写权限时,才能调用store0函数。

    2.3 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;  //热插拔事件
    };

    热插拔事件:

    内核将在什么时候产生热插拔事件呢?当驱动程序将kobject注册到设备驱动模型时, 会产生这些事件。也就是当内核调用kobject_add()和kobject_del()丽数时,会产生热插拔事件。热插拔事件产生时,内核会根据kobject的kset指针找到所属的kset结构体,执行kset结构体中uevent_ops包含的热插拔函数。这些函数的定义如下:

    struct kset_uevent_ops {
    	int (* const filter)(struct kset *kset, struct kobject *kobj);//决定是否向内核发送事件
    	const char *(* const name)(struct kset *kset, struct kobject *kobj);//得到子程序的名字
    	int (* const uevent)(struct kset *kset, struct kobject *kobj,
    		      struct kobj_uevent_env *env);
    };

    kset 和 kobject 的关系

    https://www.cnblogs.com/deng-tao/p/6026373.html

    3.设备驱动模型三大组件

    总结:

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

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

    虽然平台总线和IIC总线的实现有些不同,但是大体使一样的

    如下:

    //platform 总线
    int platform_driver_register(struct platform_driver *drv)
    {
    	if (drv->probe)
    		drv->driver.probe = platform_drv_probe;
    
    	return driver_register(&drv->driver);
    }
    static int platform_drv_probe(struct device *_dev)
    {
    	struct platform_driver *drv = to_platform_driver(_dev->driver);
    	struct platform_device *dev = to_platform_device(_dev);
    
    	return drv->probe(dev);
    }
    
    
    //    IIC总线
    static int i2c_device_probe(struct device *dev)
    {
    	status = driver->probe(client, i2c_match_id(driver->id_table, client));
    }

     

    3.1、总线

    (1)物理上的真实总线及其作用(英文bus)
    (2)驱动框架中的总线式设计
    (3)bus_type结构体,关键是match函数和uevent函数

    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;  //总线的私有数据
    };

    总线私有数据

    struct bus type private {
    
    struct kset subsys;/*代表该bus子系统,里面的kobj是该bus的主kobj,也就是最顶层*/ 
    
    struct kset *drivers kset;/*挂接到该总线上的所有驱动集合*/ 
    struct kset *devices kset;/*挂接到该总线上的所有设备集合*/ 
    struct klist klist devices;/*所有设备的列表,与devices kset中的1ist相同*/
    struct klist klist drivers;/*所有驱动程序的列表,与drivers_kset中的1ist相同*/ 
    struct blocking notifier head bus notifier;/**/ 
    unsigned int drivers autoprobe:1;/*设置是否在驱动注册时, 自动探测(probe)设备*/ 
    struct bus type *bus;/*回指包含自己的总线*/
    }

    总线属性

    struct bus_attribute {
    	struct attribute	attr;//总线属性
    	ssize_t (*show)(struct bus_type *bus, char *buf);  //属性读函数
    	ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);//属性写函数
    };

    3.2、设备  注意:结构体在不同的内核版中的成员是不同的

    在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);/*释放设备描述符的回调函数*/
    
    }

    设备属性

    struct device_attribute {
    	struct attribute	attr;
    	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
    			char *buf);
    	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
    			 const char *buf, size_t count);
    };

    用来在devic目录下创建一个属性文件

    int device_create_file(struct device *dev,  //创建
    		       const struct device_attribute *attr)
    void device_remove_file(struct device *dev,  //删除
    			const struct device_attribute *attr)

    3.3、驱动

    (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;//设备驱动私有数据
    };


    3.4、类

    (1)相关结构体:struct class 和 struct class_device
    (2)udev的使用离不开class
    (3)class的真正意义在于作为同属于一个class的多个设备的容器。也就是说,class是一种人造概念,目的就是为了对各种设备进行分类管理。当然,class在分类的同时还对每个类贴上了一些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。

    struct class {
    	const char		*name;
    	struct module		*owner;
    
    	struct class_attribute		*class_attrs;
    	struct device_attribute		*dev_attrs;
    	struct kobject			*dev_kobj;
    
    	int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
    	char *(*devnode)(struct device *dev, mode_t *mode);
    
    	void (*class_release)(struct class *class);
    	void (*dev_release)(struct device *dev);
    
    	int (*suspend)(struct device *dev, pm_message_t state);
    	int (*resume)(struct device *dev);
    
    	const struct kobj_ns_type_operations *ns_type;
    	const void *(*namespace)(struct device *dev);
    
    	const struct dev_pm_ops *pm;
    
    	struct class_private *p;
    };


    3.5、总结

    (1)模型思想很重要,其实就是面向对象的思想
    (2)全是结构体套结构体,对基本功(语言功底和大脑复杂度)要求很高

     

    platform平台总线概述

    下面的结构体都在Linux/device.h中

    Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为
    platform_driver。
    系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下
    platform总线的bus_type实例platform_bus_type

    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之间是如何进行匹配
     

    static int platform_match(struct device *dev, struct device_driver *dr
    {
     struct platform_device *pdev = to_platform_device(dev);
     struct platform_driver *pdrv = to_platform_driver(drv);
    
     /* Attempt an OF style match first */
     if (of_driver_match_device(dev, drv))
     return 1;
    
     /* Then try ACPI style match */
     if (acpi_driver_match_device(dev, drv))
     return 1;
    
     /* Then try to match against the id table */
     if (pdrv->id_table)
     return platform_match_id(pdrv->id_table, pdev) != NULL
    
     /* fall-back to driver name match */
     return (strcmp(pdev->name, drv->name) == 0);
    }

    有4种可能性,

    一是基于设备树风格的匹配;

    二是基于ACPI风格的匹配;

    三是匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内);

    第四种是匹配platform_device设备名和驱动的名字。

     

    对于Linux 2.6ARM平台而言,对platform_device的定义通常在BSP的板文件中实现。

    在板文件中,将所有 platform_device  设备归纳为一个数组,最终通过platform_add_devices()函数统一注册。
    Linux 3.x之后, ARM Linux不太喜欢人们以编码的形式去填写platform_device和注册,而倾向于根据设备树中的内容自动展开platform_device。
     

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

    4.platform平台总线工作原理1

    4.1、何为平台总线

    (1)相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的。
    (2)回顾裸机中讲的,CPU与外部通信的2种方式:地址总线式连接和专用接口式连接。平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设。
    (3)思考:为什么要有平台总线?进一步思考:为什么要有总线的概念?

     

    4.2、平台总线下管理的2员大将

    (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表
    };

    platform_driver.driver.name要等于platform_device.name

    因为设备要靠这个文件来识别驱动

    在Linux 2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。


    5.platform平台总线工作原理2

    5.1、平台总线体系的工作流程

    (1)第一步:系统启动时在bus系统中注册platform
    (2)第二步:内核移植的人负责提供platform_device
    (3)第三步:写驱动的人负责提供platform_driver
    (4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了
    5.2、代码分析:platform本身注册
    (1)每种总线(不光是platform,usb、i2c那些也是)都会带一个match方法,match方法用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但是实际上一般都是看name的。
    (2)platform_match函数就是平台总线的匹配方法。

    match函数的工作方法是:

    如果有id_table就说明驱动可能支持多个设备,所以这时候要去对比id_table中所有的name,只要找到一个相同的就匹配上了不再找了

    如果找完id_table都还没找到就说明没匹配上;

    如果没有id_table或者没匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败。


    6.platform平台总线工作原理3

    6.1、以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程
    (1)platform_driver_register
    (2)platform_device_register


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

    mach-x210.c  注册平台设备

    //led 1,2,3
    static struct s5pv210_led_platdata s5pv210_led1_pdata = {
    	.name		= "led1",
    	.gpio		= S5PV210_GPJ0(3),
    	.flags		= S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    	.def_trigger	= "heartbeat",
    };
    
    
    static struct s5pv210_led_platdata s5pv210_led2_pdata = {
    	.name		= "led2",
    	.gpio		= S5PV210_GPJ0(4),
    	.flags		= S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    	.def_trigger	= "heartbeat",
    };
    
    static struct s5pv210_led_platdata s5pv210_led3_pdata = {
    	.name		= "led3",
    	.gpio		= S5PV210_GPJ0(5),
    	.flags		= S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    	.def_trigger	= "heartbeat",
    };
    
    
    static struct platform_device s5pv210_led1 = {
    	.name		= "s5pv210_led",
    	.id		= 0,
    	.dev		= {
    		.platform_data	= &s5pv210_led1_pdata,
    	},
    };
    
    static struct platform_device s5pv210_led2 = {
    	.name		= "s5pv210_led",
    	.id		= 1,
    	.dev		= {
    		.platform_data	= &s5pv210_led2_pdata,
    	},
    };
    
    
    static struct platform_device s5pv210_led3 = {
    	.name		= "s5pv210_led",
    	.id		= 2,
    	.dev		= {
    		.platform_data	= &s5pv210_led3_pdata,
    	},
    };
    /*******************************/

     

    先定义结构体:

    #ifndef __ASM_ARCH_LEDSGPIO_H
    #define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"
    
    #define S5PV210_LEDF_ACTLOW	(1<<0)		/* LED is on when GPIO low */
    #define S5PV210_LEDF_TRISTATE	(1<<1)		/* tristate to turn off */
    
    struct s5pv210_led_platdata {
    	unsigned int		 gpio;
    	unsigned int		 flags;
    
    	char			*name;
    	char			*def_trigger;
    };
    
    #endif /* __ASM_ARCH_LEDSGPIO_H */
    #include <linux/module.h>		// module_init  module_exit
    #include <linux/init.h>			// __init   __exit
    #include <linux/fs.h>
    #include <linux/ioport.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/leds.h>
    #include <linux/platform_device.h>
    #include <linux/slab.h>
    
    #include <asm/uaccess.h>   //copy_from_user
    #include <asm/string.h>
    #include <asm/io.h>
    
    #include <mach/gpio-bank.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio.h>
    #include <mach/leds-gpio.h>
    
    #define GPJ0CON		S5PV210_GPJ0CON
    #define GPJ0DAT		S5PV210_GPJ0DAT
    
    #define GPJ0_LED1		S5PV210_GPJ0(3)
    #define GPJ0_LED2		S5PV210_GPJ0(4)
    #define GPJ0_LED3		S5PV210_GPJ0(5)
    
    #define X210_LED_ON     0
    #define X210_LED_OFF	1
    
    /* our context */
    struct s5pv210_gpio_led {
    	struct led_classdev		 cdev;
    	struct s5pv210_led_platdata	*pdata;
    };
    
    //转换
    static inline struct s5pv210_gpio_led *
    pdev_to_gpio(struct platform_device *dev)//由platform_device得到s5pv210_gpio_led
    
    {
    	return platform_get_drvdata(dev);
    }
    
    static inline struct s5pv210_gpio_led *
    to_gpio(struct led_classdev *led_cdev)//由led_classdev得到s5pv210_gpio_led
    {
    	return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
    }
    
    //change brightness 0~255
    void	
    led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness)
    {
    	struct s5pv210_gpio_led *led = to_gpio(led_cdev);
    
    	printk(KERN_INFO "This is led_brightness_set1\n");
    	
    	// 用户设置的值就是brightness
    	switch(brightness){
    	case LED_OFF:		
    		//LED灭
    		gpio_set_value(led->pdata->gpio, X210_LED_OFF);
    		break;
    			
    	case LED_FULL:		
    		//LED亮
    		gpio_set_value(led->pdata->gpio, X210_LED_ON);
    		break;
    		
    	default:
    		printk(KERN_INFO "anew input number\n");
    	}
    }
    
    //get led brightness
    enum led_brightness
    brightness_get(struct led_classdev *led_cdev)
    {
    	//enum led_brightness brightness;
    	printk(KERN_INFO "This is led_brightness1\n");	
    	
    	return 0;
    }
    
    //platform 注册函数
    static int 
    s5pv210_led_probe(struct platform_device *dev)
    {
    	
    	struct s5pv210_gpio_led *led = NULL;
    	struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
    	int ret  = -1;
    
    	led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
    	if (led == NULL) {
    		dev_err(&dev->dev, "No memory for device\n");
    		return -ENOMEM;
    	}
    	
    	//gpio register
    	ret = gpio_request(pdata->gpio, pdata->name);//注册gpio
    	if (ret == 0){
    		printk(KERN_ERR "gpio_request led 0 failed\n");
    		//printk(KERN_ERR "ret = [%d]\n", ret);
    		return -EINVAL;
    	}else{
    		//printk(KERN_ERR "ret = [%d]\n", ret);
    		gpio_direction_output(pdata->gpio, 1);
    	}
    	
    	//把led挂接到dev中的一个特殊变量上,方便后面的remove释放led
    	platform_set_drvdata(dev, led);
    	
    	//register our new led device
    	led->cdev.name = pdata->name;//得到设备名字
    	led->cdev.brightness = 0;
    	led->cdev.brightness_set = led_brightness_set;
    	led->cdev.brightness_get = brightness_get;
    
    	led->pdata = pdata;
    	
    	ret = led_classdev_register(&dev->dev, &led->cdev);//在设备文件下注册设备类leds
    	if(ret < 0){
    		printk(KERN_ERR "led_classdev_register failed\n");
    		return -EINVAL;
    	}
    	
    	printk(KERN_INFO "led register success\n");
    	return 0;
    }
    
    // platform卸载函数
    static int 
    s5pv210_led_remove(struct platform_device *dev)
    {
    	struct s5pv210_gpio_led *led = pdev_to_gpio(dev);
    	struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
    	
    	led_classdev_unregister(&led->cdev);//卸载设备类
    	gpio_free(pdata->gpio);
    	
    	kfree(led);//释放led结构体的空间
    	printk(KERN_INFO "led unregister success\n");
    	
    	return 0;
    }
    
    //led platform 结构体
    static struct platform_driver s5pv210_led_driver = {//platform驱动结构体
    	.probe		= s5pv210_led_probe,
    	.remove		= s5pv210_led_remove,
    	.driver		= {
    		.name		= "s5pv210_led",
    		.owner		= THIS_MODULE,
    	},
    };
    
    
    static int __init s5pv210_led_init(void)
    {
    	return platform_driver_register(&s5pv210_led_driver);//注册设备驱动
    }
    
    static void __exit s5pv210_led_exit(void)
    {
    	platform_driver_unregister(&s5pv210_led_driver);//注销设备驱动
    	
    }
    
    module_init(s5pv210_led_init);
    module_exit(s5pv210_led_exit);
    
    
    // MODULE_xxx这种宏作用是用来添加模块描述信息
    MODULE_LICENSE("GPL");				// 描述模块的许可证
    MODULE_AUTHOR("ljj");				// 描述模块的作者
    MODULE_DESCRIPTION("s5pv210 led");	// 描述模块的介绍信息
    MODULE_ALIAS("s5pv210_led");			// 描述模块的别名信息

    运行结果:

    [root@liu led1]# cd /sys/class/leds/
    [root@liu leds]# ls -l
    total 0
    lrwxrwxrwx    1 root     0                0 Jan  1 12:03 led1 -> ../../devices/platform/s5pv210_led.0/leds/led1
    lrwxrwxrwx    1 root     0                0 Jan  1 12:03 led2 -> ../../devices/platform/s5pv210_led.1/leds/led2
    lrwxrwxrwx    1 root     0                0 Jan  1 12:03 led3 -> ../../devices/platform/s5pv210_led.2/leds/led3
     

    [root@liu s5pv210_led.0]# cd /sys/bus/platform/devices/
    [root@liu devices]# ls -l
    s5pv210_led.0
    s5pv210_led.1
    s5pv210_led.2

    [root@liu devices]# cd /sys/bus/platform/devices/s5pv210_led.0
    [root@liu s5pv210_led.0]# ls
    driver     leds       modalias   power      subsystem  uevent
    [root@liu s5pv210_led.0]# cd leds/
    [root@liu leds]# ls
    led1
    [root@liu leds]# cd led1/
    [root@liu led1]# ls
    brightness      max_brightness  subsystem
    device          power           uevent

    [root@liu led1]# cd /sys/bus/platform/drivers/
    [root@liu drivers]# ls
    5pv210_led                                      //有些驱动没写
    [root@liu drivers]# cd s5pv210_led/
    [root@liu s5pv210_led]# ls
    bind           s5pv210_led.0  s5pv210_led.2  unbind
    module         s5pv210_led.1  uevent
    [root@liu s5pv210_led]# ls -l
    total 0
    --w-------    1 root     0             4096 Jan  1 12:16 bind
    lrwxrwxrwx    1 root     0                0 Jan  1 12:16 module -> ../../../../module/module_test
    lrwxrwxrwx    1 root     0                0 Jan  1 12:16 s5pv210_led.0 -> ../../../../devices/platform/s5pv210_led.0
    lrwxrwxrwx    1 root     0                0 Jan  1 12:16 s5pv210_led.1 -> ../../../../devices/platform/s5pv210_led.1
    lrwxrwxrwx    1 root     0                0 Jan  1 12:16 s5pv210_led.2 -> ../../../../devices/platform/s5pv210_led.2
    --w-------    1 root     0             4096 Jan  1 12:00 uevent
    --w-------    1 root     0             4096 Jan  1 12:16 unbind
     

     


     

    展开全文
  • 上一篇介绍了linux驱动的概念,以及linux设备驱动的基本分类情况及其各个分类的依据和差异,这一篇我们来描述如何写一个类似hello world的简单测试驱动程序。而这个驱动的唯一功能就是输出hello world。 在编写...

    上一篇介绍了linux驱动的概念,以及linux下设备驱动的基本分类情况及其各个分类的依据和差异,这一篇我们来描述如何写一个类似hello world的简单测试驱动程序。而这个驱动的唯一功能就是输出hello world。

    在编写具体的实例之前,我们先来了解下linux内核下调试程序的一个重要函数printk以及几个重要概念。

    printk类似c语言的printf,是内核中输出打印信息的函数。以后驱动调试中的重要性不言而喻,下面先做一个简单介绍。

    printk的级别

    日志级别一共有8个级别,printk的日志级别定义如下(在include/linux/kernel.h中):  
    #define KERN_EMERG 0/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/  
    #define KERN_ALERT 1/*报告消息,表示必须立即采取措施*/  
    #define KERN_CRIT 2/*临界条件,通常涉及严重的硬件或软件操作失败*/  
    #define KERN_ERR 3/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/  
    #define KERN_WARNING 4/*警告条件,对可能出现问题的情况进行警告*/  
    #define KERN_NOTICE 5/*正常但又重要的条件,用于提醒*/  
    #define KERN_INFO 6/*提示信息,如驱动程序启动时,打印硬件信息*/  
    #define KERN_DEBUG 7/*调试级别的消息*/

    没有指定日志级别的printk语句默认采用的级别是:DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在kernel/printk.c中可以找到。在驱动调试过程中打开所有日志信息可使用echo 7 > /proc/sys/kernel/printk,相对应关闭日志使用echo 0 > /proc/sys/kernel/printk

    下面再来介绍几个重要的概念,这些概念可以先做一个了解,后续的文章中还会提到。

    内核空间和用户空间

    linux系统分为两个级别。内核运行在最高级别,可以进行所有的操作。而应用程序运行在最低级别,处理器控制着对硬件的直接访问以及对内存的非授权访问。内核空间和用户空间不仅有不同的优先级等级,而且有不同的内存映射,有各自的地址空间。详见内存管理。

    应用程序只能通过系统调用或中断从用户空间切换到内核空间,其中系统调用是软中断(0x80号中断)。执行系统调用的系统代码运行在进程上下文中,它代表调用进程执行操作,因此能够访问进程地址空间的所有数据。而处理硬件中断的内核代码和进程是异步的,与任何一个特定进程无关。

    内核中的并发

    内核编程区别于常见应用程序编程的地方在于对并发的处理。大部分应用程序除多线程外,通常是顺序执行的,不需要关心由于其他事情的发生而改变它的运行环境。内核代码不是这样,同一时刻,可能有多个进程使用访问同一个模块。

    内核编程要考虑并发问题的原因:1.linux是通常正在运行多个并发进程,并且可能有多个进程同时使用我们的驱动程序。2.大多数设备能够中断处理器,而中断处理程序异步进行,而且可能在驱动程序正试图处理其它任务时被调用。3.一些类似内核定时器的代码在异步运行。4.运行在对称多处理器上(SMP),不止一个cpu在运行驱动程序。5.内核代码是可抢占的。

    当前进程

    内核代码可通过访问全局项current来获得当前进程。current指针指向当前正在运行的进程。在open、read、等系统调用的执行过程中,当前进程指的是调用这些系统调用的进程。内核代码可以通过current指针获得与当前进程相关的信息。

    内核中带“__”的函数:内核API函数具有这种名称的,通常都是一些接口的底层函数,应该谨慎使用。实质上,这里的双下划线就是要告诉程序员:谨慎调用,否则后果自负。以__init为例,__init表明该函数仅在初始化期间使用。在模块被装载之后,模块装载器就会将初始化函数扔掉,这样可以将函数占用的内存释放出来,已做它用。注意,不要在结束初始化之后仍要使用的函数(或者数据结构)上使用__init、__initdata标记。这里摘抄网上的一段总结,如下。

    __init, __initdata等属性标志,是要把这种属性的代码放入目标文件的.init.text节,数据放入.init.data节──这一过程是通过编译内核时为相关目标平台提供了xxx.lds链接脚本来指导ld完成的。
       对编译成module的代码和数据来说,当模块加载时,__init属性的函数就被执行;
       对静态编入内核的代码和数据来说,当内核引导时,do_basic_setup()函数调用do_initcalls()函数,后者负责所有.init节函数的执行。
       在初始化完成后,用这些关键字标识的函数或数据所占的内存会被释放掉。
    1) 所有标识为__init的函数在链接的时候都放在.init.text这个区段内,在这个区段中,函数的摆放顺序是和链接的顺序有关的,是不确定的。 
    2) 所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等),注意,这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关,和1)中所述的这些函数本身在.init.text区段中的顺序无关。 

    下面我们来看一个驱动程序的hello world程序是如何实现的:

    #include <linux/init.h>
    #include <linux/module.h>
    MODULE_LICENSE("Dual BSD/GPL");
    
    static int hello_init(void)
    {
            printk(KERN_ALERT "Hello, world\n");
            return 0;
    }
    static void hello_exit(void)
    {
    
            printk(KERN_ALERT "Goodbye, cruel world\n");
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
    

    内核模块的编译与应用程序的编译有些区别,此hello world模块的编译命令为:

    make -C /xxx/xxx/kernel_src/ M=$(PWD) modules

    其中/xxx/xxx/kernel_src/ 为已经配置编译过的内核源码路径,ubuntu下一般在/lib/modules/$(shell uname -r)/build目录下。

    此函数只有两个函数,一个是hello_init,在insmod的时候执行,这个是模块的初始化函数,另一个是hello_exit,在rmmod的时候执行,是模块卸载时要执行的函数。此模块的唯一功能就是在insmod的时候输出Hello,world,在rmmod的时候输出Goodbye,cruel world。

    在编写应用程序时,我们一般都是由多个源文件组成的,这个时候编译肯定就不能继续使用命令行编译了,就要使用到Makefile。同样,驱动模块的编译也需要使用的makefile,下面就是一个在编译含有多个源码文件的驱动模块时可以参考的Makefile文件。

    ifndef CROSS_COMPILE
    export CROSS_COMPILE ?=arm-none-linux-gnueabi-
    endif
    
    ARCH ?= arm
    
    SRC_DIR := /home/XXX/XXX
    OBJ_DIR  := $(SRC_DIR)/obj
    PWD := $(shell pwd)
    
    LINUX_SRC ?= /home/XXX/kernel
    
    CFG_INC = -I$(SRC_DIR) \
    	-I$(DIR_A) \
    	-I$(DIR_B)
    
    CFG_FLAGS += -O2
    EXTRA_CFLAGS  += $(C_FLAGS) $(CFG_INC) $(CFG_INC)
    
    obj-m := mymodule.o
    
    mymodule-objs := a.o
    mymodule-objs += b.o
    mymodule-objs += c.o
    
    modules:
    	@make ARCH=$(ARCH) -C $(LINUX_SRC) M=$(PWD) modules
    
    clean:
    	@echo "cleaning..."
    	rm -f mymodule.ko mymodule.o mymodule.mod.* modules.order Module.symvers
    	rm -f $(mymodule-objs)

    第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注个人微信公众平台:程序员互动联盟(coder_online)

    1.直接帮你解答linux设备驱动疑问点

    2.第一时间获得业内十多个领域技术文章

    3.针对文章内疑点提出问题,第一时间回复你,帮你耐心解答

    4.让你和原创作者成为很好的朋友,拓展自己的人脉资源

    扫一扫下方二维码或搜索微信号coder_online即可关注,我们可以在线交流。

    展开全文
  • Linux设备驱动之字符设备驱动

    万次阅读 多人点赞 2016-07-01 19:37:35
    一、linux系统将设备分为3类:字符设备、块设备、网络设备。 应用程序调用的流程框图: 三种设备的定义分别如下, 字符设备:只能一个字节一个字节的读写的设备,不能随机读取设备内存中的某一数据,读取...
  • 前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 —— platform 设备驱动 ,下面将通过一个实例来深入我们的学习。   一、platform 驱动的工作过程  platform模型驱动编程,需要实现platform_device...
  • linux设备驱动模型架构分析 一

    千次阅读 2018-07-05 20:34:07
    ”这个描述就是linux设备驱动模型(下面简称为LDDM)。LDDM不是独立存在,其体系如下图所示:对架构的每一部分本文都会开辟独立的章节进行描述。暂且抛开这个架构,首先从总体上了解一下LDDM。LDDM与驱动程序密切...
  • LINUX设备驱动程序第三版.pdf免费下载链接(.pdf书籍的优点是便于直接在电脑中保存有电脑就可以阅读,如果觉得这本书给你提供到了很大的帮助,可以去书店补一本纸质版) 资源保存在腾讯微云上,下载不需要微云...
  • linux设备驱动原理与本质

    千次阅读 2018-11-20 17:06:55
    在底层硬件的基础上,操作系统覆盖一层驱动,屏蔽底层硬件的操作,通过特定的软件接口去操作底层硬件,用户在用户空间可以很容易的把软件设计目标放在策略与需求上,可以很方便的屏蔽掉底层实现,从而很好的完成客户...
  • Linux设备驱动开发入门

    千人学习 2018-10-22 21:38:04
    本课程讲解Linux驱动程序开发基本知识,程序架构,字符设备编程,杂项设备编程,具体硬件模块驱动开发。
  • linux驱动编写(总结篇)

    万次阅读 多人点赞 2019-08-14 06:56:47
    【 声明:版权所有,欢迎转载,请勿用于商业用途。... 01、linux驱动编写(入门) 02、linux驱动编写(虚拟字符设备编写) ...05、linux驱动编写(块设备驱动代码) 06、linux驱动编写(platfo...
  • Linux设备驱动开发详解:基于最新的Linux 4.0内核》
  • linux UVC摄像头驱动 简介

    万次阅读 2013-02-25 15:55:41
     Linux UVC driver(uvc) 该驱动适用于符合USB视频类(USB Video Class)规范的摄像头设备,它包括V4L2内核设备驱动和用户空间工具补丁。大多数大容量存储器设备(如优盘)都遵循USB规范,因而仅用一个单一驱动就可以...
  • 本博实时更新《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)的最新进展。 目前已经完成稿件。 2015年8月9日,china-pub开始上线预售: ... 2015年8月20日,各路朋友报喜...
  • linux2.6驱动开发系列教程

    万次阅读 热门讨论 2011-11-01 21:44:33
    这段时间一直在做android下的驱动,android驱动底层跟linux如出一辙,所以这里准备做一个专题,把linux驱动做一个总结,为android接下来的驱动开发打好基础,大致的思想如下: 一、linux驱动基础开发 0、linux驱动...
  • Linux下查看网卡驱动和版本信息

    万次阅读 2017-05-26 15:25:25
    Linux下查看网卡驱动和版本信息 查看网卡生产厂商和信号 查看基本信息:lspci 查看详细信息:lspci -vvv # 3个小写的v 查看网卡信息:lspci | grep Ethernet 查看网卡驱动 查看网卡驱动信息:lspci -vvv # ...
  • linux驱动基础开发0——linux 设备驱动概述

    万次阅读 多人点赞 2011-09-22 17:40:33
    目前,Linux软件工程师大致可分为两个层次:  (1)Linux应用软件工程师(Application Software Engineer):  主要利用C库函数和Linux API进行应用软件的编写;  从事这方面的开发工作,主要需要
  • Linux从未停歇脚步。Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核。做技术,从来没有终南捷径,拼的就是坐冷板凳的傻劲。 这是一个连阅读都被...
  • Linux电源管理非常复杂,牵扯到系统级的待机、频率电压变换、系统空闲时的处理以及每个设备驱动对于系统待机的支持和每个设备的运行时电源管理,可以说和系统中的每个设备驱动都息息相关。 对于消费电子产品来说,...
1 2 3 4 5 ... 20
收藏数 228,607
精华内容 91,442
关键字:

linux设备驱动