Linux驱动开发(2)--应用与内核、杂项设备

本文介绍了Linux系统中应用层与驱动层的关系,强调了一切皆文件的概念,并讲解了如何通过copy_from_user和copy_to_user函数在内核和用户空间间传输数据。此外,讨论了物理地址和虚拟地址的区别,以及MMU在内存管理中的作用。文章还详细阐述了杂项设备的使用,包括如何注册和注销,并给出了创建和操作杂项设备的代码示例。
摘要由CSDN通过智能技术生成

Linux驱动开发(2)–应用与内核、杂项设备

前言

  • 该章节介绍 应用层与驱动层参数传输物理/虚拟地址杂项设备
  • 以迅为IMX6ULL开发板为基础,编写蜂鸣器驱动以及LED驱动;

应用程和驱动层

首先,要明确一个概念,Linux系统中–一切皆文件;驱动文件最终通过文件操作相关的系统调用或者函数来被访问,而设备驱动的结构最终也是为了提供应用程序API ;

  • 应用层和内核层是不能直接进行数据传输的,如果需要在驱动中进行数据传输,可以使用下面两个函数进行数据互传;PS: 下列函数都是在内核层中使用,以内核层为对象,来看到数据的走向 ;
static inline long copy_from_user(void * to ,const void __user *from,unsigned long n ) ;
参数内容
to目标地址 (内核空间)
from源地址 (用户空间)
n拷贝的字节数
返回值成功返回0 ,失败则返回没有拷贝成功的字节数
功能用户空间的数据拷贝到内核空间
static inline long copy_to_user(void __user * to ,const void *from,unsigned long n ) ;
参数内容
to目标地址 (用户空间)
from源地址 (内核空间)
n拷贝的字节数
返回值成功返回0 ,失败则返回没有拷贝成功的字节数
功能内核空间的数据拷贝到用户空间

物理/虚拟地址

  • 大多数的嵌入式微控制器(ARM等)中并不提供I/O空间,仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序及在程序运行中使用的变量和其数据都存在于内存空间中,内存地址可以直接有C语言指针操作。

  • 内存管理单元(MMU - memory mangement unit ) ,该单元辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和Cache缓存控制等硬件支持 。

  • MMU具有虚拟地址和物理地址 转换、内存访问权限保护等功能。这使得Linux操作系统能单独为系统的每个用户进程分配独立的内存空间并保证用户空间不能访问内核空间的地址,为操作系统的虚拟内存管理模块提供硬件基础。

  • 把物理地址转换成为虚拟地址(映射):

    内容说明
    函数原型static inline void * ioremap(phys_addr_t offset ,size_t size )
    phys_addr_t offset映射物理地址的起始地址
    size_t size所映射空间的大小
    返回值成功–返回虚拟地址的首地址,失败–返回NULL
    功能把物理地址转换为虚拟地址,映射
    内容说明
    函数原型static inline void * iounmap(void *addr )
    addr虚拟地址的首地址
    功能释放ioremap 所映射的虚拟地址
  • 可以在设备中查看物理地址映射情况

    cat /proc/iomem
    

杂项设备

Linux的三大设备驱动 ,字符设备、块设备、网络设备

  • 杂项设备是一个典型的字符设备,其主设备号固定为10(内部实现是调用register_chrdev())。

  • 杂项设备实现简单

    实现步骤:

    • 构造一个file_operations结构体,其中包含对硬件的所有操作;
    • 实现file_operations结构体的成员函数(例:read ,write);
    • 构造一个杂项设备驱动(miscdevice)实体,并且赋值前面定义的file_operations实体;
    • 在入口函数处调用**misc_register()**向系统注册杂项设备;
    • 在出口函数处调用**misc_unregister()**从系统注销杂项设备;
    • 内核的入口和出口函数;
  • misc 设备用miscdevice结构体表示,该结构体定义在include/linux/miscdevice.h中

    struct miscdevice {
    	int minor ; 
    	const char * name ;
    	const struct file_operations *fops ;
    	struct list_head list ;
    	struct device * parent ;
    	 struct device * this_device ;
    	const struct attribute_group ** groups ;
    	const char *nodename ;
    	umode_t mode ;
    }
    

    创建一个miscdevice结构体时需要初始化 minor、name、fpos这三个成员变量,其中minor表示次设备号(misc的主设备号是10),在include/linux/miscdevice.h中定义了次设备号的宏定义:

    #define PSMOUSE_MINOR 1
    #define MS_BUSMOUSE_MINOR 2
    #define ATIXL_BUSMOUSE_MINOR 3
    ...		
    
  • 结构体创建号后,需要函数对其进行操作:

    内容说明
    函数原型int misc_register(struct miscdevice * misc)
    参数misc创建好的miscdevice 结构体实体
    返回值成功返回0 , 失败返回负数
    功能向系统注册一个misc设备
    内容说明
    函数原型void misc_deregister(struct miscdevice * misc)
    参数misc需要注销的misc结构体
    返回值void
    功能注销misc设备
  • 代码示例

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/kdev_t.h>
    #include <linux/cdev.h>
    #include <linux/miscdevice.h>
    ssize_t misc_write(struct file* file ,char * userbuf, size_t size ,loff_t * loff)
    {
            printk("hello write\n");
            return 0 ;
    }
    struct file_operations ops = {
            .owner = THIS_MODULE,
            .write = misc_write ,
    };
    struct miscdevice misc = {
            .minor = MISC_DYNAMIC_MINOR,
            .name = "hello",
            .fops = &ops,
    };
    static int hello_init(void)
    {
            int ret ;
            ret = misc_register(&misc);
            if(ret < 0 )
                    printk("misc register fail\n");
            printk("hello world\r\n");
            return 0;
    }
    static void hello_exit(void)
    {
            misc_deregister(&misc) ;
            printk("hello byebye\r\n");
    }
    module_init(hello_init);
    module_exit(hello_exit);
    MODULE_LICENSE("GPL");
    
    • 对应的头文件包含

      #include <linux/miscdevice.h>  //对应miscdeivce结构体
      #include <linux/fs.h>		   //对应file_operations结构体
      
    • 创建miscdevice 和file_operations 实体

      struct file_operations ops = {
      	.owner = THIS_MODULE 
      };
      
      struct miscdevice misc = {
          .minor = MISC_DYNAMIC_MINOR,
          .name  = "hello" ,
          .fops  = &ops ,
      };
      
    • 在出入口函数加入misc对应的操作函数

学会查看芯片手册

  • 在进行编写实际的驱动程序之前,要先学会查看原理图,以及根据芯片手册来找到对应的引脚或接口功能;
  • 以蜂鸣器为例;
  • 查看硬件原理图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEHbgHtw-1681031461750)(C:\Users\ly\AppData\Roaming\Typora\typora-user-images\image-20230409091655793.png)]

上图显示 SNVS_TAMPER1引脚来控制蜂鸣器的通断;

  • 查看芯片手册

    总结查看步骤:

    • 找到SNVS_TAMPER1对应的功能表,对应的复用MUX功能;
      引脚复用功能
    • 复用功能的GPIO脚位,查看该脚位的Memory Map
      GPIO Memory Map
    • 查看GPIO映射表
      Map table

蜂鸣器–杂项设备驱动

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/kernel.h>

#define GPIO5_DR 0x020AC000
unsigned int * vir_addr ;
int misc_open(struct inode * inode,struct file * file)
{
        printk("misc open\n");
        return 0 ;
}
ssize_t misc_write(struct file* file ,char * userbuf, size_t size ,loff_t * loff)
{
        char kbuf[64] = {0} ;
        if(copy_from_user(kbuf,userbuf,size)!=0)
        {
                printk("copy fail\n");
                return -1;
        }
        printk("kbuf[0] = %d\n",kbuf[0]);
        if(kbuf[0] == 1)
                *vir_addr |= (1<<1) ;
        else
                *vir_addr &= ~(1<<1) ;
        return 0 ;

}
struct file_operations ops = {
        .owner = THIS_MODULE,
        .open  = misc_open ,
        .write = misc_write ,
};
struct miscdevice misc = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "hello",
        .fops = &ops,
};
static int hello_init(void)
{
        int ret ;
        ret = misc_register(&misc);
        if(ret < 0 )
                printk("misc register fail\n");
        vir_addr = ioremap(GPIO5_DR,4);
        if(vir_addr == NULL)
        {
                printk("map error\n");
                return -1 ;
        }
        printk("map ok\n");
        printk("hello world\r\n");
        return 0;
}
static void hello_exit(void)
{
        misc_deregister(&misc) ;
        iounmap(vir_addr) ;
        printk("hello byebye\r\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

编码问题

  1. Linux下的open 函数

    1. 头文件

      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h> 
      
    2. 模式常量

      三种常用的操作模式

      • O_RDONLY : 只读模式
      • O_WRONLY : 只写模式
      • O_RDWR :读写模式
  2. 文件流操作下,打开一个文件后,结束时一定要close 关闭文件 ;

  3. 对于指针直接操作存在的问题

    上述vir_addr指针,直接进行赋值与位操作赋值存在区别

    *vir_addr = 1; 
    *vir_addr = 0;
    
    *vir_addr |=(1<<1);
    *vir_addr &= ~(1<<1);
    

    这两个种操作方式,第一种是直接赋值,把vir_addr指向的对象直接赋值为0/1 ;

    第二种方式是位操作,针对需要操作的位进行赋值;显而易见,第二种操作方式是正确的的;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值