123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (C) STMicroelectronics SA 2015
- * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
- */
- #include <linux/rpmsg.h>
- #include "delta.h"
- #include "delta-ipc.h"
- #include "delta-mem.h"
- #define IPC_TIMEOUT 100
- #define IPC_SANITY_TAG 0xDEADBEEF
- enum delta_ipc_fw_command {
- DELTA_IPC_OPEN,
- DELTA_IPC_SET_STREAM,
- DELTA_IPC_DECODE,
- DELTA_IPC_CLOSE
- };
- #define to_rpmsg_driver(__drv) container_of(__drv, struct rpmsg_driver, drv)
- #define to_delta(__d) container_of(__d, struct delta_dev, rpmsg_driver)
- #define to_ctx(hdl) ((struct delta_ipc_ctx *)hdl)
- #define to_pctx(ctx) container_of(ctx, struct delta_ctx, ipc_ctx)
- struct delta_ipc_header_msg {
- u32 tag;
- void *host_hdl;
- u32 copro_hdl;
- u32 command;
- };
- #define to_host_hdl(ctx) ((void *)ctx)
- #define msg_to_ctx(msg) ((struct delta_ipc_ctx *)(msg)->header.host_hdl)
- #define msg_to_copro_hdl(msg) ((msg)->header.copro_hdl)
- static inline dma_addr_t to_paddr(struct delta_ipc_ctx *ctx, void *vaddr)
- {
- return (ctx->ipc_buf->paddr + (vaddr - ctx->ipc_buf->vaddr));
- }
- static inline bool is_valid_data(struct delta_ipc_ctx *ctx,
- void *data, u32 size)
- {
- return ((data >= ctx->ipc_buf->vaddr) &&
- ((data + size) <= (ctx->ipc_buf->vaddr + ctx->ipc_buf->size)));
- }
- /*
- * IPC shared memory (@ipc_buf_size, @ipc_buf_paddr) is sent to copro
- * at each instance opening. This memory is allocated by IPC client
- * and given through delta_ipc_open(). All messages parameters
- * (open, set_stream, decode) will have their phy address within
- * this IPC shared memory, avoiding de-facto recopies inside delta-ipc.
- * All the below messages structures are used on both host and firmware
- * side and are packed (use only of 32 bits size fields in messages
- * structures to ensure packing):
- * - struct delta_ipc_open_msg
- * - struct delta_ipc_set_stream_msg
- * - struct delta_ipc_decode_msg
- * - struct delta_ipc_close_msg
- * - struct delta_ipc_cb_msg
- */
- struct delta_ipc_open_msg {
- struct delta_ipc_header_msg header;
- u32 ipc_buf_size;
- dma_addr_t ipc_buf_paddr;
- char name[32];
- u32 param_size;
- dma_addr_t param_paddr;
- };
- struct delta_ipc_set_stream_msg {
- struct delta_ipc_header_msg header;
- u32 param_size;
- dma_addr_t param_paddr;
- };
- struct delta_ipc_decode_msg {
- struct delta_ipc_header_msg header;
- u32 param_size;
- dma_addr_t param_paddr;
- u32 status_size;
- dma_addr_t status_paddr;
- };
- struct delta_ipc_close_msg {
- struct delta_ipc_header_msg header;
- };
- struct delta_ipc_cb_msg {
- struct delta_ipc_header_msg header;
- int err;
- };
- static void build_msg_header(struct delta_ipc_ctx *ctx,
- enum delta_ipc_fw_command command,
- struct delta_ipc_header_msg *header)
- {
- header->tag = IPC_SANITY_TAG;
- header->host_hdl = to_host_hdl(ctx);
- header->copro_hdl = ctx->copro_hdl;
- header->command = command;
- }
- int delta_ipc_open(struct delta_ctx *pctx, const char *name,
- struct delta_ipc_param *param, u32 ipc_buf_size,
- struct delta_buf **ipc_buf, void **hdl)
- {
- struct delta_dev *delta = pctx->dev;
- struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
- struct delta_ipc_ctx *ctx = &pctx->ipc_ctx;
- struct delta_ipc_open_msg msg;
- struct delta_buf *buf = &ctx->ipc_buf_struct;
- int ret;
- if (!rpmsg_device) {
- dev_err(delta->dev,
- "%s ipc: failed to open, rpmsg is not initialized\n",
- pctx->name);
- pctx->sys_errors++;
- return -EINVAL;
- }
- if (!name) {
- dev_err(delta->dev,
- "%s ipc: failed to open, no name given\n",
- pctx->name);
- return -EINVAL;
- }
- if (!param || !param->data || !param->size) {
- dev_err(delta->dev,
- "%s ipc: failed to open, empty parameter\n",
- pctx->name);
- return -EINVAL;
- }
- if (!ipc_buf_size) {
- dev_err(delta->dev,
- "%s ipc: failed to open, no size given for ipc buffer\n",
- pctx->name);
- return -EINVAL;
- }
- if (param->size > ipc_buf_size) {
- dev_err(delta->dev,
- "%s ipc: failed to open, too large ipc parameter (%d bytes while max %d expected)\n",
- pctx->name,
- param->size, ctx->ipc_buf->size);
- return -EINVAL;
- }
- /* init */
- init_completion(&ctx->done);
- /*
- * allocation of contiguous buffer for
- * data of commands exchanged between
- * host and firmware coprocessor
- */
- ret = hw_alloc(pctx, ipc_buf_size,
- "ipc data buffer", buf);
- if (ret)
- return ret;
- ctx->ipc_buf = buf;
- /* build rpmsg message */
- build_msg_header(ctx, DELTA_IPC_OPEN, &msg.header);
- msg.ipc_buf_size = ipc_buf_size;
- msg.ipc_buf_paddr = ctx->ipc_buf->paddr;
- memcpy(msg.name, name, sizeof(msg.name));
- msg.name[sizeof(msg.name) - 1] = 0;
- msg.param_size = param->size;
- memcpy(ctx->ipc_buf->vaddr, param->data, msg.param_size);
- msg.param_paddr = ctx->ipc_buf->paddr;
- /* send it */
- ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
- if (ret) {
- dev_err(delta->dev,
- "%s ipc: failed to open, rpmsg_send failed (%d) for DELTA_IPC_OPEN (name=%s, size=%d, data=%p)\n",
- pctx->name,
- ret, name, param->size, param->data);
- goto err;
- }
- /* wait for acknowledge */
- if (!wait_for_completion_timeout
- (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
- dev_err(delta->dev,
- "%s ipc: failed to open, timeout waiting for DELTA_IPC_OPEN callback (name=%s, size=%d, data=%p)\n",
- pctx->name,
- name, param->size, param->data);
- ret = -ETIMEDOUT;
- goto err;
- }
- /* command completed, check error */
- if (ctx->cb_err) {
- dev_err(delta->dev,
- "%s ipc: failed to open, DELTA_IPC_OPEN completed but with error (%d) (name=%s, size=%d, data=%p)\n",
- pctx->name,
- ctx->cb_err, name, param->size, param->data);
- ret = -EIO;
- goto err;
- }
- *ipc_buf = ctx->ipc_buf;
- *hdl = (void *)ctx;
- return 0;
- err:
- pctx->sys_errors++;
- if (ctx->ipc_buf) {
- hw_free(pctx, ctx->ipc_buf);
- ctx->ipc_buf = NULL;
- }
- return ret;
- };
- int delta_ipc_set_stream(void *hdl, struct delta_ipc_param *param)
- {
- struct delta_ipc_ctx *ctx = to_ctx(hdl);
- struct delta_ctx *pctx = to_pctx(ctx);
- struct delta_dev *delta = pctx->dev;
- struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
- struct delta_ipc_set_stream_msg msg;
- int ret;
- if (!hdl) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, invalid ipc handle\n",
- pctx->name);
- return -EINVAL;
- }
- if (!rpmsg_device) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, rpmsg is not initialized\n",
- pctx->name);
- return -EINVAL;
- }
- if (!param || !param->data || !param->size) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, empty parameter\n",
- pctx->name);
- return -EINVAL;
- }
- if (param->size > ctx->ipc_buf->size) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, too large ipc parameter(%d bytes while max %d expected)\n",
- pctx->name,
- param->size, ctx->ipc_buf->size);
- return -EINVAL;
- }
- if (!is_valid_data(ctx, param->data, param->size)) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, parameter is not in expected address range (size=%d, data=%p not in %p..%p)\n",
- pctx->name,
- param->size,
- param->data,
- ctx->ipc_buf->vaddr,
- ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1);
- return -EINVAL;
- }
- /* build rpmsg message */
- build_msg_header(ctx, DELTA_IPC_SET_STREAM, &msg.header);
- msg.param_size = param->size;
- msg.param_paddr = to_paddr(ctx, param->data);
- /* send it */
- ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
- if (ret) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, rpmsg_send failed (%d) for DELTA_IPC_SET_STREAM (size=%d, data=%p)\n",
- pctx->name,
- ret, param->size, param->data);
- pctx->sys_errors++;
- return ret;
- }
- /* wait for acknowledge */
- if (!wait_for_completion_timeout
- (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, timeout waiting for DELTA_IPC_SET_STREAM callback (size=%d, data=%p)\n",
- pctx->name,
- param->size, param->data);
- pctx->sys_errors++;
- return -ETIMEDOUT;
- }
- /* command completed, check status */
- if (ctx->cb_err) {
- dev_err(delta->dev,
- "%s ipc: failed to set stream, DELTA_IPC_SET_STREAM completed but with error (%d) (size=%d, data=%p)\n",
- pctx->name,
- ctx->cb_err, param->size, param->data);
- pctx->sys_errors++;
- return -EIO;
- }
- return 0;
- }
- int delta_ipc_decode(void *hdl, struct delta_ipc_param *param,
- struct delta_ipc_param *status)
- {
- struct delta_ipc_ctx *ctx = to_ctx(hdl);
- struct delta_ctx *pctx = to_pctx(ctx);
- struct delta_dev *delta = pctx->dev;
- struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
- struct delta_ipc_decode_msg msg;
- int ret;
- if (!hdl) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, invalid ipc handle\n",
- pctx->name);
- return -EINVAL;
- }
- if (!rpmsg_device) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, rpmsg is not initialized\n",
- pctx->name);
- return -EINVAL;
- }
- if (!param || !param->data || !param->size) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, empty parameter\n",
- pctx->name);
- return -EINVAL;
- }
- if (!status || !status->data || !status->size) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, empty status\n",
- pctx->name);
- return -EINVAL;
- }
- if (param->size + status->size > ctx->ipc_buf->size) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, too large ipc parameter (%d bytes (param) + %d bytes (status) while max %d expected)\n",
- pctx->name,
- param->size,
- status->size,
- ctx->ipc_buf->size);
- return -EINVAL;
- }
- if (!is_valid_data(ctx, param->data, param->size)) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, parameter is not in expected address range (size=%d, data=%p not in %p..%p)\n",
- pctx->name,
- param->size,
- param->data,
- ctx->ipc_buf->vaddr,
- ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1);
- return -EINVAL;
- }
- if (!is_valid_data(ctx, status->data, status->size)) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, status is not in expected address range (size=%d, data=%p not in %p..%p)\n",
- pctx->name,
- status->size,
- status->data,
- ctx->ipc_buf->vaddr,
- ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1);
- return -EINVAL;
- }
- /* build rpmsg message */
- build_msg_header(ctx, DELTA_IPC_DECODE, &msg.header);
- msg.param_size = param->size;
- msg.param_paddr = to_paddr(ctx, param->data);
- msg.status_size = status->size;
- msg.status_paddr = to_paddr(ctx, status->data);
- /* send it */
- ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
- if (ret) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, rpmsg_send failed (%d) for DELTA_IPC_DECODE (size=%d, data=%p)\n",
- pctx->name,
- ret, param->size, param->data);
- pctx->sys_errors++;
- return ret;
- }
- /* wait for acknowledge */
- if (!wait_for_completion_timeout
- (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, timeout waiting for DELTA_IPC_DECODE callback (size=%d, data=%p)\n",
- pctx->name,
- param->size, param->data);
- pctx->sys_errors++;
- return -ETIMEDOUT;
- }
- /* command completed, check status */
- if (ctx->cb_err) {
- dev_err(delta->dev,
- "%s ipc: failed to decode, DELTA_IPC_DECODE completed but with error (%d) (size=%d, data=%p)\n",
- pctx->name,
- ctx->cb_err, param->size, param->data);
- pctx->sys_errors++;
- return -EIO;
- }
- return 0;
- };
- void delta_ipc_close(void *hdl)
- {
- struct delta_ipc_ctx *ctx = to_ctx(hdl);
- struct delta_ctx *pctx = to_pctx(ctx);
- struct delta_dev *delta = pctx->dev;
- struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
- struct delta_ipc_close_msg msg;
- int ret;
- if (!hdl) {
- dev_err(delta->dev,
- "%s ipc: failed to close, invalid ipc handle\n",
- pctx->name);
- return;
- }
- if (ctx->ipc_buf) {
- hw_free(pctx, ctx->ipc_buf);
- ctx->ipc_buf = NULL;
- }
- if (!rpmsg_device) {
- dev_err(delta->dev,
- "%s ipc: failed to close, rpmsg is not initialized\n",
- pctx->name);
- return;
- }
- /* build rpmsg message */
- build_msg_header(ctx, DELTA_IPC_CLOSE, &msg.header);
- /* send it */
- ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
- if (ret) {
- dev_err(delta->dev,
- "%s ipc: failed to close, rpmsg_send failed (%d) for DELTA_IPC_CLOSE\n",
- pctx->name, ret);
- pctx->sys_errors++;
- return;
- }
- /* wait for acknowledge */
- if (!wait_for_completion_timeout
- (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
- dev_err(delta->dev,
- "%s ipc: failed to close, timeout waiting for DELTA_IPC_CLOSE callback\n",
- pctx->name);
- pctx->sys_errors++;
- return;
- }
- /* command completed, check status */
- if (ctx->cb_err) {
- dev_err(delta->dev,
- "%s ipc: failed to close, DELTA_IPC_CLOSE completed but with error (%d)\n",
- pctx->name, ctx->cb_err);
- pctx->sys_errors++;
- }
- };
- static int delta_ipc_cb(struct rpmsg_device *rpdev, void *data,
- int len, void *priv, u32 src)
- {
- struct delta_ipc_ctx *ctx;
- struct delta_ipc_cb_msg *msg;
- /* sanity check */
- if (!rpdev) {
- dev_err(NULL, "rpdev is NULL\n");
- return -EINVAL;
- }
- if (!data || !len) {
- dev_err(&rpdev->dev,
- "unexpected empty message received from src=%d\n", src);
- return -EINVAL;
- }
- if (len != sizeof(*msg)) {
- dev_err(&rpdev->dev,
- "unexpected message length received from src=%d (received %d bytes while %zu bytes expected)\n",
- len, src, sizeof(*msg));
- return -EINVAL;
- }
- msg = (struct delta_ipc_cb_msg *)data;
- if (msg->header.tag != IPC_SANITY_TAG) {
- dev_err(&rpdev->dev,
- "unexpected message tag received from src=%d (received %x tag while %x expected)\n",
- src, msg->header.tag, IPC_SANITY_TAG);
- return -EINVAL;
- }
- ctx = msg_to_ctx(msg);
- if (!ctx) {
- dev_err(&rpdev->dev,
- "unexpected message with NULL host_hdl received from src=%d\n",
- src);
- return -EINVAL;
- }
- /*
- * if not already known, save copro instance context
- * to ensure re-entrance on copro side
- */
- if (!ctx->copro_hdl)
- ctx->copro_hdl = msg_to_copro_hdl(msg);
- /*
- * all is fine,
- * update status & complete command
- */
- ctx->cb_err = msg->err;
- complete(&ctx->done);
- return 0;
- }
- static int delta_ipc_probe(struct rpmsg_device *rpmsg_device)
- {
- struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpmsg_device->dev.driver);
- struct delta_dev *delta = to_delta(rpdrv);
- delta->rpmsg_device = rpmsg_device;
- return 0;
- }
- static void delta_ipc_remove(struct rpmsg_device *rpmsg_device)
- {
- struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpmsg_device->dev.driver);
- struct delta_dev *delta = to_delta(rpdrv);
- delta->rpmsg_device = NULL;
- }
- static struct rpmsg_device_id delta_ipc_device_id_table[] = {
- {.name = "rpmsg-delta"},
- {},
- };
- static struct rpmsg_driver delta_rpmsg_driver = {
- .drv = {.name = KBUILD_MODNAME},
- .id_table = delta_ipc_device_id_table,
- .probe = delta_ipc_probe,
- .callback = delta_ipc_cb,
- .remove = delta_ipc_remove,
- };
- int delta_ipc_init(struct delta_dev *delta)
- {
- delta->rpmsg_driver = delta_rpmsg_driver;
- return register_rpmsg_driver(&delta->rpmsg_driver);
- }
- void delta_ipc_exit(struct delta_dev *delta)
- {
- unregister_rpmsg_driver(&delta->rpmsg_driver);
- }
|