ToB企服应用市场:ToB评测及商务社交产业平台

标题: Linux设备树的驱动开辟 [打印本页]

作者: 杀鸡焉用牛刀    时间: 2024-12-20 13:03
标题: Linux设备树的驱动开辟
概述

本文介绍了platform框架下的设备驱动开辟流程和方法,主要包罗设备树、驱动程序和应用程序的开辟。以随机数驱动为例,实现了应用程序调用库函数,通过体系调用陷入内核,最后实行硬件驱动,获取真随机数的过程。
添加设备树节点

soc节点下添加名为trng子节点,内容如下:
  1. trng: trng@0x53030000 {
  2.     compatible = "acme,trng";
  3.     reg = <0x00 0x53030000 0x00 0x1000>;
  4.     interrupts = <0x34 IRQ_TYPE_LEVEL_HIGH>;
  5.     interrupt-parent = <&plic>;
  6. };
复制代码
编译设备树dts,生成相应的dtb文件:
  1. make dtbs
复制代码
使用新的dtb启动Linux内核。Linux启动成功之后查看是否有trng这个节点:
  1. ls /proc/device-tree/soc
  2. # trng@0x53030000
复制代码
进入trng目录,查看属性相关的文件:
  1. /proc/device-tree/soc/trng@0x53030000# ls
  2. compatible        interrupts        phandle
  3. interrupt-parent  name              reg
复制代码
编写设备驱动

Makefile

新建trng/driver目录,并创建Makefile,内如如下:
  1. # 内核架构
  2. ARCH := riscv
  3. # 交叉工具链
  4. CROSS_COMPILE := /path/to/riscv32-linux-
  5. # 内核目录
  6. KERNELDIR := /path/to/linux/linux-6.1
  7. # 当前目录
  8. PWD := $(shell pwd)
  9. # 目标文件
  10. obj-m := trng.o
  11. # 目标
  12. build: kernel_modules
  13. # 编译模块
  14. kernel_modules:
  15.         $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
  16. # 清理
  17. clean:
  18.         $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) clean
复制代码
驱动

在trng/driver下新建trng.c文件,内容如下:
  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/interrupt.h>
  4. #include <linux/fs.h>
  5. #include <linux/of.h>
  6. #include <linux/cdev.h>
  7. #include <linux/io.h>
  8. #include <linux/iopoll.h>
  9. #include <linux/platform_device.h>
  10. /* TRNG寄存器 */
  11. #define TRNG_CTRL           (0x0)
  12.     #define TRNG_CTRL_CMD_MASK      (0x07)
  13.     #define TRNG_CTRL_CMD_RNG       (0x01)
  14.     #define TRNG_CTRL_CMD_SEED      (0x02)
  15. #define TRNG_STAT           (0x4)
  16.     #define TRNG_STAT_SEEDED        BIT(9)
  17. #define TRNG_MODE           (0x8)
  18.     #define TRNG_MODE_R256          BIT(3)
  19. #define TRNG_ISTAT          (0x14)
  20.     #define TRNG_ISTAT_RAND_RDY     BIT(0)
  21.     #define TRNG_ISTAT_SEED_DONE    BIT(1)
  22. #define TRNG_RAND0          (0x20)
  23. #define TRNG_TIMEOUT        (50000)
  24. #define DRIVER_NAME         "trng"
  25. struct trng_dev {
  26.     dev_t devid;            /* 设备号 */
  27.     struct cdev cdev;       /* cdev */
  28.     struct class *class;    /* 类 */
  29.     struct device *dev;     /* 设备 */
  30.     int major;              /* 主设备号 */
  31.     int minor;              /* 次设备号 */
  32.     int irq;                /* 中断号 */
  33.     void __iomem *base;     /* 基地址 */
  34. };
  35. static int trng_init(void *base)
  36. {
  37.     int ret;
  38.     unsigned int value;
  39.     /* 模式 */
  40.     value = readl(base + TRNG_MODE);
  41.     value |= TRNG_MODE_R256;
  42.     writel(value, base + TRNG_MODE);   
  43.     /* 播种 */
  44.     value = readl(base + TRNG_CTRL);
  45.     value &= ~TRNG_CTRL_CMD_MASK;
  46.     value |= TRNG_CTRL_CMD_SEED;
  47.     writel(value, base + TRNG_CTRL);
  48.     /* 等待播种完成 */
  49.     ret = readl_relaxed_poll_timeout_atomic(base + TRNG_ISTAT,
  50.                         value, (value & TRNG_ISTAT_SEED_DONE),
  51.                         10, TRNG_TIMEOUT);
  52.     if (ret == 0) {
  53.         value |= TRNG_ISTAT_SEED_DONE;
  54.         writel(value, base + TRNG_ISTAT);
  55.     }
  56.     return ret;
  57. }
  58. static int trng_generate_random(void *base, unsigned char *buf)
  59. {
  60.     int ret;
  61.     unsigned int value;
  62.    
  63.     /* 启动生成随机数 */
  64.     value = readl(base + TRNG_CTRL);
  65.     value &= ~TRNG_CTRL_CMD_MASK;
  66.     value |= TRNG_CTRL_CMD_RNG;
  67.     writel(value, base + TRNG_CTRL);
  68.     /* 等待随机数准备好 */
  69.     ret = readl_relaxed_poll_timeout_atomic(base + TRNG_ISTAT,
  70.                         value, (value & TRNG_ISTAT_RAND_RDY),
  71.                         10, TRNG_TIMEOUT);   
  72.     if (ret) {
  73.         return ret;
  74.     }
  75.     /* 清除准备好标志 */
  76.     value = readl(base + TRNG_ISTAT);
  77.     value &= ~TRNG_ISTAT_RAND_RDY;
  78.     writel(value, base + TRNG_ISTAT);
  79.    
  80.     /* 读取随机数 */
  81.     for (int i = 0; i < 8; i++) {
  82.         *(unsigned int*)buf = readl(base + TRNG_RAND0 + i*4);
  83.         buf += 4;
  84.     }
  85.     return 0;
  86. }
  87. static irqreturn_t trng_irq_handler(int irq, void *dev_id)
  88. {
  89.     struct trng_dev *trng;
  90.     trng = (struct trng_dev*)dev_id;
  91.    
  92.     dev_dbg(trng->dev, "TRNG interrupt received\n");
  93.     return IRQ_HANDLED;
  94. }
  95. static int trng_open(struct inode *inode, struct file *filp)
  96. {
  97.     int ret;
  98.     struct trng_dev *trng;
  99.    
  100.     trng = container_of(inode->i_cdev, struct trng_dev, cdev);
  101.     filp->private_data = trng;
  102.     dev_dbg(trng->dev, "Open trng\n");
  103.     ret = trng_init(trng->base);
  104.     if (ret) {
  105.         dev_err(trng->dev, "Failed to init trng, ret=%d\n", ret);
  106.         return ret;
  107.     }
  108.     return 0;
  109. }
  110. static int trng_release(struct inode *inode, struct file *filp)
  111. {
  112.     struct trng_dev *trng;
  113.     trng = filp->private_data;
  114.    
  115.     dev_dbg(trng->dev, "Release trng\n");
  116.     return 0;
  117. }
  118. static ssize_t trng_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset)
  119. {  
  120.     int ret;
  121.     unsigned char random[32];
  122.     size_t copyed_len, len_to_copy;
  123.     struct trng_dev *trng;
  124.     trng = filp->private_data;
  125.     dev_info(trng->dev, "Read trng\n");
  126.     copyed_len = 0;
  127.     while (len) {
  128.         ret = trng_generate_random(trng->base, random);
  129.         if (ret) {
  130.             dev_err(trng->dev, "Failed to generate random, ret=%d\n", ret);
  131.             return ret;
  132.         }
  133.         // print_hex_dump(KERN_INFO, "random: ", DUMP_PREFIX_NONE, 16, 1, random, sizeof(random), false);
  134.         len_to_copy = (len < sizeof(random)) ? len : sizeof(random);
  135.         ret = copy_to_user(buffer, random, len_to_copy);
  136.         if (ret) {
  137.             dev_err(trng->dev, "Failed to copy to user\n");
  138.             return -EFAULT;
  139.         }
  140.         copyed_len += len_to_copy;
  141.         buffer += len_to_copy;
  142.         len -= len_to_copy;
  143.     }
  144.     return copyed_len;  
  145. }  
  146. static ssize_t trng_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset)
  147. {
  148.     struct trng_dev *trng;
  149.     trng = filp->private_data;
  150.    
  151.     dev_dbg(trng->dev, "Write trng\n");
  152.     return len;
  153. }
  154. static long trng_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  155. {
  156.     struct trng_dev *trng;
  157.     trng = filp->private_data;
  158.     dev_dbg(trng->dev, "Ioctl trng\n");
  159.     switch (cmd) {
  160.     default:
  161.             dev_err(trng->dev, "Unknown ioctl = 0x%x\n", cmd);
  162.         break;
  163.     }
  164.         return -ENOTTY;
  165. }
  166. static int trng_mmap(struct file *filp, struct vm_area_struct *vma)
  167. {
  168.     struct trng_dev *trng;
  169.     trng = filp->private_data;
  170.     dev_dbg(trng->dev, "Mmap trng\n");
  171.     return 0;
  172. }
  173. static const struct file_operations trng_fops = {
  174.     .owner = THIS_MODULE,
  175.     .open = trng_open,
  176.     .release = trng_release,
  177.     .read = trng_read,
  178.     .write = trng_write,
  179.     .unlocked_ioctl = trng_ioctl,
  180.     .mmap = trng_mmap,
  181. };
  182. static int trng_probe(struct platform_device *pdev)
  183. {
  184.         struct device *dev = &pdev->dev;
  185.         struct trng_dev *trng;
  186.         int ret;
  187.     /* 分配内存 */
  188.     trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL);
  189.     if (!trng) {
  190.         dev_err(dev, "Failed to allocate memory\n");
  191.         return -ENOMEM;
  192.     }
  193.     /* 将设备的资源映射到内存空间 */
  194.         trng->base = devm_platform_ioremap_resource(pdev, 0);
  195.         if (IS_ERR(trng->base)) {
  196.         dev_err(dev, "Failed to map device registers\n");
  197.         return PTR_ERR(trng->base);
  198.     }
  199.     /* 获取设备的中断号 */
  200.     trng->irq = platform_get_irq(pdev, 0);
  201.     if (trng->irq <= 0) {
  202.                 dev_err(dev, "Failed to get irq %d\n", trng->irq);
  203.                 return trng->irq;        
  204.     }
  205.     /* 请求中断 */
  206.     ret = devm_request_irq(dev, trng->irq, trng_irq_handler, 0,
  207.                            DRIVER_NAME, trng);
  208.     if (ret) {
  209.         dev_err(dev, "Failed to request IRQ\n");
  210.         return ret;
  211.     }
  212.     /* 申请设备号 */
  213.     ret = alloc_chrdev_region(&trng->devid, 0, 1, DRIVER_NAME);  
  214.     if (ret < 0) {  
  215.         dev_err(dev, "Failed to allocate device number\n");  
  216.         return ret;  
  217.     }
  218.     trng->major = MAJOR(trng->devid);
  219.     trng->minor = MINOR(trng->devid);
  220.     /* 初始化cdev */
  221.     trng->cdev.owner = THIS_MODULE;
  222.     cdev_init(&trng->cdev, &trng_fops);
  223.     /* 添加一个cdev */
  224.     ret = cdev_add(&trng->cdev, trng->devid, 1);
  225.     if (ret < 0) {
  226.         dev_err(dev, "Failed to add cdev\n");
  227.         unregister_chrdev_region(trng->devid, 1);
  228.         return ret;
  229.     }
  230.     /* 创建类 */
  231.     trng->class = class_create(THIS_MODULE, DRIVER_NAME);
  232.     if (IS_ERR(trng->class)) {
  233.         cdev_del(&trng->cdev);
  234.         unregister_chrdev_region(trng->devid, 1);
  235.         dev_err(dev, "Failed to create class\n");
  236.         return PTR_ERR(trng->class);
  237.     }
  238.     /* 创建设备 */
  239.     trng->dev = device_create(trng->class, NULL, trng->devid, NULL, DRIVER_NAME);
  240.     if (IS_ERR(trng->dev)) {
  241.         cdev_del(&trng->cdev);
  242.         unregister_chrdev_region(trng->devid, 1);
  243.         class_destroy(trng->class);
  244.         dev_err(dev, "Failed to create device\n");
  245.         return PTR_ERR(trng->dev);
  246.     }
  247.     /* 保存设备私有结构体 */
  248.     platform_set_drvdata(pdev, trng);
  249.     dev_info(dev, "TRNG platform driver probed\n");
  250.     return 0;
  251. }
  252. static int trng_remove(struct platform_device *pdev)
  253. {
  254.     struct trng_dev *trng;
  255.    
  256.     /* 获取设备私有结构体 */
  257.     trng = platform_get_drvdata(pdev);
  258.     /* 删除cdev */
  259.     cdev_del(&trng->cdev);
  260.     /* 释放设备号 */
  261.     unregister_chrdev_region(trng->devid, 1);
  262.     /* 删除设备 */
  263.     device_destroy(trng->class, trng->devid);
  264.     /* 删除类 */
  265.     class_destroy(trng->class);
  266.     /* 释放设备内存 */
  267.     // devm_kfree(&pdev->dev, trng);    // devm_kzalloc为设备分配的内存,在设备移除时会自动释放
  268.     dev_info(&pdev->dev, "TRNG platform driver removed\n");
  269.     return 0;
  270. }
  271. static const struct of_device_id trng_of_match[] = {
  272.         { .compatible = "acme,trng" },
  273.         { /* sentinel */ }
  274. };
  275. MODULE_DEVICE_TABLE(of, trng_of_match);
  276. static struct platform_driver trng_driver = {
  277.         .driver = {
  278.                 .name = DRIVER_NAME,
  279.                 .of_match_table = trng_of_match,
  280.         },
  281.         .probe = trng_probe,
  282.         .remove = trng_remove,
  283. };
  284. module_platform_driver(trng_driver);
  285. MODULE_AUTHOR("Author");
  286. MODULE_DESCRIPTION("Trng driver");
  287. MODULE_LICENSE("GPL");
复制代码
使用platform平台驱动设备模型编写trng的驱动程序。

实行make编译驱动程序,编译成功生成trng.ko。
Linux启动成功之后,可以挂载nfs,将trng.ko拷贝到trng目录:
  1. mkdir trng
  2. mount -t nfs -o nolock xx.xx.xx.xx:/nfs/trng /root/trng
复制代码
实行如下命令,加载设备驱动:
  1. insmod trng.ko
  2. # [  177.821055] trng 53030000.trng: TRNG platform driver probed
复制代码
如果设备驱动加载成功,可以在/dev下找到设备:
  1. ls /dev/trng
复制代码
另外可以查看trng的设备号:
  1. cat /proc/devices
  2. # 249 trng
复制代码
如果需要卸载设备驱动,实行:
  1. rmmod trng.ko
  2. # [ 2947.495906] trng 53030000.trng: TRNG platform driver removed
复制代码
应用App

Makefile

新建trng/app目录,并创建Makefile,内如如下:
  1. # 交叉工具链
  2. CROSS_COMPILE ?= /opt/andestech/nds32le-linux-glibc-v5d/bin/riscv32-linux-
  3. # 指定C编译器
  4. CC := $(CROSS_COMPILE)gcc
  5. # 目标文件名
  6. TARGET := trng
  7. # 源文件名
  8. SRC := trng.c
  9. # 默认目标
  10. all: $(TARGET)
  11. # 编译并链接
  12. $(TARGET): $(SRC)
  13.         $(CC) $(SRC) -o $(TARGET)
  14. # 清理
  15. clean:
  16.         rm -f $(TARGET)
复制代码
应用

在trng/app下新建trng.c文件,内容如下:
  1. #include <stdio.h>
  2. #include <stdint.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #define TRNG_DEVICE                       "/dev/trng"
  7. static void hexdump(const char *name, const unsigned char *buffer, unsigned int len)
  8. {
  9.     printf("****************%s****************\n", name);
  10.     for (unsigned int i = 0; i < len; i++) {
  11.         printf("%02x ", buffer[i]);
  12.         if ((i + 1) % 16 == 0) {
  13.             printf("\n");
  14.         }
  15.     }
  16.     if (len % 16 ) {
  17.         printf("\n");
  18.     }
  19. }
  20. int main(int argc, char *argv[])
  21. {
  22.     uint8_t *buf = NULL;
  23.     size_t num;
  24.     int ret, fd;
  25.     if (argc < 2) {
  26.         printf("Usage: trng <num>\n");
  27.         return -1;
  28.     }
  29.     num = atoi(argv[1]);
  30.     buf = malloc(num);
  31.     if (buf == NULL) {
  32.         printf("Failed to malloc\n");
  33.         return -1;
  34.     }
  35.     /* 打开设备 */
  36.     fd = open(TRNG_DEVICE, O_RDONLY);
  37.     if (fd < 0) {
  38.         printf("Failed to open trng device\n");
  39.         goto exit;
  40.     }
  41.    
  42.     /* 读取随机数 */
  43.     ret = read(fd, buf, num);
  44.     if (ret < 0) {
  45.         printf("Failed to read random, ret=%d\n", ret);
  46.         goto exit;
  47.     }
  48.     hexdump("random", buf, num);
  49. exit:
  50.     close(fd);
  51.     free(buf);
  52.     return ret;
  53. }
复制代码
实行make编译应用程序,编译成功生成trng。
同理,将trng应用拷贝到trng目录,并实行:
  1. ./trng 16
  2. # ****************random****************
  3. # 6c 95 ea 3c a0 1f e8 c2 03 db 66 f6 19 4b 07 e3
  4. # c0 96 a3 93 20 a9 68 c5 9f 1f a1 55 c0 9c 24 c9
  5. # 5f 06 47 45 be 2c 21 b5 11 23 23 e6 36 94 3f d6
  6. # 9a 30 68 91 da c4 6d ff af 46 26 c9 ab f8 79 7c
复制代码
如果随机数获取成功,说明驱动和应用程序运行正常。
编译进内核

在开辟前期阶段,一样平常将驱动编译成模块,方便调试。当驱动开辟完成后,可以将其编译进内核。
驱动

在linux-6.1/drivers下新建trng目录,并创建Makefile和Kconfig文件,内容分别如下:
  1. # SPDX-License-Identifier: GPL-2.0
  2. #
  3. # Makefile for the TRNG device drivers.
  4. #
  5. obj-$(CONFIG_TRNG) := trng.o
复制代码
  1. # SPDX-License-Identifier: GPL-2.0-only
  2. #
  3. # TRNG device configuration
  4. #
  5. config TRNG
  6.         tristate "TRNG support"
  7.         help
  8.           This driver provides support for TRNG in SoCs.
  9.           To compile this driver as a module, choose M here: the module
  10.           will be called acme-trng.
  11.           If unsure, say Y.
复制代码
在linux/drivers/Makefile中添加:
  1. obj-$(CONFIG_TRNG)        += trng/
复制代码
在linux/drivers/Kconfig中添加:
  1. source "drivers/trng/Kconfig"
复制代码
将trng.c驱动文件拷贝到trng目录,最终目录文件如下:
  1. $ tree linux-6.1/drivers/trng/
  2. ├── Kconfig
  3. ├── Makefile
  4. └── trng.c
复制代码
内核配置

配置内核,输入命令:
  1. make menuconfig
复制代码
选择Device Drivers->TRNG support,选择将trng编译进内核,这里可以有三种选择:

运行

编译Linux并启动,在启动日记中,如果打印如下,说明TRNG驱动运行正常:
  1. [    4.974450] trng 53030000.trng: TRNG platform driver probed
复制代码
可以实行命令,获取随机数:
  1. cat /dev/trng | hexdump -n 32
  2. 0000000 df01 f5bc de33 2509 8d16 7b5f 8868 8bea
  3. 0000010 40f3 00f2 97a4 324d 03c2 10c8 b943 3d6d
  4. 0000020
复制代码
题目解决


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4