仓库源文

.. Kenneth Lee 版权所有 2018-2020

:Authors: Kenneth Lee :Version: 1.0

Tegra TX2一瞥


最近入手了一台Tegra TX2做玩具,职业习惯,快速总结一下这个东西,供其他有兴趣的同 学参考。(注:一如过往,这只是快速形成的印象,不保证正确性,如果你信这个搞出什 么问题来,别回来找我)。

这个总结分三部分写:

  1. 第一印象

  2. IO子系统

  3. 内存子系统分析

第一印象

首先说说硬件,总的来说,我还是很喜欢这个硬件的:

    .. figure:: _static/tx2-1.jpg

关键是小巧玲珑,到处带很容易,而且机能基本上足够:本地编译Kernel,基于Docker启 动个web服务器,都没有什么困难。

对于小巧玲珑这一点,估计用惯树莓派或者HiKey这种板子的人会有不同意见。但这两个东 西用来做开发,完全不够看。要知道我平时在家里用的都是这样的货色:

    .. figure:: _static/tx2-2.jpg

接着说软件——简单总结,这个东西具有一切商业公司对待开源社区的傲慢无知的缺点。

你说你做一块自恰的单板,你直接给我做一个USB启动,直接插盘安装不就完了?它偏不, 非要做一个管理软件(JetPack),把一台服务器形态的单板做成一个嵌入式单板, JetPack需要先安装到Host上,然后用Host来安装Target。

最大的问题是,这个JetPack安装的时候,默认安装目标是安装程序本身的路径,而且一旦 你确认,立即删除同目录下的所有文件,问都不问一声:

    .. figure:: _static/tx2-3.jpg

(看不清的话,这里是手册上的内容清单:

    | OS Image:
    |       A sample file system derived from Ubuntu for Jetson
    | Libraries:
    |       CUDA Toolkit for Host PC (Ubuntu with cross-development support)
    |       CUDA Toolkit for Jetson
    |       OpenCV
    |       VisionWorks
    |       cuDNN
    |       TensorRT
    |       MultiMedia API
    | Dev Tools:
    |       NV System Profiler Tegra Graphics Debugger (OpenGL调试)
    |       Sample Code
    | Documentation

真是自来熟,不把自己当外人啊。这风格和cuda那一堆库那样,装起来就到处撒东西,装 完机器就不是你的了。一副缺乏竞争,“你不用我不行”,为所欲为的模样。

(这个安装也是一样的,直接给我的PC加了dpkg --add-architecture arm64,装binutils ,把我的本地库管理弄得一塌糊涂,直接锁住了一堆本地程序的升级——关键是包括安全升 级。弄得我装好Target就把这个Host给卸了,还卸了半天)

不说这个安装过程要翻某某神秘障碍物了,更神奇的是,你看着它是个图形界面,实际上 ,它里面是个脚本调用,而且这个调用过程调了sudo,所以,如果你不仔细看它的日志窗 口,在里面输一下密码,这个安装过程会永远挂在这里。

出现这种问题,怎么看都是毫无开源文化的商业公司,找了几个小外包,根据需求说明单 一天天算钱做出来的。

这一点也表现在内核的下载上,你说你要给我源代码,你给内核的git路径不就结了吗,结 果你做了这个:::

    git clone https://github.com/jetsonhacks/buildJetsonTX2Kernel.git

里面居然仅仅是下载kernel的脚本

而执行这个脚本,居然又在里面调sudo。大哥,我就下载个Kernel源码,这和sudo权限啥 关系啊?

有了前面被删光download目录的经验,这次我去看了一下sudo是干哈的——原来它非要把内 核给我下载到/usr/src目录下——谁告诉你我一定会在目标系统上编译这个代码啊?

再说了,makeKernel我也就只是想编译一下,我没想要替换现在的内核啊,你给我拷上去 又算怎么回事?……

无论如何吧,这次源代码中是一个.o都没有了,不再玩原来nv驱动那种“用户自己链接,所 以我不用开源”的游戏了。这也算一大进步。

再说说TensorFlow,你说你买TX2,一般就是想搞TF, Caffe,MXNet一类的东西了。这些应 该默认就可以正常工作吧——现实是,我现在都没有能够装起来。TX2默认装的是Cuda9,官 网给的方案TensorFlow是基于Cuda8的,没法用。后来给了一个Cuda9的版本,跑起来还是 去找Cuda8的库,这个我晚点再去折腾吧。

总的来说,第一印象来说,硬件不错,软件渣渣。最后给一个perf的性能数据做对比:

TX2的:::

nvidia@tegra-ubuntu:~/work/tegra-tx2-kernel/kernel-4.4/tools/perf$ sudo
./perf bench all [sudo] password for nvidia: # Running sched/messaging
benchmark...  # 20 sender and receiver processes per group # 10 groups ==
400 processes run

Total time: 0.382 [sec]

# Running sched/pipe benchmark...  # Executed 1000000 pipe operations
between two processes

Total time: 30.084 [sec]

30.084290 usecs/op 33239 ops/sec

# Running mem/memcpy benchmark...  # function 'default' (Default memcpy()
provided by glibc) # Copying 1MB bytes ...

3.985969 GB/sec

# Running mem/memset benchmark...  # function 'default' (Default memset()
provided by glibc) # Copying 1MB bytes ...

4.002305 GB/sec

作为对比,这是我的Carbon X1 2017的:::

# Running sched/messaging benchmark...  # 20 sender and receiver processes
per group # 10 groups == 400 processes run

Total time: 0.218 [sec]

# Running sched/pipe benchmark...  # Executed 1000000 pipe operations
between two processes

Total time: 4.265 [sec]

4.265017 usecs/op 234465 ops/sec

# Running mem/memcpy benchmark...  # function 'default' (Default memcpy()
provided by glibc) # Copying 1MB bytes ...

34.877232 GB/sec # function 'x86-64-unrolled' (unrolled memcpy() in
arch/x86/lib/memcpy_64.S) # Copying 1MB bytes ...

23.251488 GB/sec # function 'x86-64-movsq' (movsq-based memcpy() in
arch/x86/lib/memcpy_64.S) # Copying 1MB bytes ...

44.389205 GB/sec # function 'x86-64-movsb' (movsb-based memcpy() in
arch/x86/lib/memcpy_64.S) # Copying 1MB bytes ...

44.389205 GB/sec

# Running mem/memset benchmark...  # function 'default' (Default memset()
provided by glibc) # Copying 1MB bytes ...

54.253472 GB/sec

    .. figure:: _static/tx2-4.png

6个A57,慢不到10倍,比起模拟器,还可以接受啦。

IO子系统

TX2的启动系统使用的是UEFI+dtb=>grub=>kernel的方案,文件系统使用标准的Ubuntu ARM64版本(可能经过定制,但至少apt source是标准的)。

(但我怀疑这里做错了,如果用UEFI方案,正确的方法应该是把单独UEFI放在一个fat目录 下,这样最初的加载程序只需要解释fat目录就可以了。现在把UEFI和它东西放在一起,就 发挥不出UEFI和grub加载多种文件系统的效果了)

直接查dtb的compatible,有如下外设(我感兴趣的都标记了对应的驱动,方括号是未入主 线的代码):::

compatible = "a-edp,1080p-14-0"; [drivers/video/tegra/dc/panel/panel-a-edp-1080p-14-0.c]
compatible = "a-edp,1080p-14-0-bl";
compatible = "ak,ak89xx";(iio/imu/inv_mpu/inv_compass/inv_ak89xx_core.c)
compatible = "android,CustomIPI";(kernel/smp.c)
compatible = "android,firmware";(drivers/platform/tegra/firmwares-all.c)
compatible = "android,trusty-fiq-v1";
compatible = "android,trusty-irq-v1";
compatible = "android,trusty-log-v1";
compatible = "android,trusty-ote-v1";
compatible = "android,trusty-smc-v1";
compatible = "android,trusty-virtio-v1";
compatible = "arm,armv8-pmuv3";
compatible = "arm,armv8-timer";
compatible = "arm,coresight-etm3x", "arm,primecell";
compatible = "arm,coresight-etm4x", "arm,primecell";
compatible = "arm,coresight-funnel", "arm,primecell";
compatible = "arm,coresight-replicator";
compatible = "arm,coresight-stm", "arm,primecell";
compatible = "arm,coresight-tmc", "arm,primecell";
compatible = "arm,coresight-tpiu", "arm,primecell";
compatible = "arm,cortex-a15-gic";
compatible = "arm,cortex-a57-64bit", "arm,armv8";
compatible = "arm,mmu-500";
compatible = "arm,psci-1.0";
compatible = "bmp,bmpX80";(drivers/iio/pressure/nvi_bmpX80.c)
compatible = "bosch,mttcan";[drivers/staging/mttcan/m_ttcan_linux.c]
compatible = "bosch,mttcan-ivc";
compatible = "cache";
compatible = "capella,cm32180";(drivers/iio/light/nvs_cm3218.c)
compatible = "dp, display";
compatible = "dummy-cooling-dev";
compatible = "extcon-gpio-states";
compatible = "fixed-clock";
compatible = "gpio-keys";
compatible = "gps-wake";
compatible = "hdmi,display";
compatible = "invensense,mpu6xxx";(iio/imu/nvi_mpu/nvi.c)
compatible = "linux,spdif-dit";(sound/soc/codecs/spdif_transmitter.c)
compatible = "maxim,max16984-tegra186-cdp-phy";(drivers/phy/phy-max16984-cdp.c)
compatible = "maxim,max20024";(drivers/mfd/max77620.c)
compatible = "null,dsi-hotplug";
compatible = "nvidia, tegra-camera-platform";(drivers/video/tegra/camera/tegra_camera_platform.c)
compatible = "nvidia, tegra186-mipical";(drivers/media/platform/tegra/mipical/mipi_cal.c)
compatible = "nvidia,bwmgr";(drivers/platform/tegra/mc/emc_bwmgr.c)
compatible = "nvidia,carveouts";[drivers/video/tegra/nvmap/nvmap_init.c]
compatible = "nvidia,denver", "arm,armv8";[drivers/platform/tegra/tegra18_perf_uncore.c]
compatible = "nvidia,denver-hardwood";
compatible = "nvidia,denver15-pmu";
compatible = "nvidia,eqos";[net/ethernet/nvidia/eqos/init.c]
compatible = "nvidia,fiq-debugger";(drivers/staging/android/fiq_debugger/fiq_debugger.c)
compatible = "nvidia,generic_carveout";
compatible = "nvidia,imx185"; --i2c
compatible = "nvidia,imx219";
compatible = "nvidia,imx274";
compatible = "nvidia,mods-clocks";
compatible = "nvidia,ov23850";(drivers/media/i2c/ov23850.c)
compatible = "nvidia,ov5693";
compatible = "nvidia,pca9570";
compatible = "nvidia,ramoops";
compatible = "nvidia,smmu_test";
compatible = "nvidia,storm", "nvidia,tegra186";
compatible = "nvidia,t18x-cluster-clk-priv";
compatible = "nvidia,tegra-audio-t186ref-mobile-rt565x";
compatible = "nvidia,tegra-gic";
compatible = "nvidia,tegra-t18x-mc";
compatible = "nvidia,tegra-wdt-t18x";
compatible = "nvidia,tegra18-rtc";
compatible = "nvidia,tegra186";
compatible = "nvidia,tegra186-AXI2APB-bridge";[drivers/platform/tegra/bridge_mca.c]
compatible = "nvidia,tegra186-AXIP2P-bridge";
compatible = "nvidia,tegra186-adma";
compatible = "nvidia,tegra186-adsp-pd";(drivers/platform/tegra/pm_domains.c)
compatible = "nvidia,tegra186-ahc";[drivers/misc/tegra186-ahc/tegra186_ahc.c]
compatible = "nvidia,tegra186-ahci-sata";(drivers/ata/tegra/ahci_tegra.c)
compatible = "nvidia,tegra186-aon";[drivers/platform/tegra/tegra-aon.c]
compatible = "nvidia,tegra186-aon-ivc-echo";
compatible = "nvidia,tegra186-aon-spi";
compatible = "nvidia,tegra186-aondbg";
compatible = "nvidia,tegra186-aowake";
compatible = "nvidia,tegra186-ape-ivc";[drivers/platform/tegra/tegra-camera-rtcpu.c]
compatible = "nvidia,tegra186-ape-pd";
compatible = "nvidia,tegra186-arad";
compatible = "nvidia,tegra186-asrc";
compatible = "nvidia,tegra186-bpmp";[drivers/thermal/tegra_bpmp_thermal.c]
compatible = "nvidia,tegra186-bpmp-i2c";
compatible = "nvidia,tegra186-bpmp-thermal";
compatible = "nvidia,tegra186-cactmon";(drivers/platform/tegra/central_actmon/actmon_common.c)
compatible = "nvidia,tegra186-camera-ivc-protocol-capture";
compatible = "nvidia,tegra186-camera-ivc-protocol-capture-control";
compatible = "nvidia,tegra186-camera-ivc-protocol-dbg";
compatible = "nvidia,tegra186-camera-ivc-protocol-debug";
compatible = "nvidia,tegra186-camera-ivc-protocol-echo";
compatible = "nvidia,tegra186-camera-ivc-protocol-mods";
compatible = "nvidia,tegra186-camera-ivc-protocol-vinotify";
compatible = "nvidia,tegra186-cec";(drivers/misc/tegra-cec/tegra_cec.c)
compatible = "nvidia,tegra186-chipid";(drivers/platform/tegra/tegra_chipid.c)
compatible = "nvidia,tegra186-combined-uart";
compatible = "nvidia,tegra186-cpuidle-a57";
compatible = "nvidia,tegra186-cpuidle-a57-cluster";
compatible = "nvidia,tegra186-cpuidle-a57-thresholds";
compatible = "nvidia,tegra186-cpuidle-denver";
compatible = "nvidia,tegra186-cpuidle-denver-cluster";
compatible = "nvidia,tegra186-cpuidle-denver-thresholds";
compatible = "nvidia,tegra186-dc";[drivers/video/tegra/dc/dc.c]
compatible = "nvidia,tegra186-disa-pd";[drivers/video/tegra/dc/nvdisp/nvdisp.c]
compatible = "nvidia,tegra186-dpaux";
compatible = "nvidia,tegra186-dpaux-pinctrl";
compatible = "nvidia,tegra186-dpaux1";
compatible = "nvidia,tegra186-dpaux1-pinctrl";
compatible = "nvidia,tegra186-dsi";
compatible = "nvidia,tegra186-dspk";
compatible = "nvidia,tegra186-dspk";
compatible = "nvidia,tegra186-efuse", "nvidia,tegra210-efuse";
compatible = "nvidia,tegra186-efuse-burn";
compatible = "nvidia,tegra186-gp10b", "nvidia,gp10b";[drivers/gpu/nvgpu/nvgpu_gpuid_t18x.h]
compatible = "nvidia,tegra186-gpcdma";[drivers/dma/tegra186-gpc-dma.c]
compatible = "nvidia,tegra186-gpio";
compatible = "nvidia,tegra186-gpio-aon";
compatible = "nvidia,tegra186-host1x", "simple-bus";
compatible = "nvidia,tegra186-host1x-pd";
compatible = "nvidia,tegra186-hsp";
compatible = "nvidia,tegra186-hsp-mailbox";
compatible = "nvidia,tegra186-hsuart";
compatible = "nvidia,tegra186-i2c";
compatible = "nvidia,tegra186-iommu-context";
compatible = "nvidia,tegra186-isp";[drivers/video/tegra/host/isp/isp.c]
compatible = "nvidia,tegra186-ispa-pd";
compatible = "nvidia,tegra186-kfuse";
compatible = "nvidia,tegra186-mc-sid";
compatible = "nvidia,tegra186-mce";
compatible = "nvidia,tegra186-miscreg";
compatible = "nvidia,tegra186-msenc-pd";
compatible = "nvidia,tegra186-nvcsi";[drivers/video/tegra/host/nvcsi/nvcsi.c]
compatible = "nvidia,tegra186-nvdec";[drivers/video/tegra/host/nvdec/nvdec.c]
compatible = "nvidia,tegra186-nvdec-pd";
compatible = "nvidia,tegra186-nvdumper";
compatible = "nvidia,tegra186-nvenc"; --drm驱动,主线和外部皆有
compatible = "nvidia,tegra186-nvjpg"; --drm驱动,主线和外部皆有
compatible = "nvidia,tegra186-nvjpg-pd";
compatible = "nvidia,tegra186-pcie";
compatible = "nvidia,tegra186-pcie-pd";
compatible = "nvidia,tegra186-pinmux";
compatible = "nvidia,tegra186-pm-irq";
compatible = "nvidia,tegra186-pmc";
compatible = "nvidia,tegra186-pmc-iopower";
compatible = "nvidia,tegra186-pwm";
compatible = "nvidia,tegra186-qspi";
compatible = "nvidia,tegra186-roc-flush";
compatible = "nvidia,tegra186-safety-cmd-resp";
compatible = "nvidia,tegra186-safety-hb";
compatible = "nvidia,tegra186-safety-ivc";
compatible = "nvidia,tegra186-sata-pd";
compatible = "nvidia,tegra186-sce-ivc";
compatible = "nvidia,tegra186-sdhci";
compatible = "nvidia,tegra186-se-elp";(drivers/crypto/tegra-se-elp.c)
compatible = "nvidia,tegra186-se-pd";
compatible = "nvidia,tegra186-se1-nvhost"; --这个是未主线化的
compatible = "nvidia,tegra186-se2-nvhost";
compatible = "nvidia,tegra186-se3-nvhost";
compatible = "nvidia,tegra186-se4-nvhost";
compatible = "nvidia,tegra186-sor";
compatible = "nvidia,tegra186-sor1";
compatible = "nvidia,tegra186-spi";
compatible = "nvidia,tegra186-spi";
compatible = "nvidia,tegra186-system-config";
compatible = "nvidia,tegra186-tachometer";
compatible = "nvidia,tegra186-timer";
compatible = "nvidia,tegra186-tsec";
compatible = "nvidia,tegra186-tsec-pd";
compatible = "nvidia,tegra186-usb-cd";
compatible = "nvidia,tegra186-ve-pd";
compatible = "nvidia,tegra186-vi"; [drivers/video/tegra/host/vi/vi4.c]
compatible = "nvidia,tegra186-vi-bypass";
compatible = "nvidia,tegra186-vic";
compatible = "nvidia,tegra186-vic03-pd";
compatible = "nvidia,tegra186-xhci";
compatible = "nvidia,tegra186-xotg";
compatible = "nvidia,tegra186-xudc";
compatible = "nvidia,tegra186-xusb-mbox";
compatible = "nvidia,tegra186-xusb-padctl";
compatible = "nvidia,tegra186-xusba-pd";
compatible = "nvidia,tegra186-xusbb-pd";
compatible = "nvidia,tegra186-xusbc-pd";
compatible = "nvidia,tegra18x-adsp";
compatible = "nvidia,tegra18x-adsp-carveout";
compatible = "nvidia,tegra18x-agic";
compatible = "nvidia,tegra18x-balanced-throttle";
compatible = "nvidia,tegra18x-car";
compatible = "nvidia,tegra18x-cpufreq";
compatible = "nvidia,tegra18x-cpuidle";
compatible = "nvidia,tegra18x-eqos-ape";
compatible = "nvidia,tegra20-uart", "nvidia,tegra186-hsuart";
compatible = "nvidia,tegra210-admaif";
compatible = "nvidia,tegra210-adsp-audio";
compatible = "nvidia,tegra210-adx";
compatible = "nvidia,tegra210-afc";
ompatible = "nvidia,tegra210-amixer";
compatible = "nvidia,tegra210-amx";(sound/soc/tegra-alt/tegra210_amx_alt.c)
compatible = "nvidia,tegra210-axbar";
compatible = "nvidia,tegra210-dmic";
compatible = "nvidia,tegra210-i2s";
compatible = "nvidia,tegra210-iqc";
compatible = "nvidia,tegra210-mvc";
compatible = "nvidia,tegra210-mvc";
compatible = "nvidia,tegra210-ope";
compatible = "nvidia,tegra210-pmc-blink-pwm";
compatible = "nvidia,tegra210-sfc";
compatible = "nvidia,tegra210-spdif";
compatible = "nvidia,tegra210-spkprot";
compatible = "nvidia,tegra30-hda";
compatible = "nvidia,vpr-carveout";
compatible = "nxp,pca9546";
compatible = "pwm-fan";
compatible = "realtek,rt5658";(sound/soc/codecs/rt5659.c)
compatible = "regulator-fixed";
compatible = "regulator-fixed-sync";
compatible = "s,wqxga-10-1";
compatible = "s,wqxga-10-1-bl";
compatible = "s,wuxga-8-0";
compatible = "s,wuxga-8-0-bl";
compatible = "s-edp,uhdtv-15-6";
compatible = "s-edp,uhdtv-15-6-bl";
compatible = "sharp,lr388k7_ts";(drivers/input/touchscreen/lr388k7_ts.c)
compatible = "simple-bus";
compatible = "softdog-platform";
compatible = "synopsys,dwc_eqos_virt_test";
compatible = "tegra,ufs_variant";
compatible = "tegra-power-domains";
compatible = "thermal-fan-est";
compatible = "ti,ina3221x";--大部分是iio驱动
compatible = "ti,lp8556";
compatible = "ti,tas2552";
compatible = "ti,tca6408";
compatible = "ti,tca6416";
compatible = "ti,tca9539";
compatible = "ti,tmp451";
compatible = "ti,tps65132";

显示部分传到主线的大部分都是drm和fb的,没有上传的统一放在一个叫flcn的框架中,里 面的东西包罗万有。

用来支持cuda运算的驱动都没有主线化,也不在flcn中,而是放在driver/gpu目录下,提 供的主要是用户态的接口(通过一组字符设备),和内核的关系不大。

从大量的电源域和调频的设计看,这是从手机改过来的片子。当然,Tegra系列本来就是给 手机做的,这也没有什么可说的。

gk20a的驱动是2014年开始上传主线的,当时的内核版本是3.16,但一直仅包含drm的驱动 。从现在这个代码的样子,应该是一个内部的ML的产品线集成了计算核心到手机SoC中,然 后把对应的代码拼上来了。

查iommu_group,可以看到大部分外设都用iommu_group分割:::

sdhci x2
i2c x7
spi x3
serial x4
ether_qos
rtcpu
ahci-sata
aon
smmu_test
xhci
xudc
host1x x10
host1x:ctx0
nvcsi
vi
isp
nvdisplay
vic
nvenc
nvdec
i2c
nvjpg
tsec
tsecb
se x4
gp10b
bpmp
dma
pcie-controller
sound
hda
adsp_audio
adsp

片内带一个gpu设备(/sys/device/gpu.0),支持虚拟化,但仅包含一个iommu_group(所 以才支持了vfio-mdev?),驱动是/sys/bus/platform/drivers/gk20a。

gk20a的构架是一个统一的PCI化的设备框架nvgpu,不同的硬件实现则通过一个称为 platform的数据结构进行封装(放在device的priv中),从gk20a的实现看,主要是是些初 始化,时钟一类的东西,也就是说,标准的数据流是统一的设计。我猜这样的构架撑不了 三代(当然,Tegra也不需要),这可能是没有主线化的主要原因。

用户态接口主要暴露为如下字符设备:

/dev/nvhost-gpu:共享内存和通道管理

/dev/nvhost-as-gpu:Address Space

/dev/nvhost-ctrl-gpu:设备状态控制

/dev/nvhost-dbg-gpu:设备调试信息控制

/dev/nvhost-prof-gpu: GPU profiling事件导出和控制

/dev/nvhost-tsg-gpu: 不知道是啥,看起来是做任务控制的

/dev/nvhost-sched-gpu:看起来是tsg的辅助功能,可能前者是任务组管理,后者是调度

其他媒体codec也有类似的结构:::

/dev/nvhost-as-gpu /dev/nvhost-ctrl-isp /dev/nvhost-ctrl-vi /dev/nvhost-gpu /dev/nvhost-nvcsi /dev/nvhost-prof-gpu /dev/nvhost-tsecb /dev/nvhost-vic
/dev/nvhost-ctrl /dev/nvhost-ctrl-nvcsi /dev/nvhost-ctxsw-gpu /dev/nvhost-isp /dev/nvhost-nvdec /dev/nvhost-sched-gpu /dev/nvhost-tsg-gpu
/dev/nvhost-ctrl-gpu /dev/nvhost-ctrl-nvdec /dev/nvhost-dbg-gpu /dev/nvhost-msenc /dev/nvhost-nvjpg /dev/nvhost-tsec /dev/nvhost-vi

如果直接看一个cuda的调用过程,主要包括如下动作:::

    openat(AT_FDCWD, "/dev/nvhost-ctrl-gpu", O_RDWR|O_CLOEXEC) = 4
    ioctl(4, NVGPU_GPU_IOCTL_GET_CHARACTERISTICS
    ioctl(4, NVGPU_GPU_IOCTL_GET_TPC_MASKS
    ioctl(4, NVGPU_GPU_IOCTL_GET_FBP_L2_MASKS
    ioctl(4, NVGPU_GPU_IOCTL_ZCULL_GET_CTX_SIZE
    ioctl(4, NVGPU_GPU_IOCTL_ZCULL_GET_INFO
    ioctl(4, NVGPU_GPU_IOCTL_GET_ENGINE_INFO
    ioctl(4, NVGPU_GPU_IOCTL_ALLOC_AS //5是这里分配的句柄
    ioctl(5, NVGPU_AS_IOCTL_GET_VA_REGIONS
    ioctl(5, NVGPU_AS_IOCTL_GET_VA_REGIONS
    ioctl(5, NVGPU_AS_IOCTL_ALLOC_SPACE
    ioctl(5, NVGPU_AS_IOCTL_ALLOC_SPACE
    openat(AT_FDCWD, "/dev/nvmap", O_RDWR|O_SYNC|O_CLOEXEC) = 9
    ioctl(9, NVMAP_IOC_CREATE //和nvmap有关的我们先放下一篇分析里
    ioctl(9, NVMAP_IOC_ALLOC
    ioctl(5, NVGPU_AS_IOCTL_UNMAP_BUFFER
    ioctl(9, NVMAP_IOC_CREATE
    ioctl(9, NVMAP_IOC_ALLOC
    ...上两个调用的多次重复
    ioctl(4, NVGPU_GPU_IOCTL_OPEN_TSG 
    ioctl(4, NVGPU_GPU_IOCTL_OPEN_CHANNEL //这里也是创建句柄,从设备分配一个通道
    ioctl(5, NVGPU_AS_IOCTL_BIND_CHANNEL //把channel和AS关联起来
    ioctl(4, NVGPU_GPU_IOCTL_OPEN_CHANNEL 
    ioctl(5, NVGPU_AS_IOCTL_BIND_CHANNEL 
    ioctl(4, NVGPU_GPU_IOCTL_OPEN_CHANNEL
    ...

简单从这个语义上下文理解,一次通讯是这样的:先从进程和GPU建立一个关联,驱动GPU 的能力,然后分配一个或者多个独立的地址空间(AS),可能是把MMU的一部分复制给SMMU ,然后在设备和进程间建立一个通道,然后把AS和通道绑定,估计这个时候设定streamid 和substreamid给设备,这样剩下的缺页行为就可以交给设备,设备发起缺页中断的时候, 靠进程这边接管对应的mm.handle_fault(),完成后面的过程。

这个设计优先保性能,上传是下一步的事情(也不一定会上传)。

其他没有什么感兴趣了的了,就这样吧。

nvmap

这是这个系列最后一篇,看看TX2的GPU和host是怎么共享内存的。主要是要单独看看nvmap 这个模块的工作原理。

TX2的内核源代码是这样放的:

    | display kernel-4.4 nvgpu nvgpu-t18x nvhost nvhost-t18x nvmap
    | nvmap-t18x t18x

没有上传的代码不是Patch,而是一个个目录树的形态。但里面非常完整,连 NVIDIA-REVIEWERS这种文件都是按MAINTAINER的格式整理的,说起来应该是按随时可以 upstream的方式设计的,但整个功能通用性不强,估计upstream的难度不低。

其中gpu的驱动在nvgpu目录下(如前所述,drm驱动是已经upstream的,在kernel-4.4目录 中)。这个Kernel外的模块代码量93,913Loc)。而nvmap是个独立的目录(代码量6,334Loc ),nvgpu强依赖于nvmap。由于没有资料,nvgpu目录下的程序不太好看懂,很多概念比如 TSG是什么,估计需要分析很久,所以我们从一个相对独立逻辑空间来看看它的语义是怎么 样的。(这也是想介绍一下,应该如何具体解构一个已经存在的设计)

在分析这个逻辑前,我们先看看计算加速器设计中的一个关键问题:内存管理。

比如我做一个大型的矩阵计算,一个100x100的矩阵乘以另一个100x100的矩阵,用32位整 数,这样我就有120000个字节的空间要处理,放到4K的页中,就需要接近30页的内存需要 访问。我一开始在CPU一侧整理这些数据,这些数据当然是放在靠近CPU的内存中比较实在 ,这样CPU算起来比较快。但准备好了,我希望这个乘法让加速器(比如GPU)给我算。那 么GPU离这些内存就比较远了,每个乘法加法,都要经过层层总线节点更新到内存里,这还 不如CPU自己算。所以,这是个两难的问题,根据不同的总线带宽和时延,可以考虑的方案 有:

  1. 内存选择在CPU一侧(让加速器吃亏)

  2. 内存选择在加速器一侧(让CPU吃亏)

  3. 内存开始的时候选择在CPU一侧,但某一页的内容被加速器访问的时候,拷贝到GPU一侧 ,完成计算后,如果CPU访问这个内存,再从加速器拷贝回CPU一侧。这个过程,既可以 是人工的,也可以是动态的。

这里还有一个地址空间的问题,一般的Linux内核驱动,如果一片内存要分享给设备,需要 先做dma=dma_map(va),其中va是CPU的地址,dma是设备的地址。这样一来,如果CPU要把 一个地址提交给设备,就需要转换到另一个地址空间。这种情况对于那些“弱智外设”,比 如网卡,SAS等控制器,大部分时候做DMA就是为了拷贝一段数据到外设上,这当然没有问 题,但对于那些“智能设备”如加速器,很可能我的数据中就带有指针,我的头指针可以给 你dma_map(),你不能要求我把里面的指针也全部替换了吧?

最后是,现代加速器(特别是计算加速器),基本上要求服务于用户态,所以,前面的问 题,都要在用户态解决。

我们关键看这个代码如何处理这两个问题的。

首先,nvmap的配置项如下(把子配置项和功能无关的项忽略了,只看大特性):::

NVMAP_HIGHMEM_ONLY:分配内存时仅用HIMEM(64位系统要这东西干嘛?)
NVMAP_PAGE_POOLS:基于内存池管理内存分配
NVMAP_CACHE_MAINT_BY_SET_WAYS:Cache分配算法
NVMAP_DMABUF_STASH:重用DMABUF,降低重新分配成本
NVMAP_FORCE_ZEROED_USER_PAGES:安全功能,分配用户内存
NVMAP_DEFER_FD_RECYCLE:延迟FD号的使用(为后续分配重用)

没有什么涉及主功能的配置项,都是优化类的设计,这对构架分析来说是个好消息。

程序入口在nvmap_init中,实现为一个名叫tegra-carveouts的platform_device。查一下 carveout的含义,它是“切割出”的意思,也是CPU侧切出一片内存给GPU用这个商业特性的 名字。

以此为根,遍历整个代码树,功能分列如下:::

nvmap_init.c:平台设备驱动总入口,配置参数管理
nvmap_dev.c:platform_driver实现,注册为misc设备/dev/nvmap,核心是提供ioctl控制
nvmap_ioctl.c:具体实现nvmap_dev.c中的ioctl功能
nvmap_alloc.c:handle分配管理
nvmap.c:基于handle进行内存分配
nvmap_cache.c:在debugfs中创建接口进行cache控制,这里的cache控制指的是CPU一侧的控制,通过调用msr等动作实现的。这个接口的存在,说明CPU和GPU间不是CC的。
nvmap_dmabuf.c:nv版本的dma_buf实现,dma_buf是内核用于用户态直接对设备做DMA的一个封装[1]
nvmap_fault.c:实现vma的ops,进行fault处理,主要从handle预分配的空间中取
nvmap_handle.c:handle管理,主要是做一个rb树,建立vma和处理handle的关联
nvmap_heap.c:kmem_cache的封装(kmem_cache是内核一个管理固定大小内存的一个数据结构),用作CPU/GPU共享内存,这时称为Carveout。
nvmap_mm.c:进程的mm管理,是cache管理的一部分
nvmap_pp.c:page pool管理,自行管理了一个page list,NVMAP_IOC_ALLOC要求分配的也都在这里管理的
nvmap_tag.c:handle的名字管理,提供给handle命名和基于名字访问的能力

如果我们把优化性的语义收缩一下,比如pp和heap如果不做池化,就是个简单的 alloc_page()和kmalloc,tag如果不做rb树的管理,就是个handle name。cache操作是CPU 一侧的,不封装就是基本流程中的其中一步……

这样,我们可以初步猜测这个系统的功能是这样构造的:

实现一个平台设备,注册为misc设备,用户态通过ioctl分配handle,基于handle提供vma 的分配,分配后的页面依靠自己管理的page进行填充,提供dma_buf接口,剩下的功能都是 用户态如何基于handle对设备进行硬件交互了。

为了确认这一点,接着看看ioctl的设计:::

NVMAP_IOC_CREATE/NVMAP_IOC_FROM_FD:创建handle和dma_buf
NVMAP_IOC_FROM_VA:同上,但同时设置vma
NVMAP_IOC_FROM_IVC_ID:同上,但同时分配Carveout
NVMAP_IOC_GET_FD:查找功能,FD2handle
NVMAP_IOC_GET_IVC_ID:同上,FD2VimID
NVMAP_IOC_GET_IVM_HEAPS:列出支持IVM的所有carveout内存块
NVMAP_IOC_ALLOC:为handle分配page
NVMAP_IOC_GET_IVC_ID:同上,但从Carveout分配
NVMAP_IOC_VPR_FLOOR_SIZE:似乎是设置特定设备的最小DMA缓冲
NVMAP_IOC_FREE:靠,这是关掉handle的fd(而不是释放Page)
NVMAP_IOC_WRITE/READ:数据读写,WTF,就是说至少部分数据必须通过系统调用来访问(看不到用户库的代码,不好猜)
NVMAP_IOC_CACHE:Cache操作
NVMAP_IOC_CACHE_LIST:同上,维护类功能。
NVMAP_IOC_GUP_TEST:不用test了,这个东西逻辑上肯定是有问题的:用户态DMA的问题
NVMAP_IOC_SET_TAG_LABEL:这是给handle命名

基本不改变前面的逻辑,关键是这里有一个Carveout的概念,我猜在普通的实现上,这个 就是普通内存,从CPU直接分配kmemcache来共享,如果是高性能实现,就是不同的内存, 然后靠两边的读写来产生缺页来实现拷贝过程。

再看看handle的数据结构:::

    struct nvmap_handle {
            struct rb_node node;    /* entry on global handle tree */
            atomic_t ref;           /* reference count (i.e., # of duplications) */
            atomic_t pin;           /* pin count */
            u32 flags;              /* caching flags */
            size_t size;            /* padded (as-allocated) size */
            size_t orig_size;       /* original (as-requested) size */
            size_t align;
            struct nvmap_client *owner;
            struct dma_buf *dmabuf;
            union {
                    struct nvmap_pgalloc pgalloc;
                    struct nvmap_heap_block *carveout;
            };
            bool heap_pgalloc;      /* handle is page allocated (sysmem / iovmm) */
            bool alloc;             /* handle has memory allocated */
            bool from_va;           /* handle memory is from VA */
            u32 heap_type;          /* handle heap is allocated from */
            u32 userflags;          /* flags passed from userspace */
            void *vaddr;            /* mapping used inside kernel */
            struct list_head vmas;  /* list of all user vma's */
            atomic_t umap_count;    /* number of outstanding maps from user */
            atomic_t kmap_count;    /* number of outstanding map from kernel */
            atomic_t share_count;   /* number of processes sharing the handle */
            struct list_head lru;   /* list head to track the lru */
            struct mutex lock;
            struct list_head dmabuf_priv;
            u64 ivm_id;
            int peer;               /* Peer VM number */
    };

rbtree,成组的vma,多个dmabuf,cpu/gpu二选一的page或者carveout……基本上和前面的 逻辑一致。

再快速查一次nvgpu一侧的代码,除了by-pass-smmu以外,没有独立的SMMU操作,查所有的 中断处理,都是关于channel的,没有关于SMMU的中断处理,所以基本上可以认为,这个方 案是处理不了设备一侧的缺页的。

这个方案看起来挺……简陋的,重点还是聚焦在基本功能架构的一般优化上,没到向上提炼 构架的程度。

从功能上说,最不好的一点就是和IOMMU是结合不起来的,但它完全基于FD的管理比 WrapDrive基于vfio-mdev的管理带来一个好处,就是进程退出,通道就自动回收了,WD现 在做这个功能不好做。另外,WD需要考虑这里的另外两个需求:

  1. 它的Host和加速器之间不是CC的,得有个办法把Cache操作插入到语义中

  2. WD现在没有考虑支持大页

附录

[1] dma_buf的功能在内核Documentation/driver-api/dma-buf.rst中表述。如果我没 有记错,这是当初三星在Linaro首先推的特性,它主要解决像视频播放器这种:需要 软件动两下,转给硬件动两下,然后软件再动两下,交给下个硬件动两下这样的场景 的。它的核心就是让用户态可以分配一片DMA内存,然后让这个内存可以在多个驱动和 进程之间互相传递。