五月份嵌入式面试总结

news2025/5/17 19:03:35

目录

1、札记

1.1、芯片的bring up 主要做哪些工作:

2、Linux驱动八股文

中断与同步互斥

2.1.1 内核同步互斥的几种方式

2.1.2 互斥锁和自旋锁的区别

2.1.3 spin_lock 和 spin_lock_irqsave 的区别

2.1.4 进程上下文和中断上下文有什么区别

2.1.5 进行上下文用什么锁

2.1.6 中断上下文用什么锁

2.1.8 中断下半部的三种方式 以及有什么区别 

2.1.9 tasklet 和工作队列能否休眠?运行在中断上下文还是进程上下文

Linux驱动基础问题

2.2.1 驱动分类

2.2.2 驱动模块基本结构

2.2.3 驱动的加载方式 

2.2.4 字符驱动设备

2.2.5 文件操作结构体

2.2.6 常见面试问题

1. 字符设备驱动的主设备号和次设备号有什么作用

2.如何实现设备的并发访问控制

3.copy_to_user 和 copy_from_user 的作用是什么

2.3 中断处理

2.3.1 中断注册流程

2.3.2 中断注册流程

2.3.4 常见面试问题

1、Linux 中断下半部有哪几种机制

2、中断上下文有什么限制

3、如何处理共享中断

2.4 设备树与平台驱动

2.4.1 设备树基础

2.4.2 平台驱动模型

2.4.3 常见面试问题

1.设备树的作用是什么

2、如何在驱动中获得设备树属性

3、platform_device 和 platform_driver 关系

2.5 同步与互斥

2.5.1 常用的同步机制 

2.5.2 常见的面试问题 

1. 自旋锁和互斥锁的区别

2.死锁概念

3.死锁的四个条件

4.死锁的处理方式:防止死锁,避免死锁,检测死锁,解除死锁

5、如何避免死锁

6、在单核mcu上写多线程程序是否要加锁,

2.6 gpio与设备 io

2.6.1 内存映射 io

2.6.3 常见面试问题

1、如何处理Linux下的 gpio 中断 

2、readl、writel 与 ioread32、iowrite32的联系与区别 

3、如何处理设备的字节序问题

Linux 驱动框架系列

3.1 Linux 设备驱动模型

3.1.1 设备驱动模型基础

3.1.2 驱动匹配机制

3.1.3 常见面试问题

1、Linux 设备驱动模型的核心组件有哪些

2、驱动和设备是如何匹配

3、设备树在驱动开发中的作用是什么

3.2 GPIO 子系统

3.2.1 gpio子系统框架

3.2.2 gpio 驱动实现

3.2.3常见面试问题

1、如何在驱动中使用 gpio 

2、gpio中断是如何实现的

3.3 Pinctrl 子系统

3.3.1 pinctrl 子系统架构

3.3.2 pinctrl 驱动实现

3.3.3 常见面试问题

1、Pinctrl 子系统的作用是什么

2、Pinctrl 与 GPIO 子系统的关系是什么?

3、设备树中如何描述 Pinctrl 配置

3.4 I2C子系统

3.4.1 I2C 子系统架构

3.4.2 I2C 驱动实现

3.4.3 常见问题汇总

1、I2C 子系统的主要组件有哪些

2、I2C 设备驱动如何与设备匹配

3、如何在 I2C 驱动中进行数据传输


1、札记

1.1、芯片的bring up 主要做哪些工作:

1、sdk 编译 烧录 启动 调试串口

2、屏幕驱动正常工作 demo正常启动

2、Linux驱动八股文

中断与同步互斥

2.1.1 内核同步互斥的几种方式

互斥锁、自旋锁、原子操作、禁止抢占、内存屏障

信号量、读写锁、顺序锁

2.1.2 互斥锁和自旋锁的区别

自旋锁:忙等、不可休眠、持有时间短、适合中断上下文

互斥锁:睡眠等,持有时间长

2.1.3 spin_lock 和 spin_lock_irqsave 的区别

区别在于中断开关,通常在中断上下文,需要 对寄存器进行操作,寄存器操作需要用 spin_lock_irqsave ,而 spin_lock 只是禁止内核抢占,适用于没有中断处理的场景,确保临界区资源不被中断程序访问

2.1.4 进程上下文和中断上下文有什么区别

进程上下文:用户态进程的执行环境,例如系统调用,内核线程,可休眠(允许调用可休眠函数,如果kmalloc msleep)

中断上下文: 硬中断、软中断触发的执行条件,不可休眠

2.1.5 进行上下文用什么锁

看进程能否休眠,可以休眠的话用互斥锁,比如系统调用,内核线程等场景都是可以休眠的

不可休眠:自旋锁,比如中断处理程序的上半部,持有自旋锁、原子操作的领域

2.1.6 中断上下文用什么锁

自旋锁

2.1.8 中断下半部的三种方式 以及有什么区别 

软中断 tasklet 工作队列

tasklet 基于软中断,动态注册,而软中断是静态注册的

工作队列运行在进程上下文,可休眠 ;tasklet 和软中断是在中断上下文,不可休眠

2.1.9 tasklet 和工作队列能否休眠?运行在中断上下文还是进程上下文

tasklet : 中断上下文,禁止休眠

工作队列: 进程上下文,允许休眠

Linux驱动基础问题

2.2.1 驱动分类

  • 字符设备驱动:按字节访问 如串口 按键
  • 块设备驱动:按块访问 如硬盘 SD卡
  • 网络设别驱动:网络接口设备

2.2.2 驱动模块基本结构

#include <linux/module.h>
#include <linux/init.h>

static int __init my_driver_init(void)
{
    printk(KERN_INFO "Driver initialized\n");
    return 0;
}

static void __exit my_driver_exit(void)
{
    printk(KERN_INFO "Driver exited\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Driver");

2.2.3 驱动的加载方式 

  • 静态加载: 编译进内核镜像
  • 动态加载:编译为模块 (.ko)文件,使用 insmod/ modprobe 加载
  • 对应模块的静态加载和动态加载可以通过menuconfig 界面进行选择

config EXAMPLE_DRIVER
    tristate "Example Driver Support"
    depends on NETDEVICES
    help
      This is an example driver for Linux.

  • tristate 是支持动态加载(<M>)的关键字。

  • 通过 menuconfig 界面按 Y/M/N 切换编译方式。

  • 依赖项(depends on)和默认值(default)会影响最终行为。

2.2.4 字符驱动设备

// 分配设备号
dev_t dev;
alloc_chrdev_region(&dev, 0, 1, "my_device");

// 初始化cdev结构
struct cdev *my_cdev = cdev_alloc();
cdev_init(my_cdev, &fops);
my_cdev->owner = THIS_MODULE;

// 添加字符设备
cdev_add(my_cdev, dev, 1);

// 创建设备节点
struct class *my_class = class_create(THIS_MODULE, "my_class");
device_create(my_class, NULL, dev, NULL, "my_device");

2.2.5 文件操作结构体

static struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
    .read = my_read,
    .write = my_write,
    .unlocked_ioctl = my_ioctl,
};

2.2.6 常见面试问题

1. 字符设备驱动的主设备号和次设备号有什么作用
  • 主设备号 标识设备驱动程序
  • 此设备号 标识使用同一驱动的不同设备通过 MAJOR() 和 MINOR ()宏获取
2.如何实现设备的并发访问控制
  • 使用自旋锁、互斥锁等同步机制
3.copy_to_user 和 copy_from_user 的作用是什么
  • 安全在内核空间和用户空间之间复制数据

2.3 中断处理

2.3.1 中断注册流程

// 注册中断处理函数
int ret = request_irq(irq_num, my_interrupt_handler, 
                      IRQF_SHARED, "my_device", dev_id);

// 中断处理函数
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
    // 处理中断
    // ...
    return IRQ_HANDLED;
}

// 释放中断
free_irq(irq_num, dev_id);
  • 先 请求中断 -> 在写中断函数 -> 释放中断

2.3.2 中断注册流程

  • 上半部 中断处理函数,快速响应 
  • 下半部 延迟处理 可调度
// 工作队列实现下半部
static struct work_struct my_work;

static void my_work_handler(struct work_struct *work)
{
    // 耗时操作
}

static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
    // 快速处理
    schedule_work(&my_work);
    return IRQ_HANDLED;
}

// 初始化
INIT_WORK(&my_work, my_work_handler);

2.3.4 常见面试问题

1、Linux 中断下半部有哪几种机制
  • 软中断 : 静态分配,优先级高
  • tasklet : 基于软中断,动态创建
  • 工作队列:在进程上下文中执行,可睡眠
2、中断上下文有什么限制
  • 不能睡眠
  • 不能使用可能睡眠的函数 (互斥锁)
  • 尽量减少处理时间
3、如何处理共享中断

共享中断是指多个设备共享一个硬件中断线,当中断触发,内核需要调用所有注册到这个irq 的设备处理函数处,处理函数中回去 检查中断源 和 返回处理结果 、

2.4 设备树与平台驱动

2.4.1 设备树基础

/* 设备树节点示例 */
my_device: my_device@50000000 {
    compatible = "vendor,my-device";
    reg = <0x50000000 0x1000>;
    interrupts = <0 29 4>;
    clocks = <&clk 1>;
    status = "okay";
};

2.4.2 平台驱动模型

// 平台驱动结构体
static struct platform_driver my_platform_driver = {
    .probe = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
        .name = "my-device",
        .of_match_table = my_of_match,
        .pm = &my_pm_ops,
    },
};

// 设备树匹配表
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);

// 注册平台驱动
module_platform_driver(my_platform_driver);

2.4.3 常见面试问题

1.设备树的作用是什么
  • 描述硬件设备,实现硬件与驱动分离支持运行适合
2、如何在驱动中获得设备树属性
  • 通过设备树匹配节点(compatible)
  • 提取常用属性(of函数)
#include <linux/of.h>
#include <linux/platform_device.h>

static int my_probe(struct platform_device *pdev)
{
    struct device_node *node = pdev->dev.of_node;
    struct resource *res;
    void __iomem *regs;
    int irq, ret;
    u32 freq;

    /* 1. 获取寄存器地址(通过 reg 属性) */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    regs = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(regs))
        return PTR_ERR(regs);

    /* 2. 获取中断号 */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    /* 3. 读取自定义整数属性 */
    ret = of_property_read_u32(node, "clock-frequency", &freq);
    if (ret) {
        dev_warn(&pdev->dev, "clock-frequency not specified, using default\n");
        freq = 25000000; // 默认值
    }

    /* 4. 检查布尔属性 */
    if (of_property_read_bool(node, "dma-capable")) {
        setup_dma();
    }

    /* 注册中断处理函数 */
    ret = devm_request_irq(&pdev->dev, irq, my_irq_handler, 0, "my-device", NULL);
    if (ret)
        return ret;

    dev_info(&pdev->dev, "Device probed, freq=%d Hz\n", freq);
    return 0;
}

static const struct of_device_id my_device_ids[] = {
    { .compatible = "vendor,my-device" },
    { }
};
MODULE_DEVICE_TABLE(of, my_device_ids);

static struct platform_driver my_driver = {
    .driver = {
        .name = "my-device",
        .of_match_table = my_device_ids,
    },
    .probe = my_probe,
};
module_platform_driver(my_driver);
3、platform_device 和 platform_driver 关系
  • platform_device 描述设备资源
  • platform_driver 实现设别驱动通过总线和模型绑定

2.5 同步与互斥

2.5.1 常用的同步机制 

  • 自旋锁(spin_lock): 忙等待,适合短时间持锁,中断可用
  • 互斥锁(mutex): 可能睡眠,适合长时间持锁
  • 读写锁(rwlock): 读共享写独占
// 自旋锁示例
spinlock_t my_lock;
spin_lock_init(&my_lock);

spin_lock(&my_lock);
// 临界区
spin_unlock(&my_lock);

// 互斥锁示例
struct mutex my_mutex;
mutex_init(&my_mutex);

mutex_lock(&my_mutex);
// 临界区
mutex_unlock(&my_mutex);

2.5.2 常见的面试问题 

1. 自旋锁和互斥锁的区别
  • 自旋锁:忙等待,不释放 cpu 适合短时间持有
  • 互斥锁:可能睡眠,释放 cpu ,适合长时间持有
  • 自旋锁可以用于中断上下文,互斥锁不可以
2.死锁概念

多并发进程因争夺资源而产生的相互等待的现象

本质:(1、资源有限;2、进程推进不合理 )

3.死锁的四个条件
  • 互斥:涉及的资源非共享
  • 占有且等待:进程每次申请他所需要的一部分资源,在等待新资源的同时继续占用已分配到的资源
  • 不可剥夺:进程所获得的资源在未使用完毕之前不会被其他进程抢走
  • 循环等待:某一个进程已获得的资源会被下一个进程请求
4.死锁的处理方式:防止死锁,避免死锁,检测死锁,解除死锁

预防死锁:

  1. 资源一次性分配(破坏请求和保持条件)
  2. 可剥夺资源(当进程的资源满足是,释放已占有资源,破坏不可剥夺条件)
  3. 资源有序分配(给每个资源赋予一个标号,按照编号顺序请求资源)
  4. 当某个进程一个请求得不到满足时,则剥夺他的所有条件

避免死锁:

  1. 系统在进行资源分配时,先计算资源分配的安全性,若分配会导致系统进入不安全状态,则取消此次资源分配(银行家算法)

检测死锁:

  1. 为每个进程和资源分配一个唯一的号码,然后建立资源分配表和进程等待表

解除死锁:在检测到死锁后,可以采用以下两个方面解除死锁:

  1.        剥夺资源:从其他进程剥夺足够多的资源分配给死锁进程,以接触死锁状态
  2. 撤销进程:撤销死锁进程,直到有足够的资源可用
5、如何避免死锁

一般来说,有三种避免死锁的技术

  1. 加锁顺序:(线程按照一定的顺序加锁)
  2. 加锁时间限制:(超过时限就放弃该锁的请求,并释放自己占用的资源)
  3. 死锁检测
6、在单核mcu上写多线程程序是否要加锁,

依旧要加锁的,线程锁适用于实现线程的同步和通信的,多线程之间依旧是要线程同步的,不使用线程锁的话,会导致共享数据的修改引起的冲突。

2.6 gpio与设备 io

// 获取GPIO
int gpio = of_get_named_gpio(node, "reset-gpio", 0);
if (gpio_is_valid(gpio)) {
    gpio_request(gpio, "reset");

    // 设置方向
    gpio_direction_output(gpio, 1);

    // 设置值
    gpio_set_value(gpio, 0);
    msleep(10);
    gpio_set_value(gpio, 1);

    // 释放GPIO
    gpio_free(gpio);
}

2.6.1 内存映射 io

// 映射IO内存
void __iomem *base;
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
    return PTR_ERR(base);

// 读写寄存器
writel(value, base + OFFSET);
value = readl(base + OFFSET);

2.6.3 常见面试问题

1、如何处理Linux下的 gpio 中断 

在Linux下 处理 gpio 中断通常涉及 内核驱动 或 用户空间轮询/事件监听 两种方式 

  1. 确认 gpio 编号 
    # 查看 GPIO 编号(假设物理引脚为 GPIO17)
    echo 17 > /sys/class/gpio/export
    ls /sys/class/gpio/gpio17  # 确认 GPIO 已导出

  2. 配置 gpio 为输入并启用中断
    # 设置为输入模式
    echo "in" > /sys/class/gpio/gpio17/direction
    
    # 设置中断触发方式(可选:rising, falling, both, none)
    echo "rising" > /sys/class/gpio/gpio17/edge

  3. 在用户空间监听中断
    #include <stdio.h>
    #include <fcntl.h>
    #include <poll.h>
    
    int main() {
        int fd = open("/sys/class/gpio/gpio17/value", O_RDONLY);
        struct pollfd pfd = { fd, POLLPRI | POLLERR, 0 };
    
        while (1) {
            int ret = poll(&pfd, 1, -1); // 阻塞等待中断
            if (ret > 0) {
                lseek(fd, 0, SEEK_SET);  // 重置文件指针
                char buf[10];
                read(fd, buf, sizeof(buf));
                printf("Interrupt! Value: %s", buf);
            }
        }
        close(fd);
        return 0;
    }

2、readl、writel 与 ioread32、iowrite32的联系与区别 

功能基本相同,都是用于32位 IO 访问 readl、writel 

3、如何处理设备的字节序问题

使用 cpu_to_le32 \ le32_to_cpu 等转换函数明确区分大小端字节序使用位域

Linux 驱动框架系列

3.1 Linux 设备驱动模型

3.1.1 设备驱动模型基础

  • 设备模型三要素:总线(bus)、设备(device)、驱动(driver)
  • kobject:设备模型的基础对象,实现引用计数和 sysfs 导出
  • 设备树:描述硬件信息的数据结构,减少硬编码

3.1.2 驱动匹配机制

// 平台驱动匹配示例
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_platform_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my-device",
        .of_match_table = my_of_match,
    },
};

3.1.3 常见面试问题

1、Linux 设备驱动模型的核心组件有哪些
  • kobject : 基础对象,提供引用技术和 sysfs 接口
  • kset : kobject 的集合,管理相关对象
  • device : 表示物理或者逻辑设备
  • driver : 实现设备功能的代码
  • bus : 连接设备和驱动的媒介
2、驱动和设备是如何匹配
  • 基于总线的匹配机制设备树中的 compatible 属性与 驱动中的 of_match_table 匹配平台设备的 name 与 平台驱动的 name 匹配成功后调用 probe 函数
3、设备树在驱动开发中的作用是什么
  • 描述硬件设备信息,减少硬编码实现硬件与驱动分离支持 运行时配置修改简化

3.2 GPIO 子系统

3.2.1 gpio子系统框架

  • gpio 控制器 : 管理一组 gpio 引脚
  • gpiochip :表示其中一个 gpio 控制器
  • gpio_desc : 描述单个 gpio 引脚
  • gpiolib : 提供同一的 gpio 操作接口

3.2.2 gpio 驱动实现

// GPIO控制器驱动示例
static const struct gpio_chip my_gpio_chip = {
    .label = "my-gpio",
    .owner = THIS_MODULE,
    .base = -1,  // 动态分配
    .ngpio = 32, // 32个GPIO
    .request = my_gpio_request,
    .free = my_gpio_free,
    .direction_input = my_gpio_direction_input,
    .direction_output = my_gpio_direction_output,
    .get = my_gpio_get,
    .set = my_gpio_set,
};

static int my_gpio_probe(struct platform_device *pdev)
{
    struct my_gpio_priv *priv;
    int ret;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    priv->chip = my_gpio_chip;
    priv->chip.parent = &pdev->dev;

    ret = devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, priv);
    return 0;
}

3.2.3常见面试问题

1、如何在驱动中使用 gpio 

在 Linux 内核驱动中使用 GPIO 主要涉及 申请 GPIO、配置方向(输入/输出)、读写 GPIO 值、处理中断 等操作

2、gpio中断是如何实现的

首先需要在设备树中定义

my_device {
    compatible = "my,gpio-device";
    interrupt-parent = <&gpio>;
    interrupts = <17 IRQ_TYPE_EDGE_RISING>; // GPIO17,上升沿触发
};

然后在驱动中注册中断处理函数 

static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
    printk(KERN_INFO "GPIO Interrupt triggered!\n");
    return IRQ_HANDLED;
}

// 在 probe 函数中注册中断
int irq = gpiod_to_irq(gpio); // 新版 API
// int irq = gpio_to_irq(gpio_num); // 旧版 API

int ret = request_irq(irq, gpio_irq_handler, IRQF_TRIGGER_RISING, "my_gpio_irq", NULL);
if (ret) {
    dev_err(&pdev->dev, "Failed to request IRQ\n");
    return ret;
}

// 在 remove 函数中释放中断
free_irq(irq, NULL);

完整驱动

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>

struct my_device_data {
    struct gpio_desc *gpio;
    int irq;
};

static irqreturn_t my_gpio_irq(int irq, void *dev_id) {
    printk(KERN_INFO "Interrupt occurred!\n");
    return IRQ_HANDLED;
}

static int my_probe(struct platform_device *pdev) {
    struct my_device_data *data;

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    // 获取 GPIO
    data->gpio = gpiod_get(&pdev->dev, "my-gpios", GPIOD_IN);
    if (IS_ERR(data->gpio))
        return PTR_ERR(data->gpio);

    // 注册中断
    data->irq = gpiod_to_irq(data->gpio);
    if (request_irq(data->irq, my_gpio_irq, IRQF_TRIGGER_RISING, "my_gpio_irq", NULL)) {
        gpiod_put(data->gpio);
        return -EINVAL;
    }

    platform_set_drvdata(pdev, data);
    return 0;
}

static int my_remove(struct platform_device *pdev) {
    struct my_device_data *data = platform_get_drvdata(pdev);
    free_irq(data->irq, NULL);
    gpiod_put(data->gpio);
    return 0;
}

static const struct of_device_id my_of_match[] = {
    { .compatible = "my,gpio-device" },
    {},
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .driver = {
        .name = "my_gpio_driver",
        .of_match_table = my_of_match,
    },
    .probe = my_probe,
    .remove = my_remove,
};
module_platform_driver(my_driver);

MODULE_LICENSE("GPL");

3.3 Pinctrl 子系统

3.3.1 pinctrl 子系统架构

  • pin controller :实现 SoC 上引脚的管理
  • pinctrl driver : 实现 pin controller 的驱动
  • pin configuration :配置引脚功能 上拉下拉
  • pin muxing :配置引脚复用功能

3.3.2 pinctrl 驱动实现

// Pinctrl驱动示例
static const struct pinctrl_pin_desc my_pins[] = {
    PINCTRL_PIN(0, "gpio0"),
    PINCTRL_PIN(1, "gpio1"),
    // ...
};

static const char * const my_groups[] = {
    "uart0_grp", "i2c0_grp", "spi0_grp",
};

static const char * const my_functions[] = {
    "uart", "i2c", "spi",
};

static const struct pinmux_ops my_pmx_ops = {
    .get_functions_count = my_get_functions_count,
    .get_function_name = my_get_function_name,
    .get_function_groups = my_get_function_groups,
    .set_mux = my_set_mux,
};

static const struct pinconf_ops my_pconf_ops = {
    .pin_config_get = my_pin_config_get,
    .pin_config_set = my_pin_config_set,
};

static struct pinctrl_desc my_pinctrl_desc = {
    .name = "my-pinctrl",
    .pins = my_pins,
    .npins = ARRAY_SIZE(my_pins),
    .pctlops = &my_pctlops,
    .pmxops = &my_pmx_ops,
    .confops = &my_pconf_ops,
    .owner = THIS_MODULE,
};

3.3.3 常见面试问题

1、Pinctrl 子系统的作用是什么

管理和配置 SoC 上的引脚,实现引脚复用功能配置引脚电气特性(上拉 下拉 驱动强度等),与GPIO 子系统协同工作

2、Pinctrl 与 GPIO 子系统的关系是什么?

Pinctrl 负责引脚功能配置和复用 GPIO 子系统负责引脚的输入输出控制两者协同工作,Pinctrl 先配置引脚功能,然后 GPIO 控制引脚状态

3、设备树中如何描述 Pinctrl 配置

Pinctrl  配置通常分为两部分 Pin Controller 节点 (描述引脚控制硬件)和 Device 节点 (引用 Pin Controller ,配置具体功能)

// 1. Pin Controller 节点(由 SoC 厂商提供,一般位于 .dtsi 文件)
pinctrl: pinctrl {
    compatible = "rockchip,rk3568-pinctrl";
    reg = <0x0 0xfdc20000 0x0 0x10000>;

    // 定义 GPIO 引脚组
    gpio0: gpio0 {
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    // 定义 UART2 的引脚复用配置
    uart2m0_xfer: uart2m0-xfer {
        rockchip,pins =
            // 引脚复用为 UART2,配置电气属性
            <0 RK_PD1 1 &pcfg_pull_none>,  // TXD
            <0 RK_PD0 1 &pcfg_pull_none>;  // RXD
    };
};

在设备节点引用 Pinctrl 

// 2. Device 节点(在板级 .dts 文件中)
&uart2 {
    status = "okay";
    pinctrl-names = "default";          // 状态名
    pinctrl-0 = <&uart2m0_xfer>;       // 引用具体的 pinctrl 配置
};

配置 GPIO 引脚

// 定义 GPIO 引脚配置
gpio_led: gpio-led {
    rockchip,pins = <0 RK_PC5 0 &pcfg_pull_none>; // GPIO0_C5 作为普通 GPIO
};

// 设备节点引用
leds {
    compatible = "gpio-leds";
    pinctrl-names = "default";
    pinctrl-0 = <&gpio_led>;
    led1: led1 {
        gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>;
    };
};

配置 I2C 引脚

i2c1m0_pins: i2c1m0-pins {
    rockchip,pins =
        <0 RK_PB3 1 &pcfg_pull_none>,  // SCL
        <0 RK_PB4 1 &pcfg_pull_none>;  // SDA
};

&i2c1 {
    pinctrl-names = "default";
    pinctrl-0 = <&i2c1m0_pins>;
    status = "okay";
};

配置中断引脚

gpio_key: gpio-key {
    rockchip,pins = <0 RK_PA0 0 &pcfg_pull_up>; // GPIO0_A0 上拉输入
};

&gpio_keys {
    pinctrl-names = "default";
    pinctrl-0 = <&gpio_key>;
    button1: button1 {
        gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_LOW>;
        interrupts-extended = <&gpio0 RK_PA0 IRQ_TYPE_EDGE_FALLING>;
    };
};

调试

cat /sys/kernel/debug/pinctrl/pinctrl-devices  # 列出所有 Pin Controller
cat /sys/kernel/debug/pinctrl/pinctrl-maps    # 查看所有引脚映射

3.4 I2C子系统

3.4.1 I2C 子系统架构

  • i2c_adapter : 表示 I2C 总线控制器
  • i2c_algorithm : 表示 I2C 总线传输算法
  • i2c_client : 表示I2C设备
  • i2c_driver : 实现I2C设备驱动

3.4.2 I2C 驱动实现

// I2C设备驱动示例
static const struct i2c_device_id my_i2c_id[] = {
    { "my-i2c-device", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id);

static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-i2c-device" },
    { }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct i2c_driver my_i2c_driver = {
    .probe = my_i2c_probe,
    .remove = my_i2c_remove,
    .id_table = my_i2c_id,
    .driver = {
        .name = "my-i2c-device",
        .of_match_table = my_of_match,
    },
};

static int my_i2c_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    // 检查适配器能力
    if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
        return -ENODEV;

    // 分配设备数据
    struct my_data *data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    i2c_set_clientdata(client, data);
    data->client = client;

    // 初始化设备
    return 0;
}

// 注册I2C驱动
module_i2c_driver(my_i2c_driver);

3.4.3 常见问题汇总

1、I2C 子系统的主要组件有哪些
  • i2c_adapter : 表示 I2C 总线控制器
  • i2c_algorithm : 表示 I2C 总线传输算法
  • i2c_client : 表示I2C设备
  • i2c_driver : 实现I2C设备驱动
2、I2C 设备驱动如何与设备匹配

通过 i2c_driver_id表匹配设备名称通过 of_match_table 匹配设备树种的 compatible 属性匹配成功后调用 probe 函数

3、如何在 I2C 驱动中进行数据传输
  1. 获取 I2C 设备句柄struct i2c_client *)。

  2. 构造消息struct i2c_msg),指定读写操作、从机地址、数据缓冲区等。

  3. 调用传输函数i2c_transfer 或 i2c_master_send/recv)。

struct i2c_msg {
    __u16 addr;     // 从机地址(7位或10位)
    __u16 flags;    // 标志位(如 I2C_M_RD 表示读操作)
    __u16 len;      // 数据长度
    __u8 *buf;      // 数据缓冲区
};
#include <linux/i2c.h>
#include <linux/module.h>

static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    u8 buf[2];
    int ret;

    // 示例:读取设备ID
    ret = i2c_read_reg(client, 0x00, buf, 2);
    if (ret < 0) {
        dev_err(&client->dev, "Failed to read ID\n");
        return ret;
    }
    dev_info(&client->dev, "Device ID: 0x%02X%02X\n", buf[0], buf[1]);

    return 0;
}

static const struct i2c_device_id my_i2c_id[] = {
    { "my_i2c_device", 0 },
    {}
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id);

static struct i2c_driver my_i2c_driver = {
    .driver = {
        .name = "my_i2c_driver",
    },
    .probe = my_i2c_probe,
    .id_table = my_i2c_id,
};
module_i2c_driver(my_i2c_driver);

MODULE_LICENSE("GPL");

调试技巧

1、查看 I2C 设备是否注册成功

ls /sys/bus/i2c/devices/  # 列出所有 I2C 设备

2、手动读写 I2C 设备

# 安装 i2c-tools
sudo apt install i2c-tools

# 扫描 I2C 总线
i2cdetect -y 1           # 查看总线1上的设备

# 读取寄存器
i2cget -y 1 0x50 0x00    # 从地址0x50读寄存器0x00

常见问题

  1. 传输失败(返回 -EIO)

    • 检查从机地址是否正确。

    • 确认设备树中 I2C 控制器已启用(status = "okay")。

  2. 时钟速率不匹配

    • 在设备树中调整 clock-frequency(如 400000 表示 400kHz)。

  3. 多消息传输顺序错误

    • 确保 i2c_msg 数组的顺序符合设备协议要求。

3.5 SPI 子系统

3.5.1 SPI 子系统架构

  • spi_master : 表示 SPI 控制器
  • spi_device : 表示 SPI 设备
  • spi_driver : 表示 SPI 设备驱动
  • spi_message : 表示 SPI 传输消息
  • spi_transfer : 表示单次 SPI 传输

3.5.2 SPI 驱动实现

// SPI设备驱动示例
static const struct of_device_id my_spi_of_match[] = {
    { .compatible = "vendor,my-spi-device" },
    { }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);

static const struct spi_device_id my_spi_id[] = {
    { "my-spi-device", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, my_spi_id);

static struct spi_driver my_spi_driver = {
    .probe = my_spi_probe,
    .remove = my_spi_remove,
    .id_table = my_spi_id,
    .driver = {
        .name = "my-spi-device",
        .of_match_table = my_spi_of_match,
    },
};

static int my_spi_probe(struct spi_device *spi)
{
    struct my_data *data;

    // 检查SPI设备配置
    if (spi->max_speed_hz > MAX_SPI_SPEED)
        spi->max_speed_hz = MAX_SPI_SPEED;

    if (spi->mode != SPI_MODE_0)
        return -EINVAL;

    // 分配设备数据
    data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    spi_set_drvdata(spi, data);
    data->spi = spi;

    // 初始化设备
    return 0;
}

// 注册SPI驱动
module_spi_driver(my_spi_driver);

3.6 设备树与驱动匹配

3.6.1 设备树基础

/* 设备树节点示例 */
soc {
    i2c@12340000 {
        compatible = "vendor,my-i2c-controller";
        reg = <0x12340000 0x1000>;
        interrupts = <0 29 4>;
        #address-cells = <1>;
        #size-cells = <0>;
        
        sensor@48 {
            compatible = "vendor,my-sensor";
            reg = <0x48>;
            interrupt-parent = <&gpio1>;
            interrupts = <20 IRQ_TYPE_EDGE_FALLING>;
        };
    };
    
    spi@12350000 {
        compatible = "vendor,my-spi-controller";
        reg = <0x12350000 0x1000>;
        interrupts = <0 30 4>;
        #address-cells = <1>;
        #size-cells = <0>;
        
        flash@0 {
            compatible = "vendor,my-flash";
            reg = <0>;
            spi-max-frequency = <50000000>;
        };
    };
};

3.6.2 驱动匹配机制

  • compatible : 与驱动的 of_match_table 匹配
  • reg 属性 : 设备地址或 ID 
  • status 属性 : 控制设备是否启用

3.6.3 常见面试问题

1、设备树种的 compatible 属性有什么作用

用于匹配设备与驱动格式 "厂商,设备名",可以有多个 compatible 值 ,按照顺序匹配

2、驱动如何获得设备树中的属性

of 系列函数,可以获取设备树中的相关节点信息

3、如何处理设备树中的 GPIO 描述

Uboot 相关

4.1 u-boot 的基本概念和作用

u-boot 是一段裸机引导程序,主要的功能包括

  1. 硬件初始化: 初始化CPU 内存控制器 时钟等硬件
  2. 加载操作系统:将存储设备中的操作系统加载到内存并执行
  3. 提供命令行界面:允许用户通过串口等接口与系统交互
  4. 环境变量管理: 存储和管理系统启动参数
  5. 外设驱动支持:提供基本的外设驱动,如网络,存储设备
  6. 系统恢复机制:提供系统恢复和固件更新功能

4.2 u-boot 启动流程

u-boot 启动分一下下几个阶段:

  1. 执行最小化硬件初始化,设置时钟和内存控制器加载 U-Boot 镜像到 RAM 
  2. 完成更全面的硬件初始化,设备内存映射初始化串口等通信设备初始化环境变量显示启动信息
  3. 命令处理阶段:检查自动启动倒计时,如果倒计时中断,进入命令行界面,执行预设的启动命令
  4. 操作系统加载阶段:从指定存储设备加载内核镜像准备内核启动参数跳转到内核入口下执行
// SPL启动流程简化代码示例
void board_init_f(ulong dummy)
{
    /* 时钟初始化 */
    clock_init();

    /* 串口初始化 */
    serial_init();

    /* DRAM初始化 */
    dram_init();

    /* 加载U-Boot主镜像 */
    spl_load_image();

    /* 跳转到U-Boot主镜像 */
    jump_to_image_no_args();
}

4.3 u-boot 环境变量

u-boot 中的环境变量是一组键值对,用于配置系统启动参数和行为。

  1. 环境变量存储:通常存储在 Flash/EEPROM 中的特定区域,使用 CRC 校验确保完整性
  2. 常用环境变量:bootargs : 传递给Linux内核的启动参数 bootcmd :自动启动时执行的命令时序,网络配置参数 bootdelay:自动启动倒计时时间
  3. 环境变量操作命令: printenv :显示环境变量 ; setenv : 设备环境变量; saveenv :保存环境变量
// 环境变量操作示例
// 设置bootargs
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait'

// 设置复合命令
setenv bootcmd 'mmc dev 0; fatload mmc 0:1 ${loadaddr} uImage; bootm ${loadaddr}'

// 保存环境变量
saveenv

// 执行环境变量中的命令
run bootcmd

4.4 如何自定义 U-Boot 环境变量默认值

U-Boot 环境变量配置方式

1、配置文件方式: 在 include/config/board.h 中定义 CONFIG_EXTRA_ENV_SETTINGS 或在 board 中定义 board_env_default 

2、defconfig 方式:在板级 defconfig 中添加 CONFIG_ENV_VARS_YBOOT_CONFIG=y 然后添加 CONFIG_ENV_VAR_[name]="value"

// 在头文件中定义默认环境变量
#define CONFIG_EXTRA_ENV_SETTINGS \
    "bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait\0" \
    "bootcmd=mmc dev 0; fatload mmc 0:1 ${loadaddr} uImage; bootm ${loadaddr}\0" \
    "ipaddr=192.168.1.100\0" \
    "serverip=192.168.1.1\0"

4.5 u-boot 命令系统

u-boot 命令系统是如何实现的,如何添加自定义命令

u-boot 命令系统是基于命令表实现的,每个命令都是一个结构体,包括命令名,函数指针和帮助信息

struct cmd_tbl {
    char *name;         /* 命令名称 */
    int maxargs;        /* 最大参数数量 */
    int repeatable;     /* 是否可重复执行 */
    int (*cmd)(struct cmd_tbl *, int, int, char * const []);  /* 命令处理函数 */
    char *usage;        /* 使用说明 */
    char *help;         /* 帮助信息 */
};

4.6 u-boot设备树支持

u-boot 如何使用设备树,设备树在 u-boot 中的作用是什么

u-boot 支持设备树,用于描述硬件配置信息

  1. u-boot 中设备树的使用:硬件平台描述驱动程序配置向 LINUX 内核传递硬件信息
  2. 设备树在 u-boot 中的使用 :编译时生成的 dtb 文件 U-Boot 加载设备树到内存可以在运行时修改设备树启动内核时传递设备树地址
  3. 设备树相关命令 : fdt addr 设置设备树工作地址 ;fdt get :获取设备树节点属性;fdt set 设置设备树节点属性 fdt print:打印设备树内容
// 设备树操作示例
// 加载设备树到内存
fatload mmc 0:1 ${fdt_addr} ${fdtfile}

// 修改设备树中的属性
fdt addr ${fdt_addr}
fdt resize
fdt set /chosen bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait"

    // 启动内核并传递设备树
    bootz ${kernel_addr} - ${fdt_addr}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377906.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库行业竞争加剧,MySQL 9.3.0 企业版开始支持个人下载

最新发现&#xff0c;Oracle 官方网站放开了 MySQL 9.3.0 企业版下载链接&#xff0c;个人用户也可以免费下载&#xff0c;不过只能用于学习、开发或者原型测试&#xff0c;不能用于生产环境。 通常我们都是下载 MySQL 社区版&#xff0c;不过 MySQL 企业版可以支持更多高级功能…

Tcping详细使用教程

Tcping详细使用教程 下载地址 https://download.elifulkerson.com/files/tcping/0.39/在windows环境下安装tcping 在以上的下载地中找到exe可执行文件&#xff0c;其中tcping.exe适用于32位Windows系统&#xff0c;tcping64.exe适用于64位Windows操作系统。 其实tcping是个…

【GAN网络入门系列】一,手写字MINST图片生成

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒…

ubuntu22鼠键失灵恢复记录笔记chatgpt解决

ChatGPT 说&#xff1a; 你提到“Ubuntu 22 鼠键失灵”&#xff0c;这个问题可能涉及以下几方面&#xff1a; &#x1f9ed; 先确认问题 是鼠标问题还是键盘问题&#xff0c;还是触控板&#xff1f; “鼠键”一般理解为“鼠标键”&#xff0c;请确认你是指鼠标左键/右键失灵&a…

智能呼入:云蝠大模型赋能政府热线

政府热线作为连接政府与民众的重要桥梁&#xff0c;提升智能化水平&#xff0c;成为政府热线亟待解决的问题。 大模型呼入 大模型呼入技术基于先进的自然语言处理和机器学习算法&#xff0c;能够实现对海量语音数据的处理和理解。通过构建大规模的语言模型&#xff0c;系统可…

STM32 ADC+DMA+TIM触发采样实战:避坑指南与源码解析

知识点1【TRGO的介绍】 1、TRGO的概述 TRGO&#xff1a;Trigger Output&#xff08;触发输出&#xff09;&#xff0c;是定时器的一种功能。 它可以作为外设的启动信号&#xff0c;比如ADC转换&#xff0c;DAC输出&#xff0c;DMA请求等。 对于ADC来说&#xff0c;可以通过…

(1-4)Java Object类、Final、注解、设计模式、抽象类、接口、内部类

目录 1. Object类 1.1 equals 1.2 toString&#xff08;&#xff09; 2.final关键字 3.注解 4. 设计模式 4.1 单例模式 4.1.1 饿汉式 4.1.3 饿汉式 VS 懒汉式 5. 抽象类&抽象方法 6. 接口 7.内部类 7.1 成员内部类 7.2 静态内部类 7.3 方法内部类 7.4 匿名内…

在服务器上安装AlphaFold2遇到的问题(3)_cat: /usr/include/cudnn_version.h: 没有那个文件或目录

[rootlocalhost ~]# cat /usr/include/cudnn_version.h cat: /usr/include/cudnn_version.h: 没有那个文件或目录这个错误表明系统找不到 cudnn_version.h 头文件&#xff0c;说明 cuDNN 的开发文件&#xff08;头文件&#xff09;没有正确安装。以下是完整的解决方案&#xff…

实验-实现向量点积-RISC-V(计算机组成原理)

目录 一、实验内容 二、实验步骤 三、源代码 四、实现效果 五、实验环境 六、实验小结与思考 一、实验内容 首先&#xff0c;我们用一个简单的“向量点积”运算作为热身。你将拿到一个不完整的汇编代码“task2-向量点积”&#xff0c;我们的目标是按照C语言描述的功能&a…

描述性统计工具 - AxureMost 落葵网

描述性统计工具是用于汇总和分析数据&#xff0c;以更好地了解数据特征的工具1。以下是一些常见的描述性统计工具简介&#xff1a; 描述性统计工具 Excel 基本统计函数&#xff1a;提供了丰富的函数用于计算描述性统计量。例如&#xff0c;AVERAGE 函数用于计算平均值&#xf…

麒麟桌面系统文件保险箱快捷访问指南:让重要文件夹一键直达桌面!

往期文章链接&#xff1a;统信操作系统自定义快捷键配置音量调节功能指南 Hello&#xff0c;大家好啊&#xff0c;今天给大家带来一篇麒麟桌面操作系统上配置文件保险箱内文件夹桌面快捷方式的文章&#xff0c;欢迎大家分享点赞&#xff0c;点个在看和关注吧&#xff01;在日常…

从硬件角度理解“Linux下一切皆文件“,详解用户级缓冲区

目录 前言 一、从硬件角度理解"Linux下一切皆文件" 从理解硬件是种“文件”到其他系统资源的抽象 二、缓冲区 1.缓冲区介绍 2.缓冲区的刷新策略 3.用户级缓冲区 这个用户级缓冲区在哪呢&#xff1f; 解释关于fork再加重定向“>”后数据会打印两份的原因 4.内核缓冲…

游戏站的几种形式

游戏站点的主要形式&#xff1a;单品游戏站、游戏盒子站与单类型游戏盒子站 随着互联网的普及和游戏产业的快速发展&#xff0c;游戏站点作为玩家获取游戏资源和信息的重要平台&#xff0c;呈现出多种形式。本文将分析三种常见的游戏站点形式&#xff1a;单品游戏站、游戏盒子站…

打造智能化军工软件工厂,破解版本管理难题

在数字化浪潮席卷全球的当下&#xff0c;军工行业正经历着前所未有的软件工业化转型。作为这一进程的核心支撑&#xff0c;软件工厂模式正在重塑军工领域的研发体系。然而&#xff0c;传统版本管理方式已难以适应现代军工软件研发的复杂需求&#xff0c;成为制约行业发展的关键…

SpringbBoot nginx代理获取用户真实IP

为了演示多级代理场景&#xff0c;我们分配了以下服务器资源&#xff1a; 10.1.9.98&#xff1a;充当客户端10.0.3.137&#xff1a;一级代理10.0.4.105&#xff1a;二级代理10.0.4.129&#xff1a;三级代理10.0.4.120&#xff1a;服务器端 各级代理配置 以下是各级代理的基本配…

allure报告自定义logo和名称

根据pytest框架&#xff0c;做自动化测试的时候&#xff0c;选择的是allure测试报告&#xff0c;这个报告是目前所有报告中功能最强大最好用的测试报告之一 我们在使用这个测试报告的时候&#xff0c;怎么样去把allure的logo和名称替换成自己公司或者自己的logo呢&#xff1f;…

【AI论文】对抗性后期训练快速文本到音频生成

摘要&#xff1a;文本到音频系统虽然性能不断提高&#xff0c;但在推理时速度很慢&#xff0c;因此对于许多创意应用来说&#xff0c;它们的延迟是不切实际的。 我们提出了对抗相对对比&#xff08;ARC&#xff09;后训练&#xff0c;这是第一个不基于蒸馏的扩散/流模型的对抗加…

测试工程师如何学会Kubernetes(k8s)容器知识

Kubernetes(K8s)作为云原生时代的关键技术之一&#xff0c;对于运维工程师、开发工程师以及测试工程师来说&#xff0c;都是一门需要掌握的重要技术。作为一名软件测试工程师&#xff0c;学习Kubernetes是一个有助于提升自动化测试、容器化测试以及云原生应用测试能力的重要过程…

遥感图像露天矿区检测数据集VOC+YOLO格式1542张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1542 标注数量(xml文件个数)&#xff1a;1542 标注数量(txt文件个数)&#xff1a;1542 …

每日Prompt:迷你 3D 建筑

提示词 3D Q版迷你风格&#xff0c;一个充满奇趣的迷你星巴克咖啡馆&#xff0c;外观就像一个巨大的外带咖啡杯&#xff0c;还有盖子和吸管。建筑共两层&#xff0c;大大的玻璃窗清晰地展示出内部温馨而精致的设计&#xff1a;木质的家具、温暖的灯光以及忙碌的咖啡师们。街道…