# 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` 。 ![Screenshot from 2024-06-20 15-51-19](https://hackmd.io/_uploads/HyWTMDWIR.png) ## 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 概念圖 ![image](https://hackmd.io/_uploads/r1XhSx9i0.png) * 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 其位址會被