1.为什么厂家buildroot编译出来的Qt不能通用与busybox不同，buildroot 不仅集成了 busybox，而且还集成了各种常见的第三方库和软件，需要什么就选择什么，buildroot 会帮我们处理好各种细节，根文件系统也会更加的合理、有效。但是厂家的buildroot编译脚本指定了编译路径，有些路径是根本不存在的，根本不会在该路径下生成Qt编译文件，很有可能厂家构建这一切用的虚拟机与提供给客户的不是同一个，如下图为Qt Creator搭建交叉编译环境时的提示。之后，我查看qmake文件的hex编码，发现有些路径根本就不存在，要么是路径下没有相关文件。尝试在该路径下复制文件也是无效的。2.为什么我们自己编译Qt很容易失败在linux下有些编译器，依赖之类，他会随着版本更新变化，然而有些过去依靠他们老版本制作的文件，比如旧版Qt源文件，在我们的环境更新后会出现某些语法问题、引用问题之类，这种问题让人无奈又头疼，比如从下面这些错误可见一斑，只能尝试一点点去解决这些问题。3.Qt开发具体流程3.1前提：确保交叉编译环境在命令行输入下面的命令验证是否有交叉编译环境，确认无误才可以编译。arm-linux-gnueabihf-gcc -v3.2 下载Qt官方源码进入下面的网址下载Qt源码，我们开发板用的是5.9版本，所以linux平台源码也要用5.9的，原则上应该可以下载低于5.9版本的，应该可以向下兼容，但是不能下载高版本的，为避免有问题，最好下载对应版本。https://download.qt.io/archive/qt/3.2编辑qtbase/mkspecs/qws/linux-arm-gnueabi-g++/qmake.conf，配置相关编译器及路径。这个配置cortex-a7是通用的，imx6ull也是一样的。3.3配置编译选项，即选择开启关闭哪些功能，此处是最容易出错的地方，配置有问题会导致后面的编译失败，有些问题只有作者才能解决。我这里通过的编译选项为：./configure -release -nomake examples -no-iconv -no-opengl -no-assimp -no-qt3d-profile-jobs -no-qt3d-profile-gl -xplatform linux-arm-gnueabi-g++ -prefix home/gzpeite/qt-everywhere-opensource-src-5.9.0/arm-qt -opensource -confirm-license也可以编写脚本，直接运行脚本即可，这里指定了我们自己的路径，肯定不会有问题。3.4 交叉编译生成Arm下的qmake执行完成上面的配置之后，就可以开始编译Qt，编译时间长且容易受到配置的影响导致失败，所以一般编译失败都可以看看是不是配置项哪里有问题。执行make命令 (可以多线程make -j4，代表4线程，具体看虚拟机和电脑配置)如果编译正常没有错误，此时可以make install，将其安装到我们在配置中指定的路径中去。3.4移植Qt到开发板文件系统（A40i可省略此步骤，因其可以在buildroot构建）将上面生成的arm-qt文件打包，通过U盘拷贝到文件系统中，比如usr/local/这个路径下面，然后配置Qt5的环境变量，编辑/etc/profile文件。export QT_ROOT=/usr/local/arm-qt export QT_QPA_PLATFORM_PLUGIN_PATH=$QT_ROOT/plugins export QT_QPA_PLATFORM=linuxfb:tty=/dev/fb0 export QT_PLUGIN_PATH=$QT_ROOT/plugins export LD_LIBRARY_PATH=$QT_ROOT/lib:$QT_ROOT/plugins/platforms export QML2_IMPORT_PATH=$QT_ROOT/qml至此，Qt编译完成，下面开始安装Qt Creator，编写程序测试环境。3.5安装Qt Creator下载 qt-opensource-linux-x64-5.9.0.run 这个文件。赋予该文件可执行权限，再执行安装。chmod u+x qt-opensource-linux-x64-5.9.0.run sudo ./qt-opensource-linux-x64-5.9.0.run 安装时需要用邮箱在Qt官网注册一下，按要求做即可。具体安装过程不在赘述，默认安装即可，linux又不分盘。3.6为Qt Creator创建Arm开发环境打开tools ->Optons配置qmake为之前编译的Arm下qmake配置C++编译器为Arm交叉编译器，Qt是C++语言，C编译器可以不配置配置Kits，选择我们配置好的Compiler和Qt Version即可。至此，环境配置完成。3.7新建项目，编写Qt程序通过命令行，或者搜索Qt打开新建项目，一定要选择上面构建的arm-qt编译环境这里一定要选上编写Qt测试程序，查看其是否能在Arm环境下运行注意，因为其有两套编译环境，arm-qt是在开发板运行的，另外一个Desktop Qt 5.9.0 GCC 64bit是在linux下运行的，想要程序在开发板上运行，一定要用arm-qt去构建编译项目。编译完成之后，会在项目下生成Debug目录，只有Arm-qt里面的可执行文件test才是我们开发板需要的3.8拷贝test文件到开发板去运行执行 ./test命令可以看到，两个环境均没有问题，Qt程序正常运行。3.9OpenCV的编译过程很简单，CMAKE的GUI配置一下就可以了，这里不再赘述，提供如何在程序里加入OpenCV。在Qt项目的pro文件里加入如下内容INCLUDEPATH += /home/gzpeite/opencv-4.6.0/install/include LIBS += ../../lib/libopencv_core.so \ ../../lib/libopencv_highgui.so \ ../../lib/libopencv_imgproc.so \ ../../lib/libopencv_videoio.so \ ../../lib/libopencv_imgcodecs.so根据需要添加下面的头文件#include <opencv2/imgproc/imgproc_c.h> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc.hpp> #include <opencv/cv.h>QT的pro文件配置opencv库双环境使用TARGET_ARCH = $${QT_ARCH}获取当前编译器的架构通过contains(TARGET_ARCH, arm)来做arm与x86环境的区分，用来做库链接设置 QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 TARGET_ARCH = $${QT_ARCH} contains(TARGET_ARCH, arm){ message(">>> Building for ARM platform") # Qt 路径（ARM） QTDIR_ARM = /opt/qt5.12.12 INCLUDEPATH += $${QTDIR_ARM}/include LIBS += -L$${QTDIR_ARM}/lib -lQt5Core -lQt5Widgets -lQt5Gui PKGCONFIG += opencv INCLUDEPATH += /home/linux/rk3506/tool/opencv/opencv-4.12.0/install_arm/include/opencv4 LIBS += -L/home/linux/rk3506/tool/opencv/opencv-4.12.0/install_arm/lib \ -lopencv_core \ -lopencv_imgproc \ -lopencv_highgui \ -lopencv_videoio \ -lopencv_imgcodecs \ -lopencv_dnn \ -lopencv_features2d \ -lopencv_calib3d \ -lopencv_flann \ -lopencv_objdetect # INCLUDEPATH += /opt/qt5.12.12/libinclude # LIBS += -L/opt/qt5.12.12/lib -lQt5Core -lQt5Widgets -lQt5Gui } else { message(">>> Building for Ubuntu host") # Qt 路径（Ubuntu） QTDIR_PC = /home/linux/Qt5.12.12/5.12.12/gcc_64 INCLUDEPATH += $${QTDIR_PC}/include LIBS += -L$${QTDIR_PC}/lib -lQt5Core -lQt5Widgets -lQt5Gui INCLUDEPATH += /usr/local/include/opencv4 LIBS += -L/usr/local/lib \ -lopencv_core \ -lopencv_imgproc \ -lopencv_highgui \ -lopencv_videoio \ -lopencv_imgcodecs \ -lopencv_dnn \ -lopencv_features2d \ -lopencv_calib3d \ -lopencv_flann \ -lopencv_objdetect } SOURCES += \ main.cpp \ mainwindow.cpp HEADERS += \ mainwindow.h FORMS += \ mainwindow.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target
1.更换SDK板级配置1.1 创建配置文件夹首先在SDK/device/rockchip/.chips/目录下创建我们自己的板级配置文件目录比如名字可以是ga3506，可以从官方的配置文件夹复制改名，这样不用自己写大部分的配置文件 1.2 创建配置文件然后就可以修改配置文件，比如可以先改名为ga3506_emmmc_rtt_defconfig，具体内容修改后面详细分析1.3 SDK更换板级配置然后可以使用命令./build.sh chip 选择芯片平台与配置文件2.具体文件修改如下图，可以看到复制过来是带有ok3506配置的，我们主要就是将红框内容修改为ga3506，同时在配置所用的目录下，对应复制粘贴修改名字为ga3506即可RK_BUILDROOT_BASE_CFG的配置文件目录为/home/linux/rk3506/OK3506_Linux_Source/buildroot/configs/ rockchip_ok3506_emmc_defconfig复制粘贴后改为rockchip_ga3506_emmc_defconfig RK_UBOOT_CFG的配置文件目录为 此SDK的UBOOT为不可改固件 RK_KERNEL_CFG的配置文件目录为 /home/linux/rk3506/OK3506_Linux_Source/kernel-6.1/arch/arm/configs/ OK3506-S_linux_rtt_defconfig复制粘贴后改为GA3506_linux_rtt_defconfig RK_KERNEL_DTS_NAME的配置文件目录为 /home/linux/rk3506/OK3506_Linux_Source/kernel-6.1/arch/arm/boot/dts/ OK3506-S_linux_emmc_rtt.dts复制粘贴改为GA3506_linux_emmc_rtt.dts OK3506-S_linux_emmc.dts复制粘贴改为GA3506_linux_emmc.dts ok3506-amp-rtt.dtsi复制粘贴改为GA3506-amp-rtt.dtsi 更改完成，我们就得到了一个GA3506的平台，然后就可以具体根据实际情况修改设备树与buildroot相关配置再使用./build.sh all命令全部编译一遍注意，如果有rtos，还需要将rtos的工程名字也做更改
1.更改buildroot在SDK根目录下使用./build.sh buildroot-config进入配置页面在buildroot目录下使用make menuconfig或直接改配置文件改完配置之后，保存配置到LinuxSDK/rk3576_linux6.1_release/buildroot/configs/rockchip_rk3xxx_defconfig1.1 增删文件系统软件，更改文件系统大小使用命令进入配置界面后，进入Target packages目标安装包，就是安装在文件系统的软件、库等文件。这里可以删除不用的软件包，比如chromium浏览器，测试文件等，也可以配置软件安装包，音频、压缩包、字体、通用软件比如QT、LVGL等，达到更改文件系统大小的目的1.2 个性化文件系统System configuration 主要修改系统配置，比如主机名字，密码等在此修改。如下图，可以在图形界面里配置系统设置，比如主机名，欢迎语，密码等1.3 注意事项下图是默认配置文件，不能带有空格，而make menuconfig生成的.config是带空格和注释的，所以没法直接替换保存用，那么我一般都是生成.config后对照更改配置名添加到这个默认配置中，从下面可以看出主机名字、密码、编译后覆盖文件夹、相机、音频、GPU等文件配置为什么要这样做，因为buildroot编译脚本从这个默认配置生成.config再去编译整个系统，直接中止编译改.config然后再编译是不行的，buildroot会提示配置文件改变，让你删除整个文件系统的编译重新来过注意如果使用buildroot默认构建的QT5.15，那么需要把Wayland也编译，因为buildroot系统的QT依赖此模块，如果想去掉可以使用自己编译的QT，此时可以去掉buildroot的QT与Wayland配置，使用linuxfb直接显示自己编译的QT即可，即在profile中增加export QT_QPA_PLATFORM=linuxfb2.向文件系统增加文件，修改默认文件2.1 在文件系统编译时添加修改在LinuxSDK/rk3576_linux6.1_release/buildroot/system/skeleton目录下，可以放在文件系统编译完成后想要打包进去的文件，比如自己编译的QT库文件，脚本，应用等，注意很多系统文件是带默认值的，即编译完成后覆盖，在此处添加或者修改的系统文件编译后会被覆盖原文件2.2 文件系统编译完成后添加修改在LinuxSDK/rk3576_linux6.1_release/buildroot/board/rockchip/rk3576/fs-overlay目录下，文件覆盖等级会更高，如果在skeleton更改后系统并未发生变化，是因为有些文件在脚本中配置，那么需要在fs-overlay目录中去创建此文件覆盖，因为其会在全部文件系统编译完成后覆盖一次，比如说etc/inittab的串口登录配置在脚本中生成，发生在skeleton之后，那么就得在fs-overlay中去添加文件覆盖
内核I2C驱动GM8775一. 屏幕参数计算屏幕型号：星科QV185FHM-N81.FY 18.5寸LVDS接口屏幕屏幕参数计算（详见LVDS 屏幕 M215HGE-L21 在 rk3288 上的适配过程 - 大窟窿 - 博客园） clock-frequency = <74000000>; hactive = <1920>; vactive = <1080>; hfront-porch = <100>; hsync-len = <5>; hback-porch = <35>; vfront-porch = <20>; vsync-len = <10>; vback-porch = <15>; hsync-active = <0>; vsync-active = <0>; de-active = <0>; pixelclk-active = <1>;我们需要的参数就是这些，在大部分屏幕手册中，会给出上面这些相关参数，也有很多屏幕是没有参数的，需要自己计算我们这个屏幕，手册中的参数是只有下图这些参数 从图片中可以知道已知参数频率为clock-frequency为74.25Mhz disp_timings 中的 hback-porch, hfront-porch, vback-porch, vfront-porch, hsync-len, vsync-len 的值。它们之间的关系如下： Tv = vback-porch + vfront-porch + vsync-len + Tvd Th = hback-porch + hfront-porch + hsync-len + Thd 则 vback-porch + vfront-porch + vsync-len = 1125 - 1080 = 45 hback-porch + hfront-porch + hsync-len = 1100 - 960 = 140 这里只需要保证这 3 个参数之和等于那个数就可以了，对具体的值没有特别的要求 故得到以下参数： hback-porch = <35>; hfront-porch = <100>; hsync-len = <5>; 与 vfront-porch = <20>; vsync-len = <10>; vback-porch = <15>; 二. 生成配置指令2.1 GM8775C：MIPI转双路LVDS芯片配置方法：该芯片有一款上位机生成初始化指令的软件，分为两种方式I2C指令与MIPI指令I2C指令：指的是使用I2C写入芯片配置参数来使得芯片将SOC的MIPI信号正确转换为LVDS信号，即上面生成的参数MIPI指令：指的是上电初始化时使用MIPI写入芯片配置参数来使得芯片将SOC的MIPI信号正确转换为LVDS信号，即上面生成的参数（目前MIPI写入配置方式存在问题，待供应商支持）软件获取参数使用方法：首先填入屏幕参数，根据上面求得的参数，配置视频格式中的参考时钟、行场等参数，最后创建寄存器表，若配置方式选择I2C，直接用USB转I2C连接器写入即可显示，注意此芯片所有配置掉电丢失，也就是说每次开机都要写入配置ID看GM8775C的手册，I2C_ADDR 管脚通过上下拉选择 I2C 器件地址，拉高为：0x5A，拉低为：0x58Link01相关的参数看屏幕手册，VESA与8bit参数可在屏幕手册中找到 `配置LINK EVEN / ODD奇偶场，如果屏幕显示两侧未连续，则需要交换EVEN / ODD` `0x27是mipi指令的密码解锁地址，0x00是I2C指令的密码解锁地址` 按照图片中的指示与上述描述，在软件上填好并生成寄存器指令表三. 内核与设备树3.1 编写内核代码下列代码其实就是i2c驱动示例代码，加上前面生成寄存器指令表所写的gm8775c_reg_defaults数组，通过i2c驱动形式，将数组配置写入芯片，用以配置MIPI转LVDS，在此板卡对于不同型号的屏幕，只需要改gm8775c_reg_defaults数组即可，其余不需要改动#include <linux/clk.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/i2c.h> #include <linux/spi/spi.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/of_gpio.h> #include <linux/proc_fs.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/regmap.h> #define REGISTER_NUM 34 #define GPIO_PIN_509 509 #define GPIO_PIN_511 511 #define GPIO_PIN_493 493 struct gm8775c_priv { struct regmap *regmap; struct i2c_client *client; int gpio_509; int gpio_511; int gpio_493; }; static const struct reg_default gm8775c_reg_defaults[] = { {0x00,0xAA}, {0x48,0x02}, {0xB6,0x20}, {0x01,0x80}, {0x02,0x38}, {0x03,0x47}, {0x04,0x64}, {0x05,0x05}, {0x06,0x23}, {0x07,0x00}, {0x08,0x14}, {0x09,0x0A}, {0x0A,0x0F}, {0x0B,0x82}, {0x0C,0x13}, {0x0D,0x01}, {0x0E,0x80}, {0x0F,0x20}, {0x10,0x20}, {0x11,0x03}, {0x12,0x1B}, {0x13,0x63}, {0x14,0x01}, {0x15,0x23}, {0x16,0x40}, {0x17,0x00}, {0x18,0x01}, {0x19,0x23}, {0x1A,0x40}, {0x1B,0x00}, {0x1E,0x46}, {0x51,0x30}, {0x1F,0x10}, {0x2A,0x01}, }; static int gm8775c_read(u8 reg, u8 * rt_value, struct i2c_client *client) { int ret; u8 read_cmd[3] = { 0 }; u8 cmd_len = 0; read_cmd[0] = reg; cmd_len = 1; if (client->adapter == NULL) printk("gm8775c_read client->adapter==NULL\n"); ret = i2c_master_send(client, read_cmd, cmd_len); if (ret != cmd_len) { printk("gm8775c_read error1\n"); return -1; } ret = i2c_master_recv(client, rt_value, 1); if (ret != 1) { printk("gm8775c_read error2, ret = %d.\n", ret); return -1; } return 0; } static int gm8775c_write(u8 reg, unsigned char value, struct i2c_client *client) { int ret = 0; u8 write_cmd[2] = { 0 }; write_cmd[0] = reg; write_cmd[1] = value; ret = i2c_master_send(client, write_cmd, 2); if (ret != 2) { printk("gm8775c_write error->[REG-0x%02x,val-0x%02x]\n", reg, value); return -1; } return 0; } static const struct of_device_id gm8775_of_match[] = { { .compatible = "gm8775c,mipi_to_lvds", }, { } }; MODULE_DEVICE_TABLE(of, gm8775_of_match); static const struct i2c_device_id gm8775_i2c_id[] = { {"GM8775C_MTL", 0}, {} }; static ssize_t gm8775c_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val=0, flag=0; u8 i=0, reg, num, value_w, value_r; struct gm8775c_priv *gm8775c = dev_get_drvdata(dev); val = simple_strtol(buf, NULL, 16); flag = (val >> 16) & 0xFF; if (flag) { reg = (val >> 8) & 0xFF; value_w = val & 0xFF; printk("\nWrite: start REG:0x%02x,val:0x%02x,count:0x%02x\n", reg, value_w, flag); while(flag--) { gm8775c_write(reg, value_w, gm8775c->client); printk("Write 0x%02x to REG:0x%02x\n", value_w, reg); reg++; } } else { reg = (val >> 8) & 0xFF; num = val & 0xff; printk("\nRead: start REG:0x%02x,count:0x%02x\n", reg, num); do { value_r = 0; gm8775c_read(reg, &value_r, gm8775c->client); printk("REG[0x%02x]: 0x%02x; ", reg, value_r); reg++; i++; if ((i==num) || (i%4==0)) printk("\n"); } while (i<num); } return count; } static ssize_t gm8775c_show(struct device *dev, struct device_attribute *attr, char *buf) { printk("echo flag|reg|val > gm8775c\n"); printk("eg read star addres=0x06,count 0x10:echo 0610 >gm8775c\n"); printk("eg write star addres=0x90,value=0x3c,count=4:echo 4903c >gm8775c\n"); return 0; } static DEVICE_ATTR(gm8775c, 0644, gm8775c_show, gm8775c_store); static struct attribute *egm8775c_debug_attrs[] = { &dev_attr_gm8775c.attr, NULL, }; static struct attribute_group gm8775c_debug_attr_group = { .name = "gm8775c_debug", .attrs = egm8775c_debug_attrs, }; static int gm8775c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct gm8775c_priv *gm8775c; int i; int ret; dev_info(&client->dev, "Probing gm8775c at address 0x%02x\n", client->addr); if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BYTE_DATA)) { dev_err(&client->dev, "doesn't support I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK\n"); return -ENODEV; } gm8775c = devm_kzalloc(&client->dev, sizeof(*gm8775c), GFP_KERNEL); if (!gm8775c) return -ENOMEM; gm8775c->client = client; i2c_set_clientdata(client, gm8775c); gm8775c->gpio_509 = GPIO_PIN_509; ret = gpio_request(gm8775c->gpio_509, "gm8775c_gpio_509"); if (ret) { dev_err(&client->dev, "Failed to request GPIO %d\n", gm8775c->gpio_509); return ret; } ret = gpio_direction_output(gm8775c->gpio_509, 1); if (ret) { dev_err(&client->dev, "Failed to set GPIO %d as output\n", gm8775c->gpio_509); gpio_free(gm8775c->gpio_509); return ret; } dev_info(&client->dev, "Configured GPIO %d as output and set to 1\n", gm8775c->gpio_509); gm8775c->gpio_511 = GPIO_PIN_511; ret = gpio_request(gm8775c->gpio_511, "gm8775c_gpio_511"); if (ret) { dev_err(&client->dev, "Failed to request GPIO %d\n", gm8775c->gpio_511); gpio_free(gm8775c->gpio_509); return ret; } ret = gpio_direction_output(gm8775c->gpio_511, 0); if (ret) { dev_err(&client->dev, "Failed to set GPIO %d as output\n", gm8775c->gpio_511); gpio_free(gm8775c->gpio_511); gpio_free(gm8775c->gpio_509); return ret; } dev_info(&client->dev, "Configured GPIO %d as output and set to 0\n", gm8775c->gpio_511); gm8775c->gpio_493 = GPIO_PIN_493; ret = gpio_request(gm8775c->gpio_493, "gm8775c_gpio_493"); if (ret) { dev_err(&client->dev, "Failed to request GPIO %d\n", gm8775c->gpio_493); gpio_free(gm8775c->gpio_511); gpio_free(gm8775c->gpio_509); return ret; } ret = gpio_direction_output(gm8775c->gpio_493, 1); if (ret) { dev_err(&client->dev, "Failed to set GPIO %d as output\n", gm8775c->gpio_493); gpio_free(gm8775c->gpio_493); gpio_free(gm8775c->gpio_511); gpio_free(gm8775c->gpio_509); return ret; } dev_info(&client->dev, "Configured GPIO %d as output and set to 1\n", gm8775c->gpio_493); for (i = 0; i < REGISTER_NUM; i++) { ret = gm8775c_write(gm8775c_reg_defaults[i].reg, gm8775c_reg_defaults[i].def, gm8775c->client); dev_info(&client->dev, "Wrote and verified 0x%02x to register 0x%02x\n", gm8775c_reg_defaults[i].def, gm8775c_reg_defaults[i].reg); } ret = sysfs_create_group(&client->dev.kobj, &gm8775c_debug_attr_group); if (ret) { dev_err(&client->dev, "failed to create attr group\n"); } dev_info(&client->dev, "gm8775c driver loaded successfully.\n"); return 0; gpio_free(gm8775c->gpio_493); gpio_free(gm8775c->gpio_511); gpio_free(gm8775c->gpio_509); return ret; } static void gm8775c_remove(struct i2c_client *client) { struct gm8775c_priv *gm8775c = i2c_get_clientdata(client); sysfs_remove_group(&client->dev.kobj, &gm8775c_debug_attr_group); gpio_free(gm8775c->gpio_493); gpio_free(gm8775c->gpio_511); gpio_free(gm8775c->gpio_509); dev_info(&client->dev, "gm8775c driver removed.\n"); } static struct i2c_driver gm8775c_driver = { .driver = { .name = "gm8775c-mipi-to-lvds", .of_match_table = of_match_ptr(gm8775_of_match), }, .probe = gm8775c_probe, .remove = gm8775c_remove, .id_table = gm8775_i2c_id, }; static int __init gm8775c_modinit(void) { int ret; ret = i2c_add_driver(&gm8775c_driver); if (ret != 0) printk("Failed to register gm8775c i2c driver : %d \n", ret); return ret; } late_initcall(gm8775c_modinit); static void __exit gm8775c_exit(void) { i2c_del_driver(&gm8775c_driver); } module_exit(gm8775c_exit); //module_i2c_driver(gm8775c_driver); MODULE_AUTHOR("goa"); MODULE_DESCRIPTION("GM8775C I2C Client Driver"); MODULE_LICENSE("GPL"); 3.2 写Makefile与Kconfig，代码加入内核上述代码应该创建为名为gm8775c.c的文件，需要提前创建一个名为gm8775c的文件夹用来存放gm8775c.c，同时在gm8775c文件夹下我们需要创建Makefile与Kconfig文件，用来声明编译我们举例说明，如果SDK的内核目录是 LinuxSDK/rk3576_linux6.1_release/kernel-6.1/ 那么此文件夹可以是 LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/gm8775c 文件夹下应该有以下内容 root:~/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/gm8775c$ ls gm8775c.c Makefile Kconfig 同时在gm8775c文件夹的上级目录，比如LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/gm8775c，上级目录就是input，对input下的Makefile与Kconfig文件增加如下内容/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/Makefile + obj-y += gm8775c/ /LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/Kconfig + source "drivers/input/gm8775c/Kconfig" 完成上述操作，使用./build.sh kernel-config命令，将gm8775c配置选中后保存，进入到下一步3.3 设备树增加对应声明将前面的屏幕参数填在对应的接口时序参数节点dsi_timing0 里，用于配置显示接口的时序参数，影响分辨率、刷新率等&dsi_timing0 { clock-frequency = <74000000>; hactive = <1920>; vactive = <1080>; hfront-porch = <100>; hsync-len = <5>; hback-porch = <35>; vfront-porch = <20>; vsync-len = <10>; vback-porch = <15>; hsync-active = <0>; vsync-active = <0>; de-active = <0>; pixelclk-active = <1>; };在设备树MIPI转LVDS芯片所连接的i2c节点中新加gm8775c节点，启动这个驱动（文件夹附带的两个设备树文件直接覆盖原文件即可） &i2c6 { status = "okay"; gm8775c: gm8775c@2d { compatible = "gm8775c,mipi_to_lvds"; reg = <0x2d>; }; gt911: gt911@5d { status = "okay"; compatible = "goodix,gt911"; reg = <0x5d>; interrupt-parent = <&gpio1>; interrupts = <18 0>; irq-gpios = <&gpio1 RK_PC2 0>; reset-gpios = <&xl8574t 0 GPIO_ACTIVE_HIGH>; pinctrl-names = "default"; pinctrl-0 = <&gt911_int>; touchscreen-inverted-y; touchscreen-inverted-x; }; };./build.sh重新编译，烧录系统，开机即可正常显示，每次开机自动写配置文件。
在前面，我们已经实现了一个驱动，下面就可以写一个程序来使用一下这个驱动1.概念回顾之前的介绍中，我们提到了一个驱动的全部使用生命周期设备结构体->实现file_operations的文件读、写、打开、关闭函数->给出模块入口出口函数内核或者用户手动加载驱动->进入模块入口，注册设备结构体，在/dev下新增设备接口用户的APP启动 -> 打开设备接口 -> 通过设备结构体的file_operations调用misc_open -> 向设备写入数据 -> 通过设备结构体的file_operations调用misc_write -> 读设备的数据 -> 通过设备结构体的file_operations调用misc_read -> 关闭设备 -> 进入模块出口，释放设备结构体，调用misc_release 2.流程分析在这里，我们就要实现用户启动后的动作，辅以查看log，上面的设备驱动调用分析是否正确首先程序我们先打开设备，驱动会打印misc open然后读取设备的数据，设备驱动里我们给了默认值abcde，所以我们会读取到abcde再向设备写入了12345，驱动会在控制台打印write:12345最后我们关闭了设备，设备会注销并打印misc release通过这个流程的实现，相信Linux驱动的大致框架会在脑海里有一个初步的概念编译程序：aarch64-linux-gnu-gcc APP.c -o app将app文件拷贝到开发板上执行即可APP.C #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> int main(int argc, char * argv[]){ int fd; char buf[64] = {0}; fd = open("/dev/MISC_TEST",O_RDWR); if(fd < 0){ perror("open error\n"); return fd; } read(fd, &buf, sizeof(buf)); printf("buf %s\n",buf); char buf1[64] = "12345"; write(fd, &buf1, strlen(buf1)); close(fd); return 0; }
linux驱动加载一.insmod rmmod lsmod加载模块insmod misc_test.ko查看系统加载的模块 lsmod删除加载的模块 rmmod misc_test.ko二.modprobe加载模块 modprobe misc_test.ko此命令buildroot一般默认不带，可以自动处理多个模块之间的依赖三.静态编译进内核，系统启动自动加载可以使用 dmesg | -grep misc_test 来查看相关启动log
编译前置不管在哪里增加驱动代码都要注意做这三步1.创建驱动文件夹再写驱动代码，如mkdir misc_test2.写完驱动代码要在驱动代码同级目录增加Makefile和Kconfig3.在驱动文件夹上一级的Makefile和Kconfig增加驱动文件夹的编译配置做好这三步，才可以进行编译root:~/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/misc $ mkdir misc_test && cd misc_test root:~/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/misc/misc_test$ touch misc_test.c Makefile Kconfig 编辑完成后 在misc_test文件夹的上级目录，比如/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/misc，对misc下的Makefile与Kconfig文件增加如下内容 /LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/misc/Makefile + obj-y += misc_test/ /LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/misc/Kconfig + source "drivers/misc/misc_test/Kconfig"Makefile文件 KERNEL_DIR=../../../ #KERNEL_DIR=/home/l/share/tspi_linux_sdk_repo_20240131/kernel ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- export ARCH CROSS_COMPILE PWD=$(shell pwd) obj-m += misc_test.o all: $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules .PHONE:clean clean: $(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean Kconfig文件 config MISC_TEST tristate "MISC TEST" help misc_test 1.编译进内核./build.sh kernel-config 进入配置页面，找到MISC_TEST配置，将其选中为Y，保存配置后./build.sh编译即可2.编译为模块./build.sh kernel-config 进入配置页面，找到MISC_TEST配置，将其选中为M，保存配置后./build.sh编译即可2.手动编译在驱动文件夹下直接使用make命令执行Makefile，也可以得到驱动模块KO文件，或者找到$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules语句里面应该填的编译器，内核目录，驱动模块目录，对应填写直接执行也可编译
此文会根据misc测试驱动的代码架构来讲解简单的驱动如何编写。1.驱动框架与Linux一切皆文件思想1.1 驱动思想理解Linux系统通过文件抽象实现设备管理，所有硬件设备都通过文件接口进行访问。这种设计体现在设备节点以文件形式存在于/dev目录使用标准文件操作接口（open/close/read/write）驱动开发者通过实现文件操作函数与硬件交互比如说我们下面的misc测试驱动，就是一个典型的最小驱动，包含Linux最常用的读、写、打开、关闭函数核心就是实现设备结构体的file_operations的文件读、写、打开、关闭函数，给出模块入口出口函数这个框架就类似JAVA的CRUD，属于是同一类思想，只不过驱动操作的是硬件设备数据，JAVA操作的是软件数据库管理理解这个核心，写SPI、I2C，或者其他简单的设备驱动都是大致相同的，首先实现设备结构体，再实现file_operations的函数赋给设备结构体，最后是模块入口出口函数misc（杂项）设备是Linux内核提供的一种简化字符设备驱动开发的机制主设备号固定为10，次设备号由内核动态分配（MISC_DYNAMIC_MINOR）自动创建设备节点（需配合udev机制）相比传统字符设备注册（register_chrdev），简化了开发流程适用于功能简单的设备驱动开发1.2 驱动使用过程生命周期设备结构体->实现file_operations的文件读、写、打开、关闭函数->给出模块入口出口函数内核或者用户手动加载驱动->进入模块入口，注册设备结构体，在/dev下新增设备接口用户的APP启动 -> 打开设备接口 -> 通过设备结构体的file_operations调用misc_open -> 向设备写入数据 -> 通过设备结构体的file_operations调用misc_write -> 读设备的数据 -> 通过设备结构体的file_operations调用misc_read -> 关闭设备 -> 进入模块出口，释放设备结构体，调用misc_release 下列就是一个简单的驱动核心部分内核驱动函数 说明 misc_open 设备初始化/资源分配 misc_read 从设备获取数据 misc_write 向设备发送数据 misc_release 设备资源释放 // 文件操作结构体 struct file_operations misc_fops = { .owner = THIS_MODULE, .open = misc_open, .release = misc_release, .read = misc_read, .write = misc_write, }; // misc设备结构体 struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号 .name = "hello_misc", // 设备节点名（/dev/hello_misc） .fops = &misc_fops, // 关联文件操作 }; module_init(misc_test_init); 模块加载入口函数 module_exit(misc_test_exit); 模块出口函数 1.3 驱动代码下面的程序我们用这种框架，实现一个简单的misc驱动，当用户加载或者释放驱动，都会有相应的提示，用户写会实现从用户空间拷贝数据，用户读会实现将拷贝来的数据拷贝到用户空间misc_test.c #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/uaccess.h> //设备打开 int misc_open(struct inode *inode, struct file *file){ printk("misc open\n"); return 0; } //设备关闭 int misc_release(struct inode *inode, struct file *file){ printk("misc release\n"); return 0; } //读设备的数据 ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t){ char kbuf[64] = "qerwq"; if(copy_to_user(ubuf,kbuf,strlen(kbuf)) != 0){ printk("copy to user error\n"); return -1; } return 0; } //向设备写数据 ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){ char kbuf[64] = {0}; if(copy_from_user(kbuf,ubuf,size) != 0){ printk("copy from user error\n"); return -1; } printk("write: %s\n",kbuf); return 0; } // 文件操作结构体 struct file_operations misc_fops = { .owner = THIS_MODULE, .open = misc_open, .release = misc_release, .read = misc_read, .write = misc_write, }; // misc设备结构体 struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号 .name = "hello_misc", // 设备节点名（/dev/hello_misc） .fops = &misc_fops, // 关联文件操作 }; static int misc_test_init(void){ int ret; ret = misc_register(&misc_dev); if(ret<0){ printk("misc init faild\n"); return -1; } printk(KERN_WARNING "misc hello!\n"); return 0; } static void misc_test_exit(void){ misc_deregister(&misc_dev); printk(KERN_WARNING "bye!\n"); } module_init(misc_test_init); module_exit(misc_test_exit); MODULE_LICENSE("GPL");
一 程序位置分布RTOS中DSMC代码，drv_dsmc_host.c是主机驱动代码，drv_dsmc_slave.c是从机驱动代码，dsmc_test.c是测试程序 SDK/GA3506_Linux_Source/rtos/bsp/rockchip/common/drivers/drv_dsmc_host.c SDK/GA3506_Linux_Source/rtos/bsp/rockchip/common/drivers/drv_dsmc_slave.c SDK/GA3506_Linux_Source/rtos/bsp/rockchip/common/tests/dsmc_test.c SDK/GA3506_Linux_Source/rtos/bsp/rockchip/common/hal/lib/hal/src/hal_dsmc_host.c SDK/GA3506_Linux_Source/rtos/bsp/rockchip/common/hal/lib/hal/src/hal_dsmc_slave.c SDK/GA3506_Linux_Source/rtos/bsp/rockchip/common/drivers/dma.c 二 主机驱动代码解析drv_dsmc_host.c -> hal_dsmc_host.c -> drv_dsmc_host.c -> dsmc_test.c2.1 系统如何启动驱动？首先，在drv_dsmc_host.c主机驱动中rockchip_dsmc_host_probe(void)函数下方，使用了INIT_DEVICE_EXPORT(rockchip_dsmc_host_probe)将rockchip_dsmc_host_probe注册到了设备自动初始化函数这是RT-Thread自动初始化机制，听起来似乎很高大上，实际上只是将函数入口地址记录，在board_base.c中的rt_hw_board_init()启动板卡，完成基础初始化后，会在调用rt_components_board_init()，rt_components_board_init()内部实质上是执行函数指针，也就是执行对应的rockchip_dsmc_host_probe函数来启动驱动。2.2 主机驱动初始化代码流向rockchip_dsmc_host_probe -> HAL_DSMC_HOST_Init -> DSMC_HOST_DataInit -> DSMC_HOST_MapInit -> DSMC_HOST_LbInit -> DSMC_HOST_LbCmnConfig -> DSMC_HOST_LbCmnRgnConfig -> DSMC_HOST_LbCsrConfig -> lbReadReg与lbWriteReg在底层lbReadReg与lbWriteReg会去读写寄存器配置，然后逐级返回完成初始化打印HAL_DSMC_HOST_Init OK2.3 主机驱动代码框架drv_dsmc_host.c驱动主要做外设初始化与DMA数据拷贝搬运和直接读写指定地址的用户调用实现在RTOS中drv_dsmc_host.c的rockchip_dsmc_host_probe初始化函数会从RTOS层调用HAL层hal_dsmc_host.c文件的HAL_DSMC_HOST_Init去写寄存器初始化DSMC外设从下图drv_dsmc_host.c函数列表中可以看出来，除了初始化外设，主要就是DMA数据拷贝搬运的实现和直接读写指定地址的实现并且驱动通过ops操作集将DMA数据拷贝搬运和直接读写指定地址函数用作上层调用函数接口在drv_dsmc_host.c中，除了初始化函数，还对dsmc_host_ops这个ops操作集合做读写与底层拷贝等函数的实现，本质上类似 Linux 的 file_operations 或 platform_driver 的 ops，统一抽象了对硬件的访问接口（read/write/copy_from/copy_to等），便于上层调用和适配不同实现，（参考Linux通用的驱动篇）给ops赋值函数指针之后，在上层应用函数，就可以直接使用一个dsmc_host_ops类型的结构体，调用其读写函数，拿到DSMC通信数据struct dsmc_host_ops { rt_err_t (*read)(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs, uint32_t region, unsigned long addr, uint32_t *data); rt_err_t (*write)(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs, uint32_t region, unsigned long addr, uint32_t val); rt_err_t (*copy_from)(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs, uint32_t region, uint32_t from, uint32_t dst_phys, size_t size); rt_err_t (*copy_from_state)(struct rockchip_rt_dsmc_host *dsmc_host); rt_err_t (*copy_to)(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs, uint32_t region, uint32_t src_phys, uint32_t to, size_t size); rt_err_t (*copy_to_state)(struct rockchip_rt_dsmc_host *dsmc_host); }; static struct dsmc_host_ops rockchip_dsmc_ops = { .read = DSMC_HOST_ReadData, .write = DSMC_HOST_WriteData, .copy_from = DSMC_HOST_CopyFrom, .copy_from_state = DSMC_HOST_CopyFromState, .copy_to = DSMC_HOST_CopyTo, .copy_to_state = DSMC_HOST_CopyToState, }; 三 从机驱动代码解析drv_dsmc_slave.c -> hal_dsmc_slave.c -> drv_dsmc_slave.c -> dsmc_test.c3.1 系统如何启动驱动？与主机代码基本一致，drv_dsmc_slave.c中rockchip_dsmc_slave_probe(void)函数下方，使用了INIT_DEVICE_EXPORT(rockchip_dsmc_slave_probe)将rockchip_dsmc_slave_probe注册到了设备自动初始化函数3.2 从机驱动初始化代码流向rockchip_dsmc_slave_probe -> HAL_DSMC_SLAVE_Init -> WRITE_REG -> rockchip_dsmc_slave_probe 在底层WriteReg会去读写寄存器配置，然后逐级返回完成初始化打印rockchip dsmc local bus slave driver initialized3.3 从机驱动代码框架从机代码比较简单，只需要初始化写寄存器，然后配置中断函数即可，主机发来的数据可以直接读取dmaHAL_DSMC_SLAVE_IrqHander是中断处理入口，判断是否要启动DMA，DSMC_SLAVE_DmaTrigger则负责具体执行通知硬件搬运数据的动作，它们一起实现了从机对主机搬运请求的响应和触发static HAL_Status DSMC_SLAVE_DmaTrigger(struct LBC_SLAVE_CSR_REG *pReg) { int timeOut = 1000; /* wait interrupt register empty */ while (timeOut-- > 0) { if (!(READ_REG(pReg->LBC_S2H_INT_STA) & (0x1 << S2H_INT_FOR_DMA_NUM))) { break; } if (timeOut == 0) { HAL_DBG_ERR("Timeout waiting for s2h interrupt empty!\n"); return HAL_TIMEOUT; } HAL_CPUDelayUs(1); } /* trigger a slave to host interrupt which will start dma hardware mode copy */ WRITE_REG(pReg->APP_CON15, 0x1); return HAL_OK; } /** * @brief DSMC_SLAVE irq hander. * @param slave: pointer to a DSMC_SLAVE structure. * @return HAL_Status */ HAL_Status HAL_DSMC_SLAVE_IrqHander(struct HAL_DSMC_SLAVE *slave) { struct LBC_SLAVE_CSR_REG *pReg; HAL_ASSERT(IS_LBC_SLAVE_CSR_INSTANCE(slave->pReg)); pReg = slave->pReg; if (READ_REG(pReg->LBC_CON15)) { DSMC_SLAVE_DmaTrigger(pReg); } /* clear all h2s interrupt */ WRITE_REG(pReg->APP_H2S_INT_STA, LBC_SLAVE_CSR_APP_H2S_INT_STA_APP_H2S_INT_STA_MASK); return HAL_OK; }
一 前言在前面分析了驱动大致走向和函数，驱动细节我们无法分析，因为拿不到底层寄存器相关的细节文档所以我们应用的重中之重，就是需要仔细分析DMA读写，把DMA读写学会，就可以拿到数据给上层应用使用DMA本身没有绑定DSMC，但是使用DMA驱动时，会给DMA配置DSMC的内存地址，这样DMA读写直接按块访问内存中映射的DSMC应用请直接看后面的 三 外部调用例程做库文件 ，例程修改为库文件和头文件作为接口库，提供对外接口调用二 RTOS DMA读写分析2.1 DMA驱动大致内容具体驱动分析请看前面的DSMC代码分析之RTOSdrv_dsmc_host.c驱动主要做外设初始化与DMA数据拷贝搬运和直接读写指定地址的用户调用实现在RTOS中drv_dsmc_host.c的rockchip_dsmc_host_probe初始化函数会从RTOS层调用HAL层hal_dsmc_host.c文件的HAL_DSMC_HOST_Init去写寄存器初始化DSMC外设从下图drv_dsmc_host.c函数列表中可以看出来，除了初始化外设，主要就是DMA数据拷贝搬运的实现和直接读写指定地址的实现并且驱动通过ops操作集将DMA数据拷贝搬运和直接读写指定地址函数用作上层调用函数接口2.2 DMA用户使用流程实际上是实现主控 → DSMC控制器 → Local Bus → FPGA等来做读写测试用户程序主要是通过DMA或直接地址读写向DSMC控制器写入读取数据，数据通过Local Bus总线，读写外部设备下面所有DMA相关函数copy_to、copy_from以及dma_memcpy函数，内部都通过rt_device_control对dma设置状态w完成DMA读写rt_device_control设置的参数最终在SDK/GA3506_Linux_Source/rtos/bsp/rockchip/common/drivers/dma.c文件中的rt_dma_control函数中通过switch来实现DMA具体操作 rt_device_control(dma, RT_DEVICE_CTRL_DMA_REQUEST_CHANNEL, m2m_xfer); //请求 DMA 通道 rt_device_control(dma, RT_DEVICE_CTRL_DMA_SINGLE_PREPARE, m2m_xfer); //DMA 传输准备信号 rt_device_control(dma, RT_DEVICE_CTRL_DMA_START, m2m_xfer); //单次 DMA 传输准备 rt_device_control(dma, RT_DEVICE_CTRL_DMA_STOP, m2m_xfer); //停止 DMA rt_device_control(dma, RT_DEVICE_CTRL_DMA_RELEASE_CHANNEL, m2m_xfer); //释放 DMA 通道程序结构大致就是下方这样的伪代码，从结构上我们就能看出来，主要是初始化后，通过ops操作集来读写内存拿到数据，或者通过DMA完成读写main() └──> dsmc_test() ├──> psram_simple_test() // PSRAM基本功能测试（CPU读写+数据校验） │ ├──> ops->write() // 向PSRAM写入测试数据 │ ├──> ops->read() // 从PSRAM读取测试数据 │ └──> 数据校验 ├──> dma_test_psram() // PSRAM DMA搬运测试（DMA搬运+数据校验） │ ├──> dma_memcpy() // 用DMA搬运数据到/从PSRAM │ └──> 数据校验 ├──> plc_simple_test() // PLC类外设基本功能测试（CPU读写+数据校验） │ ├──> ops->write() // 向PLC外设写入测试数据 │ ├──> ops->read() // 从PLC外设读取测试数据 │ ├──> ops->copy_to // 从DMA拷贝数据到FPGA │ ├──> ops->copy_to_state //拷贝状态 │ ├──> ops->copy_from// 从FPGA拷贝数据到DMA │ ├──> ops->copy_from_state//拷贝状态 │ └──> 数据校验 ├──> dma_test_lb() // Local Bus外设DMA搬运测试（DMA搬运+数据校验） │ ├──> dma_memcpy() // 用DMA搬运数据到/从Local Bus外设 │ └──> 数据校验 ├──> fpga_simple_test() // FPGA外设基本功能测试（CPU读写+数据校验） │ ├──> ops->write() // 向FPGA写入测试数据 │ ├──> ops->read() // 从FPGA读取测试数据 │ └──> 数据校验 ├──> dsmc_slave_latency_cpu() // CPU方式外设访问延迟测试（多次操作统计平均延迟） │ └──> ops->read()/ops->write()（循环多次） // 多次读/写外设，统计耗时 ├──> dsmc_slave_speed_cpu() // CPU方式外设带宽测试（大块数据读写统计带宽） │ └──> ops->read()/ops->write()（大块数据） // 用CPU搬运大块数据，统计带宽 └──> dsmc_slave_speed_dma() // DMA方式外设带宽测试（大块数据搬运统计带宽） └──> dma_memcpy()（大块数据） // 用DMA搬运大块数据，统计带宽1 内存搬运/性能测试 dma_memcpy:用DMA控制器实现内存到内存的拷贝，并和CPU memcpy做对比，测试速度 2 PSRAM/外设简单功能测试 psram_simple_test:依次对PSRAM做多轮写入/读取/校验，测试不同数据模式 3 DMA 读写 PSRAM/外设测试 dma_test_psram:DDR-> SRAM，PSRAM->DDR，PSRAM->PSRAM三种方向的DMA搬运 4 PLC（Local Bus）功能测试 plc_simple_test:用DSMC的驱动内部ops接口测试主机和Local Bus外设的数据搬运。先从DDR DMA写到外设，外设DMA读回DDR 5 CPU 访问延迟测试 dsmc_slave_latency_cpu:反复用CPU读/写DSMC外设的固定地址，统计总用时，计算单次访问的平均延迟。 6 CPU 访问带宽测试 dsmc_slave_speed_cpu:用CPU连续写入/读取DSMC外设的大块数据，统计总用时，计算带宽（MB/s） 7 DMA 访问带宽测试 dsmc_slave_speed_dma：用DMA连续搬运大块数据到DSMC外设，再搬回，测试DMA带宽（MB/s） 三 外部调用例程做库文件3.1 外部文件调用库文件代码本质已经完善了读写，我们只需要把例程改为库文件，就可以给其他程序使用了外部调用基本框架都可以不变，只需要把源文件放到同级目录，rt_dsmc_lib.h头文件加上，就可以在用户新文件调用 #include "rt_dsmc_lib.h" void dsmc_test(int argc, char **argv) { rt_device_t dev = rt_device_find("dsmc_host"); if (dev == RT_NULL) { rt_kprintf("dsmc_host not found\n"); return; } struct rockchip_rt_dsmc_host *dsmc_host = (struct rockchip_rt_dsmc_host *)dev; int *src = NULL, *dst = NULL; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t size = DMA_SIZE; src = rt_dma_malloc(size); RT_ASSERT(src != RT_NULL); dst = (int *)map->phys; dma_memcpy(dst, src, size); // 只测试第0个cs psram_simple_test(dsmc_host, 0); dsmc_slave_latency_cpu(dsmc_host, 0); dsmc_slave_speed_cpu(dsmc_host, 0); dma_test_psram(dsmc_host, 0); plc_simple_test(dsmc_host, 0); } #ifdef RT_USING_FINSH #include <finsh.h> MSH_CMD_EXPORT(dsmc_test, dsmc tester); #endif 3.2 例程修改库文件rt_dsmc_lib.c #include "rt_dsmc_lib.h" #define IO_BW_32 0 #define IO_BW_16 1 #define IO_BW_8 2 #define IO_TYPE_0 0 #define DMA_SIZE (0x10000) #define COUNTS 100 static struct rt_device *dma; static rt_sem_t mem_sem; static void m2m_complete(void *param) { rt_sem_release(mem_sem); } void dma_memcpy(int *dst_mem, int *src_mem, int size) { struct rt_dma_transfer *m2m_xfer; rt_err_t ret; int i, len; uint32_t tick_s, tick_e; /* ms */ m2m_xfer = (struct rt_dma_transfer *)rt_malloc(sizeof(*m2m_xfer)); mem_sem = rt_sem_create("memSem", 0, RT_IPC_FLAG_FIFO); RT_ASSERT(m2m_xfer != RT_NULL); RT_ASSERT(src_mem != RT_NULL); RT_ASSERT(dst_mem != RT_NULL); RT_ASSERT(mem_sem != RT_NULL); len = size / sizeof(int); for (i = 0; i < len; i++) src_mem[i] = len - i; rt_memset(dst_mem, 0x0, size); rt_memset(src_mem, 0x6, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)src_mem, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)dst_mem, size); dma = rt_device_find("dmac0"); /* memcpy test */ rt_memset(m2m_xfer, 0x0, sizeof(*m2m_xfer)); m2m_xfer->direction = RT_DMA_MEM_TO_MEM; m2m_xfer->dst_addr = (rt_uint32_t)dst_mem; m2m_xfer->src_addr = (rt_uint32_t)src_mem; m2m_xfer->len = size; m2m_xfer->callback = m2m_complete; m2m_xfer->cparam = m2m_xfer; ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_REQUEST_CHANNEL, m2m_xfer); RT_ASSERT(ret == RT_EOK); tick_s = HAL_GetTick(); for (i = 0; i < COUNTS; i++) { /* dma copy start */ ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_SINGLE_PREPARE, m2m_xfer); RT_ASSERT(ret == RT_EOK); ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_START, m2m_xfer); RT_ASSERT(ret == RT_EOK); /* wait for complete */ ret = rt_sem_take(mem_sem, RT_WAITING_FOREVER); RT_ASSERT(ret == RT_EOK); ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_STOP, m2m_xfer); RT_ASSERT(ret == RT_EOK); } tick_e = HAL_GetTick(); ret = rt_memcmp(src_mem, dst_mem, size); rt_kprintf("dma memcpy [%s]: avg: %7u MB/S with src: 0x%x dst: 0x%x len: %u counts: %u\n", ret ? "FAIL" : "PASS", size * COUNTS / (tick_e - tick_s) / 1000, src_mem, dst_mem, size, COUNTS); ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_RELEASE_CHANNEL, m2m_xfer); RT_ASSERT(ret == RT_EOK); tick_s = HAL_GetTick(); for (i = 0; i < COUNTS; i++) rt_memcpy(dst_mem, src_mem, size); tick_e = HAL_GetTick(); ret = rt_memcmp(dst_mem, src_mem, size); rt_kprintf("cpu memcpy [%s]: avg: %7u MB/S with src: 0x%x dst: 0x%x len: %u counts: %u\n", ret ? "FAIL" : "PASS", size * COUNTS / (tick_e - tick_s) / 1000, src_mem, dst_mem, size, COUNTS); rt_sem_delete(mem_sem); rt_free(m2m_xfer); } int dma_test_psram(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { int *src = NULL, *dst = NULL; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t size = DMA_SIZE; rt_kprintf("dma test1: copy from ddr to psram\n"); src = rt_dma_malloc(size); RT_ASSERT(src != RT_NULL); dst = (int *)map->phys; dma_memcpy(dst, src, size); rt_dma_free(src); rt_kprintf("dma test2: copy from psram to ddr\n"); src = (int *)map->phys; dst = rt_dma_malloc(size); RT_ASSERT(dst != RT_NULL); dma_memcpy(dst, src, size); rt_dma_free(dst); rt_kprintf("dma test3: copy from psram to psram\n"); src = (int *)map->phys; dst = (int *)(map->phys + size); dma_memcpy(dst, src, size); rt_kprintf("DMA test done\n"); return 0; } void psram_simple_test(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i, j; struct dsmc_host_ops *ops = dsmc_host->ops; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t test_cap; uint32_t read, write; uint32_t test_data[] = {0x5aa5f00f, 0x0, 0xffffffff, 0x3cc3d22d}; rt_kprintf("start cs%d simple test\n", cs); for (j = 0; j < 4; j++) { test_cap = map->size; rt_kprintf("write, test_cap = 0x%x\n", test_cap); rt_kprintf(" write\n"); for (i = 0; i < test_cap; i += 4) { ops->write(dsmc_host, cs, 0, i, test_data[j]); } rt_kprintf("read\n"); test_cap = map->size; rt_kprintf(" read\n"); for (i = 0; i < test_cap; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); if (read != test_data[j]) { rt_kprintf("addr offset 0x%x: read = 0x%x, wr = 0x%x, error(0x%x)\n", i, read, test_data[j], test_data[j] ^ read); } } } rt_kprintf("phase 1: write address to data\n"); test_cap = map->size; for (i = 0; i < test_cap; i += 4) { ops->write(dsmc_host, cs, 0, i, map->phys + i); } for (i = 0; i < test_cap; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); write = map->phys + i; if (read != write) { rt_kprintf("addr offset 0x%x: read = 0x%x, wr = 0x%x, error(0x%x)\n", i, read, write, write ^ read); } } rt_kprintf("phase 2: write 0x0f0f5aa5 + address\n"); test_cap = map->size; for (i = 0; i < test_cap; i += 4) { ops->write(dsmc_host, cs, 0, i, map->phys + i + 0x0f0f5aa5); } test_cap = map->size; for (i = 0; i < test_cap; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); write = map->phys + i + 0x0f0f5aa5; if (read != write) { rt_kprintf("addr offset 0x%x: read = 0x%x, wr = 0x%x, error(0x%x)\n", i, read, write, write ^ read); } } rt_kprintf("phase 3: dma copy by software\n"); dma_test_psram(dsmc_host, cs); rt_kprintf("cs%d simple test done.\n", cs); } int plc_simple_test(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i; struct dsmc_host_ops *ops = dsmc_host->ops; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; int *src = NULL, *dst = NULL; uint32_t size = DMA_SIZE; int ret = 0; uint32_t timeout = 1000000; rt_kprintf("phase 1: dma memcopy from host DDR to slave memory\n"); src = rt_dma_malloc(size); RT_ASSERT(src != RT_NULL); /* init src address */ rt_memset(src, 0x66, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)src, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)map->phys, size); timeout = 10000; if (!ops->copy_to(dsmc_host, cs, 0, (uint32_t)src, 0x0, size)) { while (ops->copy_to_state(dsmc_host)) { rt_thread_mdelay(1); timeout--; if (!timeout) { rt_kprintf("DSMC: wait copy to complete timeout\n"); ret = -1; goto err; } } rt_memcmp(src, (int *)map->phys, size); } else { rt_kprintf("err: phase 1: test skip!!!\n"); } rt_kprintf("phase2 : dma memcopy from slave memory to host DDR\n"); dst = rt_dma_malloc(size); RT_ASSERT(dst != RT_NULL); rt_memset(dst, 0x88, size); /* init plc src address */ for (i = 0; i < size; i += 4) { ops->write(dsmc_host, cs, 0, i, 0x10100000 + i); } HAL_DCACHE_CleanInvalidateByRange((uint32_t)dst, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)map->phys, size); timeout = 10000; if (!ops->copy_from(dsmc_host, cs, 0x0, 0, (uint32_t)dst, size)) { while (ops->copy_from_state(dsmc_host)) { rt_thread_mdelay(1); timeout--; if (!timeout) { rt_kprintf("DSMC: wait copy from complete timeout\n"); ret = -1; goto err; } } rt_memcmp((int *)map->phys, dst, size); } else { rt_kprintf("err: phase 1: test skip!!!\n"); } rt_dma_free(src); rt_dma_free(dst); rt_kprintf("plc_simple_test test done\n"); return 0; err: rt_kprintf("plc_simple_test test error!\n"); return ret; } void dsmc_slave_latency_cpu(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i; struct dsmc_host_ops *ops = dsmc_host->ops; uint32_t read, counter; uint32_t kt[2], diff; rt_kprintf("cpu access dsmc latency test\n"); counter = 1000000; kt[0] = HAL_GetTick(); for (i = 0; i < counter; i++) { ops->read(dsmc_host, cs, 3, 0x100, &read); } kt[1] = HAL_GetTick(); diff = kt[1] - kt[0]; rt_kprintf("counter %d, cost %ums, read latency %uns\n", counter, diff, diff * 1000000 / counter); rt_kprintf("read = 0x%x\n", read); kt[0] = HAL_GetTick(); for (i = 0; i < counter; i++) { ops->write(dsmc_host, cs, 3, 0x100, 0x6); } kt[1] = HAL_GetTick(); diff = kt[1] - kt[0]; rt_kprintf("counter %d, cost %ums, write latency %uns\n", counter, diff, diff * 1000000 / counter); } void dsmc_slave_speed_cpu(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i; struct dsmc_host_ops *ops = dsmc_host->ops; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t kt[2], rd_diff, wr_diff; uint32_t read; rt_kprintf("cpu access dsmc speed\n"); kt[0] = HAL_GetTick(); for (i = 0; i < map->size; i += 4) { ops->write(dsmc_host, cs, 0, i, 0xffffffff); } HAL_DCACHE_CleanInvalidateByRange((uint32_t)map->phys, map->size); kt[1] = HAL_GetTick(); wr_diff = kt[1] - kt[0]; rt_kprintf("cpu write %uMB, cost %ums, %uMB/s\n", (uint32_t)map->size / 1024 / 1024, wr_diff, map->size / wr_diff / 1000); kt[0] = HAL_GetTick(); for (i = 0; i < map->size; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); } kt[1] = HAL_GetTick(); rd_diff = kt[1] - kt[0]; rt_kprintf("cpu read %uMB, cost %ums, %uMB/s\n", (uint32_t)map->size / 1024 / 1024, rd_diff, map->size / rd_diff / 1000); } int dsmc_slave_speed_dma(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { int *src = NULL, *dst = NULL; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t size = DMA_SIZE; rt_kprintf("dma access dsmc speed\n"); rt_kprintf("DMA write DSMC\n"); src = rt_dma_malloc(size); RT_ASSERT(src != RT_NULL); dst = (int *)map->phys; dma_memcpy(dst, src, size); rt_dma_free(src); rt_kprintf("DMA read DSMC\n"); src = (int *)map->phys; dst = rt_dma_malloc(size); RT_ASSERT(dst != RT_NULL); dma_memcpy(dst, src, size); rt_dma_free(dst); return 0; } 3.3 例程修改头文件RT_DSMC_LIB.H #ifndef __RT_DSMC_LIB_H__ #define __RT_DSMC_LIB_H__ #include <stdint.h> #include <rthw.h> #include <rtthread.h> #include "hal_base.h" #include "dma.h" #include "drv_dsmc_host.h" #ifdef __cplusplus extern "C" { #endif struct rockchip_rt_dsmc_host; // 供外部应用调用的测试接口 int dma_test_psram(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs); void psram_simple_test(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs); void dsmc_slave_latency_cpu(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs); void dsmc_slave_speed_cpu(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs); int dsmc_slave_speed_dma(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs); int plc_simple_test(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs); void dma_memcpy(int *dst_mem, int *src_mem, int size); #ifdef __cplusplus } #endif #endif /* __RT_DSMC_LIB_H__ */ 四 Linux DMA例程 #include <stdio.h> #include <stdint.h> #include <time.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <linux/input.h> #include <errno.h> #include <string.h> #include <getopt.h> #include <stdbool.h> #include <libgen.h> #include <sys/select.h> #include <signal.h> const char * const VERSION = "1.0"; #define DMA_MEMCPY_PATH "/dev/dsmc/cs0/region0" /* commands codes */ #define DMA_MEMCPY_SETUP 0x01000000 #define DMA_MEMCPY_START 0x02000000 #define DMA_MEMCPY_GET_TIME 0x03000000 #define DMA_MEMCPY_SHUTDOWN 0x04000000 #define DSMC_MEM 0xc0000000 uint8_t *w_buffer = NULL; uint8_t *r_buffer = NULL; struct DMASetupParams { unsigned int dev_phy_addr; unsigned int dst_phy_addr; unsigned int transfer_direction; unsigned int transfer_single_size; unsigned int transfer_total_size; }; struct DMATransferInfo { float dma_r_rate; float dma_w_rate; uint32_t dma_r_cost_time; uint32_t dma_w_cost_time; uint32_t error_count; }; struct DmaParams { uint32_t address; uint32_t size; uint32_t cycles; char dev[64]; }; struct DMAInfo { int dma_fd; int input_fd; uint16_t *map_base_addr; uint16_t *vir_addr; struct DmaParams params; struct DMATransferInfo dma_trans_info; }; struct CmdLineParams { uint32_t address; uint32_t size; uint32_t cycles; char dev[64]; }; /* Short option names */ static const char g_shortopts [] = ":s:c:vh"; /* Option names */ static const struct option g_longopts [] = { { "size", required_argument, NULL, 's' }, { "cycles", required_argument, NULL, 'c' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { 0, 0, 0, 0 } }; static void usage(char **argv) { fprintf(stdout, "Usage: %s [options]\n\n" "Options:\n" " -s | --size Data size (Unit: Byte) \n" " -c | --cycles Loop read and write times \n" " -v | --version Display version information\n" " -h | --help Show help content\n\n" "Example:\n" " # ./%s -a 0x10000000 -s 65536 -c 1000 \n" "", basename(argv[0]), basename(argv[0])); } static bool parse_parameter(struct CmdLineParams *params, int argc, char **argv) { int opt; memset(params, 0, sizeof(struct CmdLineParams)); while ((opt = getopt_long(argc, argv, g_shortopts, g_longopts, NULL)) != -1) { switch (opt) { case 's': params->size = atoi(optarg); break; case 'c': params->cycles = atoi(optarg); break; case 'h': usage(argv); exit(0); case 'v': printf("version : %s\n", VERSION); exit(0); default: fprintf(stderr, "Unknown option %c\n", optopt); break; } } return true; } //static void neon_copy(volatile void *dst, volatile void *src, int sz) static void neon_copy(void *dst, void *src, int sz) { if (sz & 63) { sz = (sz & -64) + 64; } memcpy(dst, src, sz); } static int dma_init(struct DMAInfo *dma_info) { if (dma_info == NULL) { return -1; } dma_info->dma_fd = open(DMA_MEMCPY_PATH, O_RDWR); if(dma_info->dma_fd < 0) { printf("can't open %s\n", DMA_MEMCPY_PATH); return -1; } return 0; } static void dma_destroy(struct DMAInfo *dma_info) { if (dma_info->map_base_addr != NULL) { munmap(dma_info->map_base_addr, dma_info->params.size); dma_info->map_base_addr = NULL; } if (dma_info->dma_fd != -1) close(dma_info->dma_fd); } static int dma_write(struct DMAInfo *dma_info) { struct DMASetupParams dma_setup_params; unsigned int dev_addr = 0; int i = 0, ret = -1; struct input_event input_buf; int status = 0; struct timeval start, end, time_diff; fd_set input; struct timeval timeout; unsigned time2; float rate; /* Populate the write buffer */ for (i = 0; i < dma_info->params.size ; i++) { w_buffer[i] = i+1; } /* Setup DMA */ dma_setup_params.dev_phy_addr = dma_info->params.address; dma_setup_params.transfer_total_size = dma_info->params.size; dma_setup_params.transfer_single_size = dma_info->params.size; /* 0: write, 1: read */ dma_setup_params.transfer_direction = 0; dev_addr = ioctl(dma_info->dma_fd, DMA_MEMCPY_SETUP, &dma_setup_params); if (dev_addr != 0) { printf("can't get device addr write: 0x%x\n", dev_addr); return -1; } dma_info->vir_addr = mmap(NULL, dma_info->params.size, PROT_READ | PROT_WRITE, MAP_SHARED, dma_info->dma_fd, 0); if (dma_info->vir_addr == MAP_FAILED) { printf("mmap failed\n"); return -1; } neon_copy((unsigned char *)dma_info->vir_addr, (unsigned char *)w_buffer, dma_info->params.size); /* Start DMA */ ret = ioctl(dma_info->dma_fd, DMA_MEMCPY_START, 0); if (ret != 0) { return -1; } time2 = ioctl(dma_info->dma_fd, DMA_MEMCPY_GET_TIME, 0); while(!time2){ time2 = ioctl(dma_info->dma_fd, DMA_MEMCPY_GET_TIME, 0); } rate = ((dma_info->params.size * 1.0 / (time2 / 1000000.0)) / 1024 / 1024); dma_info->dma_trans_info.dma_w_cost_time = time2; dma_info->dma_trans_info.dma_w_rate = ((dma_info->params.size * 1.0 / (dma_info->dma_trans_info.dma_w_cost_time / 1000000.0)) / 1024 / 1024); error: /* Shutdown DMA transfer */ ret = ioctl(dma_info->dma_fd, DMA_MEMCPY_SHUTDOWN, 0); if (ret < 0) { status = -1; } munmap(dma_info->vir_addr, dma_info->params.size); return status; } static int dma_read(struct DMAInfo *dma_info) { struct DMASetupParams dma_setup_params; unsigned int dev_addr = 0; int status = 0; int ret = -1, i = 0; struct input_event input_buf; struct timeval start, end, time_diff; fd_set input; unsigned time2; float rate; /* Setup DMA */ dma_setup_params.dev_phy_addr = dma_info->params.address; dma_setup_params.transfer_total_size = dma_info->params.size; dma_setup_params.transfer_single_size = dma_info->params.size; /* 0: write, 1: read */ dma_setup_params.transfer_direction = 1; dev_addr = ioctl(dma_info->dma_fd, DMA_MEMCPY_SETUP, &dma_setup_params); if (dev_addr != 0) { printf("can't get device addr read: 0x%x\n", dev_addr); return -1; } /* Start DMA */ ret = ioctl(dma_info->dma_fd, DMA_MEMCPY_START, 0); if (ret != 0) { return -1; } dma_info->vir_addr = mmap(NULL, dma_info->params.size, PROT_READ | PROT_WRITE, MAP_SHARED, dma_info->dma_fd, 0); if (dma_info->vir_addr == MAP_FAILED) { printf("mmap failed\n"); return -1; } time2 = ioctl(dma_info->dma_fd, DMA_MEMCPY_GET_TIME, 0); while(!time2){ time2 = ioctl(dma_info->dma_fd, DMA_MEMCPY_GET_TIME, 0); } rate = ((dma_info->params.size * 1.0 / (time2 / 1000000.0)) / 1024 / 1024); neon_copy((unsigned char *)r_buffer, (unsigned char *)dma_info->vir_addr, dma_info->params.size); /* Verify data */ for(i = 0; i < dma_info->params.size ; i ++) { if(r_buffer[i] != w_buffer[i]) { dma_info->dma_trans_info.error_count++; if(dma_info->dma_trans_info.error_count == 1) { printf("\nData verify error at offset %d (0x%x), expect: 0x%x, actual: 0x%x\n", i, i * 2, w_buffer[i], r_buffer[i]); } } } dma_info->dma_trans_info.dma_r_cost_time = time2; dma_info->dma_trans_info.dma_r_rate = ((dma_info->params.size * 1.0 / (dma_info->dma_trans_info.dma_r_cost_time / 1000000.0)) / 1024 / 1024); error: /* Shutdown DMA transfer */ ret = ioctl(dma_info->dma_fd, DMA_MEMCPY_SHUTDOWN, 0); if (ret < 0) { status = -1; } munmap(dma_info->vir_addr, dma_info->params.size); return status; } static void dma_test(struct DMAInfo *dma_info) { int ret = -1; unsigned int num = 0; float total_dma_w_rate = 0, total_dma_r_rate = 0; float max_dma_w_rate = 0, max_dma_r_rate = 0; float min_dma_w_rate = -1, min_dma_r_rate = -1; uint32_t total_dma_w_time = 0, total_dma_r_time = 0; uint32_t max_dma_w_time = 0, max_dma_r_time = 0; uint32_t min_dma_w_time = -1, min_dma_r_time = -1; const uint32_t total_cycles = dma_info->params.cycles; printf("\nStarting DMA test with %d cycles...\n\n", total_cycles); // Initialize total error count uint32_t total_error_count = 0; while (dma_info->params.cycles-- > 0) { ret = dma_write(dma_info); if (ret < 0) { printf("dma_write return error code: %d\r\n\n\n", ret); break; } #if 1 printf("write end ---------\n"); continue; #endif printf("start read----\n"); ret = dma_read(dma_info); if (ret < 0) { printf("dma_read return error code: %d\r\n\n\n", ret); break; } num++; // Calculate error rate for this cycle float error_rate = (dma_info->dma_trans_info.error_count / (float)(dma_info->params.size / 2)) * 100; // Accumulate total error count total_error_count += dma_info->dma_trans_info.error_count; dma_info->dma_trans_info.error_count = 0; // Print current cycle statistics printf("\rCycle %d/%d - Data Size: %d bytes | Write: %.2f MB/s | Read: %.2f MB/s | Write Time: %d us | Read Time: %d us | Error Rate: %.2f%%", num, total_cycles, dma_info->params.size, dma_info->dma_trans_info.dma_w_rate, dma_info->dma_trans_info.dma_r_rate, dma_info->dma_trans_info.dma_w_cost_time, dma_info->dma_trans_info.dma_r_cost_time, error_rate); fflush(stdout); // Accumulate for write average calculation total_dma_w_rate += dma_info->dma_trans_info.dma_w_rate; // Calculate the write maximum value if (dma_info->dma_trans_info.dma_w_rate > max_dma_w_rate) { max_dma_w_rate = dma_info->dma_trans_info.dma_w_rate; } // Calculate the write minimum value if (min_dma_w_rate == -1.0 || dma_info->dma_trans_info.dma_w_rate < min_dma_w_rate) { min_dma_w_rate = dma_info->dma_trans_info.dma_w_rate; } // Accumulate for read average calculation total_dma_r_rate += dma_info->dma_trans_info.dma_r_rate; // Calculate the write maximum value if (dma_info->dma_trans_info.dma_r_rate > max_dma_r_rate) { max_dma_r_rate = dma_info->dma_trans_info.dma_r_rate; } // Calculate the write minimum value if (min_dma_r_rate == -1.0 || dma_info->dma_trans_info.dma_r_rate < min_dma_r_rate) { min_dma_r_rate = dma_info->dma_trans_info.dma_r_rate; } // Accumulated is used to calculate the average time to write total_dma_w_time += dma_info->dma_trans_info.dma_w_cost_time; // Write maximum time calculation if (dma_info->dma_trans_info.dma_w_cost_time > max_dma_w_time) { max_dma_w_time = dma_info->dma_trans_info.dma_w_cost_time; } // Write minimum time calculation if (min_dma_w_time == -1.0 || dma_info->dma_trans_info.dma_w_cost_time < min_dma_w_time) { min_dma_w_time = dma_info->dma_trans_info.dma_w_cost_time; } // Accumulated is used to calculate the average time to read total_dma_r_time += dma_info->dma_trans_info.dma_r_cost_time; // Read maximum time calculation if (dma_info->dma_trans_info.dma_r_cost_time > max_dma_r_time) { max_dma_r_time = dma_info->dma_trans_info.dma_r_cost_time; } // Read minimum time calculation if (min_dma_r_time == -1.0 || dma_info->dma_trans_info.dma_r_cost_time < min_dma_r_time) { min_dma_r_time = dma_info->dma_trans_info.dma_r_cost_time; } } if (num > 0) { // Calculate average error rate float average_error_rate = (total_error_count / (float)(num * (dma_info->params.size / 2))) * 100; // Print final statistics printf("\n\nTest Complete! Final Statistics:\n"); printf("Completed Cycles: %d/%d\n", num, total_cycles); printf("Data Size: %d bytes\n", dma_info->params.size); printf("Write Performance:\n"); printf(" Rate: %.2f MB/s (avg)\n", total_dma_w_rate / num); printf(" Time: %d us (avg)\n", total_dma_w_time / num); printf(" Rate: %.2f MB/s (max)\n", max_dma_w_rate); printf(" Time: %d us (min)\n", min_dma_w_time); printf(" Rate: %.2f MB/s (min)\n", min_dma_w_rate); printf(" Time: %d us (max)\n", max_dma_w_time); printf("\nRead Performance:\n"); printf(" Rate: %.2f MB/s (avg)\n", total_dma_r_rate / num); printf(" Time: %d us (avg)\n", total_dma_r_time / num); printf(" Rate: %.2f MB/s (max)\n", max_dma_r_rate); printf(" Time: %d us (min)\n", min_dma_r_time); printf(" Rate: %.2f MB/s (min)\n", min_dma_r_rate); printf(" Time: %d us (max)\n", max_dma_r_time); printf("\nAverage Error Rate: %.2f%%\n", average_error_rate); } return; } int main(int argc, char *argv[]) { struct CmdLineParams params; struct DMAInfo dma_info; int ret = -1; if (parse_parameter(&params, argc, argv) == false) { printf("Please try --help to see usage.\n"); exit(1); } w_buffer = (uint8_t *)malloc(params.size); if (w_buffer == NULL) { printf("malloc w_buffer failed!\n"); exit(-1); } r_buffer = (uint8_t *)malloc(params.size); if (r_buffer == NULL) { free(w_buffer); printf("malloc r_buffer failed!\n"); exit(-1); } memset(&dma_info, 0, sizeof(struct DMAInfo)); dma_info.params.address = DSMC_MEM; dma_info.params.size = params.size; dma_info.params.cycles = params.cycles; ret = dma_init(&dma_info); if (ret < 0) { dma_destroy(&dma_info); return -1; } dma_test(&dma_info); dma_destroy(&dma_info); if (w_buffer != NULL) { free(w_buffer); } if (r_buffer != NULL) { free(r_buffer); } return 0; } 五 RTOS DMA例程/** * Copyright (c) 2024 Rockchip Electronics Co., Ltd * * SPDX-License-Identifier: Apache-2.0 ****************************************************************************** * @file dsmc_test.c * @version V1.0 * @brief dsmc test * * Change Logs: * Date Author Notes * 2024-10-10 Zhihuan He the first version * ****************************************************************************** */ #include <stdint.h> #include <rthw.h> #include <rtthread.h> #ifdef RT_USING_COMMON_TEST_DSMC #include "hal_base.h" #include "dma.h" #include "drv_dsmc_host.h" #define IO_BW_32 0 #define IO_BW_16 1 #define IO_BW_8 2 #define IO_TYPE_0 0 #define DMA_SIZE (0x10000) #define COUNTS 100 static struct rt_device *dma; static rt_sem_t mem_sem; static void m2m_complete(void *param) { rt_sem_release(mem_sem); } void dma_memcpy(int *dst_mem, int *src_mem, int size) { struct rt_dma_transfer *m2m_xfer; rt_err_t ret; int i, len; uint32_t tick_s, tick_e; /* ms */ m2m_xfer = (struct rt_dma_transfer *)rt_malloc(sizeof(*m2m_xfer)); mem_sem = rt_sem_create("memSem", 0, RT_IPC_FLAG_FIFO); RT_ASSERT(m2m_xfer != RT_NULL); RT_ASSERT(src_mem != RT_NULL); RT_ASSERT(dst_mem != RT_NULL); RT_ASSERT(mem_sem != RT_NULL); len = size / sizeof(int); for (i = 0; i < len; i++) src_mem[i] = len - i; rt_memset(dst_mem, 0x0, size); rt_memset(src_mem, 0x6, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)src_mem, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)dst_mem, size); dma = rt_device_find("dmac0"); /* memcpy test */ rt_memset(m2m_xfer, 0x0, sizeof(*m2m_xfer)); m2m_xfer->direction = RT_DMA_MEM_TO_MEM; m2m_xfer->dst_addr = (rt_uint32_t)dst_mem; m2m_xfer->src_addr = (rt_uint32_t)src_mem; m2m_xfer->len = size; m2m_xfer->callback = m2m_complete; m2m_xfer->cparam = m2m_xfer; ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_REQUEST_CHANNEL, m2m_xfer); RT_ASSERT(ret == RT_EOK); tick_s = HAL_GetTick(); for (i = 0; i < COUNTS; i++) { /* dma copy start */ ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_SINGLE_PREPARE, m2m_xfer); RT_ASSERT(ret == RT_EOK); ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_START, m2m_xfer); RT_ASSERT(ret == RT_EOK); /* wait for complete */ ret = rt_sem_take(mem_sem, RT_WAITING_FOREVER); RT_ASSERT(ret == RT_EOK); ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_STOP, m2m_xfer); RT_ASSERT(ret == RT_EOK); } tick_e = HAL_GetTick(); ret = rt_memcmp(src_mem, dst_mem, size); rt_kprintf("dma memcpy [%s]: avg: %7u MB/S with src: 0x%x dst: 0x%x len: %u counts: %u\n", ret ? "FAIL" : "PASS", size * COUNTS / (tick_e - tick_s) / 1000, src_mem, dst_mem, size, COUNTS); ret = rt_device_control(dma, RT_DEVICE_CTRL_DMA_RELEASE_CHANNEL, m2m_xfer); RT_ASSERT(ret == RT_EOK); tick_s = HAL_GetTick(); for (i = 0; i < COUNTS; i++) rt_memcpy(dst_mem, src_mem, size); tick_e = HAL_GetTick(); ret = rt_memcmp(dst_mem, src_mem, size); rt_kprintf("cpu memcpy [%s]: avg: %7u MB/S with src: 0x%x dst: 0x%x len: %u counts: %u\n", ret ? "FAIL" : "PASS", size * COUNTS / (tick_e - tick_s) / 1000, src_mem, dst_mem, size, COUNTS); rt_sem_delete(mem_sem); rt_free(m2m_xfer); } static int dma_test_psram(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { int *src = NULL, *dst = NULL; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t size = DMA_SIZE; rt_kprintf("dma test1: copy from ddr to psram\n"); src = rt_dma_malloc(size); RT_ASSERT(src != RT_NULL); dst = (int *)map->phys; dma_memcpy(dst, src, size); rt_dma_free(src); rt_kprintf("dma test2: copy from psram to ddr\n"); src = (int *)map->phys; dst = rt_dma_malloc(size); RT_ASSERT(dst != RT_NULL); dma_memcpy(dst, src, size); rt_dma_free(dst); rt_kprintf("dma test3: copy from psram to psram\n"); src = (int *)map->phys; dst = (int *)(map->phys + size); dma_memcpy(dst, src, size); rt_kprintf("DMA test done\n"); return 0; } void psram_simple_test(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i, j; struct dsmc_host_ops *ops = dsmc_host->ops; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t test_cap; uint32_t read, write; uint32_t test_data[] = {0x5aa5f00f, 0x0, 0xffffffff, 0x3cc3d22d}; rt_kprintf("start cs%d simple test\n", cs); for (j = 0; j < 4; j++) { test_cap = map->size; rt_kprintf("write, test_cap = 0x%x\n", test_cap); rt_kprintf(" write\n"); for (i = 0; i < test_cap; i += 4) { ops->write(dsmc_host, cs, 0, i, test_data[j]); } rt_kprintf("read\n"); test_cap = map->size; rt_kprintf(" read\n"); for (i = 0; i < test_cap; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); if (read != test_data[j]) { rt_kprintf("addr offset 0x%x: read = 0x%x, wr = 0x%x, error(0x%x)\n", i, read, test_data[j], test_data[j] ^ read); } } } rt_kprintf("phase 1: write address to data\n"); test_cap = map->size; for (i = 0; i < test_cap; i += 4) { ops->write(dsmc_host, cs, 0, i, map->phys + i); } for (i = 0; i < test_cap; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); write = map->phys + i; if (read != write) { rt_kprintf("addr offset 0x%x: read = 0x%x, wr = 0x%x, error(0x%x)\n", i, read, write, write ^ read); } } rt_kprintf("phase 2: write 0x0f0f5aa5 + address\n"); test_cap = map->size; for (i = 0; i < test_cap; i += 4) { ops->write(dsmc_host, cs, 0, i, map->phys + i + 0x0f0f5aa5); } test_cap = map->size; for (i = 0; i < test_cap; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); write = map->phys + i + 0x0f0f5aa5; if (read != write) { rt_kprintf("addr offset 0x%x: read = 0x%x, wr = 0x%x, error(0x%x)\n", i, read, write, write ^ read); } } rt_kprintf("phase 3: dma copy by software\n"); dma_test_psram(dsmc_host, cs); rt_kprintf("cs%d simple test done.\n", cs); } static int plc_simple_test(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i; struct dsmc_host_ops *ops = dsmc_host->ops; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; int *src = NULL, *dst = NULL; uint32_t size = DMA_SIZE; int ret = 0; uint32_t timeout = 1000000; rt_kprintf("phase 1: dma memcopy from host DDR to slave memory\n"); src = rt_dma_malloc(size); RT_ASSERT(src != RT_NULL); /* init src address */ rt_memset(src, 0x66, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)src, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)map->phys, size); timeout = 10000; if (!ops->copy_to(dsmc_host, cs, 0, (uint32_t)src, 0x0, size)) { while (ops->copy_to_state(dsmc_host)) { rt_thread_mdelay(1); timeout--; if (!timeout) { rt_kprintf("DSMC: wait copy to complete timeout\n"); ret = -1; goto err; } } rt_memcmp(src, (int *)map->phys, size); } else { rt_kprintf("err: phase 1: test skip!!!\n"); } rt_kprintf("phase2 : dma memcopy from slave memory to host DDR\n"); dst = rt_dma_malloc(size); RT_ASSERT(dst != RT_NULL); rt_memset(dst, 0x88, size); /* init plc src address */ for (i = 0; i < size; i += 4) { ops->write(dsmc_host, cs, 0, i, 0x10100000 + i); } HAL_DCACHE_CleanInvalidateByRange((uint32_t)dst, size); HAL_DCACHE_CleanInvalidateByRange((uint32_t)map->phys, size); timeout = 10000; if (!ops->copy_from(dsmc_host, cs, 0x0, 0, (uint32_t)dst, size)) { while (ops->copy_from_state(dsmc_host)) { rt_thread_mdelay(1); timeout--; if (!timeout) { rt_kprintf("DSMC: wait copy from complete timeout\n"); ret = -1; goto err; } } rt_memcmp((int *)map->phys, dst, size); } else { rt_kprintf("err: phase 1: test skip!!!\n"); } rt_dma_free(src); rt_dma_free(dst); rt_kprintf("plc_simple_test test done\n"); return 0; err: rt_kprintf("plc_simple_test test error!\n"); return ret; } void dsmc_slave_latency_cpu(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i; struct dsmc_host_ops *ops = dsmc_host->ops; uint32_t read, counter; uint32_t kt[2], diff; rt_kprintf("cpu access dsmc latency test\n"); counter = 1000000; kt[0] = HAL_GetTick(); for (i = 0; i < counter; i++) { ops->read(dsmc_host, cs, 3, 0x100, &read); } kt[1] = HAL_GetTick(); diff = kt[1] - kt[0]; rt_kprintf("counter %d, cost %ums, read latency %uns\n", counter, diff, diff * 1000000 / counter); rt_kprintf("read = 0x%x\n", read); kt[0] = HAL_GetTick(); for (i = 0; i < counter; i++) { ops->write(dsmc_host, cs, 3, 0x100, 0x6); } kt[1] = HAL_GetTick(); diff = kt[1] - kt[0]; rt_kprintf("counter %d, cost %ums, write latency %uns\n", counter, diff, diff * 1000000 / counter); } void dsmc_slave_speed_cpu(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { uint32_t i; struct dsmc_host_ops *ops = dsmc_host->ops; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t kt[2], rd_diff, wr_diff; uint32_t read; rt_kprintf("cpu access dsmc speed\n"); kt[0] = HAL_GetTick(); for (i = 0; i < map->size; i += 4) { ops->write(dsmc_host, cs, 0, i, 0xffffffff); } HAL_DCACHE_CleanInvalidateByRange((uint32_t)map->phys, map->size); kt[1] = HAL_GetTick(); wr_diff = kt[1] - kt[0]; rt_kprintf("cpu write %uMB, cost %ums, %uMB/s\n", (uint32_t)map->size / 1024 / 1024, wr_diff, map->size / wr_diff / 1000); kt[0] = HAL_GetTick(); for (i = 0; i < map->size; i += 4) { ops->read(dsmc_host, cs, 0, i, &read); } kt[1] = HAL_GetTick(); rd_diff = kt[1] - kt[0]; rt_kprintf("cpu read %uMB, cost %ums, %uMB/s\n", (uint32_t)map->size / 1024 / 1024, rd_diff, map->size / rd_diff / 1000); } static int dsmc_slave_speed_dma(struct rockchip_rt_dsmc_host *dsmc_host, uint32_t cs) { int *src = NULL, *dst = NULL; struct DSMC_MAP *map = &dsmc_host->host->dsmcHostDev.ChipSelMap[cs].regionMap[0]; uint32_t size = DMA_SIZE; rt_kprintf("dma access dsmc speed\n"); rt_kprintf("DMA write DSMC\n"); src = rt_dma_malloc(size); RT_ASSERT(src != RT_NULL); dst = (int *)map->phys; dma_memcpy(dst, src, size); rt_dma_free(src); rt_kprintf("DMA read DSMC\n"); src = (int *)map->phys; dst = rt_dma_malloc(size); RT_ASSERT(dst != RT_NULL); dma_memcpy(dst, src, size); rt_dma_free(dst); return 0; } void dsmc_test(int argc, char **argv) { uint32_t i; rt_device_t dev; struct rockchip_rt_dsmc_host *dsmc_host; rt_kprintf("this is dsmc_test\n"); dev = rt_device_find("dsmc_host"); if (dev == RT_NULL) { rt_kprintf("dsmc_host not found\n"); return; } dsmc_host = (struct rockchip_rt_dsmc_host *)dev; for (i = 0; i < DSMC_MAX_SLAVE_NUM; i++) { if (dsmc_host->host->dsmcHostDev.ChipSelCfg[i].deviceType == DEV_UNKNOWN) continue; psram_simple_test(dsmc_host, i); if (dsmc_host->host->dsmcHostDev.ChipSelCfg[i].deviceType == DEV_DSMC_LB) { plc_simple_test(dsmc_host, i); dsmc_slave_latency_cpu(dsmc_host, i); } dsmc_slave_speed_cpu(dsmc_host, i); dsmc_slave_speed_dma(dsmc_host, i); } } #ifdef RT_USING_FINSH #include <finsh.h> MSH_CMD_EXPORT(dsmc_test, dsmc tester); #endif #endif