162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Remote processor messaging transport (OMAP platform-specific bits)
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2011 Texas Instruments, Inc.
662306a36Sopenharmony_ci * Copyright (C) 2011 Google, Inc.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Ohad Ben-Cohen <ohad@wizery.com>
962306a36Sopenharmony_ci * Brian Swetland <swetland@google.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/dma-direct.h>
1362306a36Sopenharmony_ci#include <linux/dma-map-ops.h>
1462306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1562306a36Sopenharmony_ci#include <linux/export.h>
1662306a36Sopenharmony_ci#include <linux/of_reserved_mem.h>
1762306a36Sopenharmony_ci#include <linux/platform_device.h>
1862306a36Sopenharmony_ci#include <linux/remoteproc.h>
1962306a36Sopenharmony_ci#include <linux/virtio.h>
2062306a36Sopenharmony_ci#include <linux/virtio_config.h>
2162306a36Sopenharmony_ci#include <linux/virtio_ids.h>
2262306a36Sopenharmony_ci#include <linux/virtio_ring.h>
2362306a36Sopenharmony_ci#include <linux/err.h>
2462306a36Sopenharmony_ci#include <linux/kref.h>
2562306a36Sopenharmony_ci#include <linux/slab.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include "remoteproc_internal.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic int copy_dma_range_map(struct device *to, struct device *from)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	const struct bus_dma_region *map = from->dma_range_map, *new_map, *r;
3262306a36Sopenharmony_ci	int num_ranges = 0;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	if (!map)
3562306a36Sopenharmony_ci		return 0;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	for (r = map; r->size; r++)
3862306a36Sopenharmony_ci		num_ranges++;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	new_map = kmemdup(map, array_size(num_ranges + 1, sizeof(*map)),
4162306a36Sopenharmony_ci			  GFP_KERNEL);
4262306a36Sopenharmony_ci	if (!new_map)
4362306a36Sopenharmony_ci		return -ENOMEM;
4462306a36Sopenharmony_ci	to->dma_range_map = new_map;
4562306a36Sopenharmony_ci	return 0;
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	struct platform_device *pdev;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	pdev = container_of(vdev->dev.parent, struct platform_device, dev);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return platform_get_drvdata(pdev);
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic  struct rproc *vdev_to_rproc(struct virtio_device *vdev)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	return rvdev->rproc;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci/* kick the remote processor, and let it know which virtqueue to poke at */
6562306a36Sopenharmony_cistatic bool rproc_virtio_notify(struct virtqueue *vq)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct rproc_vring *rvring = vq->priv;
6862306a36Sopenharmony_ci	struct rproc *rproc = rvring->rvdev->rproc;
6962306a36Sopenharmony_ci	int notifyid = rvring->notifyid;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	dev_dbg(&rproc->dev, "kicking vq index: %d\n", notifyid);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	rproc->ops->kick(rproc, notifyid);
7462306a36Sopenharmony_ci	return true;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/**
7862306a36Sopenharmony_ci * rproc_vq_interrupt() - tell remoteproc that a virtqueue is interrupted
7962306a36Sopenharmony_ci * @rproc: handle to the remote processor
8062306a36Sopenharmony_ci * @notifyid: index of the signalled virtqueue (unique per this @rproc)
8162306a36Sopenharmony_ci *
8262306a36Sopenharmony_ci * This function should be called by the platform-specific rproc driver,
8362306a36Sopenharmony_ci * when the remote processor signals that a specific virtqueue has pending
8462306a36Sopenharmony_ci * messages available.
8562306a36Sopenharmony_ci *
8662306a36Sopenharmony_ci * Return: IRQ_NONE if no message was found in the @notifyid virtqueue,
8762306a36Sopenharmony_ci * and otherwise returns IRQ_HANDLED.
8862306a36Sopenharmony_ci */
8962306a36Sopenharmony_ciirqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct rproc_vring *rvring;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	dev_dbg(&rproc->dev, "vq index %d is interrupted\n", notifyid);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	rvring = idr_find(&rproc->notifyids, notifyid);
9662306a36Sopenharmony_ci	if (!rvring || !rvring->vq)
9762306a36Sopenharmony_ci		return IRQ_NONE;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	return vring_interrupt(0, rvring->vq);
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ciEXPORT_SYMBOL(rproc_vq_interrupt);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic struct virtqueue *rp_find_vq(struct virtio_device *vdev,
10462306a36Sopenharmony_ci				    unsigned int id,
10562306a36Sopenharmony_ci				    void (*callback)(struct virtqueue *vq),
10662306a36Sopenharmony_ci				    const char *name, bool ctx)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
10962306a36Sopenharmony_ci	struct rproc *rproc = vdev_to_rproc(vdev);
11062306a36Sopenharmony_ci	struct device *dev = &rproc->dev;
11162306a36Sopenharmony_ci	struct rproc_mem_entry *mem;
11262306a36Sopenharmony_ci	struct rproc_vring *rvring;
11362306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
11462306a36Sopenharmony_ci	struct virtqueue *vq;
11562306a36Sopenharmony_ci	void *addr;
11662306a36Sopenharmony_ci	int num, size;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/* we're temporarily limited to two virtqueues per rvdev */
11962306a36Sopenharmony_ci	if (id >= ARRAY_SIZE(rvdev->vring))
12062306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	if (!name)
12362306a36Sopenharmony_ci		return NULL;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* Search allocated memory region by name */
12662306a36Sopenharmony_ci	mem = rproc_find_carveout_by_name(rproc, "vdev%dvring%d", rvdev->index,
12762306a36Sopenharmony_ci					  id);
12862306a36Sopenharmony_ci	if (!mem || !mem->va)
12962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	rvring = &rvdev->vring[id];
13262306a36Sopenharmony_ci	addr = mem->va;
13362306a36Sopenharmony_ci	num = rvring->num;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/* zero vring */
13662306a36Sopenharmony_ci	size = vring_size(num, rvring->align);
13762306a36Sopenharmony_ci	memset(addr, 0, size);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	dev_dbg(dev, "vring%d: va %pK qsz %d notifyid %d\n",
14062306a36Sopenharmony_ci		id, addr, num, rvring->notifyid);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	/*
14362306a36Sopenharmony_ci	 * Create the new vq, and tell virtio we're not interested in
14462306a36Sopenharmony_ci	 * the 'weak' smp barriers, since we're talking with a real device.
14562306a36Sopenharmony_ci	 */
14662306a36Sopenharmony_ci	vq = vring_new_virtqueue(id, num, rvring->align, vdev, false, ctx,
14762306a36Sopenharmony_ci				 addr, rproc_virtio_notify, callback, name);
14862306a36Sopenharmony_ci	if (!vq) {
14962306a36Sopenharmony_ci		dev_err(dev, "vring_new_virtqueue %s failed\n", name);
15062306a36Sopenharmony_ci		rproc_free_vring(rvring);
15162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	vq->num_max = num;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	rvring->vq = vq;
15762306a36Sopenharmony_ci	vq->priv = rvring;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* Update vring in resource table */
16062306a36Sopenharmony_ci	rsc = (void *)rproc->table_ptr + rvdev->rsc_offset;
16162306a36Sopenharmony_ci	rsc->vring[id].da = mem->da;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	return vq;
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic void __rproc_virtio_del_vqs(struct virtio_device *vdev)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	struct virtqueue *vq, *n;
16962306a36Sopenharmony_ci	struct rproc_vring *rvring;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
17262306a36Sopenharmony_ci		rvring = vq->priv;
17362306a36Sopenharmony_ci		rvring->vq = NULL;
17462306a36Sopenharmony_ci		vring_del_virtqueue(vq);
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic void rproc_virtio_del_vqs(struct virtio_device *vdev)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	__rproc_virtio_del_vqs(vdev);
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
18462306a36Sopenharmony_ci				 struct virtqueue *vqs[],
18562306a36Sopenharmony_ci				 vq_callback_t *callbacks[],
18662306a36Sopenharmony_ci				 const char * const names[],
18762306a36Sopenharmony_ci				 const bool * ctx,
18862306a36Sopenharmony_ci				 struct irq_affinity *desc)
18962306a36Sopenharmony_ci{
19062306a36Sopenharmony_ci	int i, ret, queue_idx = 0;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	for (i = 0; i < nvqs; ++i) {
19362306a36Sopenharmony_ci		if (!names[i]) {
19462306a36Sopenharmony_ci			vqs[i] = NULL;
19562306a36Sopenharmony_ci			continue;
19662306a36Sopenharmony_ci		}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		vqs[i] = rp_find_vq(vdev, queue_idx++, callbacks[i], names[i],
19962306a36Sopenharmony_ci				    ctx ? ctx[i] : false);
20062306a36Sopenharmony_ci		if (IS_ERR(vqs[i])) {
20162306a36Sopenharmony_ci			ret = PTR_ERR(vqs[i]);
20262306a36Sopenharmony_ci			goto error;
20362306a36Sopenharmony_ci		}
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return 0;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cierror:
20962306a36Sopenharmony_ci	__rproc_virtio_del_vqs(vdev);
21062306a36Sopenharmony_ci	return ret;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic u8 rproc_virtio_get_status(struct virtio_device *vdev)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
21662306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return rsc->status;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic void rproc_virtio_set_status(struct virtio_device *vdev, u8 status)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
22662306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	rsc->status = status;
23162306a36Sopenharmony_ci	dev_dbg(&vdev->dev, "status: %d\n", status);
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic void rproc_virtio_reset(struct virtio_device *vdev)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
23762306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	rsc->status = 0;
24262306a36Sopenharmony_ci	dev_dbg(&vdev->dev, "reset !\n");
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci/* provide the vdev features as retrieved from the firmware */
24662306a36Sopenharmony_cistatic u64 rproc_virtio_get_features(struct virtio_device *vdev)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
24962306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return rsc->dfeatures;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic void rproc_transport_features(struct virtio_device *vdev)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	/*
25962306a36Sopenharmony_ci	 * Packed ring isn't enabled on remoteproc for now,
26062306a36Sopenharmony_ci	 * because remoteproc uses vring_new_virtqueue() which
26162306a36Sopenharmony_ci	 * creates virtio rings on preallocated memory.
26262306a36Sopenharmony_ci	 */
26362306a36Sopenharmony_ci	__virtio_clear_bit(vdev, VIRTIO_F_RING_PACKED);
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cistatic int rproc_virtio_finalize_features(struct virtio_device *vdev)
26762306a36Sopenharmony_ci{
26862306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
26962306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	/* Give virtio_ring a chance to accept features */
27462306a36Sopenharmony_ci	vring_transport_features(vdev);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	/* Give virtio_rproc a chance to accept features. */
27762306a36Sopenharmony_ci	rproc_transport_features(vdev);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/* Make sure we don't have any features > 32 bits! */
28062306a36Sopenharmony_ci	BUG_ON((u32)vdev->features != vdev->features);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	/*
28362306a36Sopenharmony_ci	 * Remember the finalized features of our vdev, and provide it
28462306a36Sopenharmony_ci	 * to the remote processor once it is powered on.
28562306a36Sopenharmony_ci	 */
28662306a36Sopenharmony_ci	rsc->gfeatures = vdev->features;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	return 0;
28962306a36Sopenharmony_ci}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_cistatic void rproc_virtio_get(struct virtio_device *vdev, unsigned int offset,
29262306a36Sopenharmony_ci			     void *buf, unsigned int len)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
29562306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
29662306a36Sopenharmony_ci	void *cfg;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
29962306a36Sopenharmony_ci	cfg = &rsc->vring[rsc->num_of_vrings];
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	if (offset + len > rsc->config_len || offset + len < len) {
30262306a36Sopenharmony_ci		dev_err(&vdev->dev, "rproc_virtio_get: access out of bounds\n");
30362306a36Sopenharmony_ci		return;
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	memcpy(buf, cfg + offset, len);
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cistatic void rproc_virtio_set(struct virtio_device *vdev, unsigned int offset,
31062306a36Sopenharmony_ci			     const void *buf, unsigned int len)
31162306a36Sopenharmony_ci{
31262306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
31362306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
31462306a36Sopenharmony_ci	void *cfg;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
31762306a36Sopenharmony_ci	cfg = &rsc->vring[rsc->num_of_vrings];
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	if (offset + len > rsc->config_len || offset + len < len) {
32062306a36Sopenharmony_ci		dev_err(&vdev->dev, "rproc_virtio_set: access out of bounds\n");
32162306a36Sopenharmony_ci		return;
32262306a36Sopenharmony_ci	}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	memcpy(cfg + offset, buf, len);
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic const struct virtio_config_ops rproc_virtio_config_ops = {
32862306a36Sopenharmony_ci	.get_features	= rproc_virtio_get_features,
32962306a36Sopenharmony_ci	.finalize_features = rproc_virtio_finalize_features,
33062306a36Sopenharmony_ci	.find_vqs	= rproc_virtio_find_vqs,
33162306a36Sopenharmony_ci	.del_vqs	= rproc_virtio_del_vqs,
33262306a36Sopenharmony_ci	.reset		= rproc_virtio_reset,
33362306a36Sopenharmony_ci	.set_status	= rproc_virtio_set_status,
33462306a36Sopenharmony_ci	.get_status	= rproc_virtio_get_status,
33562306a36Sopenharmony_ci	.get		= rproc_virtio_get,
33662306a36Sopenharmony_ci	.set		= rproc_virtio_set,
33762306a36Sopenharmony_ci};
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci/*
34062306a36Sopenharmony_ci * This function is called whenever vdev is released, and is responsible
34162306a36Sopenharmony_ci * to decrement the remote processor's refcount which was taken when vdev was
34262306a36Sopenharmony_ci * added.
34362306a36Sopenharmony_ci *
34462306a36Sopenharmony_ci * Never call this function directly; it will be called by the driver
34562306a36Sopenharmony_ci * core when needed.
34662306a36Sopenharmony_ci */
34762306a36Sopenharmony_cistatic void rproc_virtio_dev_release(struct device *dev)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct virtio_device *vdev = dev_to_virtio(dev);
35062306a36Sopenharmony_ci	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	kfree(vdev);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	of_reserved_mem_device_release(&rvdev->pdev->dev);
35562306a36Sopenharmony_ci	dma_release_coherent_memory(&rvdev->pdev->dev);
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	put_device(&rvdev->pdev->dev);
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci/**
36162306a36Sopenharmony_ci * rproc_add_virtio_dev() - register an rproc-induced virtio device
36262306a36Sopenharmony_ci * @rvdev: the remote vdev
36362306a36Sopenharmony_ci * @id: the device type identification (used to match it with a driver).
36462306a36Sopenharmony_ci *
36562306a36Sopenharmony_ci * This function registers a virtio device. This vdev's partent is
36662306a36Sopenharmony_ci * the rproc device.
36762306a36Sopenharmony_ci *
36862306a36Sopenharmony_ci * Return: 0 on success or an appropriate error value otherwise
36962306a36Sopenharmony_ci */
37062306a36Sopenharmony_cistatic int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	struct rproc *rproc = rvdev->rproc;
37362306a36Sopenharmony_ci	struct device *dev = &rvdev->pdev->dev;
37462306a36Sopenharmony_ci	struct virtio_device *vdev;
37562306a36Sopenharmony_ci	struct rproc_mem_entry *mem;
37662306a36Sopenharmony_ci	int ret;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	if (rproc->ops->kick == NULL) {
37962306a36Sopenharmony_ci		ret = -EINVAL;
38062306a36Sopenharmony_ci		dev_err(dev, ".kick method not defined for %s\n", rproc->name);
38162306a36Sopenharmony_ci		goto out;
38262306a36Sopenharmony_ci	}
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	/* Try to find dedicated vdev buffer carveout */
38562306a36Sopenharmony_ci	mem = rproc_find_carveout_by_name(rproc, "vdev%dbuffer", rvdev->index);
38662306a36Sopenharmony_ci	if (mem) {
38762306a36Sopenharmony_ci		phys_addr_t pa;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci		if (mem->of_resm_idx != -1) {
39062306a36Sopenharmony_ci			struct device_node *np = rproc->dev.parent->of_node;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci			/* Associate reserved memory to vdev device */
39362306a36Sopenharmony_ci			ret = of_reserved_mem_device_init_by_idx(dev, np,
39462306a36Sopenharmony_ci								 mem->of_resm_idx);
39562306a36Sopenharmony_ci			if (ret) {
39662306a36Sopenharmony_ci				dev_err(dev, "Can't associate reserved memory\n");
39762306a36Sopenharmony_ci				goto out;
39862306a36Sopenharmony_ci			}
39962306a36Sopenharmony_ci		} else {
40062306a36Sopenharmony_ci			if (mem->va) {
40162306a36Sopenharmony_ci				dev_warn(dev, "vdev %d buffer already mapped\n",
40262306a36Sopenharmony_ci					 rvdev->index);
40362306a36Sopenharmony_ci				pa = rproc_va_to_pa(mem->va);
40462306a36Sopenharmony_ci			} else {
40562306a36Sopenharmony_ci				/* Use dma address as carveout no memmapped yet */
40662306a36Sopenharmony_ci				pa = (phys_addr_t)mem->dma;
40762306a36Sopenharmony_ci			}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci			/* Associate vdev buffer memory pool to vdev subdev */
41062306a36Sopenharmony_ci			ret = dma_declare_coherent_memory(dev, pa,
41162306a36Sopenharmony_ci							   mem->da,
41262306a36Sopenharmony_ci							   mem->len);
41362306a36Sopenharmony_ci			if (ret < 0) {
41462306a36Sopenharmony_ci				dev_err(dev, "Failed to associate buffer\n");
41562306a36Sopenharmony_ci				goto out;
41662306a36Sopenharmony_ci			}
41762306a36Sopenharmony_ci		}
41862306a36Sopenharmony_ci	} else {
41962306a36Sopenharmony_ci		struct device_node *np = rproc->dev.parent->of_node;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci		/*
42262306a36Sopenharmony_ci		 * If we don't have dedicated buffer, just attempt to re-assign
42362306a36Sopenharmony_ci		 * the reserved memory from our parent. A default memory-region
42462306a36Sopenharmony_ci		 * at index 0 from the parent's memory-regions is assigned for
42562306a36Sopenharmony_ci		 * the rvdev dev to allocate from. Failure is non-critical and
42662306a36Sopenharmony_ci		 * the allocations will fall back to global pools, so don't
42762306a36Sopenharmony_ci		 * check return value either.
42862306a36Sopenharmony_ci		 */
42962306a36Sopenharmony_ci		of_reserved_mem_device_init_by_idx(dev, np, 0);
43062306a36Sopenharmony_ci	}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	/* Allocate virtio device */
43362306a36Sopenharmony_ci	vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
43462306a36Sopenharmony_ci	if (!vdev) {
43562306a36Sopenharmony_ci		ret = -ENOMEM;
43662306a36Sopenharmony_ci		goto out;
43762306a36Sopenharmony_ci	}
43862306a36Sopenharmony_ci	vdev->id.device	= id,
43962306a36Sopenharmony_ci	vdev->config = &rproc_virtio_config_ops,
44062306a36Sopenharmony_ci	vdev->dev.parent = dev;
44162306a36Sopenharmony_ci	vdev->dev.release = rproc_virtio_dev_release;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	/* Reference the vdev and vring allocations */
44462306a36Sopenharmony_ci	get_device(dev);
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	ret = register_virtio_device(vdev);
44762306a36Sopenharmony_ci	if (ret) {
44862306a36Sopenharmony_ci		put_device(&vdev->dev);
44962306a36Sopenharmony_ci		dev_err(dev, "failed to register vdev: %d\n", ret);
45062306a36Sopenharmony_ci		goto out;
45162306a36Sopenharmony_ci	}
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	dev_info(dev, "registered %s (type %d)\n", dev_name(&vdev->dev), id);
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ciout:
45662306a36Sopenharmony_ci	return ret;
45762306a36Sopenharmony_ci}
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci/**
46062306a36Sopenharmony_ci * rproc_remove_virtio_dev() - remove an rproc-induced virtio device
46162306a36Sopenharmony_ci * @dev: the virtio device
46262306a36Sopenharmony_ci * @data: must be null
46362306a36Sopenharmony_ci *
46462306a36Sopenharmony_ci * This function unregisters an existing virtio device.
46562306a36Sopenharmony_ci *
46662306a36Sopenharmony_ci * Return: 0
46762306a36Sopenharmony_ci */
46862306a36Sopenharmony_cistatic int rproc_remove_virtio_dev(struct device *dev, void *data)
46962306a36Sopenharmony_ci{
47062306a36Sopenharmony_ci	struct virtio_device *vdev = dev_to_virtio(dev);
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	unregister_virtio_device(vdev);
47362306a36Sopenharmony_ci	return 0;
47462306a36Sopenharmony_ci}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_cistatic int rproc_vdev_do_start(struct rproc_subdev *subdev)
47762306a36Sopenharmony_ci{
47862306a36Sopenharmony_ci	struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev);
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	return rproc_add_virtio_dev(rvdev, rvdev->id);
48162306a36Sopenharmony_ci}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_cistatic void rproc_vdev_do_stop(struct rproc_subdev *subdev, bool crashed)
48462306a36Sopenharmony_ci{
48562306a36Sopenharmony_ci	struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev);
48662306a36Sopenharmony_ci	struct device *dev = &rvdev->pdev->dev;
48762306a36Sopenharmony_ci	int ret;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	ret = device_for_each_child(dev, NULL, rproc_remove_virtio_dev);
49062306a36Sopenharmony_ci	if (ret)
49162306a36Sopenharmony_ci		dev_warn(dev, "can't remove vdev child device: %d\n", ret);
49262306a36Sopenharmony_ci}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_cistatic int rproc_virtio_probe(struct platform_device *pdev)
49562306a36Sopenharmony_ci{
49662306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
49762306a36Sopenharmony_ci	struct rproc_vdev_data *rvdev_data = dev->platform_data;
49862306a36Sopenharmony_ci	struct rproc_vdev *rvdev;
49962306a36Sopenharmony_ci	struct rproc *rproc = container_of(dev->parent, struct rproc, dev);
50062306a36Sopenharmony_ci	struct fw_rsc_vdev *rsc;
50162306a36Sopenharmony_ci	int i, ret;
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	if (!rvdev_data)
50462306a36Sopenharmony_ci		return -EINVAL;
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	rvdev = devm_kzalloc(dev, sizeof(*rvdev), GFP_KERNEL);
50762306a36Sopenharmony_ci	if (!rvdev)
50862306a36Sopenharmony_ci		return -ENOMEM;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	rvdev->id = rvdev_data->id;
51162306a36Sopenharmony_ci	rvdev->rproc = rproc;
51262306a36Sopenharmony_ci	rvdev->index = rvdev_data->index;
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	ret = copy_dma_range_map(dev, rproc->dev.parent);
51562306a36Sopenharmony_ci	if (ret)
51662306a36Sopenharmony_ci		return ret;
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	/* Make device dma capable by inheriting from parent's capabilities */
51962306a36Sopenharmony_ci	set_dma_ops(dev, get_dma_ops(rproc->dev.parent));
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	ret = dma_coerce_mask_and_coherent(dev, dma_get_mask(rproc->dev.parent));
52262306a36Sopenharmony_ci	if (ret) {
52362306a36Sopenharmony_ci		dev_warn(dev, "Failed to set DMA mask %llx. Trying to continue... (%pe)\n",
52462306a36Sopenharmony_ci			 dma_get_mask(rproc->dev.parent), ERR_PTR(ret));
52562306a36Sopenharmony_ci	}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	platform_set_drvdata(pdev, rvdev);
52862306a36Sopenharmony_ci	rvdev->pdev = pdev;
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	rsc = rvdev_data->rsc;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	/* parse the vrings */
53362306a36Sopenharmony_ci	for (i = 0; i < rsc->num_of_vrings; i++) {
53462306a36Sopenharmony_ci		ret = rproc_parse_vring(rvdev, rsc, i);
53562306a36Sopenharmony_ci		if (ret)
53662306a36Sopenharmony_ci			return ret;
53762306a36Sopenharmony_ci	}
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	/* remember the resource offset*/
54062306a36Sopenharmony_ci	rvdev->rsc_offset = rvdev_data->rsc_offset;
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	/* allocate the vring resources */
54362306a36Sopenharmony_ci	for (i = 0; i < rsc->num_of_vrings; i++) {
54462306a36Sopenharmony_ci		ret = rproc_alloc_vring(rvdev, i);
54562306a36Sopenharmony_ci		if (ret)
54662306a36Sopenharmony_ci			goto unwind_vring_allocations;
54762306a36Sopenharmony_ci	}
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci	rproc_add_rvdev(rproc, rvdev);
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci	rvdev->subdev.start = rproc_vdev_do_start;
55262306a36Sopenharmony_ci	rvdev->subdev.stop = rproc_vdev_do_stop;
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	rproc_add_subdev(rproc, &rvdev->subdev);
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	/*
55762306a36Sopenharmony_ci	 * We're indirectly making a non-temporary copy of the rproc pointer
55862306a36Sopenharmony_ci	 * here, because the platform device or the vdev device will indirectly
55962306a36Sopenharmony_ci	 * access the wrapping rproc.
56062306a36Sopenharmony_ci	 *
56162306a36Sopenharmony_ci	 * Therefore we must increment the rproc refcount here, and decrement
56262306a36Sopenharmony_ci	 * it _only_ on platform remove.
56362306a36Sopenharmony_ci	 */
56462306a36Sopenharmony_ci	get_device(&rproc->dev);
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	return 0;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ciunwind_vring_allocations:
56962306a36Sopenharmony_ci	for (i--; i >= 0; i--)
57062306a36Sopenharmony_ci		rproc_free_vring(&rvdev->vring[i]);
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	return ret;
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_cistatic void rproc_virtio_remove(struct platform_device *pdev)
57662306a36Sopenharmony_ci{
57762306a36Sopenharmony_ci	struct rproc_vdev *rvdev = dev_get_drvdata(&pdev->dev);
57862306a36Sopenharmony_ci	struct rproc *rproc = rvdev->rproc;
57962306a36Sopenharmony_ci	struct rproc_vring *rvring;
58062306a36Sopenharmony_ci	int id;
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	for (id = 0; id < ARRAY_SIZE(rvdev->vring); id++) {
58362306a36Sopenharmony_ci		rvring = &rvdev->vring[id];
58462306a36Sopenharmony_ci		rproc_free_vring(rvring);
58562306a36Sopenharmony_ci	}
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	rproc_remove_subdev(rproc, &rvdev->subdev);
58862306a36Sopenharmony_ci	rproc_remove_rvdev(rvdev);
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	put_device(&rproc->dev);
59162306a36Sopenharmony_ci}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci/* Platform driver */
59462306a36Sopenharmony_cistatic struct platform_driver rproc_virtio_driver = {
59562306a36Sopenharmony_ci	.probe		= rproc_virtio_probe,
59662306a36Sopenharmony_ci	.remove_new	= rproc_virtio_remove,
59762306a36Sopenharmony_ci	.driver		= {
59862306a36Sopenharmony_ci		.name	= "rproc-virtio",
59962306a36Sopenharmony_ci	},
60062306a36Sopenharmony_ci};
60162306a36Sopenharmony_cibuiltin_platform_driver(rproc_virtio_driver);
602