remoteproc_virtio.c 9.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/*
 * Remote processor messaging transport (OMAP platform-specific bits)
 *
 * Copyright (C) 2011 Texas Instruments, Inc.
 * Copyright (C) 2011 Google, Inc.
 *
 * Ohad Ben-Cohen <ohad@wizery.com>
 * Brian Swetland <swetland@google.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/export.h>
#include <linux/remoteproc.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_ring.h>
#include <linux/err.h>
#include <linux/kref.h>
#include <linux/slab.h>

#include "remoteproc_internal.h"

/* kick the remote processor, and let it know which virtqueue to poke at */
33
static bool rproc_virtio_notify(struct virtqueue *vq)
34
{
35 36 37
	struct rproc_vring *rvring = vq->priv;
	struct rproc *rproc = rvring->rvdev->rproc;
	int notifyid = rvring->notifyid;
38

39
	dev_dbg(&rproc->dev, "kicking vq index: %d\n", notifyid);
40

41
	rproc->ops->kick(rproc, notifyid);
42
	return true;
43 44 45 46 47
}

/**
 * rproc_vq_interrupt() - tell remoteproc that a virtqueue is interrupted
 * @rproc: handle to the remote processor
48
 * @notifyid: index of the signalled virtqueue (unique per this @rproc)
49 50 51 52 53
 *
 * This function should be called by the platform-specific rproc driver,
 * when the remote processor signals that a specific virtqueue has pending
 * messages available.
 *
54
 * Returns IRQ_NONE if no message was found in the @notifyid virtqueue,
55 56
 * and otherwise returns IRQ_HANDLED.
 */
57
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid)
58
{
59 60
	struct rproc_vring *rvring;

61
	dev_dbg(&rproc->dev, "vq index %d is interrupted\n", notifyid);
62 63 64 65 66 67

	rvring = idr_find(&rproc->notifyids, notifyid);
	if (!rvring || !rvring->vq)
		return IRQ_NONE;

	return vring_interrupt(0, rvring->vq);
68 69 70 71
}
EXPORT_SYMBOL(rproc_vq_interrupt);

static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
72
				    unsigned int id,
73 74 75
				    void (*callback)(struct virtqueue *vq),
				    const char *name)
{
76
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
77
	struct rproc *rproc = vdev_to_rproc(vdev);
78
	struct device *dev = &rproc->dev;
79
	struct rproc_vring *rvring;
80 81
	struct virtqueue *vq;
	void *addr;
82
	int len, size, ret;
83

84 85 86 87
	/* we're temporarily limited to two virtqueues per rvdev */
	if (id >= ARRAY_SIZE(rvdev->vring))
		return ERR_PTR(-EINVAL);

88 89 90
	if (!name)
		return NULL;

91 92 93
	ret = rproc_alloc_vring(rvdev, id);
	if (ret)
		return ERR_PTR(ret);
94

95
	rvring = &rvdev->vring[id];
96 97
	addr = rvring->va;
	len = rvring->len;
98

99 100 101
	/* zero vring */
	size = vring_size(len, rvring->align);
	memset(addr, 0, size);
102

103
	dev_dbg(dev, "vring%d: va %p qsz %d notifyid %d\n",
104
		id, addr, len, rvring->notifyid);
105

106 107 108 109
	/*
	 * Create the new vq, and tell virtio we're not interested in
	 * the 'weak' smp barriers, since we're talking with a real device.
	 */
110
	vq = vring_new_virtqueue(id, len, rvring->align, vdev, false, addr,
111
				 rproc_virtio_notify, callback, name);
112
	if (!vq) {
113
		dev_err(dev, "vring_new_virtqueue %s failed\n", name);
114
		rproc_free_vring(rvring);
115
		return ERR_PTR(-ENOMEM);
116 117
	}

118 119
	rvring->vq = vq;
	vq->priv = rvring;
120 121 122 123

	return vq;
}

124
static void __rproc_virtio_del_vqs(struct virtio_device *vdev)
125 126
{
	struct virtqueue *vq, *n;
127
	struct rproc_vring *rvring;
128 129

	list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
130 131
		rvring = vq->priv;
		rvring->vq = NULL;
132
		vring_del_virtqueue(vq);
133
		rproc_free_vring(rvring);
134 135 136
	}
}

137 138 139 140 141
static void rproc_virtio_del_vqs(struct virtio_device *vdev)
{
	__rproc_virtio_del_vqs(vdev);
}

142
static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
143 144 145
				 struct virtqueue *vqs[],
				 vq_callback_t *callbacks[],
				 const char * const names[])
146 147 148 149 150 151 152 153 154 155 156 157 158 159
{
	int i, ret;

	for (i = 0; i < nvqs; ++i) {
		vqs[i] = rp_find_vq(vdev, i, callbacks[i], names[i]);
		if (IS_ERR(vqs[i])) {
			ret = PTR_ERR(vqs[i]);
			goto error;
		}
	}

	return 0;

error:
160
	__rproc_virtio_del_vqs(vdev);
161 162 163 164 165
	return ret;
}

static u8 rproc_virtio_get_status(struct virtio_device *vdev)
{
166 167 168 169 170 171
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
	struct fw_rsc_vdev *rsc;

	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;

	return rsc->status;
172 173 174 175
}

static void rproc_virtio_set_status(struct virtio_device *vdev, u8 status)
{
176 177 178 179 180 181
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
	struct fw_rsc_vdev *rsc;

	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;

	rsc->status = status;
182
	dev_dbg(&vdev->dev, "status: %d\n", status);
183 184 185 186
}

static void rproc_virtio_reset(struct virtio_device *vdev)
{
187 188 189 190 191 192
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
	struct fw_rsc_vdev *rsc;

	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;

	rsc->status = 0;
193 194 195 196
	dev_dbg(&vdev->dev, "reset !\n");
}

/* provide the vdev features as retrieved from the firmware */
197
static u64 rproc_virtio_get_features(struct virtio_device *vdev)
198
{
199
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
200 201 202
	struct fw_rsc_vdev *rsc;

	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
203

204
	return rsc->dfeatures;
205 206
}

207
static int rproc_virtio_finalize_features(struct virtio_device *vdev)
208
{
209
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
210 211 212
	struct fw_rsc_vdev *rsc;

	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
213 214 215 216

	/* Give virtio_ring a chance to accept features */
	vring_transport_features(vdev);

217 218 219
	/* Make sure we don't have any features > 32 bits! */
	BUG_ON((u32)vdev->features != vdev->features);

220 221 222 223
	/*
	 * Remember the finalized features of our vdev, and provide it
	 * to the remote processor once it is powered on.
	 */
224
	rsc->gfeatures = vdev->features;
225 226

	return 0;
227 228
}

229 230
static void rproc_virtio_get(struct virtio_device *vdev, unsigned int offset,
			     void *buf, unsigned int len)
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
{
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
	struct fw_rsc_vdev *rsc;
	void *cfg;

	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
	cfg = &rsc->vring[rsc->num_of_vrings];

	if (offset + len > rsc->config_len || offset + len < len) {
		dev_err(&vdev->dev, "rproc_virtio_get: access out of bounds\n");
		return;
	}

	memcpy(buf, cfg + offset, len);
}

247 248
static void rproc_virtio_set(struct virtio_device *vdev, unsigned int offset,
			     const void *buf, unsigned int len)
249 250 251 252 253 254 255 256 257 258 259 260 261 262
{
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
	struct fw_rsc_vdev *rsc;
	void *cfg;

	rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
	cfg = &rsc->vring[rsc->num_of_vrings];

	if (offset + len > rsc->config_len || offset + len < len) {
		dev_err(&vdev->dev, "rproc_virtio_set: access out of bounds\n");
		return;
	}

	memcpy(cfg + offset, buf, len);
263 264
}

265
static const struct virtio_config_ops rproc_virtio_config_ops = {
266 267 268 269 270 271 272
	.get_features	= rproc_virtio_get_features,
	.finalize_features = rproc_virtio_finalize_features,
	.find_vqs	= rproc_virtio_find_vqs,
	.del_vqs	= rproc_virtio_del_vqs,
	.reset		= rproc_virtio_reset,
	.set_status	= rproc_virtio_set_status,
	.get_status	= rproc_virtio_get_status,
273 274
	.get		= rproc_virtio_get,
	.set		= rproc_virtio_set,
275 276 277 278
};

/*
 * This function is called whenever vdev is released, and is responsible
279
 * to decrement the remote processor's refcount which was taken when vdev was
280 281 282 283 284 285 286 287
 * added.
 *
 * Never call this function directly; it will be called by the driver
 * core when needed.
 */
static void rproc_vdev_release(struct device *dev)
{
	struct virtio_device *vdev = dev_to_virtio(dev);
288
	struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
289 290
	struct rproc *rproc = vdev_to_rproc(vdev);

291 292 293
	list_del(&rvdev->node);
	kfree(rvdev);

294
	put_device(&rproc->dev);
295 296 297
}

/**
298 299
 * rproc_add_virtio_dev() - register an rproc-induced virtio device
 * @rvdev: the remote vdev
300
 *
301 302
 * This function registers a virtio device. This vdev's partent is
 * the rproc device.
303
 *
304
 * Returns 0 on success or an appropriate error value otherwise.
305
 */
306
int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id)
307
{
308
	struct rproc *rproc = rvdev->rproc;
309
	struct device *dev = &rproc->dev;
310
	struct virtio_device *vdev = &rvdev->vdev;
311 312
	int ret;

313 314 315 316
	vdev->id.device	= id,
	vdev->config = &rproc_virtio_config_ops,
	vdev->dev.parent = dev;
	vdev->dev.release = rproc_vdev_release;
317 318 319 320 321 322 323 324 325

	/*
	 * We're indirectly making a non-temporary copy of the rproc pointer
	 * here, because drivers probed with this vdev will indirectly
	 * access the wrapping rproc.
	 *
	 * Therefore we must increment the rproc refcount here, and decrement
	 * it _only_ when the vdev is released.
	 */
326
	get_device(&rproc->dev);
327

328
	ret = register_virtio_device(vdev);
329
	if (ret) {
330
		put_device(&rproc->dev);
331
		dev_err(dev, "failed to register vdev: %d\n", ret);
332
		goto out;
333 334
	}

335 336 337
	dev_info(dev, "registered %s (type %d)\n", dev_name(&vdev->dev), id);

out:
338 339 340 341
	return ret;
}

/**
342 343
 * rproc_remove_virtio_dev() - remove an rproc-induced virtio device
 * @rvdev: the remote vdev
344
 *
345
 * This function unregisters an existing virtio device.
346
 */
347
void rproc_remove_virtio_dev(struct rproc_vdev *rvdev)
348 349 350
{
	unregister_virtio_device(&rvdev->vdev);
}