异构设计在嵌入式开发过程中非常的重要,比如mcu与mpu的异构,还有两个不同架构的cpu或者两个不同架构的mpu等等。本文主要介绍树莓派的cpu与gpu通信的设计思想。并且通过在树莓派4上进行测试,测试访问gpu所提供的功能。
树莓派上电启动时,首先启动的是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的功能比较齐全。所以访问这些信息是如何进行的呢?
如果要实现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。
上述基本上讲述了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。我们不用去关心具体的时序逻辑问题,当然,可能需要注意的是图像传输完成之后的中断。
在访问GPU的时候,需要注意的是寄存器的地址一定需要通过MMU映射成非cache的模式,否则可能会出现内存一致性问题,导致实际上通道的数据没有写到寄存器中。访问图像的时候,也需要注意这个问题,因为framebuff也需要非cache访问,这些都是在实际项目设计中需要注意的问题。总之在使用树莓派GPU和CPU的通信过程中,弄清楚BCM的SOC的设计思想,注意几个寄存器,并且注意消息的传输格式,那么访问GPU时就不是什么很大的问题了。其中比较有借鉴意义的是共享内存的方式访问,还有就是采用不同的tag进行消息机制的传递。