Linux 核心專題 - 問題紀錄

rpmsg_client_sample

這個是位在 linux/samples 目錄底下的一支範例程式,可以編譯成核心模組。這個模組現在的編譯方法是連同核心一起編譯成動態模組,並且目前可以正常掛載並卸除在 Milk-V 的 linux 環境當中。但是現在還無法驗證這個 driver 模組是否可以運作,因為我還沒找到其相對應的 device ,照 Linux 的說明書文件來看,這個 device 的名稱叫作 channel , 是與另一個核的溝通界面。 所以接著可能要觀察 milkv-duo SoC 的 dts 檔案。

RPMsg-lite

在這個專案中,有個需要引入的檔案名為 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

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

linux-zephyr rpmsg on milkv-duo

論壇文章: link

modified milkv linux kernel : link
milkv-zephyr : link

這個是我在論壇找到的文章,這個作者將原本的 FreeRTOS 改為 ZephyrOS , 並且使用 OpenAMP 提供的 RPMsg 作為大小核的通訊方式。 我發現我可以參考它對 Linux 核心的改動,來確保我現在的 milkv 可以同時運作 mailbox 、 virtio 、 rpmsg 。

想要解決的問題

  1. (已解決) 為了讓 RPMsg-lite 可以順利運作,我想先讓 rpmsg_client_sample 可以配對到 channel 裝置,並且檢查 proberemovecallback 功能, 但是目前還是沒有很確定實作內容,因此我會參考 linux-zephyr 這篇它對 linux 核心的改動。 想請問老師是否有更好的辦法可以解決目前的問題。

先分析遇到的問題,例如 Mailbox 的中斷處理以及佇列配置,不要急著下手寫程式。

  1. (已解決) ThreadX 當中的 tx_api.h 存在許多已宣告但未被實作的函式,而 rpmsg_env_threadx.c 又大量引用裡面的函式,例如 tx_semaphore_create ,這導致在連結階段時出現大量問題,想請問有什麼比較可行的解決辦法。

ThreadX 應該編譯並用 ar 彙整為 libtx.a
謝謝老師! 後來使用 ThreadX-to-RISC-V64 編譯出的 libkernel.a ,解決了連結階段的問題,這個靜態函式庫包含了 ThreadX API 的相關實作。

  1. (已解決) 目前已經有將運行於大核 (C906B) 的 RPMsg-lite 執行檔編譯出來,但隨即遇到執行階段的問題,當前是使用 dmesg 命令將核心偵測到的錯誤內容顯示出來,但是沒有對應的程式碼可以除錯。想請問若是 Milk-V Duo ,該使用 Valgrind 或是 GDB 作為除錯工具較好,目前論壇有人使用 GDB 應用在 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 這篇演講的方式將 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
/**
 * 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)
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

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)
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

  • 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 其位址會被