首先感谢 milkv 的崔工程师提供的Arduino测试环境与代码,让我最终能定位到之前的问题所在。
最近在研究如何使用milkv-duo采集传感器数据,途中理所当然的碰到了大小核之间数据传输的问题。
根据论坛里的这篇帖子,大小核之间的内存共享需要使用到CVI_SYS_IonAlloc
函数,通过mailbox传递内存地址。
第一个问题,在官方提供的mailbox示例中,并没有这个函数对应的头文件和库文件,所以需要将对应的文件夹连接到编译目录中。
头文件在[middleware/v2/include]下,链接到include
文件夹。
库文件分duo与duo256m,分别在[middleware/v2/cv180x/lib_musl_riscv64]与[middleware/v2/cv181x/lib_musl_riscv64]下,链接到lib
文件夹。
引入之后修改makefile文件,将这两个文件夹加入编译,添加以下代码到makefile对应位置:
CFLAGS += -I./include
LDFLAGS += -L./lib
LDFLAGS += -lsys -latomic
之后修改mailbox_test.c
加入共享内存分配相关内容。
#这部分测试代码由崔工程师提供
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "cvi_sys.h"
enum SYSTEM_CMD_TYPE {
CMDQU_SEND = 1,
CMDQU_SEND_WAIT,
CMDQU_SEND_WAKEUP,
};
#define RTOS_CMDQU_DEV_NAME "/dev/cvi-rtos-cmdqu"
#define RTOS_CMDQU_SEND _IOW('r', CMDQU_SEND, unsigned long)
#define RTOS_CMDQU_SEND_WAIT _IOW('r', CMDQU_SEND_WAIT, unsigned long)
#define RTOS_CMDQU_SEND_WAKEUP _IOW('r', CMDQU_SEND_WAKEUP, unsigned long)
enum SYS_CMD_ID {
CMD_TEST_A = 0x10,
CMD_TEST_B,
CMD_TEST_C,
CMD_DUO_LED,
SYS_CMD_INFO_LIMIT,
};
enum DUO_LED_STATUS {
DUO_LED_ON = 0x02,
DUO_LED_OFF,
DUO_LED_DONE,
};
struct valid_t {
unsigned char linux_valid;
unsigned char rtos_valid;
} __attribute__((packed));
typedef union resv_t {
struct valid_t valid;
unsigned short mstime; // 0 : noblock, -1 : block infinite
} resv_t;
typedef struct cmdqu_t cmdqu_t;
/* cmdqu size should be 8 bytes because of mailbox buffer size */
struct cmdqu_t {
unsigned char ip_id;
unsigned char cmd_id : 7;
unsigned char block : 1;
union resv_t resv;
unsigned int param_ptr;
} __attribute__((packed)) __attribute__((aligned(0x8)));
int main() {
int ret = 0;
int fd = open(RTOS_CMDQU_DEV_NAME, O_RDWR);
CVI_U64 phyAddr = 0;
CVI_VOID *pVirAddr = NULL;
int *data;
if (fd <= 0) {
printf("open failed! fd = %d\n", fd);
return 0;
}
struct cmdqu_t cmd = {0};
cmd.ip_id = 0;
cmd.cmd_id = CMD_DUO_LED;
cmd.resv.mstime = 100;
cmd.param_ptr = DUO_LED_ON;
printf("CVI_SYS_IonAlloc:\n");
if (CVI_SYS_IonAlloc(&phyAddr, &pVirAddr, "ION_test", sizeof(int))) {
printf("Ion alloc failed!\n");
return -1;
}
data = (int *)pVirAddr;
printf("Send Mailbox_msg:\n");
cmd.param_ptr = phyAddr;
ret = ioctl(fd, RTOS_CMDQU_SEND_WAIT, &cmd);
if (ret < 0) {
printf("ioctl error!\n");
close(fd);
}
sleep(1);
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
while (*data != 9999 && data != NULL) {
printf("contant of data,set 9999 to stop:\n");
scanf("%d", data);
}
memset(data, 0, sizeof(int));
// sleep(3);
cmd.cmd_id = CMD_DUO_LED;
cmd.param_ptr = 0x00;
ret = ioctl(fd, RTOS_CMDQU_SEND, &cmd);
if (ret < 0) {
printf("ioctl error!\n");
close(fd);
}
sleep(1);
printf("data buffered:%d\n", *data);
printf("C906B: cmd.param_ptr = 0x%x\n", cmd.param_ptr);
CVI_SYS_IonFree(phyAddr, pVirAddr);
close(fd);
return 0;
}
现在,mailbox_test
会将Ionalloc函数分配的共享内存物理地址通过cmd.param_ptr
字段发出,并且根据命令行输入更改对应内存位置的数据。
第二个问题,milkv duo的arduino固件默认的IONSIZE
为0,所以CVI_SYS_IonAlloc
函数必定会失败。只能使用非arduino固件或者换用milkv duo256m。(顺便我试着修改IONIZE重新编译,但是会直接进不了linux系统,提了个issue到现在还没有回复)
选对了固件之后,修改小核固件代码来读写mailbox传过来的地址,这边贴出崔工程师给我的arduino示例:
#这部分测试代码由崔工程师提供
#include "mailbox.h"
int *data = NULL;
struct valid_t {
uint8_t linux_valid;
uint8_t rtos_valid;
} __attribute__((packed));
typedef union resv_t {
struct valid_t valid;
unsigned short mstime; // 0 : noblock, -1 : block infinite
} resv_t;
typedef struct cmdqu_t cmdqu_t;
/* cmdqu size should be 8 bytes because of mailbox buffer size */
struct cmdqu_t {
uint8_t ip_id;
uint8_t cmd_id : 7;
uint8_t block : 1;
union resv_t resv;
unsigned int param_ptr;
} __attribute__((packed)) __attribute__((aligned(0x8)));
void showmsg(MailboxMsg msg) {
cmdqu_t *cmdq;
Serial.print("Get Msg: ");
Serial.println(*(msg.data), HEX);
cmdq = (cmdqu_t *)msg.data;
Serial.printf("cmdq->ip_id = %d\r\n", cmdq->ip_id);
Serial.printf("cmdq->cmd_id = %x\r\n", cmdq->cmd_id);
Serial.printf("cmdq->block = %d\r\n", cmdq->block);
Serial.printf("cmdq->para_ptr = %x\r\n", cmdq->param_ptr);
if(cmdq->cmd_id == 0x13)
{
if(cmdq->param_ptr==0)
*data = 12321;
data = (int *)cmdq->param_ptr;
}
// if(cmdq->cmd_id == 0x13)
// {
// if(cmdq->param_ptr == 0x02)
// {
// digitalWrite(0,HIGH);
// }
// else if(cmdq->param_ptr == 0x03)
// {
// digitalWrite(0,LOW);
// }
// }
*(msg.data) = 0;
}
void setup() {
Serial.begin(115200);
pinMode(0,OUTPUT);
mailbox_init(false);
mailbox_register(0, showmsg);
mailbox_enable_receive(0);
Serial.println("Mailbox Start");
digitalWrite(0,HIGH);
delay(100);
digitalWrite(0,LOW);
}
void loop() {
if(data != NULL)
Serial.printf("data:%d\n",*data);
delay (1000);
}
RT-Thread由于没有mailbox驱动,所以暂时无法使用。不过我正在把arduino的mailbox驱动往RT-Thread移植,现在还有几个bug没有解决,估计下周会在RTT主仓库提交。
Freertos的mailbox部分在[freertos/cvitek/task/comm/src/riscv64/comm_main.c],照葫芦画瓢改一下
#开头初始化变量
int *data = NULL;
#加入一个用来打印数据的TASK 修改31行的这个变量
TASK_CTX_S gTaskCtx[1] = {
{
.name = "CMDQU",
.stack_size = configMINIMAL_STACK_SIZE,
.priority = tskIDLE_PRIORITY + 5,
.runTask = prvCmdQuRunTask,
.queLength = 30,
.queHandle = NULL,
},
{
.name = "memprint",
.stack_size = configMINIMAL_STACK_SIZE,
.priority = tskIDLE_PRIORITY + 5,
.runTask = memprint,
.queLength = 30,
.queHandle = NULL,
},
};
#实现一下
void memprint(void *pvParameters)
{
/* Remove compiler warning about unused parameter. */
(void)pvParameters;
printf("memprint run\n");
for (;;) {
if (data != NULL) {
Serial.printf("data:%d\n", *data);
}
vTaskDelay(1000);
}
}
#把原本用来控制LED的逻辑改成赋值共享内存地址
case CMD_DUO_LED:
rtos_cmdq.cmd_id = CMD_DUO_LED;
printf("recv cmd(%d) from C906B, param_ptr [0x%x]\n",
rtos_cmdq.cmd_id, rtos_cmdq.param_ptr);
if (rtos_cmdq->param_ptr == 0)
*data = 12321;
data = (int *)rtos_cmdq->param_ptr;
rtos_cmdq.param_ptr = DUO_LED_DONE;
rtos_cmdq.resv.valid.rtos_valid = 1;
rtos_cmdq.resv.valid.linux_valid = 0;
printf("recv cmd(%d) from C906B...send [0x%x] to C906B\n",
rtos_cmdq.cmd_id, rtos_cmdq.param_ptr);
goto send_label;
第三个问题,假如现在把程序烧上去做测试,你会发现arduino上的程序能够正常使用,在linux端的修改能够在小核输出及时看到,但是在freertos上,修改之后小核端并不会有任何反应。
这是因为freertos默认开启了Icache与Dcache缓存,读写操作并没有传递到内存,而是在缓存侧就直接结束了,所以大核端的修改并不会及时反映在小核端。
不过在arduino上,默认没有开启这两个缓存,所以测试能够正常运行。
缓存相关的说明可以参考C906核心的用户手册。
解决方法也很简单,修改[freertos/cvitek/arch/riscv64/src/start.S]文件
#把这几行
// invalidate all memory for BTB,BHT,DCACHE,ICACHE
li x3, 0x30013
csrs mcor, x3
// enable ICACHE,DCACHE,BHT,BTB,RAS,WA
li x3, 0x7f
csrs mhcr, x3
// enable data_cache_prefetch, amr
li x3, 0x610c
csrs mhint, x3 #mhint
#改成这样
// invalidate all memory for BTB,BHT,DCACHE,ICACHE
li x3, 0x30013
csrs mcor, x3
// enable ICACHE,BPE,BTB,RS
li x3, 0x71
csrs mhcr, x3
// enable IPLD,IWPE
li x3, 0x6500
csrs mhint, x3
这样就之开启了Icache相关功能,而关闭了Dcache相关功能。
(当然也可以反向修改arduino的start.S文件开启缓存让它跑的更快一点,大概路径在arduino的package路径下sophgo/hardware/SG200X/0.2.5/cores/sg200x/bsp/startup目录)
重新编译上传之后,共享内存就能正确运作了。