一种协处理器的实现方式

概述

当一块板子上有2个cpu(不是一个双核CPU),比如一种典型的应用,一个ARM做主控,一个DSP芯片做算法运算,此时就涉及本文要描述的情形,我们叫协处理器的实现方式,ARM作为主控,大部分的业务逻辑都在主控端,比如操作系统、内存管理、协处理器的控制,DSP在这里叫做协处理器,主要是为了分担主控的一些运算压力,以及比如涉及浮点运算的算法,这些需要跑在DSP上。本文主要讲述这种情形下的一种实现方式。

前提、硬件支持

1、主控和协处理器共享总线。这里可能是AHB总线,通过AHB可以访问到DDR或者主存上。
2、在主控芯片的设计上,需要一些协处理器的寄存器及中断资源才可以运行。
      1)协处理器的运行起始地址
      2)协处理器的时钟源及分频配置
      3)协处理器到主控的软中断的分配
      4)协处理器固件的内存空间的分配
      5)协处理器和主控之间的FIFO
3、为协处理器预留一段内核空间用于存放rpc数据,当然了这个地址主控也是能访问的,等于是一段共享的内存用于传递rpc参数

协处理器的准备

先不管主控,我们先看协处理器的操作。
首先我们要编译协处理器的固件代码,我们协处理器其实可以看作一个单片机在执行在执行一个wile(1)的循环,检查主控传过来的数据(通过硬件FIFO),数据结构后面再讲。数据的内容为两边协定好的数据格式,协处理器读到FIFO有数据就进行匹配,匹配成功就执行对应的操作,并返回运算结果(如果有,也是通过硬件FIFO以及软中断),匹配失败就丢掉。
这里碎碎念一点东西:
1、进入协处理器的main函数之前,是有一段汇编代码的,会定义一些data段、bss段、栈、中断这些,定义好后才会跳转到主函数(br main)
2、主函数在进入while(1)之前会先做一些自己资源的初始化(serial、timer)
3、两个CPU之间要有硬件FIFO可以进行传输,且有两个FIFO,一个作为数据输入,一个作为数据输出,因为一直检查输入FIFO,输入就不需要辅助的机制了,但是作为输出的FIFO还要有个软中断机制作为辅助。就是当你有数据写入到输出FIFO中时,写完后你要踩下这个中断告知主控。这里的机制的命名,叫mailbox,中断叫doorbell
4、固件编出来生成了.bin文件(跟makefile有关),固件内容里会包括一些信息提供给主控端,比如:内部ram大小,rpc通信地址,
5、主控会将.bin文件特殊处理下,后面在讲
主控端对协处理器的管理
主控端是先行启动并运行的,要想引导协处理器的启动,需要如下几步:
1、申请内存,将协处理器固件读入。并将启示地址写入寄存器(芯片设计时会定义这个)。这里有个处理,是将协处理器的固件(.bin文件)转化为.o文件,处理成一个数组结构体。这里使用的是makefile中的OBJCOPY。
BUNCOPY := $(OBJCOPY) -I binary -O elf32-littlearm -B arm ../bin/firmware.bin ../bin/firmware.o
这里会把bin文件夹下的firmware.bin文件转化为firmware.o文件,还放在bin路径下,这里怎么写会影响生成的结构体的名字,像上面这么写,就会生成两个全局变量:extern u32 _binary____bin_firmware_bin_star和extern u32 _binary____bin_firmware_bin_end。_binary____bin_firmware_bin_end-_binary____bin_firmware_bin_star就是这个.o文件的size,_binary____bin_firmware_bin_star就是数组结构体的名字,可以把他当作一个指针,这样就可以通过(u32)*((u32 *)_binary____bin_firmware_bin_star)去访问这个.o文件了。
2、固件启动。要为固件配置时钟、分频、reset操作,然后start。这些都是主控芯片设计好的寄存器,只需要是能就好了。
3、驱动。其实1、2也是数据驱动的一部分,除了这些,还不够,还要有中断的处理。前面讲到协处理器处理完毕后要按“门铃”,就是踩一个中断告诉主控做完了,主控这边要对这个中断进行处理。一开始的时候中断采用的是异步通知(fasync),在收到中断后kill_fasync,当然了fasync要在应用层进行配置,将设备的打开方式设置为FASYNC,系统会操作file_operations->fasync函数,然后将进程pid传给fasync_struct->fa_file->f_onwer->pid.
处理如下:
signal(SIGIO,handlefunc(注册信号来了的回掉函数));
pid=getpid();
fcntl(fd,F_SETOWN,pid);
flag = fcntl(fd, FGETFL);
fcntl(fd, F_SETFL, flag | FASYNC);
这样就绑定了异步通知的pid号,等驱动中处理了这个信号后,会调到对应的handlefunc。
但是统一进程多线程时,pid是相同的,这样处理没半办法区分。后面采用了信号量处理,对应的唤醒线程放在了rpc数据中,这样中断触发以后,读到rpc数据后,唤醒对应的sem即可(驱动会为每个rpc申请一个sem).
RPC通信
rpc是远程调用协议的简称,主机可以通过rpc在远程服务器上执行对应的操作,这里我们的主控作为服务端,协处理器作为客户端,主控通过rpc进行控制客户端执行计算,并获取运算结果的过程。
在使用rpc之前,有一前提条件需要知道:
主控和协处理器可以访问同一个物理内存。两端的虚拟地址各不相同,但是对应一片物理地址,是两个处理器都能访问的,是实现的基础。
然后通过如下一步步操作进行rpc通信:
1、检查固件、rpc初始化。因为每一次rpc通信都是相对独立的动作,没有过多的依赖关系,所以并不知道是否有接口进行了rpc初始化、固件加载这些动作。当固件加载和rpc初始化好了后要有对应的标志位。后面的rpc只要检查一下就好了。
2、获取一块rpc内存。这个rpc内存为主控和协处理器都能访问的地址,由协处理器侧定义的大小和起始地址。这些数据会放在固件的头部,主控在加载固件的时候可以拿到这些数据,然后用这个数据初始化rpc。经历这些以后,这里才可以直接获取一块rpc地址。这个地址内包含一次rpc要填充的数据,我们这里结构体是这么样子的:
struct rpc_param
{
u32 funid;
u32 waitforreturn;
u32 pid;
u32 null;
phyaddr wakeup;
phyaddr ret;
phyaddr param[10];
}
其中funid为标记使用的函数,等于是有个表标记编号和对应的函数的对应关系。param为函数的参数,这里最大支持10个参数。
这里还有2点
1)同一个父函数(比如一个音频函数有open、close、encode,那么音频韩功能就是父函数,open、encode这些叫子函数)下的不同子函数公用一段rpc内存,这样设计一个是rpc资源本来就有限,另一个协处理器那边是一个函数一个函数运算的,同一个进程下不存在open和encode同时传递的过程,所以同一个父函数下不同的子函数公用同一个rpc
2)多进程下,在申请rpc的时候就要做互斥,而且是进程间的互斥,这样的锁可能没有,我们要采取硬件锁来实现

3、填充rpc数据
4、发送rpc数据
5、等待rpc返回
6、处理rpc返回数据
7、释放rpc
六级标题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值