通过Ionalloc实现大小核内存共享

首先感谢 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目录)
重新编译上传之后,共享内存就能正确运作了。

1 Like

小核使用freertos可以不关闭Dcache,使用flush_dcache_range刷新缓存

1 Like

我测试了一下,头文件应该是cvitek/arch/riscv64/include/arch_helpers.h?
只要在读前写后调用一下这个函数就可以,而且不会像禁用Dcache一样有比较大的性能影响,确实是一个更好的方法,感谢!
不过默认不是inline函数有点不理解,明明是段短汇编。