面试(八)

目录

一. 设备树

1.1 驱动的设备树匹配

1.2 驱动的Probe函数

1.3 驱动的Remove函数

1.4 驱动结构体

二. 老版字符设备驱动框架

2.1 文件操作函数的实现

三. 新字符设备驱动框架

3.1 设备模型

3.2 设备树

3.3 kobject 和 sysfs

3.4 fasync 和 poll

3.5 file_operations的改进

四. plateform驱动框架

五. SPI驱动框架和编写流程

5.1 定义服务接口

5.2 实现服务提供者

5.3 配置服务提供者

5.4 加载服务

5.5 部署和测试

六. SPI 总线接口

七. IIC总线接口

八. UART


一. 设备树

把这个词分开其实就是"设备"和"树", 树的主干就是系统总线,分支则是IIC,SPI等各硬件。而之前没有设备树的时候,linux是如何描述板级信息的?

通过硬编码在内核源代码中描述板级信息,直接写在内核源码中。

而dts用于描述板级信息,如设备节点,属性及配置;dtsi用于描述SOC信息,

设备树的语法结构:

主要的语法元素包含:节点和属性

节点的定义:

每个节点以一个标签和花括号开始,如

cpus {
    cpu@0 {
        compatible = "arm,cortex-a53";
        reg = <0>;
        clock-frequency = <1200000000>; /* 1.2 GHz */
    };
    cpu@1 {
        compatible = "arm,cortex-a53";
        reg = <1>;
        clock-frequency = <1200000000>; /* 1.2 GHz */
    };
};

在这个例子中,cpus 是一个节点,cpu@0cpu@1 是它的子节点

设备树的标准属性:

compatible

指定设备的兼容性字符串,帮助操作系统或驱动程序确定如何处理该设备。

reg

描述设备寄存器的地址和大小,通常用于内存映射的设备。

interrupts

定义设备的中断信息,包括中断号和中断触发方式。

那么我们驱动到底是怎么使用设备树的呢?

那么我们驱动到底是怎么使用设备树的呢?

那么我们驱动到底是怎么使用设备树的呢?

比如,我们有在设备树中有下面的定义:

led_device: led@0
{
    compatible = "my_led";
    reg = <0x0 0x0 0x4>
    led-color = "red"
}

1.1 驱动的设备树匹配

驱动需要在'of_match_table'中指定设备树节点的兼容性信息,以便内核可以将设备树中的节点与驱动匹配起来

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>

struct led_device {
    void __iomem *base_addr;
    const char *color;
};

static const struct of_device_id led_of_match[] = {
    { .compatible = "my_led", },
    { /* sentinel */ }
};
// 一个用于定义设备树的匹配表,以便内核可以根据设备树中的信息识别和加载驱动
MODULE_DEVICE_TABLE(of, led_of_match);

1.2 驱动的Probe函数

在'probe'函数中,驱动通过设备树API获取设备的配置信息。

static int led_probe(struct platform_device *pdev)
{
    //定义一个指向'led_device'结构体的指针,用于保存LED设备的相关信息
    struct led_device *led;
    //用于获取设备的内存资源
    struct resource *res;
    //从 platform_device 结构体中获取设备树节点信息,用于访问设备树中定义的属性。
    struct device_node *node = pdev->dev.of_node;
    
    // 分配内存给 led_device 结构体,并初始化为零。devm_kzalloc 函数确保在设备释放时自动释放内存。
    led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
    // 检查内存分配是否成功。如果失败,返回 -ENOMEM 表示内存不足。
    if (!led)
        return -ENOMEM;

    // 获取寄存器的资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENODEV;

    // 映射寄存器地址
    led->base_addr = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(led->base_addr))
        return PTR_ERR(led->base_addr);

    // 从设备树中获取 LED 颜色信息
    if (of_property_read_string(node, "led-color", &led->color)) {
        dev_err(&pdev->dev, "Failed to get led-color from device tree\n");
        return -EINVAL;
    }

    dev_info(&pdev->dev, "LED color is %s\n", led->color);

    // 其他初始化代码...

    platform_set_drvdata(pdev, led);
    return 0;
}

1.3 驱动的Remove函数

在'remove'函数中,你可以执行清理操作,例如取消映射的内存

static int led_remove(struct platform_device *pdev)
{
    struct led_device *led = platform_get_drvdata(pdev);

    // 释放资源等操作...

    return 0;
}

1.4 驱动结构体

最后,将'probe'和'remove'函数与设备树匹配表一起定义,并注册驱动

static struct platform_driver led_driver = {
    // 指定当平台设备匹配驱动时调用的'probe'函数,在这里它指向'ledn_porbe'函数
    // 该函数负责初始化和配置设备
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "led_driver",
        .of_match_table = led_of_match,
    },
};

module_platform_driver(led_driver);

MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("LED Driver");
MODULE_LICENSE("GPL");

二. 老版字符设备驱动框架

主要指的就是传统的设备驱动模型,通常包含文件操作函数如openreadwriteclose。他们通过设备号和设备文件进行交互,实现数据的读写和设备控制。

2.1 文件操作函数的实现

static int my_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "mychardev: Device opened\n");
    return 0;
}

static int my_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "mychardev: Device closed\n");
    return 0;
}

static ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *offset) {
    ssize_t ret = 0;

    if (*offset >= BUFFER_SIZE)
        return 0;

    if (len > BUFFER_SIZE - *offset)
        len = BUFFER_SIZE - *offset;

    if (copy_to_user(buf, device_buffer + *offset, len) != 0)
        return -EFAULT;

    *offset += len;
    ret = len;
    printk(KERN_INFO "mychardev: Read %zu bytes\n", len);

    return ret;
}

static ssize_t my_write(struct file *file, const char __user *buf, size_t len, loff_t *offset) {
    ssize_t ret = 0;

    if (*offset >= BUFFER_SIZE)
        return 0;

    if (len > BUFFER_SIZE - *offset)
        len = BUFFER_SIZE - *offset;

    if (copy_from_user(device_buffer + *offset, buf, len) != 0)
        return -EFAULT;

    *offset += len;
    ret = len;
    printk(KERN_INFO "mychardev: Wrote %zu bytes\n", len);

    return ret;
}

三. 新字符设备驱动框架

新添加的关键特性有:

3.1 设备模型

struct devicestruct device_driver::新版内核引入了设备模型,提供了一种统一的方式来管理设备和驱动程序,这些结构体提供了设备和驱动程序之间的关联和通信机制

3.2 设备树

设备树用于描述硬件设备的结构,内核通过设备树来初始化和配置设备,而不是通过硬编码的方式

3.3 kobjectsysfs

kobject是一个内核对象,用于管理内核中的对象;sysfs是一个文件系统,通过它可以在用户空间中暴露内核对象的信息

可以通过sysfs创建和管理设备的属性,从而使用户空间能够访问和修改设备的状态

3.4 fasyncpoll

这两个机制提供了异步事件通知的能力,可以用来处理字符设备的事件驱动操作

3.5 file_operations的改进

新版本内核在file_operations结构体中添加了一些新的方法,如unlocked_ioctl,支持无锁的IO控制操作。

四. plateform驱动框架

平台驱动(Platform Driver)框架在Linux内核中是一种专门用于处理平台设备的机制。平台设备通常指的是那些直接与系统硬件紧密耦合的设备,例如嵌入式系统中的设备,或那些通过设备树(Device Tree)描述的设备。

五. SPI驱动框架和编写流程

5.1 定义服务接口

创建一个接口或抽象类,定义服务的标准方法和功能

public interface MyService {
    void execute();
}

5.2 实现服务提供者

编写一个或多个服务实现类,实现服务接口

public class MyServiceImpl implements MyService {
    @Override
    public void execute() {
        System.out.println("Service Executed!");
    }
}

5.3 配置服务提供者

  • 创建一个描述服务提供者的配置文件,通常放在META-INF/services目录下,文件名为服务接口的完全限定名。
  • 文件内容是服务提供者的实现类的完全限定名
com.example.MyServiceImpl

5.4 加载服务

使用ServiceLoader类动态加载并使用服务提供者。

ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService service : loader) {
    service.execute();
}

5.5 部署和测试

  • 打包应用程序,确保配置文件正确放置在META-INF/services目录中。
  • 测试服务加载和执行是否符合预期。

六. SPI 总线接口

SPI主要包括以下4条信号线:MOSI,MISO,SS/CS,SCK

通信模式主要是4种,由时钟极性和时钟相位决定

它是一种全双工,串行总线,时钟同步

七. IIC总线接口

主要包括两条信号线:SCL,SDA

时序主要包括:开始信号,应答信号,地址信号,停止信号

它是一种半双工,串行总线,时钟同步,但它支持多主机

八. UART

起始位:标识数据传输的开始,起始位通常是逻辑低电平,在数据传输开始时,数据线会从高电平(空闲状态)变为低电平

数据位:通常是5到9位,通常是8位

奇偶校验位:用于错误检测。奇偶校验位用于检测数据传输过程中是否发生了错误。

停止位:止位的数量可以是1位、1.5位或2位,具体取决于通信协议的配置。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值