驱动相关命令,指令,API,结构体,路径

【linux内核模块编程】

内核模块
编译:make modlues
安装模块:insmod ***.ko
查看已安装模块:lsmod
卸载模块:rmmod ***
查看模块相关信息:modinfo ***.ko

【printk打印 & 输出级别 & dmesg命令】

打印函数:
printk((消息输出级别 "格式控制符",输出列表); //输出级别可以省略

输出消息级别
查看消息默认级别:cat /proc/sys/kernel/printk
修改ubuntu消息默认级别:echo 4 3 1 7 > /proc/sys/kernel/printk  //以管理员身份
修改开发板消息默认级别:修改 ~/nfs/rootfs/etc/init.d/rcS 在这个文件最后添加一行:echo 4 3 1 7 > /proc/sys/kernel/printk

虚拟终端
进入虚拟终端:ctrl+alt+[f2-f6](fn)
退出虚拟终端:ctrl+alt+f1(fn)

dmesg命令
回显打印消息:dmesg
清除当前dmesg的buf中保存的所有打印消息dmesg -c/dmesg -C

【导出符号表 EXPORT_SYMBOL_GPL】

被调用者导出:EXPORT_SYMBOL(变量名/函数名) 或者 EXPORT_SYMBOL_GPL(变量名/函数名) //用后面的

符号表文件:Module.symvers

新版本不支持复制解决办法:
    在模块2(调用者)的Makefile中指定模块1的符号表文件路径
    KBUILD_EXTRA_STMBOLS += /home/ubuntu/23051班驱动/day2/1/Module.symvers

【字符设备驱动的注册和注销 & 创建设备文件】

1,操作方法结构体 struct file_operations

操作方法结构体
struct file_operations {
    ...
    int (*open) (struct inode *, struct file *);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    int (*release) (struct inode *, struct file *);
    ...
};

 2,一步注册字符设备驱动

        注册 register_chrdev 注销 unregister_chrdev
一步注册:
注册
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
功能:实现字符设备驱动的注册(申请了一定数量(256)的设备资源)
参数:
    major:驱动的主设备号
        ==0:动态申请主设备号
        >0:静态指定一个主设备号
            //次设备号有256个,范围是(0-255)
    name:驱动名字
    fops:操作方法结构体指针
返回值:失败返回错误码
    成功:
        major==0,返回申请得到的主设备号
        major>0:返回0
        //可以通过 cat /proc/devices查看已经注册成功的驱动的名字以及主设备号

注销
void unregister_chrdev(unsigned int major, const char *name)
    功能:注销字符设备驱动
    参数:
        major:注册时申请的主设备号
        name:注册时填写的名字
    返回值:无

3,分步注册字符设备驱动

1.分配字符设备驱动对象
struct cdev *cdev = cdev_alloc();
        struct cdev *cdev_alloc(void)
        功能:申请一个字符设备驱动对象空间
        参数:无
        返回值:成功返回申请的空间首地址
        失败返回NULL
 2.字符设备驱动对象初始化
     void cdev_init(struct cdev *cdev, const struct file_operations *fops)
     功能:实现字符设备驱动的部分初始化
     参数:
         cdev:字符设备驱动对象指针
         fops:操作方法结构体指针
    返回值:无
3.设备号的申请
3.1 静态指定设备号
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    功能:静态申请设备号并注册一定数量的设备资源
    参数:
        from:静态指定的设备号(第一个设备的设备号)
        count:申请的设备数量
        name:设备名或者驱动名
    返回值:成功返回0,失败返回错误码
3.2 动态申请设备号
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)
    功能:动态申请设备号并注册一定数量的设备资源
    参数:
        dev:存放申请的到的设备号的空间首地址
        baseminor:次设备号的起始值
        count:申请的设备资源数量
        name:设备名或者驱动名
     返回值:成功返回0,失败返回错误码   
 4.根据申请的设备号和驱动对象注册驱动
     int cdev_add(struct cdev *p, dev_t dev, unsigned count)
     功能:注册字符设备驱动对象
     参数:
         cdev:字符设备驱动对象指针
         dev:申请的设备号的第一个值
         count:申请的设备资源的数量
    返回值:成功返回0,失败返回错误码

1.注销驱动对象
    void cdev_del(struct cdev *p)
    参数:
        p:要注销的对象空间指针
    返回值:无

2.释放申请的设备号和设备资源
    void unregister_chrdev_region(dev_t from, unsigned count)
    参数:
        from:申请的第一个设备号
        count:申请的设备资源的数量
    返回值:无

3.释放字符设备驱动对象空间
    void kfree(void *addr)
    功能:释放申请的内核空间
    参数:要释放的空间首地址
    返回值:无

4,手动创建设备文件 mknod

手动创建设备文件:mknod /dev/mychrdev c 241 0
    mknod:创建设备文件的命令码
    /dev/mychrdev:创建的设备文件的名字以及路径
    c:设备文件类型为字符设备文件  b表示块设备文件
    241:主设备号
    0:次设备号(0-255)

5,自动创建设备文件 & 错误码 & 设备号相关宏

1.向上提交目录信息
struct class * class_create(struct module *owner,const char *name );
    功能:申请一个设备类并初始化,向上提交目录信息
    参数:
        owner:指向当前内核模块自身的一个模块指针,填写THIS_MODULE
        name:向上提交的目录名
    返回值:成功返回申请的struct class对象空间首地址,失败返回错误码指针

2.销毁目录
    void class_destroy(struct class *cls)
    功能:销毁目录信息
    参数:cls:指向class对象的指针
    返回值:无

3.向上提交节点信息
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
    功能:创建一个设备对象,向上提交设备节点信息
    参数:
        cls:向上提交目录时的到的类对象指针
        parent:当前申请的对象前一个节点的地址,不知道就填 NULL
        devt:设备号    主设备号<<20|次设备号
        dridata:申请的device对象的私有数据,填写NULL
        fmt:向上提交的设备节点名
        ...:不定长参数   
    返回值:成功返回申请到的device对象首地址,失败返回错误码指针,指向4K预留空间

4.销毁设备节点信息
    void device_destroy(struct class *class, dev_t devt)
    功能:销毁设备节点信息
    参数:
        class:向上提交目录时得到的类对象指针
        devt:向上提交设备节点信息时提交的设备号
    返回值:无

5.错误码相关
    在内核空间最顶层会预留4K空间,当struct class函数调用失败后函数会返回一个指向这4K空间的指针
    bool __must_check IS_ERR(__force const void *ptr)
    功能:判断指针是否指向4K预留空间
    参数:要判断的指针
    返回值:如果指着指向4K预留空间返回逻辑真,否则返回逻辑假
     long __must_check PTR_ERR(__force const void *ptr)
     功能:通过错误码指针得到错误码
     ex:struct class_create *cls=struct class_create(THIS_MODULE,"mycdev");
     if(IS_ERR(cls))
     {
         printk("向上提交目录失败\n");
         return -PRT_ERR(cls);     
     }
    */
6.设备号相关宏
MKDEV(主设备号,次设备号):根据主设备号和次设备号得到设备号
MAJOR(dev):根据设备号获取主设备号
MINOR(dev):根据设备号获取次设备号

6,用户和内核的数据拷贝

        copy_to_user  copy_from_user
1.long copy_to_user(void __user *to, const void *from, unsigned long n)
    功能:实现内核空间数据向用户空间拷贝
    参数:
        to:用户空间存放拷贝来的数据的buf首地址
        from:内核空间存放要拷贝的数据的buf首地址
        n:要拷贝的数据大小
    返回值:成功返回0失败返回未拷贝的字节数

2.long copy_from_user(void *to, const void __user *from, unsigned long n)
    功能:实现用户空间数据向内核空间拷贝
    参数:
        to:内核空间存放拷贝来的数据的buf首地址
        from:用户空间存放要拷贝的数据的buf首地址
        n:要拷贝的数据大小
    返回值:成功返回0失败返回未拷贝的字节数

7,物理内存映射为虚拟内存

        映射 ioremap 取消映射 iounmap
映射:
void  *ioremap(unsigned long port, unsigned long size)
    功能:映射指定大小的物理内存为虚拟内存
    参数:
        port:要映射的物理内存首地址
        size:映射的物理内存大小
    返回值:成功返回映射得到的虚拟内存首地址,失败返回NULL

取消映射:
void iounmap(volatile void __iomem *addr)
    功能:取消物理内存映射
    参数:
        addr:虚拟内存首地址
    返回值:无

【ioctl函数及功能码】

应用程序
    int ioctl(int fd, unsigned long request, ...);
    功能:进行IO选择控制
    参数:
        fd:文件描述符
        request:要进行的功能控制的功能码
        ...:可以写也可以不写,如果写的话传递一个整型变量或者一个地址
    返回值:成功返回0,失败返回错误码

驱动程序
    long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg)
    解释:当应用程序中调用ioctl函数时,驱动中的ioctl操作方法被回调
    参数分析:
         file:文件指针
         cmd:功能码,由ioctl第二个参数传递得到
         arg:由ioctl第三个参数传递得到 
 
功能码
    查询内核帮助手册:~/linux-5.10.61/Documentation/userspace-api/ioctl
    vi ioctl-decoding.rst

一个功能码的组成部分如下:
     ====== ==================================
     bits   meaning
     ====== ==================================
     31-30    00 - no parameters: uses _IO macro // 00表示不传递第三个参数
        10 - read: _IOR
        01 - write: _IOW
        11 - read/write: _IOWR   //31-30读写标志位
     29-16    size of arguments   //29-16ioctl第三个参数所占的大小(填数据类型即可) 
     15-8    ascii character supposedly //15-8标志标志,ASCII 常用a-z
        unique to each driver
     7-0    function #                //7-0 功能位:例,0低电平,1高电平...
 ====== ==================================

内核提供的构建功能码的API
    type:15-8 标志位
    nr:7-0 功能位
    size:29-16 第三个参数的所占字节大小(类型)
四个宏
    #define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
    #define _IOR(type,nr,size)    _IOC(_IOC_READ,(type),(nr),sizeof(size))
    #define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
    #define _IOWR(type,nr,size)    _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)
例,
    //构建LED开关的功能码,添加ioctl第三个参数int
    #define LED_ON _IOW('l',1,int)
    #define LED_OFF _IOW('l',0,int)

【字符设备驱动的内部实现】

 struct cdev 字符驱动信息结构体

struct cdev {
    struct kobject kobj;               //基类对象
    struct module *owner;              //模块对象指针  THIS_MODULE
    const struct file_operations *ops; //操作方法结构体指针
    struct list_head list;             //用于构成链表的成员
    dev_t dev;                         //第一个设备号  
    unsigned int count;                //设备资源数量
};

struct inode 文件信息结构体

struct inode {
    umode_t            i_mode;//文件的权限
    unsigned short        i_opflags;
    kuid_t            i_uid;//文件的用户ID
    kgid_t            i_gid;//组ID
    unsigned int        i_flags;
    dev_t            i_rdev;//设备号
    union {
        
        struct block_device    *i_bdev;//块设备
        struct cdev        *i_cdev;//字符设备
        char            *i_link;
        unsigned        i_dir_seq;
    };

struct task_struct 进程信息结构体

struct task_struct {
    volatile long            state;//进程状态
    int             on_cpu;//表示进程在哪个CPU上执行
    int                prio;//进程优先级
    pid_t                pid;//进程号
    struct task_struct __rcu    *real_parent;//父进程
    struct files_struct        *files;//打开的文件相关结构体
};

struct files_struct {
    struct file __rcu * fd_array[NR_OPEN_DEFAULT];//结构体指针数组
};
    fd_array是一个指针数组,数组中每一个成员都指向一个struct file类型的对象,而数组的下标就是我们常说的文件描述符

struct file 进程中打开的文件信息结构体

struct file {
  struct path        f_path;//文件路径
    struct inode        *f_inode;    /* cached value */
    const struct file_operations    *f_op;//操作方法结构体
    unsigned int         f_flags;//open函数的第二个参数赋值给f_flags
    fmode_t            f_mode;//打开的文件的权限
    void            *private_data;//私有数据,可以实现函数件数据的传递
};

【linux内核中的并发和竞态】

        自旋锁 & 信号量 & 互斥体 & 原子操作

自旋锁
1.定义自旋锁
    spinlock_t lock;
2.初始化自旋锁
    spin_lock_init(&lock);
3.上锁(获取锁)
    void spin_lock(spinlock_t *lock)
4.解锁(释放锁)
    void spin_unlock(spinlock_t *lock)

信号量
1.定义一个信号量
    struct semaphore sema;
2.初始化信号量
    void sema_init(struct semaphore *sem, int val)
    参数:
        sem:信号量指针
        val:给信号量的初始值
3.获取信号量(上锁)
    void down(struct semaphore *sem)//信号量数值-1
4.释放信号量(解锁)
    void up(struct semaphore *sem);

互斥体
1.定义互斥体
    struct mutex mutex;
2.初始化互斥体
    mutex_init(&mutex);
3.上锁
    void  mutex_lock(struct mutex *lock)
4.解锁
    void  mutex_unlock(struct mutex *lock)

原子操作
1.定义原子变量并且初始化
    atomic_t atm=ATOMIC_INIT(1);//将原子变量的数值初始化为1
2.将原子变量的数值-1并且和0比较
    int atomic_dec_and_test(atomic_t *atm)
    功能:将原子变量的数值-1并且和0比较
    参数:
        atm:原子变量的指针
    返回值:如果原子变量-1后结果为0,则返回真,否则返回假
3.void atomic_inc(atomic_t *atm)
    功能:原子变量的数值+1

【IO模型】

        阻塞IO & 信号驱动IO & IO多路复用

    S    interruptible sleep (waiting for an event to complete)//可中断休眠态,可以被外部信号打断,ctrl+c等
    D    uninterruptible sleep (usually IO)//不可中断休眠态,不可以被外部信号打断,可以用kill -9等杀死

***************阻塞IO实现相关的API**************

1.定义一个等待队列头
    wait_queue_head_t wq_head;
2.初始化等待队列
    init_waitqueue_head(&wq_head);
3.1将进程切换为不可中断的休眠态
    wait_event(wq_head, condition)
    功能: 将进程切换为不可中断的休眠态
    参数:
        wq_head:等待队列头
        condition:标识硬件数据是否就绪的标志变量
    返回值:无
3.2将进程切换为可中断的休眠态
    wait_event_interruptible(wq_head, condition)
    功能:将进程切换为可中断的休眠态
    参数:
        wq_head:等待队列头
        condition:标识硬件数据是否就绪的标志变量
    返回值:当硬件数据准备好后进程正常被唤醒返回0,如果进程被外部信号中断休眠则返回错误码 -ERESTARTSYS
4.1wake_up(&wq_head)
    功能:唤醒不可中断休眠态的进程,如果在condition为假的情况下调用此函数,休眠的进程被唤醒后会马上再次休眠
    参数:
        wq_head:等待队列头指针
    返回值:无
4.2wake_up_interruptible(&wq_head)
    功能:唤醒可中断休眠态的进程,如果在condition为假的情况下调用此函数,休眠的进程被唤醒后会马上再次休眠
    参数:wq_head:等待队列头指针
    返回值:无
**************IO多路复用*******************
所有的io复用方式在驱动中对应的操作方法都是poll方法

********** 驱动层************
__poll_t (*poll) (struct file *file, struct poll_table_struct *wait)

1.定义一个等待队列头
    wait_queue_head_t wq_head;   
2.向上提交等待队列头
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
       功能:将等待队列头向上层提交
       参数:
           filp:文件指针,将poll方法第一个参数填进去
           wait_address:要向上提交的等待队列头地址
           p:设备驱动和上层关联的通道,将poll方法的第二个参数填进去
       返回值:无
            判断condition的值,根据事件是否发生给一个合适的返回值
            if(condition){
                return POLLIN;/ /POLLIN表示读   POLLLOUT表示写            
            }else{
                return 0;            
            }

************epoll相关API***************
1.创建epoll句柄
    int epoll_create(int size);
    功能:创建一个epoll句柄//创建红黑树根节点
        epoll把要监测的事件文件描述符挂载到红黑树上
    参数:size 没有意义,但是必须>0
    返回值:成功返回根节点对应的文件描述符,失败返回-1
2.对epoll控制:添加事件/修改事件类型/删除事件
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    功能:实现对于epoll的控制
    参数:
        epfd:epoll_create创建的句柄
        op:控制方式
             EPOLL_CTL_ADD:添加要监测的事件文件描述符
             EPOLL_CTL_MOD:修改epoll检测的事件类型
             EPOLL_CTL_DEL:将文件描述符从epoll删除
    fd:要操作的文件描述符
    event:事件结构体指针
        struct epoll_event {
               uint32_t     events; //EPOLLIN(读) EPOLLOUT(写)
               epoll_data_t data;        /* User data variable */
        }; //事件结构体
              typedef union epoll_data {
                   void        *ptr;
                   int          fd;//使用这个
                   uint32_t     u32;
                   uint64_t     u64;
               } epoll_data_t;  
3.阻塞等待准备好的文件描述符
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    功能:阻塞等待准备好的文件描述符
    参数:
        epfd:epoll句柄
        events:存放就绪事件描述符的结构体数组首地址
        maxevents:监听的事件最大个数
        timeout:超时检测
            >0:毫秒级检测
            ==0:立即返回
            -1:不关心是否超时
    返回值:
        >0:准备好的文件描述符的个数
        ==0:超时
        <0:失败
*******************信号驱动IO********************

************应用程序************
1.打开设备文件
2.signal(SIGIO,信号处理函数)//注册信号的信号处理函数
3.回调驱动中的fasync方法,完成发送信号之前的准备工作
    int flags=fcntl(fd,F_GETFL);//获取文件描述符属性
    fcntl(fd,F_SETFL,flags|FASYNC);//在文件描述符表的flags中添加FASYNC,就可以回调fasync方法
4.设置fd对应的驱动程序发送SIGIO信号只发送给当前进程
    fcntl(fd,F_SETOWN,getpid());
    while(1)//不让主程序结束
    {
        printf("aaaaa\n");
        sleep(1);
    }
**********驱动程序************
1.定义异步对象指针
    struct fasync_struct *fp;
2.封装异步操作方法
    int (*fasync) (int fd, struct file * file, int on)//异步操作方法
3.完成异步对象的空间分配和初始化
    int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)//功能:完成异步对象的空间分配和初始化
4.封装中断处理函数,完成信号发送
    void kill_fasync(struct fasync_struct **fp, int sig, int band)
    功能:向进程发送信号
    参数:
        fp:异步对象的二级指针
        sig:要发生的信号  SIGIO
        band:发送信号时添加的事件标志   POLL_IN表述读数据操作

【设备树】

设备树linux官方手册:Device Tree Usage - eLinux.org
设备树源码的路径:~/linux-5.10.61/arch/arm/boot/dts/stm32mp157a-fsmp1a.dts
***.dts//设备树的源码文件
***.dtsi//设备树的补充文件,类似于c里的h文件
|
DTC(编译工具)  ///make dtbs(编译设备树文件的命令)
|
***.dtb//设备树的镜像文件

如何启用设备树和设备树的编译工具DTC:
    打开内核的顶层目录下的.config文件,在文件中有如下两个配置则说明当前内核已经启用的设备树和DTC工具
如何启用设备树和设备树的编译工具DTC:
    打开内核的顶层目录下的.config文件,在文件中有如下两个配置则说明当前内核已经启用的设备树和DTC工具:
    CONFIG_DTC = y
    CONFIG_OF  = y

设备树节点的命名格式:<name>[@<unit-address>
    <name>是一个简单的 ASCII 字符串,长度最多为 31 个字符。通常,节点是根据它所代表的设备类型来命名的。
    如果节点使用地址描述设备,则包含单元地址。通常,单元地址是用于访问设备的主要地址,列在节点的属性中。
ex:
    1.gpio@50006000{};//gpioe控制器节点的名字,gpio支持寻址,所以要在名字里加上地址
    2.LED1{};

示例:
/dts-v1/;//设备树的版本号

/{      // ‘/’表示根节点
    node1 {  //node1是根节点的子节点
        a-string-property = "A string";//node1节点的属性键值对
        a-string-list-property = "first string", "second string";
        // hex is implied in byte arrays. no '0x' prefix is required
        a-byte-data-property = [01 23 34 56];
        child-node1 {  //child-node1是node1节点的子节点
            first-child-property;//child-node1节点的额属性键值对,空属性
            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 {
        };
    };
};
属性键值对的数据类型
1.文本字符串(以 null 结尾)用双引号表示:
    string-property = "a string";
2.“cell”是 32 位无符号整数,由尖括号分隔:
    cell-property = <0xbeef 123 0xabcd1234>;
3.单字节数据用方括号分隔:
    binary-property = [0x01 0x23 0x45 0x67];
4.不同表示形式的数据可以使用逗号连接在一起:
    mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
5.逗号也用于创建字符串列表:
    string-list = "red fish", "blue fish";

常用标准化的属性键值对
    在设备树中有一些特定的键值对用来表示特定的含义:
    - compatible = "芯片厂商,芯片型号";//描述当前设备的厂商信息
    - device_type:用于描述当前设备的设备类型
    - reg=<地址,内存大小>:用于描述当前节点对应设备的寻址内存首地址和大小
    - #address-cells=<n>:用于指定子节点中reg属性用来描述地址的u32的个数
    - #size-cells=<n>:用于指定子节点中reg属性用来描述地址对应内存大小的u32的个数

添加一个自定义的设备树节点到设备树源码中被内核解析示例:

1.添加设备树节点
    在stm32mp157a-fsmp1a.dts文件的根节点内部添加如下内容:
    //自定义设备树
    mynode@0x12345678{
        compatible = "hqyj,mynode";
        astring="hello 23051";
        uint  =<0xaabbccdd 0x11223344>;
        binarry=[00 0c 29 7b f9 be];
        mixed ="hello",[11 22],<0x12345678>;
     };
2.编译设备树
    返回到内核顶层目录下执行编译设备树的命令make dtbs
3.将镜像复制到~/tftpboot中,重启开发板
4.查看自己添加的节点是否被成功解析
    开发板系统目录:/proc/device-tree/目录下是否有以节点名为名的文件夹生成

struct device_node 设备树节点结构体

当设备树中的信息加载到内核空间之后,每一个节点都是一个struct device_node类型
struct device_node {
    const char *name;//设备树节点的名字mynode
    phandle phandle;//节点标识
    const char *full_name;//全名  mynode@0x12345678
    struct  property *properties;//属性链表首地址
    struct  device_node *parent;//父节点指针
    struct  device_node *child;//子节点指针
    struct  device_node *sibling;//兄弟节点指针
};

struct property 节点属性结构体

一个设备树节点中存在多个属性,组成成了一个链表,链表中每一个节点保存了设备的一个信息,链表节点的类型是struct property类型
struct property {
    char    *name;//键名
    int length;//数值的大小
    void    *value;//数值首地址
    struct property *next;//下一个属性对象指针
};

设备树节点解析API & 大端转主机字节序

1.根据名字解析
    struct device_node *of_find_node_by_name(struct device_node *from, const char *name)
    功能:根据设备树节点的名字解析指定的设备树节点信息
    参数:
        from:要解析的节点所在子树的跟节点,填NULL,默认从根节点解析
        name:要解析的设备树节点的名字
    返回值:成功返回目标节点首地址,失败返回NULL
2.根据路径解析
    struct device_node *of_find_node_by_path(const char *path)
    功能:根据设备树节点路径解析设备树节点信息
    参数:
        path:设备树所在的节点路径   /mynode@0X12345678
    返回值:成功返回目标节点首地址,失败返回NULL
3.根据厂商信息(兼容性)
struct device_node *of_find_compatible_node( struct device_node *from, const char *type, const char *compat)
    功能:根据节点的厂商信息解析指定的节点
    参数:
        from:要解析的节点所在子树的跟节点,填NULL,默认从根节点解析
        type:设备类型,填NULL
        compat:compatible值(兼容性)
    返回值:成功返回目标节点首地址,失败返回NULL

1.字节序转换
__u32 __be32_to_cpup(const __be32 *p)
 功能:将大端字节序32位的数据转换成主机字节序
 参数:要转换的数据指针
 返回值:转换后的值

根据设备树节点和键值对键名 解析键值对的属性

0.根据键名可以得到指向属性对象的指针,再根据指针获取属性信息
            下面几个是根据键名直接获得相应类型的属性信息

    struct property *of_find_property(const struct device_node *np, const char *name, int *lenp)
    功能:解析指定键名的属性信息
    参数:
        np:设备树节点对象指针
        name:要解析的属性键名
        lenp:解析到的属性的值的长度
    返回值:成功返回属性对象指针,失败返回NULL


1.根据键名和索引号获取u32类型的值
    int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
    功能:获取u32类型的值
    参数:
        np:节点指针
        propname:键名
        index:值中32位数的索引号,第一个值索引号为0
        out_value:输出的数值
    返回值:成功返回0,失败返回错误码

2.根据键名获取数组类型的值
    int of_property_read_variable_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz_min, size_t sz_max)
    功能:获取u32类型的数组的值
    参数:
        np:节点指针
        propname:键名
        out_values:存放获取到的值的空间的首地址
        min:期待读取的数据的最小个数
        max:期待读取的数据的最大个数
    返回值:成功返回0,失败返回错误码
3.根据键名获取字符串类型的值
    int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string)
    功能:获取字符串类型的值
    参数:
        np:节点指针
        propname:键名
        out_string :字符串首地址
    返回值:成功返回0,失败返回错误码
4.根据键名获取u8类型的值
int of_property_read_variable_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz_min, size_t sz_max)
    功能:获取u8类型的数组的值
    参数:
        np:节点指针
        propname:键名
        out_values:存放获取到的值的空间的首地址
        min:期待读取的数据的最小个数
        max:期待读取的数据的最大个数
返回值:成功返回0,失败返回错误码

5.根据键名获取u8类型的数组的值
    int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
    功能:获取u8类型的数组的值
    参数:
        np:节点指针
        propname:键名
        out_values:存放获取到的值的空间的首地址
        sz:数组成员个数
    返回值:成功返回0,失败返回错误码

【GPIO子系统】

        API & 新版API

1.解析GPIO相关的设备树节点(可根据路径/名字/compatible解析)
    struct device_node *of_find_node_by_path(const char *path)
    功能:根据设备树节点路径解析设备树节点信息
    参数:path:设备树所在的节点路径   /mynode@0X12345678
    返回值:成功返回目标节点首地址,失败返回NULL

2.根据解析的GPIO相关节点信息获取GPIO编号
    int of_get_named_gpio(struct device_node *np,const char *propname, int index)
    功能:获取GPIO编号
    参数:
        np:设备树节点指针
        proname:gpio编号信息对应到的键名
        index:管脚在这个属性键值对中的索引号
    返回值:成功返回GPIO编号,失败返回错误码

3.向内核申请要使用的GPIO编号
    int gpio_request(unsigned gpio, const char *label)
    功能:申请GPIO编号
    参数:
        gpio:要申请的GPIO编号
        label:标签,填NULL
    返回值:成功返回0,失败返回错误码

4. 设置引脚输入/输出功能
    int gpio_direction_input(unsigned gpio)
    功能:将gpio编号对应的gpio管脚设置为输入
    参数:gpio:gpio编号
    返回值:成功返回0,失败返回错误码

    int gpio_direction_output(unsigned gpio, int value)
    功能:将gpio编号对应的gpio管脚设置为是输出
    参数:
        gpio:gpio编号
        value:默认的输出值  (1)输出高电平  (0)输出低电平
    返回值:成功返回0,失败返回错误码

5.输出功能下:设置输出高低电平
    void gpio_set_value(unsigned gpio, int value)
    功能:设置gpio编号对应的gpio管脚 输出高低电平
    参数:
        gpio:gpio编号
        value:默认的输出值  (1)输出高电平  (0)输出低电平
    返回值:无 

6.获取引脚的值
    int gpio_get_value(unsigned gpio)
    功能:获取gpio编号对应到的GPIO引脚状态值
    参数:
        gpio:gpio编号
    返回值:1(高电平)  0(低电平状态)

7.释放GPIO编号
    void gpio_free(unsigned gpio)
    参数:要释放的gpio编号

**********************新版API****************************
    新版本的API操作核心不再是GPIO编号,而是GPIO对象
    新版API函数名基本上以gpiod_***为特点

0. 可代替2-5步解析出GPIO对象,完成申请,设置输入/输出模式
    struct gpio_desc *gpiod_get_from_of_node(struct device_node *node, const char *propname, int index, enum gpiod_flags dflags, const char *label)
功能:在设备树节点中解析出GPIO对象,并向内核申请
参数:
node:设备树节点信息指针
proname:键名
index:索引号
dflags:设置GPIO默认状态
        GPIOD_IN:输入
        GPIOD_OUT_LOW:输出低电平
        GPIOD_OUT_HIGH:输出高电平
label:标签,填NULL
返回值:成功返回gpio对象指针,失败返回内核4K预留空间指针(错误码指针)

1.新版的其他相关API单独使用
    int gpiod_direction_output(struct gpio_desc *desc, int value) //设置为输出
    int gpiod_direction_input(struct gpio_desc *desc) //设置位输入
    void gpiod_set_value(struct gpio_desc *desc, int value) //设置引脚状态
2.int gpiod_get_value(const struct gpio_desc *desc) //获取引脚状态
3.void gpiod_put(struct gpio_desc *desc)//释放gpi对象指针

分析GPIO控制器节点

定义位置:stm32mp151.dtsi 
    pinctrl: pin-controller@50002000 {  //pinctrl是gpioe的父节点
            #address-cells = <1>;//子节点中reg属性中1个u32描述地址
            #size-cells = <1>;//子节点中reg属性中1个u32描述地址大小
            compatible = "st,stm32mp157-pinctrl";//描述厂商信息
            ranges = <0 0x50002000 0xa400>;//指定当前节点映射的地址范围     
       
            gpioe: gpio@50006000 {
                gpio-controller;//空属性,起到标识作用
                #gpio-cells = <2>; //用于指定在别的节点中引用当前节点用于gpio控制时需要有2个u32进行描述,根据父节点,需要 1个描述地址,1 个描述地址大小              
                reg = <0x4000 0x400>;//gpio的地址信息
                clocks = <&rcc GPIOE>;//当前控制器的使能时钟
                st,bank-name = "GPIOE";//指定控制器名字为GPIOE
                status = "disabled";  //GPIO控制器状态为disable
                        //okay:使能   disable:不工作
            };

引用gpio节点的位置:stm32mp15xxac-pinctrl.dtsi
&pinctrl {
        gpioe: gpio@50006000 {
        status = "okay"; //描述当前gpio状态为使能
        ngpios = <16>; //当前gpio控制器管理的管脚有16个
        gpio-ranges = <&pinctrl 0 64 16>; //指定管脚范围
    };
 };
查询内核帮助文档:
    ~/linux-5.10.61/Documentation/devicetree/bindings/gpio
    vi gpio.txt
说明:
    The following example could be used to describe GPIO pins used as device enable
and bit-banged data signals:

gpio0: gpio1 {
        gpio-controller;
        #gpio-cells = <2>;
    };

    data-gpios = <&gpio0 12 0>,
             <&gpio0 13 0>,
             <&gpio0 14 0>,
             <&gpio0 15 0>;
In the above example, &gpio0 uses 2 cells to specify a gpio. The first cell is
a local offset to the GPIO line and the second cell represent consumer flags,
such as if the consumer desire the line to be active low (inverted) or open
drain. This is the recommended practice.

Example of a node using GPIOs:
    node {
        enable-gpios = <&qe_pio_e 18 GPIO_ACTIVE_HIGH>;
    };

示例:
*********添加LED的设备树节点************
1.在stm32mp157a-fsmp1a.dts文件的根节点中添加如下内容
myled{
    led1-gpio=<&gpioe 10 0>;//10表示使用的gpioe第几个管脚  0,表示gpio默认属性
    led2-gpio=<&gpiof 10 0>;
    led3-gpio=<&gpioe 8 0>;
};
或者
myled{
    led-gpios=<&gpioe 10 0>,<&gpiof 10 0>,<&gpioe 8 0>;
};
2.添加完毕,返回内核顶层目录,执行make dtbs编译设备树
3.将编译生成的设备树镜像拷贝到~/tftpboot目录下,重启开发板

【linux内核定时器】

1.定时器使用步骤
    1.分配一个定时器对象
    2.初始化定时器对象
    3.注册定时器
    4.启用定时器
    5.注销定时器

2.内核定时器对象分析
struct timer_list {
    struct hlist_node entry;                  //用于构成一个对象链表
    unsigned long expires;                    //设置的时间阈值  定时一秒:jiffies+CONFIG_HZ
    void (*function)(struct timer_list *);    //定时器处理函数指针
    u32  flags;                               //标志,新版才有,填0即可
};

3.查看内核频率:内核顶层目录下的.config文件中: CONFIG_HZ = 100

*****************API****************
1.分配定时器对象
    struct timer_list mytimer;

2.初始化定时器对象
    void timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags);
    功能:初始化定时器对象,定时器对象中的expires需要手动初始化
    参数:
        timer:定时器对象指针
        func:定时器处理函数的函数指针
        flags:0
    返回值:无

3.注册定时器并默认启动
    void add_timer(struct timer_list *timer)
    功能:注册定时器对象并启用定时器
    参数:
        timer:定时器对象指针
    返回值:无

4.再次启用定时器
    int mod_timer(struct timer_list *timer, unsigned long expires)
    功能:再次启用定时器
    参数:
        timer:定时器对象指针
        expires:重新设置的定时器阈值
    返回值:启用之前没启用的定时器返回0,启用之前启用的定时器返回1

5.注销定时器对象
    int del_timer(struct timer_list *timer)

【linux内核中断】

         API

    中断注册进内核之后,中断信息会保存至一个struct irq_desc对象中,内核中存在一个struct irq_desc类型的数组,数组中每一个成员都是保存了一个注册进内核的设备中断信息

*******************API*********************
1.解析中断相关的设备树节点(可根据名字/路径/compatible解析)
    struct device_node *of_find_compatible_node( struct device_node *from, const char *type, const char *compat)
    功能:根据节点的厂商信息解析指定的节点
    参数:
        from:要解析的节点所在子树的跟节点,填NULL,默认从根节点解析
        type:设备类型,填NULL
        compat:compatible值
    返回值:成功返回目标节点首地址,失败返回NULL

2. 根据设备树节点解析设备中断的软中断号
     unsigned int irq_of_parse_and_map(struct device_node *node, int index)
     功能:解析设备中断的软中断号
     参数:
         node:设备树节点指针
         index:索引号
    返回值:成功返回软中断号,失败返回0

3.将中断注册进内核
    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
    功能:将中断注册进内核
    参数:
        irq:当前中断的软中断号
        handler:中断的中断处理函数
             中断处理函数返回值问题:
             typedef enum irqreturn irqreturn_t;
             typedef irqreturn_t (*irq_handler_t)(int , void *);
                    enum irqreturn {
                        IRQ_NONE        = (0 << 0),//这个中断不是被这个设备触发,没被处理
                        IRQ_HANDLED     = (1 << 0),//中断被正常处理
                        IRQ_WAKE_THREAD     = (1 << 1),//唤醒一个线程处理中断
                     };

        flags:注册中断时添加的设备中断相关标志
            IRQF_TRIGGER_RISING  //上升沿触发
            IRQF_TRIGGER_FALLING //下降沿触发
            IRQF_TRIGGER_HIGH    //高电平触发
            IRQF_TRIGGER_LOW    //低电平触发
            IRQF_SHARED      //共享中断,多个设备共享一个中断线 
        name:中断名
        dev:传递给中断处理函数的参数,也用于标识irqaction对象
    返回值:成功返回0,失败返回错误码
3.注销中断
    void *free_irq(unsigned int irq, void *dev_id)
    功能:注销中断
    参数:
        irq:软中断号
        dev_id:注册时填写的传递给中断处理函数的参数,这里用于释放对应的irqaction空间
    返回值:成功返回注册时填写的name

添加按键中断的设备树

1.查看已经添加的中断相关的设备树节点

******************GPIOF******************
定义位置:stm32mp151.dtsi
pinctrl: pin-controller@50002000 {
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "st,stm32mp157-pinctrl";
            ranges = <0 0x50002000 0xa400>;
            interrupt-parent = <&exti>;//引用中断父节点为exti

            gpiof: gpio@50007000 {     
                interrupt-controller;//空属性,标识当前设备为一个中断控制器节点
                #interrupt-cells = <2>;//在别的节点中引用当前节点为中断父节点时添加两个u32进行描述
                reg = <0x5000 0x400>;
                clocks = <&rcc GPIOF>;
                st,bank-name = "GPIOF";
                status = "disabled";
            };
   };
引用位置:stm32mp15xxac-pinctrl.dtsi 对gpiof节点相关信息进行了填充和更新
&pinctrl {
    gpiof: gpio@50007000 {
        status = "okay";
        ngpios = <16>;
        gpio-ranges = <&pinctrl 0 80 16>;
    };
 };
 
  ***************exti*************
 soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        interrupt-parent = <&intc>;//中断父节点为intc

        exti: interrupt-controller@5000d000 {
            compatible = "st,stm32mp1-exti", "syscon";
            interrupt-controller;
            #interrupt-cells = <2>;
            reg = <0x5000d000 0x400>;
        };
 };
***************GIC*****************   
    intc: interrupt-controller@a0021000 {
        compatible = "arm,cortex-a7-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0xa0021000 0x1000>,
              <0xa0022000 0x2000>;
    };
   

2.查询内核帮助手册:
    ~/linux-5.10.61/Documentation/devicetree/bindings/interrupt-controller
    vi interrupts.txt
***************************************************************

 1) Interrupt client nodes
  Nodes that describe devices which generate interrupts must contain an
"interrupts" property, an "interrupts-extended" property, or both. If both are
present, the latter should take precedence; the former may be provided simply
for compatibility with software that does not recognize the latter. These
properties contain a list of interrupt specifiers, one per output interrupt
    Example:
    interrupt-parent = <&intc1>;
    interrupts = <5 0>, <6 0>;
The "interrupt-parent" property is used to specify the controller to which
interrupts are routed and contains a single phandle referring to the interrupt
controller node. 
The "interrupts-extended" property is a special form; useful when a node needs
to reference multiple interrupt parents or a different interrupt parent than
the inherited one
 Example:
    interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;

  2) Interrupt controller nodes
-----------------------------
A device is marked as an interrupt controller with the "interrupt-controller"
property. This is a empty, boolean property. An additional "#interrupt-cells"
property defines the number of cells needed to specify a single interrupt.
 b) two cells
  ------------
  The #interrupt-cells property is set to 2 and the first cell defines the
  index of the interrupt within the controller, while the second cell is used
  to specify any of the following flags:
    - bits[3:0] trigger type and level flags
        1 = low-to-high edge triggered
        2 = high-to-low edge triggered
        4 = active high level-sensitive
        8 = active low level-sensitive

3.添加中断相关节点
在stm32mp157a-fsmp1a.dtsi文件的根节点内部添加如下内容:
   myirq{
       compatible="hqyj,myirq";
       interrupt-parent=<&gpiof>; 
       interrupts=<9 0>,<7 0>,<8 0>;  
   };
   或者
     myirq{
       compatible="hqyj,myirq";
       interrupts-extended=<&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;//8表示索引号,0表示不设置触发状态  
   };
   
4.添加完毕,在内核顶层目录下执行make dtbs编译设备树源码,将设备树源码拷贝到~/tftpboot下
5.重启开发板

中断底半部之 tasklet及tasklet_struct结构体 & 工作队列及struct work_struct结构体

*******************tasklet*********************
1.分配一个tasklet对象
    struct tasklet_struct tasklet;//分配对象
    结构体解析:
    struct tasklet_struct
    {
        struct tasklet_struct *next;//指向下一个tasklet对象
        unsigned long state;//底半部标志位
        atomic_t count;//用于记录当前触发的底板次数
        bool use_callback;//根据选择的底半部处理函数的不同设置为不同的值
        //如果使用func类型处理函数,这个值会被设置为false,如果使用callback,则被设置为true
        union {
            void (*func)(unsigned long data);
            void (*callback)(struct tasklet_struct *t);
        };
        unsigned long data;//传递给func回调函数的参数
    };

2.初始化taklet对象
    void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
    功能:当底半部处理函数是func类型时用此函数初始化对象

    void tasklet_setup(struct tasklet_struct *t, void (*callback)(struct tasklet_struct *))
    功能:当底半部处理函数是callback类型时用此函数初始化对象

3.开启底半部
    void tasklet_schedule(struct tasklet_struct *t)

**********************工作队列***********************
1.分配工作队列项
    struct work_struct work;//分配对象
    struct work_struct {   
        unsigned long data;//保存底半部触发次数  /*  atomic_long_t data; */
        struct list_head entry;//用于构成内核链表
        work_func_t func;//底半部函数指针 /*typedef void (*work_func_t)(struct work_struct *work);*/
    
    };
2.初始化队列项
    INIT_WORK(&work,底半部函数指针);

3.开启底半部
    bool schedule_work(struct work_struct *work)

【platform驱动】

        设备端编写流程 & 设备端结构体:platform_device设备信息结构体及 其父类结构体device & resource资源结构体 (被设备树替代,不用编写设备端)

**********************设备端结构体*********************
struct platform_device { 
    const char  *name;//设备名字,可以用于和驱动端的匹配
    int     id;//总线编号   PLATFORM_DEVID_AUTO  内核自动分配总线编号
    bool        id_auto;
    struct device   dev;//是platform_device结构体的父类对象
    u32     num_resources;//用于记录保存的设备信息的个数
    struct resource *resource;//存放设备信息的数组首地址
};

//父类结构体
struct device {
    void   (*release)(struct device *dev);//设备信息从内核卸载时用这个函数释放资源
};

//资源结构体
struct resource {
    resource_size_t start;//资源的起始值 0X50006000     0X50006000          71
    resource_size_t end;//资源的终止值   0X50006000+4   0X50006000+0X400     71
    const char *name;//资源的名称
    unsigned long flags;//资源的类型  IORESOURCE_IO|IORESOURCE_MEM|IORESOURCE_IRQ
};



***************设备端编写流程********************

1.分配设备信息对象并且初始化
    1.1封装父类中的释放资源函数
        void   pdev_release(struct device *dev){
            printk("%s:%s:%d\n",__FINE__,__func__,__LINE__);
        }
    1.2定义资源结构体数组并且初始化资源信息
        struct resource res[]={
            [0]={
               .start=0X12345678,
               .end=0X12345678+59,
               .flags=IORESOURCE_MEM,   //内存类型额资源  
            },
            [1]={
               .start=71,
               .end=71,
               .flags=IORESOURCE_IRQ,   //中断类型的资源  
            },
        };
    1.3分配设备信息对象并初始化
        struct platform_device pdev={
            .name="aaaaa",                //名字
            .id=PLATFORM_DEVID_AUTO,    //总线编号
            .dev={                     //父类对象
                .release=pdev_release,    
            },
            .resource=res,            //资源数组首地址
            .num_resources=ARRAY_AIZE(res);//资源的个数
        };
2.注册设备信息对象
    int platform_device_register(struct platform_device *pdev)
    功能:注册设备信息
3.注销设备对象
    void platform_device_unregister(struct platform_device *pdev)

驱动端编写流程 & 驱动端结构体:platform_driver驱动信息结构体及 其父类结构体device_driver

****************驱动信息对象结构体分析***********
struct platform_driver {
    int (*probe)(struct platform_device *);//当驱动和设备匹配成功后执行
    int (*remove)(struct platform_device *);//当设备和驱动分离时执行
    struct device_driver driver;//platform_driver的父类,用于描述驱动,设备信息匹配成功后会在这里面填充
    //在driver成员里面也可以设置和设备的匹配方式
    const struct platform_device_id *id_table;//用于设置名字表匹配,用于匹配的名字表首地址
};

//父类对象结构体
struct device_driver {
    const char      *name;//驱动名,可用于和设备的匹配
    const struct of_device_id  *of_match_table;//用于通过设备树的形式匹配设备信息
};

***************设备端编写流程********************
1.分配驱动信息对象并且初始化
    1.1封装probe函数 
        int pdrv_probe(struct platform_device *pdev){
            printk("%s:%s:%d\n",__FINE__,__func__,__LINE__);
            return 0;   
        }
    1.2封装remove函数
        int pdrv_remove(struct platform_device *pdev){
            printk("%s:%s:%d\n",__FINE__,__func__,__LINE__);
            return 0;   
        }
    1.3定义驱动信息并初始化
        struct platform_driver pdrv={
            .probe=pdrv_probe,
            .remove=pdrv_remove,
            .driver={
                .name="aaaaa",    
            },
        };
2.通过一键注册宏 注册对象进内核
module_platform_driver(pdrv);

安装卸载: 
    1.安装驱动,没有匹配设备信息,无现象
    2.安装设备,驱动和设备匹配成功后,执行驱动的probe
    3.卸载驱动端,设备端和驱动分离,执行驱动的remove
    4.卸载设备端,设备端的release执行,释放资源

驱动端获取设备端的设备信息API

1.获取任意类型的设备资源的地址
    struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
    功能:获取任意类型的设备资源
    参数:
        dev: 设备信息对象指针
        type: 获取到资源类型  IORESOURCE_IO|IORESOURCE_MEM|IORESOURCE_IRQ
        num: 要获取分资源在同类型中的索引号,从0开始
    返回值:成功返回要获取的资源对象指针,失败返回NULL

2.获取中断类型的中断号
    int platform_get_irq(struct platform_device *dev, unsigned int num)
    功能:获取中断类型的资源
    参数:
        dev:设备信息对象指针
        num:要获取的中断资源在中断资源中的索引号
    返回值:成功返回中断号,失败返回错误码

驱动端和设备端匹配函数 platform_match

有五个匹配优先级

static int platform_match(struct device *dev, struct device_driver *drv)
{
    //根据设备端和驱动端父类对象获取platfotm设备对象和platform驱动对象
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* When driver_override is set, only bind to the matching driver */
    //第一优先级 使用设备端的driver_override与驱动端的name进行匹配,无论匹配是否成功,都不再继续往下执行
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

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

platform_device_id 名字表结构体

***************名字表****************
名字表
struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];//保存名字的数组
    kernel_ulong_t driver_data;//当前设备对应的私有数据
};

构建示例
struct platform_device_id idtable[]=
{
    {"aaaaa",0},
    {"bbbbb",1},
    {"ccccc",2},
    {"ddddd",3},
    {},            //防止数组越界
}

设备树匹配

0.设备树匹配匹配项的类型
struct of_device_id {
    char    name[32];//要匹配的节点名
    char    type[32];//要匹配的设备类型
    char    compatible[128];//要匹配的设备树节点的compatible
    const void *data;//当前匹配项的私有数据
};

1.驱动端构建用于设备树匹配的表,//赋给struct platform_driver对象的父节点的.of_match_table
    struct of_device_id odtable[] = {
        { .compatible = "hqyj,myplatform", },
        {},//防止数组越界
    };

2.添加用于设备树匹配的设备树节点
    在stm32mp157a-fsmp1a.dts的根节点内部添加如下内容:
myplatform{
        compatible="hqyj,myplatform";
        reg=<0x11223344 59>;
        interrupt-parent=<&gpiof>;
        interrupts=<9 0>;
        led3-gpio=<&gpioe 8 0>;

    };

3.重新编译设备树,将镜像拷贝到~/tftpboot下
4.安装驱动后,即可完成匹配执行probe函数

【I2C子系统】

将核心层和总线驱动层配置进内核make menuconfig

*********************配置核心层*************************
 1.找到核心层代码目录:内核顶层目录/drivers/i2c
 2. 内核顶层目录执行make menuconfig
 3. > Device Drivers > I2C support  ->-*-I2C support
 4.保存退出
 ********************配置总线驱动层************
 1.找到iic总线驱动层代码目录:内核顶层目录/drivers/i2c/busses
 2.内核顶层目录执行make menuconfig
 3. > Device Drivers > I2C support > I2C Hardware Bus support-》
 <*> STMicroelectronics STM32F7 I2C support 
 4.保存退出
 
 
 *************编译**********
 1.内核顶层目录下执行make uImage   LOADADDR=0XC2000000
 2.cp 内核层目录/arch/arm/boot/uImage ~/tftpboot
 3.重启开发板

驱动层API

1.对象结构体
struct i2c_driver {
        int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);//与设备匹配成功执行
        int (*remove)(struct i2c_client *client);//设备分离时执行
        struct device_driver driver; //父节点设置名字匹配和设备树匹配    
        const struct i2c_device_id *id_table;//设置id_table匹配
    };
    struct device_driver {
        const char      *name;
        const struct of_device_id  *of_match_table;
    };
2.给对象分配空间并且初始化
    int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id){
        return 0;
    }
    int i2c_remove(struct i2c_client *client){
        return 0;
    }
    struct i2c_driver i2c_drv={
        .probe=i2c_probe,
        .remove=i2c_remove,
        .driver={
            .name="si7006",
            .of_match_table=设备树匹配表名,   
        },
    };
3.注册
    i2c_add_driver(struct i2c_driver *driver) 
4.注销
    void i2c_del_driver(struct i2c_driver *driver)
5.一键注册宏
    module_i2c_driver(__i2c_driver)

IIC设备树的修改以及si7006从机节点的添加

*******************查看已经添加好的i2c1设备树节点***************
在stm32mp151.dtsi内部,有如下内容:
i2c1: i2c@40012000 {
            compatible = "st,stm32mp15-i2c";//厂商信息
            reg = <0x40012000 0x400>;//地址信息
            interrupt-names = "event", "error";//中断模式列表
            interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>,
                          <&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&rcc I2C1_K>;//使能时钟
            resets = <&rcc I2C1_R>;//复位时钟
            #address-cells = <1>;
            #size-cells = <0>;
            dmas = <&dmamux1 33 0x400 0x80000001>,
                   <&dmamux1 34 0x400 0x80000001>;
            dma-names = "rx", "tx";
            
            status = "disabled";//控制器的工作状态
        };
****************修改I2C1设备树节点以及添加si7006的子节点***********
查询内核帮助手册:
    ~/linux-5.10.61/Documentation/devicetree/bindings/i2c
    vi i2c.txt

    Required properties (per bus)
    -----------------------------

    - #address-cells  - should be <1>. Read more about addresses below.
    - #size-cells     - should be <0>.
    - compatible      - name of I2C bus controller
    Optional properties (per bus)
    -----------------------------
    - pinctrl
        add extra pinctrl to configure SCL/SDA pins to GPIO function for bus
        recovery, call it "gpio" or "recovery" (deprecated) state
    Required properties (per child device)
    --------------------------------------
    - compatible
        name of I2C slave device
    - reg
        One or many I2C slave addresses.

*********************   添加    **********************

在stm32mp157a-fsmp1a.dts文件的根节点外部添加如下内容:
&i2c1 {
    pinctrl-names = "default", "sleep";//描述当前控制器的工作模式
 //"default"表示默认工作模式  "sleep"表示低功耗工作模式
    pinctrl-0 = <&i2c1_pins_b>;//设置默认工作模式下的管脚复用
    pinctrl-1 = <&i2c1_sleep_pins_b>;//设置低功耗模式下的管脚复用
    i2c-scl-rising-time-ns = <100>;//时钟线下降沿的时间
    i2c-scl-falling-time-ns = <7>;//时钟线上升沿的时间
    status = "okay";//状态设置为OKAY
    /delete-property/dmas;//屏蔽不必要的属性
    /delete-property/dma-names;
          si7006@40{
      compatible="hqyj,si7006";
      reg=<0X40>;  
  };  
};

I2C设备驱动中数据收发相关函数 i2c_transfer()

         &  struct i2c_client 与驱动匹配成功的设备及总线结构体

                & struct i2c_msg 消息结构体

*****************存放与驱动匹配成功的设备和驱动的信息的结构体**************
0.当驱动匹配设备信息成功后内核中就会存在一个struct i2c_client 对象,对象内部保存的是匹配成功的设备的信息以及总线相关的信息
struct i2c_client {
    unsigned short flags;//读写标志   0写   1读
    unsigned short addr; //匹配到的设备的从机地址
    char name[I2C_NAME_SIZE];//匹配到设备的名字
    struct i2c_adapter *adapter; //用于索引总线驱动的对象指针
    struct device dev;//设备端的父类对象
    int init_irq;//中断初始化的标志
    int irq;  //中断号
    struct list_head detected;//构成内核链表
};

 
*************i2c数据传输的函数  i2c_transfer()***************
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
    功能:基于I2C总线进行数据传输
    参数:
        adap:用于索引总线驱动的对象指针  client->adapter
        msgs:要传输的一个或者多个消息   一个消息是以起始信号作为出发点
        num:传输的消息的数量
    返回值:成功返回传输的消息个数,失败返回错误码

******************消息结构体**********************
I2C总线上传输的内容属于消息,一条消息中要包含从机地址、读写标志位以及消息的正文
struct i2c_msg {
    __u16 addr; //从机地址client->addr 
    __u16 flags;//读写标志位   0写    1读
    __u16 len;   //消息正文的大小
    __u8 *buf;//保存消息正文的buf首地址   
};
*********************消息的封装*****************

1.写消息的封装
start+7bit从机地址(高位在前低位在后)+1bit写(0)+ack(从机给主机发)+寄存器的地址(不同芯片寄存器地址不一样,有的是8bit址,也有16bit,如果是16bit,会拆分成高8bit和低8bit)+ack+向寄存器中写的数据(8bit)+ack+stop
char w_buf[]={寄存器地址,data};
struct i2c_msg w_msg={
    .addr=client->addr,
    .flags=0,
    .len=sizeof(w_buf),
    .buf=w_buf,
};

2.读消息的封装
char r_buf[]={寄存器地址};
char value;
struct i2c_msg r_msg[]={
    [0]={
        .addr=client->addr,
        .flags=0,
        .len=sizeof(r_buf),
        .buf=r_buf,   
    },
    [1]={
        .addr=client->addr,
        .flags=1,
        .len=1,
        .buf=&value,    
    },
};

【SPI子系统】

配置核心层和总线驱动层源码参与内核编译

1.进入内核顶层目录
  make menuconfig
> Device Drivers > 
        [*]SPI support >
                 <*>   STMicroelectronics STM32 SPI controller  (M->Y)
2.make uImage LOADADDR=0XC2000000
3.cp arch/arm/boot/uImage ~/tftpboot
4.重启开发板

SPI设备驱动API

1.分配设备驱动对象
    struct spi_driver {
        const struct spi_device_id *id_table;
        int         (*probe)(struct spi_device *spi);
        int         (*remove)(struct spi_device *spi);
        struct device_driver    driver;
    };
2.注册对象
    spi_register_driver(struct spi_driver *driver)
3.注销
    void spi_unregister_driver(struct spi_driver *driver)
4.一键注册宏 代替2 3
    module_spi_driver(__spi_driver)

SPI设备树节点的修改和子节点的添加

1.查看已经添加好的SPI4的设备树节点
    spi4: spi@44005000 {
            #address-cells = <1>;
            #size-cells = <0>;
            compatible = "st,stm32h7-spi";
            reg = <0x44005000 0x400>;
            interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&rcc SPI4_K>;
            resets = <&rcc SPI4_R>;
            dmas = <&dmamux1 83 0x400 0x01>,
                   <&dmamux1 84 0x400 0x01>;
            dma-names = "rx", "tx";
            power-domains = <&pd_core>;
            status = "disabled";
        };

2.查看帮助文档:
    ~/linux-5.10.61/Documentation/devicetree/bindings/spi
    vi spi-controller.yaml
cs-gpios:
    description: |
      GPIOs used as chip selects.
      If that property is used, the number of chip selects will be
      increased automatically with max(cs-gpios, hardware chip selects).

      So if, for example, the controller has 4 CS lines, and the
      cs-gpios looks like this
        cs-gpios = <&gpio1 0 0>, <0>, <&gpio1 1 0>, <&gpio1 2 0>;

spi-max-frequency:
        $ref: /schemas/types.yaml#/definitions/uint32
        description:
          Maximum SPI clocking speed of the device in Hz.
3.修改spi4设备树节点以及添加m74hc595从机节点
    在stm32mp157a-fsmp1a.dts文件的根节点外部添加如下内容:
&spi4{
    pinctrl-names="default","sleep";
    pinctrl-0=<&spi4_pins_b>;//设置管脚复用
    pinctrl-1=<&spi4_sleep_pins_b>;
    cs-gpios = <&gpioe 11 0>;//设置片选引脚
      status="okay";      
      m74hc595@0{//添加m74hc595从机节点
      compatible="hqyj,m74hc595";
      reg=<0>;  
      spi-max-frequency=<10000000>;//max频率59MHZ
  };               
};

SPI数据收发相关的API & struct spi_device保存匹配成功的设备的相关信息的结构体

当SPI设备与驱动匹配成功之后在内核中会存在一个struct spi_device对象保存匹配成功的设备的相关信息
struct spi_device {
    struct device       dev;//父类对象
    struct spi_controller   *controller;//用于索引总线驱动的对象指针
    struct spi_controller   *master;    /* compatibility layer */
    u32         max_speed_hz;//传输最大频率
    u8          chip_select;//片选编号
    u8          bits_per_word;//每个数据包包含的字节数
    bool            rt;//数据收发的标志
    u32         mode;//工作模式
    int         irq;
    void            *controller_state;
    void            *controller_data;
    char            modalias[SPI_NAME_SIZE];
    const char      *driver_override;
    int         cs_gpio;  //片选的GPIO编号
    struct gpio_desc    *cs_gpiod;  /* chip select gpio desc */
    struct spi_delay    word_delay; /* inter-word delay */

    /* the statistics */
    struct spi_statistics   statistics;

};

数据收发的函数
    int spi_read(struct spi_device *spi, void *buf, size_t len)//读
    int  spi_write(struct spi_device *spi, const void *buf, size_t len)//写
    int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx)

【linux内核分配】

1.1申请物理内存映射区内存 3G - 3G+896M区间
void *kmalloc(size_t s, gfp_t gfp)
    功能:分配对应的虚拟内存 (物理内存映射区)
    参数:size:分配内存区的大小
        flags:内存分配标志
        GFP_KERNEL:内核可能被休眠,用于进程上下文中
        GFP_ATOMIC:处理紧急的事务,用在中断上下文
    返回值:对应虚拟地址 
    特点:最大128k , 分配虚拟地址,其虚拟地址空间连续, 物理地址空间也是连续,分配的内存必须是2的次幂的形式 
    类似函数:kzalloc = kmalloc+memset(,0,):分配虚拟内存区并清零 

1.2释放
void kfree(const void *x) 
功能:释放对应的虚拟内存 
参数:x:虚拟内存的起始地址 
返回值:无 

2.1申请vmalloc分配器区内额u才能
void *vmalloc(unsigned long size) 
    功能:分配对应的虚拟内存 
    参数:size:分配内存区的大小 
    返回值:对应虚拟地址 
    特点:分配虚拟地址,其虚拟地址空间连续, 但是物理地址空间不一定连续 
2.2释放
    void vfree(const void *addr) 
    功能:释放对应的虚拟内存 
    参数:addr:虚拟内存区的首地址 
    返回值:无 

3.1申请一个页的内存
    unsigned long __get_free_page(gfp_t gfp) 
    功能:分配一个页的内存 4K 
3.2释放
    void free_page(unsigned long addr) 
    释放一个页的内存 
4.1申请多个页的内存
    unsigned long __get_free_pages(gfp_t gfp_mask, get_order(57600)) 
    功能:分配多个页的内存 
        57600-->2^n :第二个参数填写的是n 
        n = get_order(57600) 
4.2释放 
    void free_pages(unsigned long addr, unsigned long order) 
    释放多个页的内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值