Linux驱动——杂项驱动&GPIO子系统

一:内核层框架

在介绍linux驱动之前先介绍一下系统。

系统分为两层:

        1.系统层

        2.内核层

对于内核层就要说一下其中的内核层运行的框架了

代码如下:

//头文件
#include "linux/kernel.h"
#include "linux/module.h"
//入口函数
static int __init myled_init(void)
{
return 0;
}
//出口函数
static void __exit myled_exit(void)
{ }
//函数声明
module_init(myled_init);
module_exit(myled_exit);
//协议
MODULE_LICENSE("GPL");

内核的框架已经写好了,那应该如何编译了

        这代码不同于以往的系统代码,如果直接编译会报错,它是运行在内核层,所以要靠内核编译。

所以就要提起一个名为:Makefile的驱动编译了,适用于任何驱动编译,任意的内核版本

通用makefile代码:

obj-m += led.o//如果要用来编译自己的程序代码,修改成自己的
#obj-m:代表模块 module (驱动) 目标-> led.o->led.c
KDIR:=/home/lyx/RK3588S/kernel
#代表你的编译的所用的内核的位置
CROSS_COMPILE_FLAG=/home/lyx/RK3588S/prebuilts/gcc/linuxx86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linuxgnu/bin/aarch64-none-linux-gnu-
#这是你的交叉编译器路径
all:
    make -C $(KDIR) M=$(PWD) modules ARCH=arm64
    CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
#调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
# 架构 ARCH=arm64
clean:
    rm -f *.ko *.o *.mod.o *.mod.c *mod *.symvers *.markers *.order

二:驱动开发

1:何为驱动

我的理解就是,驱动硬件正常工作的程序代码就是驱动。

在 STM32 里面:
无非就是编写寄存器代码初始化外设->通信传感器
在 Linux 下驱动:
        不像 STM32 一样,拿一个 LED 灯为例子
STM32:
        初始化 GPIO 寄存器(库函数)
        在 main-> 点灯/关灯/闪灯
Linux 下:
        也可以像 STM32 一样在内核层直接开灯!
        内核层不断的加载卸载实际上很浪费资源。效率很低
        Linux 下内核模块代码,是独立运行的单元
        很难像 STM32 那样一个代码一个工程代表整个单片机运行程序
        就算是我像 STM32 一样写代码把灯的功能写死->运行在内核层!
        请问系统开发工程师(不懂驱动) 他怎么操作 LED 灯

我写了一个驱动
        你作为智能家居的软件开发工程师
        你想在你的应用里面控制我写的驱动底层的 LED 灯!
        真正的 Linux 驱动:
                不像 STM32 一样把驱动写死了
                首先 Linux 下的驱动符合驱动的原则:驱使硬件正常工作代码
                把 LED 灯这个设备在 内核层 "抽象" 为一个 文件(设备文件)
                我们内部写的驱动就是在完成这样的事->硬件变成文件
                系统上层开发者他操作底层设备->操作文件
        以 LED 灯为例:
                        LED 灯->我写驱动->led 文件 /dev/myled
                        上层开发者->打开/dev/led->灯就开了
                上层开发者->关闭/dev/led->灯就灭了

2:linux分层思想

在是stm32中


是直接操作对应寄存器,这样的特点是高效,简单明了。

缺点为:

        难移植,不通用。如果换个芯片就要推到重新再来一遍。

而在linux中提出了分层的思想,如何实现的了,如下图所示:

        我认为所谓的分层就是对应的中间接口层,通用的接口函数,能最大的保证代码的通用性,而在Linux系统中,实现的是所有的系统都在做的是软硬件分离。

Linux因为分层软件硬件,我们不管使用什么平台->开发驱动的框架,代码都是一样的。

在 Linux 下分层思想处处可见,不仅仅把驱动做了分层
        底层的各种外设也做了大量的分层
        SPI 分为: Linux 通用接口层
                厂商 BSP 适配的驱动层
甚至最新的 platfrom 也在统一分层思想:
        一个简简单单的驱动他也想分为两层:
                硬件信息:提供 LED 灯的 引脚和电平状态
                通用驱动: LED 灯驱动->没有指定具体 LED 灯引脚、电平状态

总结:

Linux 分层思想:
        主要就是告诉大家, Linux 下的所有的函数接口
        是通用的,隔离硬件的,软硬件分离
        你写的代码原则上适用于任何的平台
        实际上在发展中:
                把驱动分层: 硬件层 : 硬件信息层 ->渐渐演变成 设备树
                软件层 : 软件代码层 ->通用了 所有的平台
                把 BSP 厂商驱动:分为两层: 硬件层
                软件层
Linux 下的驱动:
        我们现在初学驱动的时候暂时写驱动不考虑分层
Linux 下驱动的特点:
        不是直接在内核层操作硬件
        而是写好 "接口"(文件 打开 关闭 读写)
        让上层操作!

3:驱动开发的框架

1:写内核框架

2:在内核框架->

        把设备抽象为文件(内核驱动接口->杂项驱动)Linux中有一句话,叫一切皆文件

        设备的设备号->设备ID ->让内核管理

        设备内核操作接口->文件操作接口(内核你驱动开发者要单独实现一套)
        你写的内核层的 open close read write 跟 上层(系统层一一对应)
        这是也是你留给上层的 操作接口!

3:编译成 .ko –> insmod xxx.ko –>生成设备文件(/dev/xxxx)
4:调用 加载/入口函数->内核框架->生成设备文件
5:上层/你自己 调用 文件操作 -> 操作设备

4:驱动文件的特点:

1:系统的特殊文件之一:

管道、套接字、块设备文件、字符设备文件、低级IO/非缓冲区IO来操作文件

一共分为三大类:

        字符设备文件:

        一般指的是除了存储 网络 设备之外的所有的其他设备

块设备文件:

        正常的芯片->厂商都完成了这类芯片初始化

        小型存储器->SPI_FLAHS

        网络设备文件:

                基本上你开发网络设备只有两种:wifi和4G、5G

        设备号
                原则上是一个 32bit 的无符号数字
                理论来说内核最大挂在的设备-> 2^32->40 亿
                分为主设备号(占设备号的高 12bit)
                次设备号(占设备号的低 20bit)
                        其中主设备号的范围-> 0-254
                        其中次设备号的范围-> 0-255
                实际上系统最多运行挂载:
                        255 个 255: 255*256

字符设备传输方式:按字节传输

三:杂项的驱动开发

1:杂项驱动设备文件的特点

杂项:

        指一般设备原则上是不分类的设备。

        一类设备独占一个设备

        所有的杂项设备的主设备号为10

        次设备号杂项自动分配->杂项开发不需要考虑设备号的问题

2:杂项驱动开发的接口

        主要就是两个函数:

        misc_register();//杂项注册函数

        misc_deregister();//杂项注销函数

头文件为:

<linux/miscdevice.h>

函数原型:

int misc_register(struct miscdevice *misc)

函数的参数:
        misc:
                注册杂项设备的核心结构体
                内部尤其注册详细信息
                struct miscdevice {
                int minor;
                const char *name;
                const struct file_operations *fops;
                ...........
                其他成员变量不用理会->已经被函数内部做过了初始化
        };
        * minor:
                major:主设备号

                minor:次设备号
                原则上我们不清楚哪个设备号可以被占用
                我们一般提供 255 ->让函数自动分配可用的次设备号
        * name:
                这个就是你注册设备要生成的设备文件名
                比如你填写的是 xyd_led
                就会在 /dev/xyd_led 这个设备文件!
        * fops:
                这个结构体内部是大量的函数指针
                也是你注册这个设备文件对应的内核层的接口!
                虽说接口多的多,并不是让你都实现
                你根据自己的实际情况 选择性实现
        最起码需要实现三个成员变量:
                struct module *owner;
                int (*open) (struct inode *, struct file *);
                int (*release) (struct inode *, struct file *);
        其中 owner:
                固定填写 THIS_MODULE
        函数返回值:
注册设备成功返回 0
注册设备失败返回 非 0

然后是注销函数

void misc_deregister(struct miscdevice *misc)

3:举例使用

#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/miscdevice.h"
//1:申请一个杂项核心结构体
struct miscdevice misc;//变量全局的变量,空间为编译器系统开辟的全局区空间
//申请内核层文件操作接口集合结构体
struct file_operations misc_fops;
int xyd_led_open(struct inode * i, struct file * f)
{
    printk(KERN_EMERG"这是内核层的 open 被调用了!\r\n");
    return 0;
}
int xyd_led_close(struct inode * i, struct file * f)
{
    printk(KERN_EMERG"这是内核层的 close 被调用了!\r\n");
    return 0;
}
//加载函数
static int __init myled_init(void)
{
    //2:填充杂项核心结构体
    misc.minor = 255;//让系统自动给我分配一个次设备号
    misc.name = "xyd_led";//生成一个设备文件-> /dev/xyd_led
    misc.fops = &misc_fops; //指定一个操作函数集
    //3:填充 misc_fops 里面的函数接口
    misc_fops.owner = THIS_MODULE; //固定写法
    misc_fops.open = xyd_led_open;//系统层打开文件调用内核层接口函数
    misc_fops.release = xyd_led_close;//系统层关闭文件调用内核层接口函数
    return misc_register(&misc);
}
//卸载函数
static void __exit myled_exit(void)
{
    //4:卸载的时候取消注册杂项设备
    misc_deregister(&misc);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

四:GPIO子系统

1:什么是GPIO子系统

        在Linux提供的中间层接口中,这个接口是通用的接口之一,这个接口可以在任意的Linux系统下控制GPIO口,这个功能有限->只能控制GPIO的两大功能:

输入:获取点平状态

输出:输出 0/1

2 :GPIO子系统的接口

gpio_free(unsigned gpio);//释放不再使用这个 IO 口
gpio_request(unsigned gpio,const char *label);//获取/申请 一个 IO 口使用

gpio:
        他就是你要控制的 GPIO 口的编号
        可以算出来->Linux 下 GPIO 规律:
        引脚都是从 0 编号开始的

几乎所有的厂商的芯片的 GPIO 口 引脚都是规整(除了三星的一些芯片)
以瑞芯微:
        分大组和小组
        大组->数字编号 0 1 2 3
        每个大组有四个小组 ABCD
        小组->字母编号 A B C D
        每个小组有 8 个引脚
总结:
        一个大组: 32 个引脚 一个小组 8 个引脚
        举个例子:
                GPIO0_A0:
                就是 0 组的 第 0 小组的第 0 个引脚
                0*32 + 0*8 + 0 = 0
        GPIO4_B6
                就是 4 组的 第 1 小组的第 6 个引脚
                4*32 + 1*8 + 6 = 142
        GPIO0_C5: (LED1)
                0 + 2*8 + 5 = 21
        GPIO0_C6:(LED2)
                0 + 2*8 + 6 = 22
label:
标签名字,这个无所谓随便填写
函数返回值:
如果 这个 IO 口 没有人/没有驱动/没有程序之前申请过
那他就给你 返回一个正确-> 0
如果检测到了 有人之前申请过还没释放->返回非 0
gpio_direction_output(gpio_num,value);//引脚调节为 输出模式
gpio_direction_input(gpio_num);//调节引脚为 输入模式
gpio_set_value(gpio_num,value); // 设置引脚当前的输出状态
gpio_get_value(gpio_num); // 获取当前引脚的状态->不限制输入还是输出

头文件为:

#include <linux/gpio.h>

3:举例:点亮LED灯

#include "linux/module.h"
#include "linux/kernel.h"
#include "linux/miscdevice.h"
#include "linux/gpio.h"
#include "linux/fs.h" // struct file_operations 结构体定义处
struct miscdevice xyd_led_device;
struct file_operations misc_device_ops;
//加载函数
int xyd_led_open(struct inode * i , struct file * f)
{
    gpio_set_value(21,1);
    gpio_set_value(22,1);
    return 0;
}
int xyd_led_close(struct inode * i , struct file * f)
{
    gpio_set_value(21,0);
    gpio_set_value(22,0);
    return 0;
}
static int __init xyd_init_led(void)
{
    //1:把我的 GPIO 口申请一下
    gpio_request(21, "xyd_led1");
    gpio_request(22, "xyd_led2");
    //2:我把我的引脚输出
    gpio_direction_output(21,0);
    gpio_direction_output(22,0);
    //3: 注册杂项设备
    xyd_led_device.minor = 255;
    xyd_led_device.name = "xyd_led";
    xyd_led_device.fops = &misc_device_ops;
    misc_device_ops.owner = THIS_MODULE;
    misc_device_ops.open = xyd_led_open;
    misc_device_ops.release = xyd_led_close;
    misc_register(&xyd_led_device);
    return 0;
}
static void __exit xyd_exit_led(void)
{
    misc_deregister(&xyd_led_device); //取消设备的注册
    gpio_free(21);
    gpio_free(22);
}
    module_init(xyd_init_led);
    module_exit(xyd_exit_led);
    MODULE_LICENSE("GPL");

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值