|
| 1 | +/* |
| 2 | + * Remote processor messaging transport (OMAP platform-specific bits) |
| 3 | + * |
| 4 | + * Copyright (C) 2011 Texas Instruments, Inc. |
| 5 | + * Copyright (C) 2011 Google, Inc. |
| 6 | + * |
| 7 | + * Ohad Ben-Cohen <[email protected]> |
| 8 | + * Brian Swetland <[email protected]> |
| 9 | + * |
| 10 | + * This software is licensed under the terms of the GNU General Public |
| 11 | + * License version 2, as published by the Free Software Foundation, and |
| 12 | + * may be copied, distributed, and modified under those terms. |
| 13 | + * |
| 14 | + * This program is distributed in the hope that it will be useful, |
| 15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | + * GNU General Public License for more details. |
| 18 | + */ |
| 19 | + |
| 20 | +#include <linux/export.h> |
| 21 | +#include <linux/remoteproc.h> |
| 22 | +#include <linux/rpmsg.h> |
| 23 | +#include <linux/virtio.h> |
| 24 | +#include <linux/virtio_config.h> |
| 25 | +#include <linux/virtio_ids.h> |
| 26 | +#include <linux/virtio_ring.h> |
| 27 | +#include <linux/err.h> |
| 28 | +#include <linux/kref.h> |
| 29 | +#include <linux/slab.h> |
| 30 | + |
| 31 | +#include "remoteproc_internal.h" |
| 32 | + |
| 33 | +/** |
| 34 | + * struct rproc_virtio_vq_info - virtqueue state |
| 35 | + * @vq_id: a unique index of this virtqueue (unique for this @rproc) |
| 36 | + * @rproc: handle to the remote processor |
| 37 | + * |
| 38 | + * Such a struct will be maintained for every virtqueue we're |
| 39 | + * using to communicate with the remote processor |
| 40 | + */ |
| 41 | +struct rproc_virtio_vq_info { |
| 42 | + __u16 vq_id; |
| 43 | + struct rproc *rproc; |
| 44 | +}; |
| 45 | + |
| 46 | +/* kick the remote processor, and let it know which virtqueue to poke at */ |
| 47 | +static void rproc_virtio_notify(struct virtqueue *vq) |
| 48 | +{ |
| 49 | + struct rproc_virtio_vq_info *rpvq = vq->priv; |
| 50 | + struct rproc *rproc = rpvq->rproc; |
| 51 | + |
| 52 | + dev_dbg(rproc->dev, "kicking vq id: %d\n", rpvq->vq_id); |
| 53 | + |
| 54 | + rproc->ops->kick(rproc, rpvq->vq_id); |
| 55 | +} |
| 56 | + |
| 57 | +/** |
| 58 | + * rproc_vq_interrupt() - tell remoteproc that a virtqueue is interrupted |
| 59 | + * @rproc: handle to the remote processor |
| 60 | + * @vq_id: index of the signalled virtqueue |
| 61 | + * |
| 62 | + * This function should be called by the platform-specific rproc driver, |
| 63 | + * when the remote processor signals that a specific virtqueue has pending |
| 64 | + * messages available. |
| 65 | + * |
| 66 | + * Returns IRQ_NONE if no message was found in the @vq_id virtqueue, |
| 67 | + * and otherwise returns IRQ_HANDLED. |
| 68 | + */ |
| 69 | +irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id) |
| 70 | +{ |
| 71 | + return vring_interrupt(0, rproc->rvdev->vq[vq_id]); |
| 72 | +} |
| 73 | +EXPORT_SYMBOL(rproc_vq_interrupt); |
| 74 | + |
| 75 | +static struct virtqueue *rp_find_vq(struct virtio_device *vdev, |
| 76 | + unsigned id, |
| 77 | + void (*callback)(struct virtqueue *vq), |
| 78 | + const char *name) |
| 79 | +{ |
| 80 | + struct rproc *rproc = vdev_to_rproc(vdev); |
| 81 | + struct rproc_vdev *rvdev = rproc->rvdev; |
| 82 | + struct rproc_virtio_vq_info *rpvq; |
| 83 | + struct virtqueue *vq; |
| 84 | + void *addr; |
| 85 | + int ret, len; |
| 86 | + |
| 87 | + rpvq = kmalloc(sizeof(*rpvq), GFP_KERNEL); |
| 88 | + if (!rpvq) |
| 89 | + return ERR_PTR(-ENOMEM); |
| 90 | + |
| 91 | + rpvq->rproc = rproc; |
| 92 | + rpvq->vq_id = id; |
| 93 | + |
| 94 | + addr = rvdev->vring[id].va; |
| 95 | + len = rvdev->vring[id].len; |
| 96 | + |
| 97 | + dev_dbg(rproc->dev, "vring%d: va %p qsz %d\n", id, addr, len); |
| 98 | + |
| 99 | + vq = vring_new_virtqueue(len, AMP_VRING_ALIGN, vdev, addr, |
| 100 | + rproc_virtio_notify, callback, name); |
| 101 | + if (!vq) { |
| 102 | + dev_err(rproc->dev, "vring_new_virtqueue %s failed\n", name); |
| 103 | + ret = -ENOMEM; |
| 104 | + goto free_rpvq; |
| 105 | + } |
| 106 | + |
| 107 | + rvdev->vq[id] = vq; |
| 108 | + vq->priv = rpvq; |
| 109 | + |
| 110 | + return vq; |
| 111 | + |
| 112 | +free_rpvq: |
| 113 | + kfree(rpvq); |
| 114 | + return ERR_PTR(ret); |
| 115 | +} |
| 116 | + |
| 117 | +static void rproc_virtio_del_vqs(struct virtio_device *vdev) |
| 118 | +{ |
| 119 | + struct virtqueue *vq, *n; |
| 120 | + struct rproc *rproc = vdev_to_rproc(vdev); |
| 121 | + |
| 122 | + list_for_each_entry_safe(vq, n, &vdev->vqs, list) { |
| 123 | + struct rproc_virtio_vq_info *rpvq = vq->priv; |
| 124 | + vring_del_virtqueue(vq); |
| 125 | + kfree(rpvq); |
| 126 | + } |
| 127 | + |
| 128 | + /* power down the remote processor */ |
| 129 | + rproc_shutdown(rproc); |
| 130 | +} |
| 131 | + |
| 132 | +static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs, |
| 133 | + struct virtqueue *vqs[], |
| 134 | + vq_callback_t *callbacks[], |
| 135 | + const char *names[]) |
| 136 | +{ |
| 137 | + struct rproc *rproc = vdev_to_rproc(vdev); |
| 138 | + int i, ret; |
| 139 | + |
| 140 | + /* we maintain two virtqueues per remote processor (for RX and TX) */ |
| 141 | + if (nvqs != 2) |
| 142 | + return -EINVAL; |
| 143 | + |
| 144 | + /* boot the remote processor */ |
| 145 | + ret = rproc_boot(rproc); |
| 146 | + if (ret) { |
| 147 | + dev_err(rproc->dev, "rproc_boot() failed %d\n", ret); |
| 148 | + goto error; |
| 149 | + } |
| 150 | + |
| 151 | + for (i = 0; i < nvqs; ++i) { |
| 152 | + vqs[i] = rp_find_vq(vdev, i, callbacks[i], names[i]); |
| 153 | + if (IS_ERR(vqs[i])) { |
| 154 | + ret = PTR_ERR(vqs[i]); |
| 155 | + goto error; |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + return 0; |
| 160 | + |
| 161 | +error: |
| 162 | + rproc_virtio_del_vqs(vdev); |
| 163 | + return ret; |
| 164 | +} |
| 165 | + |
| 166 | +/* |
| 167 | + * We don't support yet real virtio status semantics. |
| 168 | + * |
| 169 | + * The plan is to provide this via the VIRTIO HDR resource entry |
| 170 | + * which is part of the firmware: this way the remote processor |
| 171 | + * will be able to access the status values as set by us. |
| 172 | + */ |
| 173 | +static u8 rproc_virtio_get_status(struct virtio_device *vdev) |
| 174 | +{ |
| 175 | + return 0; |
| 176 | +} |
| 177 | + |
| 178 | +static void rproc_virtio_set_status(struct virtio_device *vdev, u8 status) |
| 179 | +{ |
| 180 | + dev_dbg(&vdev->dev, "new status: %d\n", status); |
| 181 | +} |
| 182 | + |
| 183 | +static void rproc_virtio_reset(struct virtio_device *vdev) |
| 184 | +{ |
| 185 | + dev_dbg(&vdev->dev, "reset !\n"); |
| 186 | +} |
| 187 | + |
| 188 | +/* provide the vdev features as retrieved from the firmware */ |
| 189 | +static u32 rproc_virtio_get_features(struct virtio_device *vdev) |
| 190 | +{ |
| 191 | + struct rproc *rproc = vdev_to_rproc(vdev); |
| 192 | + |
| 193 | + /* we only support a single vdev device for now */ |
| 194 | + return rproc->rvdev->dfeatures; |
| 195 | +} |
| 196 | + |
| 197 | +static void rproc_virtio_finalize_features(struct virtio_device *vdev) |
| 198 | +{ |
| 199 | + struct rproc *rproc = vdev_to_rproc(vdev); |
| 200 | + |
| 201 | + /* Give virtio_ring a chance to accept features */ |
| 202 | + vring_transport_features(vdev); |
| 203 | + |
| 204 | + /* |
| 205 | + * Remember the finalized features of our vdev, and provide it |
| 206 | + * to the remote processor once it is powered on. |
| 207 | + * |
| 208 | + * Similarly to the status field, we don't expose yet the negotiated |
| 209 | + * features to the remote processors at this point. This will be |
| 210 | + * fixed as part of a small resource table overhaul and then an |
| 211 | + * extension of the virtio resource entries. |
| 212 | + */ |
| 213 | + rproc->rvdev->gfeatures = vdev->features[0]; |
| 214 | +} |
| 215 | + |
| 216 | +static struct virtio_config_ops rproc_virtio_config_ops = { |
| 217 | + .get_features = rproc_virtio_get_features, |
| 218 | + .finalize_features = rproc_virtio_finalize_features, |
| 219 | + .find_vqs = rproc_virtio_find_vqs, |
| 220 | + .del_vqs = rproc_virtio_del_vqs, |
| 221 | + .reset = rproc_virtio_reset, |
| 222 | + .set_status = rproc_virtio_set_status, |
| 223 | + .get_status = rproc_virtio_get_status, |
| 224 | +}; |
| 225 | + |
| 226 | +/* |
| 227 | + * This function is called whenever vdev is released, and is responsible |
| 228 | + * to decrement the remote processor's refcount taken when vdev was |
| 229 | + * added. |
| 230 | + * |
| 231 | + * Never call this function directly; it will be called by the driver |
| 232 | + * core when needed. |
| 233 | + */ |
| 234 | +static void rproc_vdev_release(struct device *dev) |
| 235 | +{ |
| 236 | + struct virtio_device *vdev = dev_to_virtio(dev); |
| 237 | + struct rproc *rproc = vdev_to_rproc(vdev); |
| 238 | + |
| 239 | + kref_put(&rproc->refcount, rproc_release); |
| 240 | +} |
| 241 | + |
| 242 | +/** |
| 243 | + * rproc_add_rpmsg_vdev() - create an rpmsg virtio device |
| 244 | + * @rproc: the rproc handle |
| 245 | + * |
| 246 | + * This function is called if virtio rpmsg support was found in the |
| 247 | + * firmware of the remote processor. |
| 248 | + * |
| 249 | + * Today we only support creating a single rpmsg vdev (virtio device), |
| 250 | + * but the plan is to remove this limitation. At that point this interface |
| 251 | + * will be revised/extended. |
| 252 | + */ |
| 253 | +int rproc_add_rpmsg_vdev(struct rproc *rproc) |
| 254 | +{ |
| 255 | + struct device *dev = rproc->dev; |
| 256 | + struct rproc_vdev *rvdev = rproc->rvdev; |
| 257 | + int ret; |
| 258 | + |
| 259 | + rvdev->vdev.id.device = VIRTIO_ID_RPMSG, |
| 260 | + rvdev->vdev.config = &rproc_virtio_config_ops, |
| 261 | + rvdev->vdev.dev.parent = dev; |
| 262 | + rvdev->vdev.dev.release = rproc_vdev_release; |
| 263 | + |
| 264 | + /* |
| 265 | + * We're indirectly making a non-temporary copy of the rproc pointer |
| 266 | + * here, because drivers probed with this vdev will indirectly |
| 267 | + * access the wrapping rproc. |
| 268 | + * |
| 269 | + * Therefore we must increment the rproc refcount here, and decrement |
| 270 | + * it _only_ when the vdev is released. |
| 271 | + */ |
| 272 | + kref_get(&rproc->refcount); |
| 273 | + |
| 274 | + ret = register_virtio_device(&rvdev->vdev); |
| 275 | + if (ret) { |
| 276 | + kref_put(&rproc->refcount, rproc_release); |
| 277 | + dev_err(dev, "failed to register vdev: %d\n", ret); |
| 278 | + } |
| 279 | + |
| 280 | + return ret; |
| 281 | +} |
| 282 | + |
| 283 | +/** |
| 284 | + * rproc_remove_rpmsg_vdev() - remove an rpmsg vdev device |
| 285 | + * @rproc: the rproc handle |
| 286 | + * |
| 287 | + * This function is called whenever @rproc is removed _iff_ an rpmsg |
| 288 | + * vdev was created beforehand. |
| 289 | + */ |
| 290 | +void rproc_remove_rpmsg_vdev(struct rproc *rproc) |
| 291 | +{ |
| 292 | + struct rproc_vdev *rvdev = rproc->rvdev; |
| 293 | + |
| 294 | + unregister_virtio_device(&rvdev->vdev); |
| 295 | +} |
0 commit comments