树莓派的cpu与gpu通信设计浅析
树莓派的cpu与gpu通信设计浅析
- 1.本文介绍
- 2.树莓派的videocoreiv
- 3.访问策略
- 4.framebuff图像访问
- 5.注意事项
1.本文介绍
异构设计在嵌入式开发过程中非常的重要,比如mcu与mpu的异构,还有两个不同架构的cpu或者两个不同架构的mpu等等。本文主要介绍树莓派的cpu与gpu通信的设计思想。并且通过在树莓派4上进行测试,测试访问gpu所提供的功能。
2.树莓派的videocoreiv
树莓派上电启动时,首先启动的是GPU,然后从sd卡中加载启动文件,紧接着启动CPU,所以GPU在学习使用树莓派时非常重要。可以通过下面的仓库看到底层的GPU的使用。
https://github.com/hermanhermitage/videocoreiv
要想CPU与GPU之间访问,首先需要了解两个设计的架构,下面从树莓派3b摄像头传输图像的角度去理解一下这个架构的设计。
BCM2835 SOC是芯片的设计架构,里面集成了一个ARM Cortex A53的CPU与VideoCore IV GPU。摄像头的MIPI数据传输线连接在GPU上,其摄像头SCCB连接在CPU上。
GPU上运行着一个RTOS,就是VCOS其实是基于ThreadX
系统实现的。CPU与GPU共享RAM。当启动图像传输的时候,实际上就是首先由GPU出来图像时序,然后将图像放到RAM中,CPU与GPU通过VCHI管道进行通信,启动DMA将图像传递到CPU可以访问的内存区域。
那么GPU有哪些功能呢?
具体可以看下面的信息:
https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
1.得到固件信息
2.电源管理
3.频率管理
4.内存管理
5.framebuff
6.摄像头
7.触摸屏
整体来看GPU的功能比较齐全。所以访问这些信息是如何进行的呢?
3.访问策略
如果要实现CPU与GPU的通信,树莓派做了一个控制器,就是一个独立的外设接口,叫做Mailbox Peripheral
。这个Mailbox的外设的寄存器布局如下:
0 4 8 12 16 20 24 28 32
+-------+-------------------------------------------------------+
0x00 |rd chn | read data |
+-------+-------------------------------------------------------+
0x04 | Unused |
... \ \
0x14 | Unused |
+-----------------------------------------------------------+-+-+
0x18 | status reserved |E|F|
+-----------------------------------------------------------+-+-+
0x1C | Unused |
+-----------------------------------------------------------+-+-+
0x20 |wt chn | write data |
+-----------------------------------------------------------+-+-+
寄存器的起始地址为外设起始地址加速0xB880
的偏移量。例如在树莓派4上其外设的地址为0xFE000000
。上述就是CPU核操作GPU时的寄存器的布局。寄存器并不多,只需要判断状态即可。
当进行通信时,要往寄存器写的数据是什么?
#define MBOX_CH_POWER 0
#define MBOX_CH_FB 1
#define MBOX_CH_VUART 2
#define MBOX_CH_VCHIQ 3
#define MBOX_CH_LEDS 4
#define MBOX_CH_BTNS 5
#define MBOX_CH_TOUCH 6
#define MBOX_CH_COUNT 7
#define MBOX_CH_PROP 8
一般来说是有9个通道可以指定,每个通道有着特定的用途,如上定义所示。写数据(write data)实际上写的是一个消息列表的地址,这个消息列表可以是一个数组。叫做msgbox。这个地址一般都是要求4字节对齐的,因为上图寄存器中前面4字节是用于存放通道信息的。
一般一个消息的空间布局如下:
0 4 8 12 16 20 24 28 32
+---------------------------------------------------------------+
0x00 | Buffer Size |
+---------------------------------------------------------------+
0x04 | Request/Response Code |
+---------------------------------------------------------------+
0x08 | Tags |
... \ \
0xXX | Tags |
+---------------------------------------------------------------+
0xXX+4 | End Tag (0) |
+---------------------------------------------------------------+
0xXX+8 | Padding |
... \ \
0xXX+16| Padding |
+---------------------------------------------------------------+
获取版本信息,传递消息实际实现代码如下:
int bcm283x_mbox_hardware_get_revison(void)
{
mbox[0] = 8*4; // length of the message
mbox[1] = MBOX_REQUEST; // this is a request message
mbox[2] = MBOX_TAG_HARDWARE_GET_REV;
mbox[3] = 4; // buffer size
mbox[4] = 0; // len
mbox[5] = 0;
mbox[6] = 0;
mbox[7] = MBOX_TAG_LAST;
mbox_call(MBOX_CH_PROP, MMU_DISABLE);
return mbox[5];
}
这样就将消息传递给CPU的寄存器,寄存器通过访问状态,并且将消息传递给GPU,GPU得到信息后,将消息填充,然后通过DMA将返回结果的消息包传递到原来的地址中,这样就可以实现基本的通信逻辑了。
具体的完整的实现细节可以参考rt-thread/bsp/raspberry-pi/raspi3-64/driver/mbox.c。
4.framebuff图像访问
上述基本上讲述了cpu和gpu的访问流程,那么如果想使用树莓派的hdmi接口进行图像显示,该如何进行设计呢?首先树莓派在设计的时候,并未在CPU集成图像控制接口,那只能通过GPU来实现了。访问其实就是利用mbox的通信进行实现,利用TAG的消息进行区分。
下面是framebuff相关TAG的宏定义
#define TAG_ALLOCATE_BUFFER 0x00040001
#define TAG_SET_PHYS_WIDTH_HEIGHT 0x00048003
#define TAG_SET_VIRT_WIDTH_HEIGHT 0x00048004
#define TAG_SET_DEPTH 0x00048005
#define TAG_SET_PIXEL_ORDER 0x00048006
#define TAG_GET_PITCH 0x00040008
#define TAG_SET_VIRT_OFFSET 0x00048009
#define TAG_END 0x00000000
通过mbox的数组传递消息,其tag为TAG_ALLOCATE_BUFFER,并且指定图像的depth、width、height等参数。
当传递消息后GPU会将申请到的framebuff的地址通过mbox[5]传递回来,当访问这个地址的时候,实际上就是访问这个framebuff。我们不用去关心具体的时序逻辑问题,当然,可能需要注意的是图像传输完成之后的中断。
5.注意事项
在访问GPU的时候,需要注意的是寄存器的地址一定需要通过MMU映射成非cache的模式,否则可能会出现内存一致性问题,导致实际上通道的数据没有写到寄存器中。访问图像的时候,也需要注意这个问题,因为framebuff也需要非cache访问,这些都是在实际项目设计中需要注意的问题。总之在使用树莓派GPU和CPU的通信过程中,弄清楚BCM的SOC的设计思想,注意几个寄存器,并且注意消息的传输格式,那么访问GPU时就不是什么很大的问题了。其中比较有借鉴意义的是共享内存的方式访问,还有就是采用不同的tag进行消息机制的传递。
- pangrank算法--PageRank算法并行实现
- 刷爆朋友圈的 deepfakes 视频人物换脸是怎样炼成的?
- 干货 | ElasticSearch相关性打分机制
- FCN 的简单实现
- 2.运行一个demo
- ROWNUMBER() OVER( PARTITION BY COL1 ORDER BY COL2)/ ROWNUMBER() OVER( PARTITION BY COL1 ORDER BY CO
- 干货 | 前端常用的通信技术
- TP-LINK WR941N路由器研究
- ORA-01113问题的简单分析(r6笔记第3天)
- Tensorflow 中 learning rate decay 的奇技淫巧
- hive数据:名词解释
- 巧妙使用exchange partition的一个案例(r6笔记第1天)
- r与rjava
- 使用expect运行动态脚本(r6笔记第19天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 数据结构算法操作试题(C++/Python)——删除链表的倒数第N个节点
- Day 7:斐波那契数列
- Day8 :跳台阶
- Linux Signal 一网打尽
- Day9 :变态跳台阶
- Day10 :矩形覆盖
- vue3.0新特性初体验(二)
- 数据结构算法操作试题(C++/Python)——有效的括号
- 数据结构算法操作试题(C++/Python)——合并两个有序链表
- Day11 :二进制中1的个数
- 数据结构算法操作试题(C++/Python)——括号生成
- 数据结构算法操作试题(C++/Python)——合并K个排序链表
- 数据结构算法操作试题(C++/Python)——删除排序数组中的重复项
- 数据结构算法操作试题(C++/Python)——k个一组翻转链表
- 数据结构算法操作试题(C++/Python)——移除元素