【notes12】kbuild,内核模块化,字符设备驱动,设备树,platform总线,设备驱动模型

文章目录


1.kbuild编译系统

kernel源码目录第一层有Kconfig(后厨原材料)和.config(点菜的清单)文件。menuconfig(菜单),Deconfig(预制菜单 熟客使用)。

c 复制代码
obj-y += foo.o bar.o  // 表示foo.c和bar.c文件被编译成foo.o和bar.o
// kernel/driver/spi/build-in.a静态库是该目录下所有目标文件(.o文件)打包而成

目标由多个源文件编译得到:追加的效果




malloc不是内核API而是libc函数,参考【notes12】。

1.1 Kconfig

驱动模块使用的资源一摸一样(如gpio),只能加载一个,如下只能选一个,互斥:

depends on只有依赖条件成立,配置项才显示。

1.2 Makefile

条件编译:最常用的两种格式#ifdef和#ifndef 。#undef :取消已定义的标识符

如下book145.c和_public.c都有 #include"_public.h",会重复包含。

在_public.c中如下这样写,_public.h就不会被重复包含。

如下$前一个tab键不能8个空格。make默认是make all,如果将all这行book3删除,则make不会编译book3,可指定make book3,book3相当于标签。-欧2是让编译效率最高,一般正式发布用。gcc命令选项 :-c编译不链接。

如下是编译内核,第一行判断路径是否存在。


EXRA_CFLAGS用于额外编译选项:

2.内核模块化



2.1 传参

权限位S_IRUGO是用在sysfs文件系统里,static int...只是初始化给默认值。


2.2 符号导出

驱动程序编译生成的 ko 文件是相互独立的, 即模块之间变量或者函数在正常情况下无法进行互相访问。extern:声明来自另一个模块。

2.3 设备号

具有相同主设备号(哪一类)设备使用相同驱动程序,次设备号(哪一个)用来标识连接系统中相同的设备。l:符号链接,s:套接字,p:管道。

如下次设备号个数不是次设备号(hello_minor)。

块设备申请主设备号

hello_major为0是系统自动分配的主设备号即register_blkdev函数分配。

2.4 错误码


如下fail里将return ret。如上地址是64位内核空间最后一页,指针落到这段地址说明是错误的无效指针。

2.5 uevent

c 复制代码
struct kobject *mykobject01;
struct kset *mykset;
struct kobj_type mytype;
static int mykobj_init(void)
{
    int ret;
    // 创建并添加一个kset
    mykset = kset_create_and_add("mykset", NULL, NULL);
    // 分配并初始化一个kobject
    mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    mykobject01->kset = mykset;
    // 初始化并添加kobject到kset
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01"); // mykobject01
    // 触发一个uevent事件,表示kobject的属性发生了变化
    ret = kobject_uevent(mykobject01, KOBJ_CHANGE);
    return 0;
}
static void mykobj_exit(void)
{
    kobject_put(mykobject01); // 释放kobject
    kset_unregister(mykset);
}
module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE("GPL");

// insmod 上面.ko
// ls /sys/mykset/mykobject01/  (里面是空的,没有任何属性文件)
// dmesg | tail -n 1   或  udevadm monitor --kernel
// kobject_uevent: KOBJ_CHANGE for mykobject01



如下device_create里调用kobject_uevent告诉用户态的二进制udev机制,说有一个设备节点的创建,udev帮对设备文件创建。


3.字符设备驱动

如下crw---中c就是char字符设备(以字节为单位输入输出,没有固定大小,也没有缓冲区,数据会立即传输: 串口、鼠标)。块设备以b开头(以块为单位输入输出,有缓冲区,数据下发以后会在缓冲区中缓存,可以被分区,还可以格式化它的文件系统【不能裸设备访问,坏块多,都会上文件系统】:硬盘、U盘)。网络设备ifconfig查看。

cdev_init是方法填充,cdev_add是属性(如主次设备号)的填充。


3.1 设备读写

write函数中一个进程写没问题,两进程写:第一个进程运行到kzalloc时,第二个进程也执行了kzalloc,只第二个进程地址保存在c中,第一个进程分配内存空间地址丢失造成内存泄漏。第一个进程运行到kzalloc时第二个进程调用了kfree,这时第一个进程执行到copy_from_user出现问题。

如下cdev_map是整个绿色:

3.2 杂项设备

c 复制代码
struct file_operations misc_fops = { //文件操作集
    .owner = THIS_MODULE ////将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模
    .read = 
    .write = 
};
struct miscdevice misc_dev = {       //杂项设备结构体
    .minor = MISC_DYNAMIC_MINOR,     //动态申请的次设备号
    .name = "test",                  //杂项设备名字是hello_misc
    .fops = &misc_fops,              //文件操作集

};
static int __init misc_init(void)           
{ 
    int ret;
    ret = misc_register(&misc_dev); //在初始化函数中注册杂项设备
    if (ret < 0)
    {
        printk("misc registe is error \n"); //打印注册杂项设备失败
    }
    return 0;
}
static void __exit misc_exit(void)
{
    misc_deregister(&misc_dev);     //在卸载函数中注销杂项设备
    printk(" misc goodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

4.设备树

dts -》 dtb - 》device_node - 》platform_device。

DTC(Device Tree Compiler,源代码和相关工具放在scripts/dtc目录,CONFIG_DTC,也可反编译)编译器将dts/dtsi文件编译成二进制dtb文件。

c 复制代码
reg=<0x100 0x4> : 地址从0x100开始的4字节寄存器区域

spi和i2c设备属于非内存映射设备,它们的地址对cpu不可访问,而父设备的驱动(即总线控制器驱动)代表cpu访问,spi reg代表spi chip select 0 , i2c reg代表设备地址


时钟(类似心跳)生产者(板卡厂商,定义):1.clock-cells:=0表示一个时钟,=1表示多个时钟。

2.clock-frequency:频率,单位HZ

3.assigned-clocks:

4.assigned-clocks-rates:

时钟消费者(驱动,引用)

1.clocks:类似assigned-clocks

2.clock-names:

4.1 中断属性

1.interrupts:下图有3位(父亲是gic控制器),interrupt-cells给引用的子节点用的,不是当前节点。
2.interrupt-controller:当前节点是一个中断控制器。

c 复制代码
ft5x06:ft5x06@38{ //触摸屏有事件 → 产生中断信号 → 通过RK_PB5引脚 → GPIO控制器接收 →  GPIO控制器向GIC报告中断号33 → CPU处理中断
	interrupts=<RK_PB5   IRQ_TYPE_LEVEL_LOW>;
	interrupt-parent = <&gpio0>
};

RK_PB5是Rockchip(瑞芯微)平台的GPIO引脚命名:
RK:Rockchip缩写
P:Port(端口)
B5:B组的第5个引脚
RK_PB5 通常对应具体的物理引脚号,比如GPIO0_B5

// 获取中断号
int irq = gpio_to_irq(RK_PB5);  // 将GPIO引脚转换为Linux内核中断号
// 注册中断处理函数
request_irq(irq, touch_handler, IRQF_TRIGGER_LOW, "ft5x06", ...);

3.interrupt-cells:约束children的interrupt属性有几位组成,为了解析方便。
4.interrupt-parent:指向谁提供中断即中断控制器。

irq_of_parse_and_map函数 :解析设备节点的"interrupts"属性, 并将对应的中断号映射到系统的中断号,拿到irq,为了后续request_irq。

irq_get_trigger_type 函数:获取interrupts属性的最后一位即触发方式。

4.2 pinctrl & gpio

pinctrl指定pin复用功能(gpio,串口,i2c...),

/proc/device-tree/...

如下gpio0:32到64。每个gpio控制器里有4组,每组8个。

5.platform总线



5.1 案例


c 复制代码
//  paltform_device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define GPIO0_BASE (0xfdd60000)
#define GPIO0_DR (GPIO0_BASE + 0x0004)
#define GPIO0_DDR (GPIO0_BASE + 0x000C)
static struct resource led_resource[] = {
    [0] = DEFINE_RES_MEM(GPIO0_DR, 4),  //数据 寄存器
    [1] = DEFINE_RES_MEM(GPIO0_DDR, 4), //方向 寄存器
};
static void led_release(struct device *dev)
{}
unsigned int led_hwinfo[1] = { 7 };  // 片选偏移 ,led - GPIIO0_C7引脚
static struct platform_device led_pdev = {   // 没有设备树之前   //////////////////////////重点
    .name = "led_pdev",   //匹配下面paltform_driver.c
    .id = 0,
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,
    .dev = {
            .release = led_release,
            .platform_data = led_hwinfo,
        },
};

///////////////////////////////////////////////////////////////////////////////////////
static __init int led_pdev_init(void)
{
    printk("pdev init\n");
    platform_device_register(&led_pdev);  // platform总线添加一设备
    return 0;
}
module_init(led_pdev_init);
static __exit void led_pdev_exit(void)
{
    printk("pdev exit\n");
    platform_device_unregister(&led_pdev);
}
module_exit(led_pdev_exit);
MODULE_DESCRIPTION("Platform LED device");
MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");
c 复制代码
// paltform_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define DEV_MAJOR 243
#define DEV_NAME  "led"
static struct class *led_test_class;
//结构体led_data来管理我们LED灯的硬件信息
struct led_data {
    unsigned int led_pin;
    unsigned int __iomem *va_DDR;
    unsigned int __iomem *va_DR;
    struct cdev led_cdev;
};
static int led_cdev_open(struct inode *inode, struct file *filp)
{
    unsigned int val = 0;
    struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
    printk("led_cdev_open() \n");
    // 设置引脚输出
    val = readl(cur_led->va_DDR);  // 方向
    val |= ((unsigned int)0X1 << (cur_led->led_pin+16));
    val |= ((unsigned int)0X1 << (cur_led->led_pin));
    writel(val,cur_led->va_DDR);
    //设置默认输出高电平
    val = readl(cur_led->va_DR); // 数据
    val |= ((unsigned int)0X1 << (cur_led->led_pin+16));
    val |= ((unsigned int)0x1 << (cur_led->led_pin));
    writel(val, cur_led->va_DR);
    filp->private_data = cur_led;
    return 0;
}
static int led_cdev_release(struct inode *inode, struct file *filp)
{
    return 0;
}
static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
                  size_t count, loff_t * ppos)
{
    unsigned long val = 0;
    unsigned long ret = 0;
    int tmp = count;
    struct led_data *cur_led = (struct led_data *)filp->private_data;
    val = kstrtoul_from_user(buf, tmp, 10, &ret);
    val = readl(cur_led->va_DR); // 方向
    if (ret == 0)
    {
        val |= ((unsigned int)0x1 << ((cur_led->led_pin)+16));
        val &= ~((unsigned int)0X1 << (cur_led->led_pin));
    }
    else
    {
        val |= ((unsigned int)0x1 << (cur_led->led_pin+16));
        val |= ((unsigned int)0X1 << (cur_led->led_pin));
    }
    writel(val, cur_led->va_DR);
    *ppos += tmp;  // 更新文件位置指针
    return tmp; // 返回写入的字节数
}
static struct file_operations led_cdev_fops = {
    .open = led_cdev_open,
    .release = led_cdev_release,
    .write = led_cdev_write,
};

/////////////////////////////////////////////////////////////////////////////////////////
static int led_pdrv_probe(struct platform_device *pdev)  // 传入device,驱动需要去提取设备的资源,完成字符设备的注册等工作
{
    struct led_data *cur_led;
    unsigned int *led_hwinfo;
    struct resource *mem_DR;
    struct resource *mem_DDR;
    dev_t cur_dev;
    int ret = 0;
    printk("led platform driver probe\n");
    //第一步:提取平台设备提供的资源
    //devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小
    cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
    if(!cur_led)
        return -ENOMEM;
    led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
    if(!led_hwinfo)
        return -ENOMEM;
    led_hwinfo = dev_get_platdata(&pdev->dev); // LED灯的寄存器偏移量 即 7
    cur_led->led_pin = led_hwinfo[0];
    mem_DR = platform_get_resource(pdev, IORESOURCE_MEM, 0);  // 數據寄存器
    mem_DDR = platform_get_resource(pdev, IORESOURCE_MEM, 1); //方向寄存器
    cur_led->va_DR = devm_ioremap(&pdev->dev, mem_DR->start, resource_size(mem_DR)); // 寄存器地址转化为虚拟地址
    cur_led->va_DDR = devm_ioremap(&pdev->dev, mem_DDR->start, resource_size(mem_DDR));

    //第二步:注册字符设备(才有fops)
    cur_dev = MKDEV(DEV_MAJOR, pdev->id);
    register_chrdev_region(cur_dev, 1, "led_cdev");
    cdev_init(&cur_led->led_cdev, &led_cdev_fops);
    ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
    if(ret < 0)
    {
        printk("fail to add cdev\n");
        goto add_err;
    }
    device_create(led_test_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id); // /dev/led0
    //platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中
    platform_set_drvdata(pdev, cur_led);
    return 0;
add_err:
    unregister_chrdev_region(cur_dev, 1);
    return ret;
}
static int led_pdrv_remove(struct platform_device *pdev)
{
    dev_t cur_dev; 
    //platform_get_drvdata,获取当前LED灯对应的结构体
    struct led_data *cur_data = platform_get_drvdata(pdev);
    printk("led platform driver remove\n");
    cur_dev = MKDEV(DEV_MAJOR, pdev->id);
    //cdev_del删除对应的字符设备
    cdev_del(&cur_data->led_cdev);
    //删除/dev目录下的设备
    device_destroy(led_test_class, cur_dev);
    //unregister_chrdev_region, 注销掉当前的字符设备编号
    unregister_chrdev_region(cur_dev, 1);
    return 0;
}
static struct platform_device_id led_pdev_ids[] = {
    {.name = "led_pdev"}, //匹配
    {}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);
//平台总线匹配过程中 ,只会根据id_table中的name值进行匹配,若和平台设备的name值相等,则表示匹配成功; 反之,则匹配不成功,表明当前内核没有该驱动能够支持的设备。
static struct platform_driver led_pdrv = {    //////////////////////////重点
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver.name = "led_pdev",
    .id_table = led_pdev_ids,
};
static __init int led_pdrv_init(void)
{
    printk("led platform driver init\n");
    //class_create,来创建一个led类
    led_test_class = class_create(THIS_MODULE, "test_leds");
    //调用函数platform_driver_register,注册我们的平台驱动结构体,这样当加载该内核模块时, 就会有新的平台驱动加入到内核中。 第20-27行,注销
    platform_driver_register(&led_pdrv);
    return 0;
}
module_init(led_pdrv_init);
static __exit void led_pdrv_exit(void)
{
    printk("led platform driver exit\n");   
    platform_driver_unregister(&led_pdrv);
    class_destroy(led_test_class);
}
module_exit(led_pdrv_exit);
MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");

6.设备驱动模型

c 复制代码
... __list_add(struct list_head *new,    //node3
                              struct list_head *prev,   //head
                              struct list_head *next)   //node2 
{
        next->prev = new;  //node2->prev 等于 node3
        new->next = next; //node3->next等于 node2
        new->prev = prev; //node3->prev 等于 head
}

如下是类似管道,先入先出,node123从左往右按顺序排列。list_add函数调用如下栈,先入后出,如上图。

c 复制代码
// kobject是最基础的结构体,/sys/device目录是一个kobject,里面还有很多kobject
// drivers/base/core.c:
static struct kobject * class_kobj   = NULL; 
static struct kobject * devices_kobj = NULL; 
/* 创建/sys/class */ 
class_kobj = kobject_create_and_add("class", NULL); 
/* 创建 /sys/devices */ 
devices_kobj = kobject_create_and_add("devices", NULL); 

如上注册完bus后生成如下目录。

如下创建链接就行,没必要重复。

如下比device注册做的事情少很多。

创建链接指向module,表示该驱动是由哪个内核模块提供能力,同样module也反向指向驱动,表示它提供什么样的驱动能力。

driver去扫匹配成功后不退出,因为挂在这总线上设备不一定只有一个,比如usb上挂鼠标,挂键盘。device只扫一个driver就停止。bus_for_each_drv扫的是klist_driver。如下粉色框是driver_register封装。

相关推荐
乔碧萝成都分萝2 小时前
二十六、IIO子系统 + SPI子系统 + ICM20608
linux·驱动开发·嵌入式
A星空1236 小时前
二、交叉编译工具链(arm-linux-gnueabihf-gcc)安装与验证,搭建 TFTP+NFS 服务,调试开发板网络连通性;
linux·c++·驱动开发·单片机·嵌入式硬件
嵌入式-老费10 小时前
Linux camera驱动开发(camera和飞控的结合)
驱动开发
一路往蓝-Anbo1 天前
第 10 章:OpenAMP 实战——构建 M33 与 Linux 的 RPMsg 消息隧道
linux·运维·服务器·驱动开发·stm32·单片机·嵌入式硬件
『往事』&白驹过隙;1 天前
瑞芯微(RK平台)调试指令常用整理
linux·arm开发·驱动开发
哈哈浩丶2 天前
安卓系统全流程启动
android·linux·驱动开发
哈哈浩丶3 天前
ATF (ARM Trusted Firmware) -2:完整启动流程(冷启动)
android·linux·arm开发·驱动开发
哈哈浩丶3 天前
OP-TEE-OS:综述
android·linux·驱动开发
哈哈浩丶3 天前
ATF (ARM Trusted Firmware) -1:综述
linux·arm开发·驱动开发