# Linux 核心專題 - 問題紀錄
## rpmsg_client_sample
這個是位在 `linux/samples` 目錄底下的一支[範例程式](https://github.com/milkv-duo/duo-buildroot-sdk/blob/develop/linux_5.10/samples/rpmsg/rpmsg_client_sample.c),可以編譯成核心模組。這個模組現在的編譯方法是連同核心一起編譯成動態模組,並且目前可以正常掛載並卸除在 Milk-V 的 linux 環境當中。但是現在還無法驗證這個 driver 模組是否可以運作,因為我還沒找到其相對應的 device ,照 Linux 的[說明書文件](https://docs.kernel.org/staging/rpmsg.html)來看,這個 device 的名稱叫作 `channel` , 是與另一個核的溝通界面。 所以接著可能要觀察 milkv-duo SoC 的 `dts` 檔案。
## RPMsg-lite
在這個[專案](https://github.com/nxp-mcuxpresso/rpmsg-lite?fbclid=IwAR23jMwjjoP3MRcEJcmbBd2mePneieOT0Xcsfe_NDwLTNrWm5xgDqykspXQ)中,有個需要引入的檔案名為 `rpmsg_platform.c` (下圖中的 platfrom.c ), 專案有因應不同的 SoC 分別製作屬於他們的 `rpmsg_platform.c` , 但是顯然 cvitek 的 `cv1812cp` 並未被涵蓋在裡面,因此我必須製作屬於 milkv-duo256m 的 `rpmsg_platform.c` 。
目前發現我只須實作 3 個方法,就能支援 `RPMsg-lite` 專案的所有功能。這三個函式分別是 `platform_notify` , `platform_init_interrupt` , `platform_deinit_interrupt` . 而其中這三個函式與處理器相關的程式碼,分別是 觸發、啟動、停用 關於 Virtio 的中斷事件。
因此我現在想要研究關於 milkv-duo256m 所採用的 `cv1812cp` 中 Virtio 的機制,來找出實作 `RPMsg-lite` 的方法。但是我在想最快的方法是,確認 rpmsg_client_sample 可以正常運作 , 如果這個模組可以運作也就代表下層的 Virtio 以及 Mailbox 這兩個機制是有正常運行的,並且我就有機會實作 `RPMsg-lite` 。

## linux-zephyr rpmsg on milkv-duo
> 論壇文章: [link](https://community.milkv.io/t/zephyr-os-running-on-the-small-core/1493)
> modified milkv linux kernel : [link](https://github.com/kinsamanka/milkv-linux/tree/cvitek-v5.10.199)
> milkv-zephyr : [link](https://github.com/kinsamanka/milkv-zephyros)
這個是我在論壇找到的文章,這個作者將原本的 FreeRTOS 改為 ZephyrOS , 並且使用 OpenAMP 提供的 RPMsg 作為大小核的通訊方式。 我發現我可以參考它對 Linux 核心的改動,來確保我現在的 milkv 可以同時運作 mailbox 、 virtio 、 rpmsg 。
## 想要解決的問題
1. (已解決) 為了讓 RPMsg-lite 可以順利運作,我想先讓 rpmsg_client_sample 可以配對到 channel 裝置,並且檢查 `probe` 、 `remove` 及 `callback` 功能, 但是目前還是沒有很確定實作內容,因此我會參考 linux-zephyr 這篇它對 linux 核心的改動。 想請問老師是否有更好的辦法可以解決目前的問題。
> 先分析遇到的問題,例如 Mailbox 的中斷處理以及佇列配置,不要急著下手寫程式。
2. (已解決) ThreadX 當中的 [tx_api.h](https://github.com/eclipse-threadx/threadx/blob/master/common/inc/tx_api.h) 存在許多已宣告但未被實作的函式,而 [rpmsg_env_threadx.c](https://github.com/nxp-mcuxpresso/rpmsg-lite/blob/main/lib/rpmsg_lite/porting/environment/rpmsg_env_threadx.c) 又大量引用裡面的函式,例如 tx_semaphore_create ,這導致在連結階段時出現大量問題,想請問有什麼比較可行的解決辦法。
> ThreadX 應該編譯並用 ar 彙整為 `libtx.a`
> 謝謝老師! 後來使用 [ThreadX-to-RISC-V64](https://github.com/saicogn/ThreadX-to-RISC-V64) 編譯出的 libkernel.a ,解決了連結階段的問題,這個靜態函式庫包含了 ThreadX API 的相關實作。
3. (已解決) 目前已經有將運行於大核 (C906B) 的 RPMsg-lite 執行檔編譯出來,但隨即遇到執行階段的問題,當前是使用 dmesg 命令將核心偵測到的錯誤內容顯示出來,但是沒有對應的程式碼可以除錯。想請問若是 Milk-V Duo ,該使用 Valgrind 或是 GDB 作為除錯工具較好,目前論壇有人使用 [GDB](https://community.milkv.io/t/duo-cv1800b-first-time-c-gdb/56/1) 應用在 Milk-V Duo 上。
* 目前 dmesg 內容:
```
[ 49.983154] helloworld[364]: unhandled signal 11 code 0x1 at 0x0000000000000060 in helloworld[10000+7000]
[ 49.983197] CPU: 0 PID: 364 Comm: helloworld Tainted: GF O 5.10.4-tag- #1
[ 49.983207] epc: 0000000000013f84 ra : 00000000000110f2 sp : 0000003fffb07c80
[ 49.983215] gp : 0000000000019e18 tp : 0000003fbfa4aa38 t0 : ffffffffffffffff
[ 49.983224] t1 : 000000000001065c t2 : 0000000000000000 s0 : 0000003fffb07c90
[ 49.983232] s1 : 0000003fbfa4a7c8 a0 : 0000000000000000 a1 : 0000000000000000
[ 49.983240] a2 : 0000003fffb07d58 a3 : 0000000000000000 a4 : 0000000000000000
[ 49.983247] a5 : 0000000000000000 a6 : 0000000000000002 a7 : 0000000000000004
[ 49.983256] s2 : 0000003fbfa4a970 s3 : 0000003fbfa4ab68 s4 : 0000003fbfa48990
[ 49.983264] s5 : 0000003fbfa4ab30 s6 : 0000000000000000 s7 : 00000000000000e0
[ 49.983273] s8 : 0000003fbfa459e0 s9 : 0000003fffb07db0 s10: 0000003fc9dde8c0
[ 49.983281] s11: 0000000000000001 t3 : 0000003fbf9de97a t4 : 0000000000010390
[ 49.983288] t5 : 00000000000198d8 t6 : 0000000000000003
[ 49.983296] status: 8000000201804020 badaddr: 0000000000000060 cause: 000000000000000d
```
---
### 問題四
關於 RPMsg , 目前是依照 [Linux-and-Zephyr](https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Linux-and-Zephyr-%E2%80%9CTalking%E2%80%9D-to-Each-Other-in-the-Same-SoC-Diego-Sueiro-Sepura-Embarcados-1.pdf) 這篇演講的方式將 Linux 上的 RPMsg 及 Virtio Driver 組態開啟,並且按照演講中的方式編譯 imx_rpmsg.c 作為 Virtio 的裝置 , 但是編譯完的裝置在 virtio_rpmsg_bus.c 這個 Virtio Driver 的 probe 動作下,會出現問題導致 Kernel Oops 。 其中 RPMsg 以及 Virtio 這兩個原始碼皆是原本 linux kernel 5.10 mainline 的內容 , 額外新加入的就是 imx_rpmsg.c 以及 DTS 的修改 。
接著說明 Kernel Oops 的報錯內容 , `Oops - load access fault` , 出錯位置來自於這個巨集 `#define sg_is_last(sg) ((sg)->page_link & SG_END)` 。 我推測原因在於 `virtqueue_add_inbuf` 的函式當中我所加入的 buffer 其配置的空間是來自於 vmalloc , 而非 kmalloc , 導致 scatterlist 初始化的結果與預期不符,而要求記憶體配置的函式並非 vmalloc 、 kmalloc 而是 dma_alloc_coherent ,有趣的是這項函式我們預期得到的是 kmalloc 的空間但並非如此,於是才導致了 `Oops - load access fault` 的問題發生。
至於 imx_rpmsg.c 的初始化過程有順利得到兩個 vring 以及註冊 virtio 裝置 , DTS 我所新增的節點也沒有問題 ,不過我認為上述所產生的錯誤也與這兩個我所更改的部份有關,於是我會放到附件當中 , 我會先附上出錯的程式片段到共筆當中。
* 初始化錯誤訊息
```
[ 0.509253] imx_rpmsg_probe rpdev0 vdev1: vring0 0x1902000, vring1 0x190a000
[ 0.516783] imx rpmsg driver is registered.
[ 0.521181] vring0: phys 0x1902000, virt 0x(____ptrval____)
[ 0.548068] vring1: phys 0x190a000, virt 0x(____ptrval____)
[ 0.577851] rpmsg_probe: virtio_rpmsg num_bufs = 512
[ 0.600338] rpmsg_probe: buffers: va (____ptrval____), dma 0x0000000080e00000
[ 0.607789] rpmsg_probe: cpu_addr: (____ptrval____)
[ 0.617733] rpmsg_probe: sg: (____ptrval____)
[ 0.624801] Oops - load access fault [#1]
[ 0.629003] Modules linked in:
[ 0.632236] CPU: 0 PID: 1 Comm: swapper Not tainted 5.10.4-tag- #2
[ 0.638684] epc: ffffffe0003b1f1a ra : ffffffe000436ac0 sp : ffffffe000a6bad0
[ 0.646119] gp : ffffffe0008af600 tp : ffffffe000a60000 t0 : ffffffe000846f00
[ 0.653646] t1 : 0000000000000001 t2 : 0000000000000090 s0 : 0000000000000001
[ 0.661172] s1 : 0000000000000001 a0 : ffffffe000a6bbd0 a1 : 0000000000080000
[ 0.668698] a2 : 000000000000ffff a3 : ffffffe000a6bbd0 a4 : ffffffe00afb7002
[ 0.676225] a5 : ffffffd0056e8000 a6 : 0000000000000000 a7 : 0000000000000001
[ 0.683752] s2 : ffffffd0056e8000 s3 : 0000000000000000 s4 : ffffffe000bba540
[ 0.691278] s5 : 0000000000000000 s6 : ffffffe000a6bb98 s7 : 0000000000000000
[ 0.698804] s8 : ffffffe000a6bbd0 s9 : 0000000000000000 s10: 0000000000000001
[ 0.706331] s11: ffffffffffffffff t3 : 0000000000000000 t4 : ffffffe000895a38
[ 0.713856] t5 : ffffffe00afb7e08 t6 : ffffffe000a6b888
[ 0.719412] status: 0000000200000120 badaddr: 000000000190200e cause: 0000000000000005
[ 0.727653] Call Trace:
[ 0.730265] [<ffffffe0003b1f1a>] sg_next+0x2/0x1c
[ 0.735193] [<ffffffe00024b9d8>] vprintk_emit+0x176/0x17c
[ 0.740845] [<ffffffe000436b9e>] virtqueue_add_inbuf+0x12/0x1c
```
* scatterlist.c
```c
/**
* sg_next - return the next scatterlist entry in a list
* @sg: The current sg entry
*
* Description:
* Usually the next entry will be @sg@ + 1, but if this sg element is part
* of a chained scatterlist, it could jump to the start of a new
* scatterlist array.
*
**/
struct scatterlist *sg_next(struct scatterlist *sg)
{
if (sg_is_last(sg))
return NULL;
sg++;
if (unlikely(sg_is_chain(sg)))
sg = sg_chain_ptr(sg);
return sg;
}
EXPORT_SYMBOL(sg_next);
```
* virtio_rpmsg_bus.c (line 71 error)
```c=
static int rpmsg_probe(struct virtio_device *vdev)
{
vq_callback_t *vq_cbs[] = { rpmsg_recv_done, rpmsg_xmit_done };
static const char * const names[] = { "input", "output" };
struct virtqueue *vqs[2];
struct virtproc_info *vrp;
void *bufs_va;
int err = 0, i;
size_t total_buf_space;
bool notify;
vrp = kzalloc(sizeof(*vrp), GFP_KERNEL);
if (!vrp)
return -ENOMEM;
vrp->vdev = vdev;
idr_init(&vrp->endpoints);
mutex_init(&vrp->endpoints_lock);
mutex_init(&vrp->tx_lock);
init_waitqueue_head(&vrp->sendq);
/* We expect two virtqueues, rx and tx (and in this order) */
err = virtio_find_vqs(vdev, 2, vqs, vq_cbs, names, NULL);
if (err)
goto free_vrp;
vrp->rvq = vqs[0];
vrp->svq = vqs[1];
/* we expect symmetric tx/rx vrings */
WARN_ON(virtqueue_get_vring_size(vrp->rvq) !=
virtqueue_get_vring_size(vrp->svq));
/* we need less buffers if vrings are small */
if (virtqueue_get_vring_size(vrp->rvq) < MAX_RPMSG_NUM_BUFS / 2)
vrp->num_bufs = virtqueue_get_vring_size(vrp->rvq) * 2;
else
vrp->num_bufs = MAX_RPMSG_NUM_BUFS;
vrp->buf_size = MAX_RPMSG_BUF_SIZE;
total_buf_space = vrp->num_bufs * vrp->buf_size;
/* allocate coherent memory for the buffers */
bufs_va = dma_alloc_coherent(vdev->dev.parent,
total_buf_space , &vrp->bufs_dma,
GFP_KERNEL);
if (!bufs_va) {
err = -ENOMEM;
pr_info("virtio_rpmsg 934\n");
goto vqs_del;
}
pr_info("buffers: va %pK, dma %pad\n",
bufs_va, &vrp->bufs_dma);
/* half of the buffers is dedicated for RX */
vrp->rbufs = bufs_va;
/* and half is dedicated for TX */
vrp->sbufs = bufs_va + total_buf_space / 2;
/* set up the receive buffers */
for (i = 0; i < vrp->num_bufs / 2; i++) {
struct scatterlist sg;
void *cpu_addr = vrp->rbufs + i * vrp->buf_size;
rpmsg_sg_init(&sg, cpu_addr, vrp->buf_size);
err = virtqueue_add_inbuf(vrp->rvq, &sg, 1, cpu_addr,
GFP_KERNEL);
WARN_ON(err); /* sanity check; this can't really happen */
}
/* suppress "tx-complete" interrupts */
virtqueue_disable_cb(vrp->svq);
vdev->priv = vrp;
/* if supported by the remote processor, enable the name service */
if (virtio_has_feature(vdev, VIRTIO_RPMSG_F_NS)) {
/* a dedicated endpoint handles the name service msgs */
vrp->ns_ept = __rpmsg_create_ept(vrp, NULL, rpmsg_ns_cb,
vrp, RPMSG_NS_ADDR);
if (!vrp->ns_ept) {
dev_err(&vdev->dev, "failed to create the ns ept\n");
err = -ENOMEM;
goto free_coherent;
}
}
/*
* Prepare to kick but don't notify yet - we can't do this before
* device is ready.
*/
notify = virtqueue_kick_prepare(vrp->rvq);
/* From this point on, we can notify and get callbacks. */
virtio_device_ready(vdev);
/* tell the remote processor it can start sending messages */
/*
* this might be concurrent with callbacks, but we are only
* doing notify, not a full kick here, so that's ok.
*/
if (notify)
virtqueue_notify(vrp->rvq);
dev_info(&vdev->dev, "rpmsg host is online\n");
return 0;
free_coherent:
dma_free_coherent(vdev->dev.parent, total_buf_space,
bufs_va, vrp->bufs_dma);
vqs_del:
vdev->config->del_vqs(vrp->vdev);
free_vrp:
kfree(vrp);
return err;
}
```
* virtio_ring.c
```c
static inline int virtqueue_add_packed(struct virtqueue *_vq,
struct scatterlist *sgs[],
unsigned int total_sg,
unsigned int out_sgs,
unsigned int in_sgs,
void *data,
void *ctx,
gfp_t gfp)
{
struct vring_virtqueue *vq = to_vvq(_vq);
struct vring_packed_desc *desc;
struct scatterlist *sg;
unsigned int i, n, c, descs_used, err_idx;
__le16 head_flags, flags;
u16 head, id, prev, curr, avail_used_flags;
START_USE(vq);
BUG_ON(data == NULL);
BUG_ON(ctx && vq->indirect);
if (unlikely(vq->broken)) {
END_USE(vq);
return -EIO;
}
LAST_ADD_TIME_UPDATE(vq);
BUG_ON(total_sg == 0);
if (virtqueue_use_indirect(_vq, total_sg))
return virtqueue_add_indirect_packed(vq, sgs, total_sg,
out_sgs, in_sgs, data, gfp);
head = vq->packed.next_avail_idx;
avail_used_flags = vq->packed.avail_used_flags;
WARN_ON_ONCE(total_sg > vq->packed.vring.num && !vq->indirect);
desc = vq->packed.vring.desc;
i = head;
descs_used = total_sg;
if (unlikely(vq->vq.num_free < descs_used)) {
pr_debug("Can't add buf len %i - avail = %i\n",
descs_used, vq->vq.num_free);
END_USE(vq);
return -ENOSPC;
}
id = vq->free_head;
BUG_ON(id == vq->packed.vring.num);
curr = id;
c = 0;
for (n = 0; n < out_sgs + in_sgs; n++) {
for (sg = sgs[n]; sg; sg = sg_next(sg)) {
dma_addr_t addr = vring_map_one_sg(vq, sg, n < out_sgs ?
DMA_TO_DEVICE : DMA_FROM_DEVICE);
if (vring_mapping_error(vq, addr))
goto unmap_release;
flags = cpu_to_le16(vq->packed.avail_used_flags |
(++c == total_sg ? 0 : VRING_DESC_F_NEXT) |
(n < out_sgs ? 0 : VRING_DESC_F_WRITE));
if (i == head)
head_flags = flags;
else
desc[i].flags = flags;
desc[i].addr = cpu_to_le64(addr);
desc[i].len = cpu_to_le32(sg->length);
desc[i].id = cpu_to_le16(id);
if (unlikely(vq->use_dma_api)) {
vq->packed.desc_extra[curr].addr = addr;
vq->packed.desc_extra[curr].len = sg->length;
vq->packed.desc_extra[curr].flags =
le16_to_cpu(flags);
}
prev = curr;
curr = vq->packed.desc_state[curr].next;
if ((unlikely(++i >= vq->packed.vring.num))) {
i = 0;
vq->packed.avail_used_flags ^=
1 << VRING_PACKED_DESC_F_AVAIL |
1 << VRING_PACKED_DESC_F_USED;
}
}
}
if (i < head)
vq->packed.avail_wrap_counter ^= 1;
/* We're using some buffers from the free list. */
vq->vq.num_free -= descs_used;
/* Update free pointer */
vq->packed.next_avail_idx = i;
vq->free_head = curr;
/* Store token. */
vq->packed.desc_state[id].num = descs_used;
vq->packed.desc_state[id].data = data;
vq->packed.desc_state[id].indir_desc = ctx;
vq->packed.desc_state[id].last = prev;
/*
* A driver MUST NOT make the first descriptor in the list
* available before all subsequent descriptors comprising
* the list are made available.
*/
virtio_wmb(vq->weak_barriers);
vq->packed.vring.desc[head].flags = head_flags;
vq->num_added += descs_used;
pr_debug("Added buffer head %i to %p\n", head, vq);
END_USE(vq);
return 0;
unmap_release:
err_idx = i;
i = head;
vq->packed.avail_used_flags = avail_used_flags;
for (n = 0; n < total_sg; n++) {
if (i == err_idx)
break;
vring_unmap_desc_packed(vq, &desc[i]);
i++;
if (i >= vq->packed.vring.num)
i = 0;
}
END_USE(vq);
return -EIO;
}
/*
* Generic functions and exported symbols.
*/
static inline int virtqueue_add(struct virtqueue *_vq,
struct scatterlist *sgs[],
unsigned int total_sg,
unsigned int out_sgs,
unsigned int in_sgs,
void *data,
void *ctx,
gfp_t gfp)
{
struct vring_virtqueue *vq = to_vvq(_vq);
return vq->packed_ring ? virtqueue_add_packed(_vq, sgs, total_sg,
out_sgs, in_sgs, data, ctx, gfp) :
virtqueue_add_split(_vq, sgs, total_sg,
out_sgs, in_sgs, data, ctx, gfp);
}
/**
* virtqueue_add_inbuf - expose input buffers to other end
* @vq: the struct virtqueue we're talking about.
* @sg: scatterlist (must be well-formed and terminated!)
* @num: the number of entries in @sg writable by other side
* @data: the token identifying the buffer.
* @gfp: how to do memory allocations (if necessary).
*
* Caller must ensure we don't call this with other virtqueue operations
* at the same time (except where noted).
*
* Returns zero or a negative error (ie. ENOSPC, ENOMEM, EIO).
*/
int virtqueue_add_inbuf(struct virtqueue *vq,
struct scatterlist *sg, unsigned int num,
void *data,
gfp_t gfp)
{
return virtqueue_add(vq, &sg, num, 0, 1, data, NULL, gfp);
}
EXPORT_SYMBOL_GPL(virtqueue_add_inbuf);
```
* imx_rpmsg.c (register virtio device)
```c
for (j = 0; j < rpdev->vdev_nums; j++) {
pr_debug("%s rpdev%d vdev%d: vring0 0x%x, vring1 0x%x\n",
__func__, i, rpdev->vdev_nums,
rpdev->ivdev[j].vring[0],
rpdev->ivdev[j].vring[1]);
rpdev->ivdev[j].vdev.id.device = VIRTIO_ID_RPMSG;
rpdev->ivdev[j].vdev.config = &imx_rpmsg_config_ops;
rpdev->ivdev[j].vdev.dev.parent = &pdev->dev;
rpdev->ivdev[j].vdev.dev.release = imx_rpmsg_vproc_release;
rpdev->ivdev[j].base_vq_id = j * 2;
ret = register_virtio_device(&rpdev->ivdev[j].vdev);
if (ret) {
pr_err("%s failed to register rpdev: %d\n",
__func__, ret);
return ret;
}
}
```
* cv181x_base.dtsi
```
rpmsg: rpmsg {
compatible = "cvitek,rpmsg";
vdev-nums = <1>;
reg = <0x0 0x01902000 0x0 0x16FD000>;
status = "okay";
};
```
* IMX RPMsg 概念圖

* Virtio DMA 架構
```
CPU CPU Virtio Bus
Virtual Physical Address
Address Address Space
Space Space
+-------+ +------+ +------+
| | |MMIO | Offset | |
| | Virtual |Space | applied | |
C +-------+ --------> B +------+ ----------> +------+ A
| | mapping | | by host | |
+-----+ | | | | bridge | | +------------+
| | | | +------+ | | | RPMsg |
| CPU | | | | | | | | Device |
| | | | | DMA | | | | |
+-----+ +-------+ |Buffer| +------+ +------------+
| | Virtual | | Mapping | |
X(bufs_va)+-------+ --------> Y +------+ <---------- +------+ Z
| | mapping | RAM | by IOMMU
| | | |
| | | |
+-------+ +------+
```
問題簡述: 目前已知 imx_rpmsg.c 會生成兩個 vring 以及 virtio device , 到了 virtio_rpmsg_bus.c 的 virtio driver probe 階段,會透過 dma_alloc_coherent 得到 vring 的虛擬位址,
### 問題五
我會盡量把問題講的詳盡一點,同上一題問題四出現的問題,程式在呼叫 `sg_next` 這個函式時,會出現 Load Access Fault 這項問題,經過我查詢的結果發現這是 RISC-V 的記憶體保護機制所偵測到的問題,也發現到出現存取問題的位址在 `000000000190200e` , 正好是我 rpmsg 裝置的起始位址加上 `14` 。 之所以會有這個結果,首先要從我的 virtqueue 講起,我的 virtqueue 是透過 `dma_alloc_coherent` 的方式,映射我的 rpmsg 裝置到實體記憶體當中,而回傳的虛擬位址 `bufs_va` 也就當做是我 virtqueue 的起始位址 。 之後依序加入 virtqueue 的 buffer 其位址會被