From fb210c4c035aa949ffe127ecf4edf4523b27590d Mon Sep 17 00:00:00 2001 From: Kepontry Date: Thu, 11 May 2023 01:07:52 -0700 Subject: [PATCH 1/3] add articles/20230511-l2-prefetch-driver.md --- articles/20230511-l2-prefetch-driver.md | 291 ++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 articles/20230511-l2-prefetch-driver.md diff --git a/articles/20230511-l2-prefetch-driver.md b/articles/20230511-l2-prefetch-driver.md new file mode 100644 index 0000000..a32f918 --- /dev/null +++ b/articles/20230511-l2-prefetch-driver.md @@ -0,0 +1,291 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [tounix spaces codeinline tables urls pangu autocorrect]
+> Author: Kepontry
+> Date: 2023/5/11
+> Revisor: Falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Proposal: [VisionFive 2 开发板软硬件评测及软件 gap 分析](https://gitee.com/tinylab/riscv-linux/issues/I64ESM)
+> Sponsor: PLCT Lab, ISCAS + +# 基于 RISC-V SoC JH7110 的 L2 预取器驱动示例 + +## 简介 + +### L2 预取器 + +预取是一种预测 CPU 未来需要的数据,从而将其提前从内存取进 Cache 的技术。赛昉科技推出的 VisionFive 2 开发板搭载的 JH7110 SoC 具有 L2 预取功能,具体介绍与参数调优可以参见 [上一篇文章][003]。 + +### 设备驱动 + +设备通常分为字符设备、块设备与网络设备,字符设备以字节为单位传输数据,块设备则以固定大小的块为单位。设备驱动是内核与外设的接口,字符驱动中通常需要实现 file_operations 结构体中定义的成员函数,如 open()、ioctl()、read() 和 write() 等,以定义数据交互的方式。此外,还可以通过设置 `_ATTR` 宏的方式,在用户空间查看和写入设备属性。 + +## L2 预取器驱动示例 + +通过查阅[数据手册](https://starfivetech.com/uploads/u74mc_core_complex_manual_21G1.pdf),JH7110 SoC 中的四个 U74 核都配备两个 32 位的 L2 预取控制寄存器。第一个预取控制寄存器 basicCtrl 中包含的预取参数如下表所示,第二个控制寄存器 additionalCtrl 中包含的预取参数可以在 [数据手册][005] 的第 224 页找到。在 [上一篇文章][003] 中,我们通过在 U-Boot 中写入物理内存中的 IO 空间来实现控制寄存器写入,但启动 Linux 后无法修改控制寄存器的值,十分不便。本文介绍另一种通过设备驱动实现预取参数设置的方式。 + +| 位范围 | 变量名 | 设置值 | 用途描述 | +|--------|------------------|--------|-----------------------------------| +| 0 | en | 1 | 开启/关闭预取器 | +| 1 | crossPageOptmDis | 0 | 关闭/开启跨页优化 | +| 7-2 | dist | 3 | 设置初始预取距离 | +| 13-8 | maxAllowedDist | 10 | 设置最大允许预取距离 | +| 19-14 | linToExpThrd | 5 | 设置预取距离的调节速度,值越小越快 | +| 20 | ageOutEn | 0 | 开启/关闭替换机制 | +| 27-21 | numLdsToAgeOut | 64 | 触发替换所需的预测错误次数 | +| 28 | crossPageEn | 0 | 开启/关闭跨页预取 | +| 31-29 | 保留 | | | + +驱动与设备进行数据交互可以通过定义 file_operations 结构体中的 read() 和 write() 函数实现,但这是以字节为单位的,而预取参数以位为单位且长度不固定,在读写时会有很多不便。而且用户需要编写程序,调用 open()、read() 等一系列设备操作函数。另一种方式是将每个参数设置为设备属性,并定义 show 和 store 方法,在设备注册到 sysfs 时,在/sys 目录下生成对应参数的可读写文件,在用户空间仅需使用 cat 和 echo 命令即可实现参数读写。接下来将介绍后一种方式的示例驱动代码。 + +### 添加设备描述 + +与设备树相关的常见文件有三种,`.dts` 是设备树源码文件,`.dtb` 是编译后得到的二进制文件,而 `.dtsi` 是设备树的头文件。为了让驱动获取到各 CPU 预取控制寄存器的 IO 地址,需要在 `jh7110.dtsi` 文件中进行声明。以定义的第一个设备为例,冒号前的 l2pf0 是节点标签,可在设备树文件内使用,冒号后的 l2pf0 是节点名称,驱动根据该名称找到节点,`@` 后为设备地址。compatible 声明的是兼容属性,可用于驱动绑定兼容设备。reg 声明地址范围,前 64 位表示首址,后 64 位表示长度,第一个设备声明的是 0x2032000-0x2034000 这块 IO 空间。 + +```shell +$ cd linux +$ vim arch/riscv/boot/dts/starfive/jh7110.dtsi +soc: soc { + ...... + cachectrl: cache-controller@2010000 { + ...... + }; + ++ l2pf0: l2pf0@2032000 { ++ compatible = "sifive,l2pf"; ++ reg = <0x0 0x2032000 0x0 0x2000>; ++ }; ++ ++ l2pf1: l2pf1@2034000 { ++ compatible = "sifive,l2pf"; ++ reg = <0x0 0x2034000 0x0 0x2000>; ++ }; ++ ++ l2pf2: l2pf2@2036000 { ++ compatible = "sifive,l2pf"; ++ reg = <0x0 0x2036000 0x0 0x2000>; ++ }; ++ ++ l2pf3: l2pf3@2038000 { ++ compatible = "sifive,l2pf"; ++ reg = <0x0 0x2038000 0x0 0x2000>; ++ }; +``` + +### 设置设备属性 + +为了方便读写各预取参数,需要使用 `DEVICE_ATTR_RW` 宏,以在/sys 目录下生成对应参数的可读写文件。查找定义可以发现,这个宏定义一个 device_attribute 结构体的设备属性,并调用了 `__ATTR_RW` 宏。`__ATTR_RW` 宏设置属性的读写权限并调用了 `_ATTR` 宏。`_ATTR` 宏最后指定属性的名称、show 函数和 store 函数。 + +```shell +$ cat include/linux/device.h +...... +#define DEVICE_ATTR_RW(_name) \ + struct device_attribute dev_attr_##_name = __ATTR_RW(_name) + +$ cat include/linux/sysfs.h +...... +#define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store) + +#define __ATTR(_name, _mode, _show, _store) { \ + .attr = {.name = __stringify(_name), \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ + .show = _show, \ + .store = _store, \ +} +``` + +show 函数和 store 函数是属性对应的读写函数,命名方式为属性名后加 `_show` 和 `_store`。我们定义了 `basic_attr_func(name, high, low)` 和 `add_attr_func(name, high, low)` 宏,分别辅助 basicCtrl 和 additionalCtrl 控制寄存器中各参数的定义,并且可以精简代码。其中,name 指属性名,high 和 low 分别指该参数在对应控制寄存器中的最高位和最低位。该宏自动定义了属性的 show 函数和 store 函数,并调用了 `DEVICE_ATTR_RW` 宏,声明设备属性。 + +接下来以 `basic_attr_func(name, high, low)` 宏为例进行介绍。l2_pf_base 是控制寄存器基址,由上面的设备树中定义的 IO 地址经虚实地址转换后得到。SIFIVE_L2_PF_BASIC_CTRL 和 SIFIVE_L2_PF_ADD_CTRL 定义的是两个控制寄存器的地址偏移。reg_basic_ctrl 数组用于暂存各 CPU 对应的 basicCtrl 控制寄存器值,每次读写都将更新。`name##_mask` 变量通过移位操作,定义各属性对应的掩码,便于读写。该宏使用 readl 和 writel 函数读写控制寄存器,一次读写 4 字节,与控制寄存器大小相等。 + +```c +#define SIFIVE_L2_PF_BASIC_CTRL 0x00 +#define SIFIVE_L2_PF_ADD_CTRL 0x04 +#define NUM_CPUS 4 + +static u32 reg_basic_ctrl[NUM_CPUS], reg_add_ctrl[NUM_CPUS], temp[NUM_CPUS]; +static void __iomem *l2_pf_base[NUM_CPUS]; + +#define basic_attr_func(name, high, low) \ +static u32 name##_mask = (((1 << (high - low + 1)) - 1) << low); \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + reg_basic_ctrl[dev->id] = readl(l2_pf_base[dev->id] \ + + SIFIVE_L2_PF_BASIC_CTRL); \ + return sprintf(buf, "%u\n", (reg_basic_ctrl[dev->id] \ + & name##_mask) >> low); \ +} \ +static ssize_t name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + sscanf(buf, "%u", &temp[dev->id]); \ + reg_basic_ctrl[dev->id] = (reg_basic_ctrl[dev->id] & ~name##_mask) \ + | ((temp[dev->id] << low) & name##_mask); \ + writel(reg_basic_ctrl[dev->id], l2_pf_base[dev->id] + SIFIVE_L2_PF_BASIC_CTRL); \ + return size; \ +} \ +static DEVICE_ATTR_RW(name); + +basic_attr_func(prefetch_enable, 0, 0) +basic_attr_func(cross_page_opt_dis, 1, 1) +basic_attr_func(distance, 7, 2) +basic_attr_func(max_allow_dist, 13, 8) +basic_attr_func(line_to_exp_thrd, 19, 14) +basic_attr_func(age_out_enable, 20, 20) +basic_attr_func(num_loads_to_age_out, 27, 21) +basic_attr_func(cross_page_enable, 28, 28) +``` + +设置完各属性后,需要定义 attribute 属性的指针数组 l2_prefetch_attrs,包含上面定义的所有属性,并以 NULL 结尾。之后定义属性组 l2_prefetch_attr_group,指定 attrs 和 name 成员变量,这将在/sys/devices/system/cpu/cpux 目录下生成一个名为 l2_prefetch 的文件夹,里面包含了上面定义的所有属性。 + +```shell +static struct attribute *l2_prefetch_attrs[] = { + &dev_attr_prefetch_enable.attr, + &dev_attr_cross_page_opt_dis.attr, + &dev_attr_distance.attr, + &dev_attr_max_allow_dist.attr, + &dev_attr_line_to_exp_thrd.attr, + &dev_attr_age_out_enable.attr, + &dev_attr_num_loads_to_age_out.attr, + &dev_attr_cross_page_enable.attr, + &dev_attr_q_full_thrd.attr, + &dev_attr_hit_cache_thrd.attr, + &dev_attr_hit_mshr_thrd.attr, + &dev_attr_window.attr, + NULL, +}; + +static const struct attribute_group l2_prefetch_attr_group = { + .attrs = l2_prefetch_attrs, + .name = "l2_prefetch" +}; +``` + +### 驱动初始化操作 + +定义了设备属性后,还需要定义初始化函数 l2_prefetch_add_dev,通过传入的 CPU 序号,查找对应设备,在 sysfs 上创建节点并挂接属性。of_find_node_by_name 函数通过指定的名称在设备树中查找设备节点。of_iomap 函数根据找到的设备节点直接进行 ioremap 操作,将物理 IO 地址映射为虚拟地址,第二个参数为设备树节点的 reg 段索引,由于只定义了一个内存段,所以索引为 0。之后使用 readl 函数读取该 CPU 的控制寄存器,初始化 reg_basic_ctrl 和 reg_add_ctrl 数组中对应值。get_cpu_device 函数用于获取指定 CPU 的 device 结构体,并传入 sysfs_create_group 函数,在该 CPU 的 sysfs 节点下创建属性组。 + +l2_prefetch_remove_dev 函数用于指定 CPU 被移除时的行为,这里调用 sysfs_remove_group 函数,移除属性组。使用 cpuhp_setup_state 函数以支持 CPU 热插拔功能,后两个参数用于指定 CPU 上线和下线的回调函数。最后调用 device_initcall 函数,注册 sifive_l2_pf_init 为设备初始化函数。 + +```C +static int l2_prefetch_add_dev(unsigned int cpu) +{ + struct device_node *np; + char buf[10]; + + sprintf(buf, "l2pf%u", cpu); + np = of_find_node_by_name(NULL, buf); + if (!np) + return -ENODEV; + + l2_pf_base[cpu] = of_iomap(np, 0); + if (!l2_pf_base[cpu]) + return -ENOMEM; + + reg_basic_ctrl[cpu] = readl(l2_pf_base[cpu] + SIFIVE_L2_PF_BASIC_CTRL); + reg_add_ctrl[cpu] = readl(l2_pf_base[cpu] + SIFIVE_L2_PF_ADD_CTRL); + + struct device *dev = get_cpu_device(cpu); + if(cpu != dev->id) + pr_err("L2PF: cpu %u != dev_id %u", cpu, dev->id); + return sysfs_create_group(&dev->kobj, &l2_prefetch_attr_group); +} + +static int l2_prefetch_remove_dev(unsigned int cpu) +{ + struct device *dev = get_cpu_device(cpu); + sysfs_remove_group(&dev->kobj, &l2_prefetch_attr_group); + return 0; +} + +static int __init sifive_l2_pf_init(void) +{ + return cpuhp_setup_state(CPUHP_L2PREFETCH_PREPARE, + "soc/l2prefetch:prepare", l2_prefetch_add_dev, + l2_prefetch_remove_dev); +} + +device_initcall(sifive_l2_pf_init); +``` + +## 使用示例 + +### 编译内核 + +完整的示例代码 sifive_l2_prefetcher.c 放在 [GitHub 仓库][004] 中,需要将其拷贝至内核代码的 `drivers/soc/sifive/` 目录下。并向该目录下的 Makefile 中添加 `sifive_l2_prefetcher.o` 文件,以编译链接驱动代码。这里为简洁起见,默认与 L2 Cache 控制器使用同一个变量 CONFIG_SIFIVE_L2,也可以在 Kconfig 文件中定义自己的 CONFIG_SIFIVE_L2_PREFETCH 变量。 + +```shell +$ export LINUX_DIR=/path/to/linux +$ git clone https://github.com/Kepontry/sifive-l2pf-driver.git +$ cp sifive-l2pf-driver/sifive_l2_prefetcher.c $LINUX_DIR/drivers/soc/sifive/ +$ vim $LINUX_DIR/drivers/soc/sifive/Makefile + # SPDX-License-Identifier: GPL-2.0 + + obj-$(CONFIG_SIFIVE_L2) += sifive_l2_cache.o ++ obj-$(CONFIG_SIFIVE_L2) += sifive_l2_prefetcher.o +``` + +此外,还需要在 `cpuhotplug.h` 中定义枚举变量 CPUHP_L2PREFETCH_PREPARE,以支持 CPU 热插拔功能。 + +```shell +$ vim include/linux/cpuhotplug.h + CPUHP_TOPOLOGY_PREPARE, ++ CPUHP_L2PREFETCH_PREPARE, + CPUHP_NET_IUCV_PREPARE, +``` + +编译内核,更新内核压缩镜像 vmlinuz 和设备树 dtb 文件。重启后选择从刚编译的内核镜像启动,启动项的添加请参见 [之前的文章][001]。 + +```shell +# 编译内核 +$ make CROSS_COMPILE=riscv64-linux-gnu- ARCH=riscv -j$(nproc) +$ mkdir -p vmlinuz +# 生成 vmlinuz 文件 +$ make CROSS_COMPILE=riscv64-linux-gnu- ARCH=riscv INSTALL_PATH=~/linux/vmlinuz zinstall -j$(nproc) +$ cp vmlinuz/* /boot/boot +$ cp arch/riscv/boot/dts/starfive/jh7110-visionfive-v2.dtb /boot/boot/dtbs/starfive/ +# 确保落盘 +$ sync +``` + +### 参数读写与验证 + +启动新内核后,使用 cat 和 echo 命令可以读写在/sys 目录下创建的属性文件,注意写入操作需要 root 权限。以下示例尝试读写 CPU 0 的 prefetch_enable 变量,以关闭 CPU 0 的预取功能。 + +```shell +# cat /sys/devices/system/cpu/cpu0/l2_prefetch/prefetch_enable +1 +# echo "0" > /sys/devices/system/cpu/cpu0/l2_prefetch/prefetch_enable +# cat /sys/devices/system/cpu/cpu0/l2_prefetch/prefetch_enable +0 +``` + +使用 taskset 命令在 CPU 0 上运行 [之前文章][002] 中介绍的存储器山程序进行验证,与通过 U-Boot 写入物理内存从而关闭预取的现象一致。 + +```shell +$ cd CpuCacheMountainViewer +$ taskset -c 0 ./mountain +Clock frequency is approx. 4.0 MHz +Memory mountain (MB/sec) + s1 s3 s5 s7 s9 s11 s13 s15 s17 s19 s21 s23 s25 s27 s29 s31 +128m 190 111 80 62 51 43 37 33 31 31 31 31 31 31 31 31 +64m 190 111 80 62 51 43 37 33 31 31 31 31 31 31 31 31 +32m 190 111 80 62 51 43 37 33 31 31 31 31 31 31 31 31 +16m 190 111 80 62 51 43 37 33 31 31 31 31 31 31 32 32 +8m 191 112 81 63 52 44 38 33 32 32 32 33 34 35 35 36 +4m 199 125 91 72 60 51 44 39 38 42 46 51 58 71 89 113 +2m 260 229 201 184 171 157 145 133 150 155 157 157 157 157 157 156 +1m 272 248 228 213 198 185 173 162 157 157 157 157 157 157 157 156 +``` + +## 总结 + +本文为 RISC-V SoC JH7110 实现了一个简单的 L2 预取器驱动,使得能够通过读写/sys 目录下的属性文件,查看和修改预取控制参数,便于进行预取功能的性能调优。 + +## 参考资料 + +[001]: https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230227-vf2-kernel-compile.md +[002]: https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230425-vf2-mem-mountain.md +[003]: https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230509-vf2-hw-prefetch.md +[004]: https://github.com/Kepontry/sifive-l2pf-driver +[005]: https://starfivetech.com/uploads/u74mc_core_complex_manual_21G1.pdf -- Gitee From 43cfe305f1e7018626fd6ecd5cb5012d41d9d11d Mon Sep 17 00:00:00 2001 From: walimis Date: Mon, 15 May 2023 07:20:20 +0000 Subject: [PATCH 2/3] Update articles/20230511-l2-prefetch-driver.md --- articles/20230511-l2-prefetch-driver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230511-l2-prefetch-driver.md b/articles/20230511-l2-prefetch-driver.md index a32f918..8a9a051 100644 --- a/articles/20230511-l2-prefetch-driver.md +++ b/articles/20230511-l2-prefetch-driver.md @@ -20,7 +20,7 @@ ## L2 预取器驱动示例 -通过查阅[数据手册](https://starfivetech.com/uploads/u74mc_core_complex_manual_21G1.pdf),JH7110 SoC 中的四个 U74 核都配备两个 32 位的 L2 预取控制寄存器。第一个预取控制寄存器 basicCtrl 中包含的预取参数如下表所示,第二个控制寄存器 additionalCtrl 中包含的预取参数可以在 [数据手册][005] 的第 224 页找到。在 [上一篇文章][003] 中,我们通过在 U-Boot 中写入物理内存中的 IO 空间来实现控制寄存器写入,但启动 Linux 后无法修改控制寄存器的值,十分不便。本文介绍另一种通过设备驱动实现预取参数设置的方式。 +通过查阅[数据手册](https://starfivetech.com/uploads/u74mc_core_complex_manual_21G1.pdf),JH7110 SoC 中的四个 U74 核都配备两个 32 位的 L2 预取控制寄存器。第一个预取控制寄存器 basicCtrl 中包含的预取参数如下表所示,第二个控制寄存器 additionalCtrl 中包含的预取参数可以在 [数据手册][005] 的第 224 页找到。在 [上一篇文章][003] 中,我们通过在 U-Boot 中写入 SOC 的 MMIO 地址空间来实现控制寄存器的修改,但启动 Linux 后无法修改控制寄存器的值,十分不便。本文介绍另一种通过设备驱动实现设置预取参数的方式。 | 位范围 | 变量名 | 设置值 | 用途描述 | |--------|------------------|--------|-----------------------------------| -- Gitee From 0e432d8039867b947af41b737bc40218fafea328 Mon Sep 17 00:00:00 2001 From: Kepontry <9089707+Kepontry@user.noreply.gitee.com> Date: Mon, 15 May 2023 13:05:46 +0000 Subject: [PATCH 3/3] Apply all suggestions from code review --- articles/20230511-l2-prefetch-driver.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/articles/20230511-l2-prefetch-driver.md b/articles/20230511-l2-prefetch-driver.md index 8a9a051..a798a1d 100644 --- a/articles/20230511-l2-prefetch-driver.md +++ b/articles/20230511-l2-prefetch-driver.md @@ -6,17 +6,17 @@ > Proposal: [VisionFive 2 开发板软硬件评测及软件 gap 分析](https://gitee.com/tinylab/riscv-linux/issues/I64ESM)
> Sponsor: PLCT Lab, ISCAS -# 基于 RISC-V SoC JH7110 的 L2 预取器驱动示例 +# 基于 RISC-V SoC JH7110 的 L2 预取器驱动分析 ## 简介 ### L2 预取器 -预取是一种预测 CPU 未来需要的数据,从而将其提前从内存取进 Cache 的技术。赛昉科技推出的 VisionFive 2 开发板搭载的 JH7110 SoC 具有 L2 预取功能,具体介绍与参数调优可以参见 [上一篇文章][003]。 +预取是一种预测 CPU 未来需要的数据,并将其提前从内存取进 Cache 的技术。赛昉科技推出的 VisionFive 2 开发板搭载的 JH7110 SoC 具有 L2 预取功能,具体介绍与参数调优可以参见 [上一篇文章][003]。 ### 设备驱动 -设备通常分为字符设备、块设备与网络设备,字符设备以字节为单位传输数据,块设备则以固定大小的块为单位。设备驱动是内核与外设的接口,字符驱动中通常需要实现 file_operations 结构体中定义的成员函数,如 open()、ioctl()、read() 和 write() 等,以定义数据交互的方式。此外,还可以通过设置 `_ATTR` 宏的方式,在用户空间查看和写入设备属性。 +设备通常分为字符设备、块设备与网络设备,字符设备以字节为单位传输数据,块设备则以固定大小的块为单位。设备驱动是内核与外设的接口,字符驱动中通常需要实现 file_operations 结构体中定义的成员函数,如 open()、ioctl()、read() 和 write() 等,以完成数据交互。此外,还可以通过设置 `_ATTR` 宏的方式,在用户空间通过 sysfs 文件系统查看和写入设备属性。 ## L2 预取器驱动示例 @@ -43,6 +43,7 @@ ```shell $ cd linux $ vim arch/riscv/boot/dts/starfive/jh7110.dtsi +... soc: soc { ...... cachectrl: cache-controller@2010000 { @@ -68,11 +69,12 @@ soc: soc { + compatible = "sifive,l2pf"; + reg = <0x0 0x2038000 0x0 0x2000>; + }; +... ``` ### 设置设备属性 -为了方便读写各预取参数,需要使用 `DEVICE_ATTR_RW` 宏,以在/sys 目录下生成对应参数的可读写文件。查找定义可以发现,这个宏定义一个 device_attribute 结构体的设备属性,并调用了 `__ATTR_RW` 宏。`__ATTR_RW` 宏设置属性的读写权限并调用了 `_ATTR` 宏。`_ATTR` 宏最后指定属性的名称、show 函数和 store 函数。 +为了方便读写各预取参数,需要使用 `DEVICE_ATTR_RW` 宏,以在 `/sys` 目录下生成对应参数的可读写文件。查找定义可以发现,这个宏定义一个 device_attribute 结构体的设备属性,并调用了 `__ATTR_RW` 宏。`__ATTR_RW` 宏设置属性的读写权限并调用了 `_ATTR` 宏。`_ATTR` 宏最后指定属性的名称、show 函数和 store 函数。 ```shell $ cat include/linux/device.h @@ -136,7 +138,7 @@ basic_attr_func(num_loads_to_age_out, 27, 21) basic_attr_func(cross_page_enable, 28, 28) ``` -设置完各属性后,需要定义 attribute 属性的指针数组 l2_prefetch_attrs,包含上面定义的所有属性,并以 NULL 结尾。之后定义属性组 l2_prefetch_attr_group,指定 attrs 和 name 成员变量,这将在/sys/devices/system/cpu/cpux 目录下生成一个名为 l2_prefetch 的文件夹,里面包含了上面定义的所有属性。 +设置完各属性后,需要定义 attribute 属性的指针数组 l2_prefetch_attrs,包含上面定义的所有属性,并以 NULL 结尾。之后定义属性组 l2_prefetch_attr_group,指定 attrs 和 name 成员变量,这将在 `/sys/devices/system/cpu/cpux` 目录下生成一个名为 l2_prefetch 的文件夹,里面包含了上面定义的所有属性。 ```shell static struct attribute *l2_prefetch_attrs[] = { @@ -250,7 +252,7 @@ $ sync ### 参数读写与验证 -启动新内核后,使用 cat 和 echo 命令可以读写在/sys 目录下创建的属性文件,注意写入操作需要 root 权限。以下示例尝试读写 CPU 0 的 prefetch_enable 变量,以关闭 CPU 0 的预取功能。 +启动新内核后,使用 cat 和 echo 命令可以读写在 `/sys` 目录下创建的属性文件,注意写入操作需要 root 权限。以下示例尝试读写 CPU 0 的 prefetch_enable 变量,以关闭 CPU 0 的预取功能。 ```shell # cat /sys/devices/system/cpu/cpu0/l2_prefetch/prefetch_enable @@ -280,7 +282,7 @@ Memory mountain (MB/sec) ## 总结 -本文为 RISC-V SoC JH7110 实现了一个简单的 L2 预取器驱动,使得能够通过读写/sys 目录下的属性文件,查看和修改预取控制参数,便于进行预取功能的性能调优。 +本文为 RISC-V SoC JH7110 实现了一个简单的 L2 预取器驱动,使得能够通过读写 `/sys` 目录下的属性文件,查看和修改预取控制参数,便于进行预取功能的性能调优。 ## 参考资料 -- Gitee