概述
本文介绍了platform框架下的设备驱动开辟流程和方法,主要包罗设备树、驱动程序和应用程序的开辟。以随机数驱动为例,实现了应用程序调用库函数,通过体系调用陷入内核,最后实行硬件驱动,获取真随机数的过程。
添加设备树节点
在soc节点下添加名为trng子节点,内容如下:
- trng: trng@0x53030000 {
- compatible = "acme,trng";
- reg = <0x00 0x53030000 0x00 0x1000>;
- interrupts = <0x34 IRQ_TYPE_LEVEL_HIGH>;
- interrupt-parent = <&plic>;
- };
复制代码 编译设备树dts,生成相应的dtb文件:
使用新的dtb启动Linux内核。Linux启动成功之后查看是否有trng这个节点:
- ls /proc/device-tree/soc
- # trng@0x53030000
复制代码 进入trng目录,查看属性相关的文件:
- /proc/device-tree/soc/trng@0x53030000# ls
- compatible interrupts phandle
- interrupt-parent name reg
复制代码 编写设备驱动
Makefile
新建trng/driver目录,并创建Makefile,内如如下:
- # 内核架构
- ARCH := riscv
- # 交叉工具链
- CROSS_COMPILE := /path/to/riscv32-linux-
- # 内核目录
- KERNELDIR := /path/to/linux/linux-6.1
- # 当前目录
- PWD := $(shell pwd)
- # 目标文件
- obj-m := trng.o
- # 目标
- build: kernel_modules
- # 编译模块
- kernel_modules:
- $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
- # 清理
- clean:
- $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) clean
复制代码 驱动
在trng/driver下新建trng.c文件,内容如下:
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/interrupt.h>
- #include <linux/fs.h>
- #include <linux/of.h>
- #include <linux/cdev.h>
- #include <linux/io.h>
- #include <linux/iopoll.h>
- #include <linux/platform_device.h>
- /* TRNG寄存器 */
- #define TRNG_CTRL (0x0)
- #define TRNG_CTRL_CMD_MASK (0x07)
- #define TRNG_CTRL_CMD_RNG (0x01)
- #define TRNG_CTRL_CMD_SEED (0x02)
- #define TRNG_STAT (0x4)
- #define TRNG_STAT_SEEDED BIT(9)
- #define TRNG_MODE (0x8)
- #define TRNG_MODE_R256 BIT(3)
- #define TRNG_ISTAT (0x14)
- #define TRNG_ISTAT_RAND_RDY BIT(0)
- #define TRNG_ISTAT_SEED_DONE BIT(1)
- #define TRNG_RAND0 (0x20)
- #define TRNG_TIMEOUT (50000)
- #define DRIVER_NAME "trng"
- struct trng_dev {
- dev_t devid; /* 设备号 */
- struct cdev cdev; /* cdev */
- struct class *class; /* 类 */
- struct device *dev; /* 设备 */
- int major; /* 主设备号 */
- int minor; /* 次设备号 */
- int irq; /* 中断号 */
- void __iomem *base; /* 基地址 */
- };
- static int trng_init(void *base)
- {
- int ret;
- unsigned int value;
- /* 模式 */
- value = readl(base + TRNG_MODE);
- value |= TRNG_MODE_R256;
- writel(value, base + TRNG_MODE);
- /* 播种 */
- value = readl(base + TRNG_CTRL);
- value &= ~TRNG_CTRL_CMD_MASK;
- value |= TRNG_CTRL_CMD_SEED;
- writel(value, base + TRNG_CTRL);
- /* 等待播种完成 */
- ret = readl_relaxed_poll_timeout_atomic(base + TRNG_ISTAT,
- value, (value & TRNG_ISTAT_SEED_DONE),
- 10, TRNG_TIMEOUT);
- if (ret == 0) {
- value |= TRNG_ISTAT_SEED_DONE;
- writel(value, base + TRNG_ISTAT);
- }
- return ret;
- }
- static int trng_generate_random(void *base, unsigned char *buf)
- {
- int ret;
- unsigned int value;
-
- /* 启动生成随机数 */
- value = readl(base + TRNG_CTRL);
- value &= ~TRNG_CTRL_CMD_MASK;
- value |= TRNG_CTRL_CMD_RNG;
- writel(value, base + TRNG_CTRL);
- /* 等待随机数准备好 */
- ret = readl_relaxed_poll_timeout_atomic(base + TRNG_ISTAT,
- value, (value & TRNG_ISTAT_RAND_RDY),
- 10, TRNG_TIMEOUT);
- if (ret) {
- return ret;
- }
- /* 清除准备好标志 */
- value = readl(base + TRNG_ISTAT);
- value &= ~TRNG_ISTAT_RAND_RDY;
- writel(value, base + TRNG_ISTAT);
-
- /* 读取随机数 */
- for (int i = 0; i < 8; i++) {
- *(unsigned int*)buf = readl(base + TRNG_RAND0 + i*4);
- buf += 4;
- }
- return 0;
- }
- static irqreturn_t trng_irq_handler(int irq, void *dev_id)
- {
- struct trng_dev *trng;
- trng = (struct trng_dev*)dev_id;
-
- dev_dbg(trng->dev, "TRNG interrupt received\n");
- return IRQ_HANDLED;
- }
- static int trng_open(struct inode *inode, struct file *filp)
- {
- int ret;
- struct trng_dev *trng;
-
- trng = container_of(inode->i_cdev, struct trng_dev, cdev);
- filp->private_data = trng;
- dev_dbg(trng->dev, "Open trng\n");
- ret = trng_init(trng->base);
- if (ret) {
- dev_err(trng->dev, "Failed to init trng, ret=%d\n", ret);
- return ret;
- }
- return 0;
- }
- static int trng_release(struct inode *inode, struct file *filp)
- {
- struct trng_dev *trng;
- trng = filp->private_data;
-
- dev_dbg(trng->dev, "Release trng\n");
- return 0;
- }
- static ssize_t trng_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset)
- {
- int ret;
- unsigned char random[32];
- size_t copyed_len, len_to_copy;
- struct trng_dev *trng;
- trng = filp->private_data;
- dev_info(trng->dev, "Read trng\n");
- copyed_len = 0;
- while (len) {
- ret = trng_generate_random(trng->base, random);
- if (ret) {
- dev_err(trng->dev, "Failed to generate random, ret=%d\n", ret);
- return ret;
- }
- // print_hex_dump(KERN_INFO, "random: ", DUMP_PREFIX_NONE, 16, 1, random, sizeof(random), false);
- len_to_copy = (len < sizeof(random)) ? len : sizeof(random);
- ret = copy_to_user(buffer, random, len_to_copy);
- if (ret) {
- dev_err(trng->dev, "Failed to copy to user\n");
- return -EFAULT;
- }
- copyed_len += len_to_copy;
- buffer += len_to_copy;
- len -= len_to_copy;
- }
- return copyed_len;
- }
- static ssize_t trng_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset)
- {
- struct trng_dev *trng;
- trng = filp->private_data;
-
- dev_dbg(trng->dev, "Write trng\n");
- return len;
- }
- static long trng_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
- {
- struct trng_dev *trng;
- trng = filp->private_data;
- dev_dbg(trng->dev, "Ioctl trng\n");
- switch (cmd) {
- default:
- dev_err(trng->dev, "Unknown ioctl = 0x%x\n", cmd);
- break;
- }
- return -ENOTTY;
- }
- static int trng_mmap(struct file *filp, struct vm_area_struct *vma)
- {
- struct trng_dev *trng;
- trng = filp->private_data;
- dev_dbg(trng->dev, "Mmap trng\n");
- return 0;
- }
- static const struct file_operations trng_fops = {
- .owner = THIS_MODULE,
- .open = trng_open,
- .release = trng_release,
- .read = trng_read,
- .write = trng_write,
- .unlocked_ioctl = trng_ioctl,
- .mmap = trng_mmap,
- };
- static int trng_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct trng_dev *trng;
- int ret;
- /* 分配内存 */
- trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL);
- if (!trng) {
- dev_err(dev, "Failed to allocate memory\n");
- return -ENOMEM;
- }
- /* 将设备的资源映射到内存空间 */
- trng->base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(trng->base)) {
- dev_err(dev, "Failed to map device registers\n");
- return PTR_ERR(trng->base);
- }
- /* 获取设备的中断号 */
- trng->irq = platform_get_irq(pdev, 0);
- if (trng->irq <= 0) {
- dev_err(dev, "Failed to get irq %d\n", trng->irq);
- return trng->irq;
- }
- /* 请求中断 */
- ret = devm_request_irq(dev, trng->irq, trng_irq_handler, 0,
- DRIVER_NAME, trng);
- if (ret) {
- dev_err(dev, "Failed to request IRQ\n");
- return ret;
- }
- /* 申请设备号 */
- ret = alloc_chrdev_region(&trng->devid, 0, 1, DRIVER_NAME);
- if (ret < 0) {
- dev_err(dev, "Failed to allocate device number\n");
- return ret;
- }
- trng->major = MAJOR(trng->devid);
- trng->minor = MINOR(trng->devid);
- /* 初始化cdev */
- trng->cdev.owner = THIS_MODULE;
- cdev_init(&trng->cdev, &trng_fops);
- /* 添加一个cdev */
- ret = cdev_add(&trng->cdev, trng->devid, 1);
- if (ret < 0) {
- dev_err(dev, "Failed to add cdev\n");
- unregister_chrdev_region(trng->devid, 1);
- return ret;
- }
- /* 创建类 */
- trng->class = class_create(THIS_MODULE, DRIVER_NAME);
- if (IS_ERR(trng->class)) {
- cdev_del(&trng->cdev);
- unregister_chrdev_region(trng->devid, 1);
- dev_err(dev, "Failed to create class\n");
- return PTR_ERR(trng->class);
- }
- /* 创建设备 */
- trng->dev = device_create(trng->class, NULL, trng->devid, NULL, DRIVER_NAME);
- if (IS_ERR(trng->dev)) {
- cdev_del(&trng->cdev);
- unregister_chrdev_region(trng->devid, 1);
- class_destroy(trng->class);
- dev_err(dev, "Failed to create device\n");
- return PTR_ERR(trng->dev);
- }
- /* 保存设备私有结构体 */
- platform_set_drvdata(pdev, trng);
- dev_info(dev, "TRNG platform driver probed\n");
- return 0;
- }
- static int trng_remove(struct platform_device *pdev)
- {
- struct trng_dev *trng;
-
- /* 获取设备私有结构体 */
- trng = platform_get_drvdata(pdev);
- /* 删除cdev */
- cdev_del(&trng->cdev);
- /* 释放设备号 */
- unregister_chrdev_region(trng->devid, 1);
- /* 删除设备 */
- device_destroy(trng->class, trng->devid);
- /* 删除类 */
- class_destroy(trng->class);
- /* 释放设备内存 */
- // devm_kfree(&pdev->dev, trng); // devm_kzalloc为设备分配的内存,在设备移除时会自动释放
- dev_info(&pdev->dev, "TRNG platform driver removed\n");
- return 0;
- }
- static const struct of_device_id trng_of_match[] = {
- { .compatible = "acme,trng" },
- { /* sentinel */ }
- };
- MODULE_DEVICE_TABLE(of, trng_of_match);
- static struct platform_driver trng_driver = {
- .driver = {
- .name = DRIVER_NAME,
- .of_match_table = trng_of_match,
- },
- .probe = trng_probe,
- .remove = trng_remove,
- };
- module_platform_driver(trng_driver);
- MODULE_AUTHOR("Author");
- MODULE_DESCRIPTION("Trng driver");
- MODULE_LICENSE("GPL");
复制代码 使用platform平台驱动设备模型编写trng的驱动程序。
- 当设备树中的节点与驱动匹配成功会实行trng_probe函数,完成驱动的加载。
- 当应用需要获取随机数时,读取这个trng设备,陷入内核调用函数trng_read,进而调用函数trng_generate_random完成从硬件获取随机数。
- 如果需要释放设备,会调用trng_remove函数卸载设备驱动。
实行make编译驱动程序,编译成功生成trng.ko。
Linux启动成功之后,可以挂载nfs,将trng.ko拷贝到trng目录:
- mkdir trng
- mount -t nfs -o nolock xx.xx.xx.xx:/nfs/trng /root/trng
复制代码 实行如下命令,加载设备驱动:
- insmod trng.ko
- # [ 177.821055] trng 53030000.trng: TRNG platform driver probed
复制代码 如果设备驱动加载成功,可以在/dev下找到设备:
另外可以查看trng的设备号:
- cat /proc/devices
- # 249 trng
复制代码 如果需要卸载设备驱动,实行:
- rmmod trng.ko
- # [ 2947.495906] trng 53030000.trng: TRNG platform driver removed
复制代码 应用App
Makefile
新建trng/app目录,并创建Makefile,内如如下:
- # 交叉工具链
- CROSS_COMPILE ?= /opt/andestech/nds32le-linux-glibc-v5d/bin/riscv32-linux-
-
- # 指定C编译器
- CC := $(CROSS_COMPILE)gcc
-
- # 目标文件名
- TARGET := trng
-
- # 源文件名
- SRC := trng.c
-
- # 默认目标
- all: $(TARGET)
-
- # 编译并链接
- $(TARGET): $(SRC)
- $(CC) $(SRC) -o $(TARGET)
-
- # 清理
- clean:
- rm -f $(TARGET)
复制代码 应用
在trng/app下新建trng.c文件,内容如下:
- #include <stdio.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <fcntl.h>
- #define TRNG_DEVICE "/dev/trng"
- static void hexdump(const char *name, const unsigned char *buffer, unsigned int len)
- {
- printf("****************%s****************\n", name);
- for (unsigned int i = 0; i < len; i++) {
- printf("%02x ", buffer[i]);
- if ((i + 1) % 16 == 0) {
- printf("\n");
- }
- }
- if (len % 16 ) {
- printf("\n");
- }
- }
- int main(int argc, char *argv[])
- {
- uint8_t *buf = NULL;
- size_t num;
- int ret, fd;
- if (argc < 2) {
- printf("Usage: trng <num>\n");
- return -1;
- }
- num = atoi(argv[1]);
- buf = malloc(num);
- if (buf == NULL) {
- printf("Failed to malloc\n");
- return -1;
- }
- /* 打开设备 */
- fd = open(TRNG_DEVICE, O_RDONLY);
- if (fd < 0) {
- printf("Failed to open trng device\n");
- goto exit;
- }
-
- /* 读取随机数 */
- ret = read(fd, buf, num);
- if (ret < 0) {
- printf("Failed to read random, ret=%d\n", ret);
- goto exit;
- }
- hexdump("random", buf, num);
- exit:
- close(fd);
- free(buf);
- return ret;
- }
复制代码 实行make编译应用程序,编译成功生成trng。
同理,将trng应用拷贝到trng目录,并实行:
- ./trng 16
- # ****************random****************
- # 6c 95 ea 3c a0 1f e8 c2 03 db 66 f6 19 4b 07 e3
- # c0 96 a3 93 20 a9 68 c5 9f 1f a1 55 c0 9c 24 c9
- # 5f 06 47 45 be 2c 21 b5 11 23 23 e6 36 94 3f d6
- # 9a 30 68 91 da c4 6d ff af 46 26 c9 ab f8 79 7c
复制代码 如果随机数获取成功,说明驱动和应用程序运行正常。
编译进内核
在开辟前期阶段,一样平常将驱动编译成模块,方便调试。当驱动开辟完成后,可以将其编译进内核。
驱动
在linux-6.1/drivers下新建trng目录,并创建Makefile和Kconfig文件,内容分别如下:
- # SPDX-License-Identifier: GPL-2.0
- #
- # Makefile for the TRNG device drivers.
- #
- obj-$(CONFIG_TRNG) := trng.o
复制代码- # SPDX-License-Identifier: GPL-2.0-only
- #
- # TRNG device configuration
- #
- config TRNG
- tristate "TRNG support"
- help
- This driver provides support for TRNG in SoCs.
- To compile this driver as a module, choose M here: the module
- will be called acme-trng.
- If unsure, say Y.
复制代码 在linux/drivers/Makefile中添加:
- obj-$(CONFIG_TRNG) += trng/
复制代码 在linux/drivers/Kconfig中添加:
- source "drivers/trng/Kconfig"
复制代码 将trng.c驱动文件拷贝到trng目录,最终目录文件如下:
- $ tree linux-6.1/drivers/trng/
- ├── Kconfig
- ├── Makefile
- └── trng.c
复制代码 内核配置
配置内核,输入命令:
选择Device Drivers->TRNG support,选择将trng编译进内核,这里可以有三种选择:
- *:将该功能编译进内核
- 空:不编译该功能
- M:将该功能编译成内核中的模块
运行
编译Linux并启动,在启动日记中,如果打印如下,说明TRNG驱动运行正常:
- [ 4.974450] trng 53030000.trng: TRNG platform driver probed
复制代码 可以实行命令,获取随机数:
- cat /dev/trng | hexdump -n 32
- 0000000 df01 f5bc de33 2509 8d16 7b5f 8868 8bea
- 0000010 40f3 00f2 97a4 324d 03c2 10c8 b943 3d6d
- 0000020
复制代码 题目解决
- 加载KO报非常trng: loading out-of-tree module taints kernel.
原因在于没有将此驱动模块加入到Kconfig导致。
- 使用函数devm_kzalloc为设备分配的内存,在设备移除时会自动释放,可以不进行体现释放devm_kfree
- 宏container_of用于从布局体的某个成员的地址反推出整个布局体的地址,尤其留意第一个参数必须为成员的地址,如果布局体成员为指针变量,需要取该指针变量的地址。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |