19.1 设备树

设备树(Device Tree)是一种描述硬件设备的数据结构,用于在Linux内核中分离硬件描述和驱动代码,使得内核能够适应多种硬件平台。

设备树源文件示例(.dts)
/dts-v1/;
// 节点:根节点,包含整个设备树的信息
/ {
    // 兼容属性,用于内核匹配设备驱动
    compatible = "acme,custom-board";
    model = "Custom Board";

    // 节点:一个简单的GPIO设备节点
    my_gpio_device: my-gpio-device@1234 {
        // 设备的兼容属性,格式为厂商,设备名
        compatible = "acme,my-gpio-device";
        // 设备地址,这里假设为0x1234
        reg = <0x1234 0x100>;
        // GPIO控制器的编号,假设为0
        gpio-controller;
        #gpio-cells = <2>;
    };
};
内核驱动代码示例(匹配上述设备树节点)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>

// 驱动匹配表,用于匹配设备树中的兼容属性
static const struct of_device_id my_of_match[] = {
    {.compatible = "acme,my-gpio-device", },
    {},
};
MODULE_DEVICE_TABLE(of, my_of_match);

// 平台驱动的probe函数,当驱动与设备匹配时调用
static int my_gpio_probe(struct platform_device *pdev) {
    // pdev: 指向平台设备结构体的指针
    struct device_node *np = pdev->dev.of_node;
    int gpio;

    if (!np) {
        dev_err(&pdev->dev, "No device node found\n");
        return -ENODEV;
    }

    // 从设备树中获取GPIO编号
    gpio = of_get_named_gpio(np, "gpio", 0);
    if (gpio < 0) {
        dev_err(&pdev->dev, "Failed to get GPIO\n");
        return gpio;
    }

    // 这里可以进行GPIO相关的初始化操作,例如设置GPIO方向
    dev_info(&pdev->dev, "Found GPIO %d\n", gpio);

    return 0;
}

// 平台驱动的remove函数,当设备移除时调用
static int my_gpio_remove(struct platform_device *pdev) {
    // pdev: 指向平台设备结构体的指针
    // 这里可以进行清理操作,例如释放GPIO资源
    return 0;
}

// 定义平台驱动结构体
static struct platform_driver my_gpio_driver = {
  .probe = my_gpio_probe,
  .remove = my_gpio_remove,
  .driver = {
      .name = "my-gpio-driver",
      .of_match_table = my_of_match,
    },
};

// 模块初始化函数,用于注册平台驱动
static int __init my_gpio_driver_init(void) {
    return platform_driver_register(&my_gpio_driver);
}

// 模块退出函数,用于注销平台驱动
static void __exit my_gpio_driver_exit(void) {
    platform_driver_unregister(&my_gpio_driver);
}

module_init(my_gpio_driver_init);
module_exit(my_gpio_driver_exit);
MODULE_LICENSE("GPL");

19.2 Linux驱动中的电源管理

Linux驱动中的电源管理允许设备在不同的电源状态之间切换,以节省能源。常见的电源状态包括运行、睡眠、暂停等。

使能设备电源管理支持的驱动代码示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/pm.h>

// 平台驱动的probe函数
static int my_pm_probe(struct platform_device *pdev) {
    // pdev: 指向平台设备结构体的指针
    // 这里进行设备的初始化操作
    return 0;
}

// 平台驱动的remove函数
static int my_pm_remove(struct platform_device *pdev) {
    // pdev: 指向平台设备结构体的指针
    // 这里进行设备移除时的清理操作
    return 0;
}

// 设备进入睡眠状态的回调函数
static int my_pm_suspend(struct device *dev, pm_message_t state) {
    // dev: 指向设备结构体的指针
    // state: 表示设备要进入的睡眠状态
    dev_info(dev, "Going to sleep\n");
    // 这里进行设备进入睡眠状态的操作,例如关闭时钟
    return 0;
}

// 设备从睡眠状态唤醒的回调函数
static int my_pm_resume(struct device *dev) {
    // dev: 指向设备结构体的指针
    dev_info(dev, "Waking up\n");
    // 这里进行设备从睡眠状态唤醒的操作,例如打开时钟
    return 0;
}

// 定义电源管理操作结构体
static struct dev_pm_ops my_pm_ops = {
  .suspend = my_pm_suspend,
  .resume = my_pm_resume,
};

// 定义平台驱动结构体
static struct platform_driver my_pm_driver = {
  .probe = my_pm_probe,
  .remove = my_pm_remove,
  .driver = {
      .name = "my-pm-driver",
      .pm = &my_pm_ops,
    },
};

// 模块初始化函数,用于注册平台驱动
static int __init my_pm_driver_init(void) {
    return platform_driver_register(&my_pm_driver);
}

// 模块退出函数,用于注销平台驱动
static void __exit my_pm_driver_exit(void) {
    platform_driver_unregister(&my_pm_driver);
}

module_init(my_pm_driver_init);
module_exit(my_pm_driver_exit);
MODULE_LICENSE("GPL");

19.3 Linux驱动中的安全问题

在Linux驱动开发中,安全问题至关重要,包括防止缓冲区溢出、权限控制、防止恶意访问等。

防止缓冲区溢出的代码示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

// 定义设备号
dev_t dev_num;
// 定义字符设备结构体
struct cdev my_cdev;
// 定义设备文件操作结构体
static struct file_operations my_fops = {
  .owner = THIS_MODULE,
  .write = my_write,
};

// 定义一个固定大小的缓冲区
#define BUFFER_SIZE 1024
char buffer[BUFFER_SIZE];

// 字符设备的写函数,防止缓冲区溢出
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *off) {
    // filp: 文件结构体指针,代表当前打开的设备文件
    // buf: 用户空间缓冲区,包含要写入的数据
    // count: 用户期望写入的字节数
    // off: 文件偏移量

    // 计算实际能写入的字节数,防止超出缓冲区大小
    size_t write_count = min(count, BUFFER_SIZE - *off);
    if (copy_from_user(buffer + *off, buf, write_count)) {
        return -EFAULT;
    }
    *off += write_count;
    return write_count;
}

// 模块初始化函数
static int __init secure_char_dev_init(void) {
    int ret;
    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "secure_char_dev");
    if (ret) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    // 初始化字符设备
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;

    // 添加字符设备到系统
    ret = cdev_add(&my_cdev, dev_num, 1);
    if (ret) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to add cdev\n");
        return ret;
    }

    printk(KERN_INFO "Secure character device registered successfully\n");
    return 0;
}

// 模块退出函数
static void __exit secure_char_dev_exit(void) {
    // 从系统移除字符设备
    cdev_del(&my_cdev);
    // 释放设备号
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Secure character device unregistered successfully\n");
}

module_init(secure_char_dev_init);
module_exit(secure_char_dev_exit);
MODULE_LICENSE("GPL");

以上代码示例展示了设备树的使用、电源管理的实现以及安全编程中的缓冲区溢出防范。在实际的驱动开发中,需要根据具体的设备和需求进行更深入的优化和调整。

更多推荐