123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751 |
- /*
- * Android/Easel coprocessor communication DMA routines.
- *
- * Copyright 2016 Google Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
- /* #define DEBUG */
- #include <uapi/linux/google-easel-comm.h>
- #include "google-easel-comm-shared.h"
- #include "google-easel-comm-private.h"
- #include <linux/uaccess.h>
- #include <linux/completion.h>
- #include <linux/device.h>
- #include <linux/kernel.h>
- #include <linux/list.h>
- #include <linux/miscdevice.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/types.h>
- /*
- * Server receives DMA scatter-gather list from client. Store this and
- * wake up the local process handling the DMA request.
- */
- void easelcomm_handle_cmd_dma_sg(
- struct easelcomm_service *service, char *command_args,
- size_t command_arg_len)
- {
- struct easelcomm_dma_sg_header *sg_header;
- void *cmd_sg;
- struct easelcomm_message_metadata *msg_metadata;
- if (WARN_ON(command_arg_len < sizeof(struct easelcomm_dma_sg_header)))
- return;
- sg_header = (struct easelcomm_dma_sg_header *) command_args;
- command_args += sizeof(struct easelcomm_dma_sg_header);
- command_arg_len -= sizeof(struct easelcomm_dma_sg_header);
- if (WARN_ON(command_arg_len < sg_header->scatterlist_size))
- return;
- if (sg_header->dma_dir == EASELCOMM_DMA_DIR_TO_CLIENT) {
- msg_metadata =
- easelcomm_find_local_message(
- service, sg_header->message_id);
- } else {
- msg_metadata =
- easelcomm_find_remote_message(
- service, sg_header->message_id);
- }
- if (!msg_metadata) {
- dev_err(easelcomm_miscdev.this_device,
- "DMA_SG msg %u:%s%llu not found\n",
- service->service_id,
- sg_header->dma_dir == EASELCOMM_DMA_DIR_TO_CLIENT ?
- "l" : "r", sg_header->message_id);
- return;
- }
- dev_dbg(easelcomm_miscdev.this_device,
- "recv cmd DMA_SG msg %u:%s%llu size=%u\n",
- service->service_id,
- easelcomm_msgid_prefix(msg_metadata), sg_header->message_id,
- sg_header->scatterlist_size);
- if (WARN_ON(msg_metadata->dma_xfer.sg_remote))
- goto out;
- msg_metadata->dma_xfer.sg_remote_size = sg_header->scatterlist_size;
- if (sg_header->scatterlist_size) {
- cmd_sg = command_args;
- msg_metadata->dma_xfer.sg_remote =
- kmalloc(sg_header->scatterlist_size, GFP_KERNEL);
- if (WARN_ON(!msg_metadata->dma_xfer.sg_remote)) {
- msg_metadata->dma_xfer.sg_remote_size = 0;
- goto out;
- }
- memcpy(msg_metadata->dma_xfer.sg_remote, cmd_sg,
- sg_header->scatterlist_size);
- }
- /* Let the local waiter know the remote scatterlist ready. */
- complete(&msg_metadata->dma_xfer.sg_remote_ready);
- out:
- easelcomm_drop_reference(service, msg_metadata, false);
- }
- EXPORT_SYMBOL(easelcomm_handle_cmd_dma_sg);
- /*
- * Client receives DMA transfer instructions from server. Server has chosen
- * single- vs. multi-block as necessary, and has provided either the single
- * block server-side source or destination address, or the multi-block
- * linked-list address. Wake up the local process to perform the DMA
- * transfer.
- */
- void easelcomm_handle_cmd_dma_xfer(
- struct easelcomm_service *service, char *command_args,
- size_t command_arg_len)
- {
- struct easelcomm_dma_xfer_arg *dma_xfer;
- struct easelcomm_message_metadata *msg_metadata;
- if (WARN_ON(command_arg_len <
- sizeof(struct easelcomm_dma_xfer_arg)))
- return;
- dma_xfer = (struct easelcomm_dma_xfer_arg *) command_args;
- dev_dbg(easelcomm_miscdev.this_device,
- "recv cmd DMA_XFER msg %u:%s%llu type=%u saddr=%llx\n",
- service->service_id,
- dma_xfer->dma_dir == EASELCOMM_DMA_DIR_TO_SERVER ? "l" : "r",
- dma_xfer->message_id, dma_xfer->xfer_type,
- dma_xfer->server_addr);
- if (dma_xfer->dma_dir == EASELCOMM_DMA_DIR_TO_SERVER) {
- msg_metadata =
- easelcomm_find_local_message(
- service, dma_xfer->message_id);
- } else {
- msg_metadata =
- easelcomm_find_remote_message(
- service, dma_xfer->message_id);
- }
- if (!msg_metadata) {
- dev_err(easelcomm_miscdev.this_device,
- "DMA_XFER msg %u:%s%llu not found\n",
- service->service_id,
- dma_xfer->dma_dir == EASELCOMM_DMA_DIR_TO_SERVER ?
- "l" : "r", dma_xfer->message_id);
- return;
- }
- msg_metadata->dma_xfer.xfer_type = dma_xfer->xfer_type;
- msg_metadata->dma_xfer.server_addr = dma_xfer->server_addr;
- /* Let the local waiter know the DMA request is received */
- complete(&msg_metadata->dma_xfer.xfer_ready);
- easelcomm_drop_reference(service, msg_metadata, false);
- }
- EXPORT_SYMBOL(easelcomm_handle_cmd_dma_xfer);
- /*
- * Server receives DMA done indication from client.
- * Safe to unmap buffers and return to waiting DMA originator or receiver.
- */
- void easelcomm_handle_cmd_dma_done(
- struct easelcomm_service *service, char *command_args,
- size_t command_arg_len)
- {
- struct easelcomm_dma_done_arg *dma_done_arg;
- struct easelcomm_message_metadata *msg_metadata;
- if (WARN_ON(command_arg_len < sizeof(struct easelcomm_dma_done_arg)))
- return;
- dma_done_arg = (struct easelcomm_dma_done_arg *) command_args;
- dev_dbg(easelcomm_miscdev.this_device,
- "recv cmd DMA_DONE msg %u:%s%llu err=%u\n",
- service->service_id,
- dma_done_arg->dma_dir == EASELCOMM_DMA_DIR_TO_CLIENT ?
- "l" : "r", dma_done_arg->message_id, dma_done_arg->errcode);
- if (dma_done_arg->dma_dir == EASELCOMM_DMA_DIR_TO_CLIENT) {
- msg_metadata =
- easelcomm_find_local_message(
- service, dma_done_arg->message_id);
- } else {
- msg_metadata =
- easelcomm_find_remote_message(
- service, dma_done_arg->message_id);
- }
- if (!msg_metadata) {
- dev_err(easelcomm_miscdev.this_device,
- "CMD_DMA_DONE msg %u:%s%llu not found\n",
- service->service_id,
- dma_done_arg->dma_dir == EASELCOMM_DMA_DIR_TO_CLIENT ?
- "l" : "r", dma_done_arg->message_id);
- return;
- }
- msg_metadata->dma_xfer.errcode = dma_done_arg->errcode;
- /*
- * Wakeup local waiter waiting on remote SG (if this is an abort) or
- * DMA completion.
- */
- complete(&msg_metadata->dma_xfer.sg_remote_ready);
- complete(&msg_metadata->dma_xfer.xfer_done);
- easelcomm_drop_reference(service, msg_metadata, false);
- }
- EXPORT_SYMBOL(easelcomm_handle_cmd_dma_done);
- static void *easelcomm_create_dma_scatterlist(
- struct easelcomm_kbuf_desc *buf_desc, uint32_t *scatterlist_size,
- void **sglocaldata, enum easelcomm_dma_direction dma_dir)
- {
- return easelcomm_hw_build_scatterlist(buf_desc, scatterlist_size,
- sglocaldata, dma_dir);
- }
- /*
- * Client sends its local DMA dest scatter-gather list to the server, which
- * will use this info to choose a single- or multi-block transfer, and build
- * an MNH DMA Linked List structure based on the local and remote
- * scatter-gather lists, if needed.
- *
- * If the client is discarding the DMA transfer then a zero-length
- * scatter-gather list is sent.
- */
- static int easelcomm_send_dma_scatterlist(
- struct easelcomm_service *service,
- struct easelcomm_message_metadata *msg_metadata,
- enum easelcomm_dma_direction dma_dir)
- {
- struct easelcomm_dma_sg_header sg_header;
- int ret;
- sg_header.message_id = msg_metadata->msg->desc.message_id;
- sg_header.dma_dir = dma_dir;
- sg_header.scatterlist_size = msg_metadata->dma_xfer.sg_local_size;
- dev_dbg(easelcomm_miscdev.this_device,
- "send cmd DMA_SG msg %u:%s%llu size=%u\n",
- service->service_id, easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id,
- msg_metadata->dma_xfer.sg_local_size);
- ret = easelcomm_start_cmd(
- service, EASELCOMM_CMD_DMA_SG,
- sizeof(struct easelcomm_dma_sg_header) +
- msg_metadata->dma_xfer.sg_local_size);
- if (ret)
- return ret;
- ret = easelcomm_append_cmd_args(
- service, &sg_header, sizeof(struct easelcomm_dma_sg_header));
- if (ret)
- return ret;
- /* If not discarding locally append the scatterlist */
- if (msg_metadata->dma_xfer.sg_local_size) {
- ret = easelcomm_append_cmd_args(
- service, msg_metadata->dma_xfer.sg_local,
- msg_metadata->dma_xfer.sg_local_size);
- if (ret)
- return ret;
- }
- return easelcomm_send_cmd(service);
- }
- /* Server sends DMA_XFER command to client */
- static int easelcomm_send_dma_xfer(
- struct easelcomm_service *service,
- struct easelcomm_message_metadata *msg_metadata,
- enum easelcomm_dma_direction dma_dir)
- {
- struct easelcomm_dma_xfer_arg dma_xfer;
- int ret;
- dma_xfer.message_id = msg_metadata->msg->desc.message_id;
- dma_xfer.dma_dir = dma_dir;
- dma_xfer.xfer_type = msg_metadata->dma_xfer.xfer_type;
- dma_xfer.server_addr = msg_metadata->dma_xfer.server_addr;
- ret = easelcomm_start_cmd(
- service, EASELCOMM_CMD_DMA_XFER,
- sizeof(struct easelcomm_dma_xfer_arg));
- if (ret)
- return ret;
- ret = easelcomm_append_cmd_args(
- service, &dma_xfer, sizeof(struct easelcomm_dma_xfer_arg));
- if (ret)
- return ret;
- dev_dbg(easelcomm_miscdev.this_device, "send cmd DMA_XFER msg %u:%s%llu type=%u saddr=%llx\n",
- service->service_id,
- easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id,
- msg_metadata->dma_xfer.xfer_type,
- msg_metadata->dma_xfer.server_addr);
- return easelcomm_send_cmd(service);
- }
- /*
- * Server-side DMA handling. When the client sends its scatterlist, inspect
- * the client and server lists to see if the DMA can be a single-block
- * transfer or whether it must be a multi-block transfer. Construct the
- * multi-block linked list if needed. Tell the client to proceed with a
- * single-block or multi-block transfer, wait for DMA done command from
- * client, and clean up.
- */
- static int easelcomm_server_handle_dma_request(
- struct easelcomm_service *service,
- struct easelcomm_message_metadata *msg_metadata,
- enum easelcomm_dma_direction dma_dir)
- {
- void *mblk_ll_data = NULL;
- bool discard = false;
- int ret;
- /* Wait for client to send its scatterlist, if not already */
- ret = wait_for_completion_interruptible(
- &msg_metadata->dma_xfer.sg_remote_ready);
- if (ret || msg_metadata->dma_xfer.aborting)
- goto abort;
- if (!msg_metadata->dma_xfer.sg_remote_size) {
- dev_dbg(easelcomm_miscdev.this_device,
- "client discards DMA for msg %u:%s%llu\n",
- service->service_id,
- easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id);
- return 0;
- }
- /* If discarding locally then send an abort to client. */
- if (msg_metadata->dma_xfer.sg_local_size == 0) {
- discard = true;
- goto abort;
- }
- /*
- * Choose SBLK vs. MBLK DMA based on whether scatterlists need more
- * than one block.
- */
- if (easelcomm_hw_scatterlist_block_count(
- msg_metadata->dma_xfer.sg_local_size) == 1 &&
- easelcomm_hw_scatterlist_block_count(
- msg_metadata->dma_xfer.sg_remote_size) == 1) {
- /*
- * Single-block DMA. server_addr for the DMA_XFER command
- * is the Easel-side starting physical address.
- */
- msg_metadata->dma_xfer.xfer_type = EASELCOMM_DMA_XFER_SBLK;
- msg_metadata->dma_xfer.server_addr =
- easelcomm_hw_scatterlist_sblk_addr(
- msg_metadata->dma_xfer.sg_local);
- } else {
- /*
- * Multiple-block DMA. server_addr is the physical address
- * of the Linked List we create here.
- */
- msg_metadata->dma_xfer.xfer_type = EASELCOMM_DMA_XFER_MBLK;
- /* Build DMA Linked List for the transfer*/
- if (dma_dir == EASELCOMM_DMA_DIR_TO_SERVER)
- ret = easelcomm_hw_easel_build_ll(
- msg_metadata->dma_xfer.sg_remote,
- msg_metadata->dma_xfer.sg_local,
- &mblk_ll_data);
- else
- ret = easelcomm_hw_easel_build_ll(
- msg_metadata->dma_xfer.sg_local,
- msg_metadata->dma_xfer.sg_remote,
- &mblk_ll_data);
- /* If error, tell client to abort DMA transfer */
- if (ret < 0 || !mblk_ll_data)
- goto abort;
- msg_metadata->dma_xfer.server_addr =
- easelcomm_hw_easel_ll_addr(mblk_ll_data);
- }
- /* Send DMA_XFER to client */
- ret = easelcomm_send_dma_xfer(service, msg_metadata, dma_dir);
- if (ret)
- goto abort;
- /* Wait for client to send a DMA Done for this message */
- ret = wait_for_completion_interruptible(
- &msg_metadata->dma_xfer.xfer_done);
- if (ret || msg_metadata->dma_xfer.aborting)
- goto abort;
- ret = msg_metadata->dma_xfer.errcode;
- goto out;
- abort:
- dev_dbg(easelcomm_miscdev.this_device,
- "aborting DMA for msg %u:%s%llu\n",
- service->service_id, easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id);
- msg_metadata->dma_xfer.aborting = true;
- /* Send a DMA abort to remote */
- msg_metadata->dma_xfer.xfer_type = EASELCOMM_DMA_XFER_ABORT;
- ret = easelcomm_send_dma_xfer(service, msg_metadata, dma_dir);
- if (ret)
- dev_err(easelcomm_miscdev.this_device,
- "send DMA abort for msg %u:%s%llu failed: %d\n",
- service->service_id,
- easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id, ret);
- ret = discard ? 0 : -EIO;
- /*
- * TODO-LATER: Need to make sure DMA hardware no longer refers to
- * LL before destroy. Related: Ensure DMA hardware no longer
- * accessing the locally-pinned memory pages prior to SG unmap.
- */
- out:
- /* Destroy LL if allocated for MBLK DMA */
- if (mblk_ll_data)
- easelcomm_hw_easel_destroy_ll(mblk_ll_data);
- return ret;
- }
- /* Client sends DMA Done command to server to let it know it can clean up. */
- static int easelcomm_send_dma_done(
- struct easelcomm_service *service,
- struct easelcomm_message_metadata *msg_metadata,
- enum easelcomm_dma_direction dma_dir, int32_t errcode)
- {
- struct easelcomm_dma_done_arg dma_done_arg;
- int ret = 0;
- /* Send DMA Done command to server */
- dma_done_arg.message_id =
- msg_metadata->msg->desc.message_id;
- dma_done_arg.dma_dir = dma_dir;
- dma_done_arg.errcode = errcode;
- ret = easelcomm_start_cmd(
- service, EASELCOMM_CMD_DMA_DONE,
- sizeof(struct easelcomm_dma_done_arg));
- if (ret)
- return ret;
- ret = easelcomm_append_cmd_args(
- service, &dma_done_arg, sizeof(struct easelcomm_dma_done_arg));
- if (ret)
- return ret;
- dev_dbg(easelcomm_miscdev.this_device,
- "send cmd DMA_DONE msg %u:%s%llu errcode=%d\n",
- service->service_id, easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id, errcode);
- ret = easelcomm_send_cmd(service);
- return ret;
- }
- /* Client performs single-block DMA transfer */
- static int easelcomm_client_perform_dma_sblk(
- struct easelcomm_service *service,
- struct easelcomm_message_metadata *msg_metadata,
- enum easelcomm_dma_direction dir)
- {
- uint64_t client_addr = easelcomm_hw_scatterlist_sblk_addr(
- msg_metadata->dma_xfer.sg_local);
- dev_dbg(easelcomm_miscdev.this_device, "sblk dma msg %u:%s%llu dir=%d size=%u local=%llx saddr=%llx\n",
- service->service_id, easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id, dir,
- msg_metadata->msg->desc.dma_buf_size, client_addr,
- msg_metadata->dma_xfer.server_addr);
- return easelcomm_hw_ap_dma_sblk_transfer(
- client_addr, msg_metadata->dma_xfer.server_addr,
- msg_metadata->msg->desc.dma_buf_size,
- dir == EASELCOMM_DMA_DIR_TO_SERVER);
- }
- /* Client performs multi-block DMA transfer */
- static int easelcomm_client_perform_dma_mblk(
- struct easelcomm_service *service,
- struct easelcomm_message_metadata *msg_metadata,
- enum easelcomm_dma_direction dir)
- {
- dev_dbg(easelcomm_miscdev.this_device, "mblk dma msg %u:%s%llu dir=%d size=%u ll=%llx\n",
- service->service_id, easelcomm_msgid_prefix(msg_metadata),
- msg_metadata->msg->desc.message_id, dir,
- msg_metadata->msg->desc.dma_buf_size,
- msg_metadata->dma_xfer.server_addr);
- return easelcomm_hw_ap_dma_mblk_transfer(
- msg_metadata->dma_xfer.server_addr,
- dir == EASELCOMM_DMA_DIR_TO_SERVER);
- }
- /*
- * Client handles DMA transfer. Send local scatterlist to server, wait for
- * instructions back on how to proceed with single- or multi-block transfer,
- * perform the transfer, and send the DMA done command to server.
- */
- static int easelcomm_client_handle_dma_request(
- struct easelcomm_service *service,
- struct easelcomm_message_metadata *msg_metadata,
- enum easelcomm_dma_direction dma_dir,
- int timeout_ms)
- {
- int ret, ret2;
- /* Send scatterlist to server in a DMA_SG command */
- ret = easelcomm_send_dma_scatterlist(
- service, msg_metadata, dma_dir);
- if (ret)
- return ret;
- /* If sent a DMA discard then done */
- if (!msg_metadata->dma_xfer.sg_local_size)
- return 0;
- /* Wait for server to return the DMA request info */
- ret = wait_for_completion_interruptible_timeout(
- &msg_metadata->dma_xfer.xfer_ready,
- msecs_to_jiffies(timeout_ms));
- if (ret == -ERESTARTSYS) {
- dev_info(easelcomm_miscdev.this_device,
- "Wait for DMA_XFER from server interrupted\n");
- goto abort;
- } else if (ret == 0) {
- dev_err(easelcomm_miscdev.this_device,
- "Wait for DMA_XFER from server timed out\n");
- goto abort;
- } else if (msg_metadata->dma_xfer.aborting)
- goto abort;
- /*
- * If server discards/aborts the DMA request then done. If
- * this is a client DMA send then silently discard. If the
- * client is receiving a DMA buffer then return an error on
- * the read.
- */
- if (msg_metadata->dma_xfer.xfer_type == EASELCOMM_DMA_XFER_ABORT)
- return dma_dir == EASELCOMM_DMA_DIR_TO_CLIENT ? -EIO : 0;
- /* Sanity check that list is properly terminated */
- if (easelcomm_hw_verify_scatterlist(&msg_metadata->dma_xfer)) {
- dev_err(easelcomm_miscdev.this_device,
- "sg fails verification\n");
- goto abort;
- }
- /* Single-Block or Multi-Block DMA? */
- if (msg_metadata->dma_xfer.xfer_type == EASELCOMM_DMA_XFER_SBLK) {
- /* Perform the SBLK DMA transfer */
- ret = easelcomm_client_perform_dma_sblk(
- service, msg_metadata, dma_dir);
- } else {
- /* Perform the MBLK DMA transfer */
- ret = easelcomm_client_perform_dma_mblk(
- service, msg_metadata, dma_dir);
- }
- /* Tell server DMA transfer is done */
- ret2 = easelcomm_send_dma_done(service, msg_metadata, dma_dir, ret);
- return ret || ret2;
- /*
- * We are aborting the transfer before it started (local flush or
- * signal received)
- */
- abort:
- msg_metadata->dma_xfer.aborting = true;
- /* send abort to server */
- msg_metadata->dma_xfer.xfer_type = EASELCOMM_DMA_XFER_ABORT;
- easelcomm_send_dma_done(service, msg_metadata, dma_dir, -EIO);
- return -EIO;
- }
- /* RECVDMA ioctl processing. */
- int easelcomm_receive_dma(
- struct easelcomm_service *service,
- struct easelcomm_kbuf_desc *buf_desc)
- {
- struct easelcomm_message_metadata *msg_metadata;
- enum easelcomm_dma_direction dma_dir;
- int ret = 0;
- dev_dbg(easelcomm_miscdev.this_device,
- "RECVDMA msg %u:r%llu buf_type=%d buf_size=%u timeout_ms=%d dma_buf_fd=%d dma_buf_off=%u dma_buf_width=%u dma_buf_stride=%u\n",
- service->service_id, buf_desc->message_id,
- buf_desc->buf_type, buf_desc->buf_size,
- buf_desc->wait.timeout_ms,
- buf_desc->dma_buf_fd,
- buf_desc->dma_buf_off,
- buf_desc->dma_buf_width,
- buf_desc->dma_buf_stride);
- msg_metadata =
- easelcomm_find_remote_message(service, buf_desc->message_id);
- if (!msg_metadata) {
- dev_err(easelcomm_miscdev.this_device, "RECVDMA msg r%llu not found\n",
- buf_desc->message_id);
- return -EINVAL;
- }
- if (buf_desc->buf_size &&
- buf_desc->buf_size != msg_metadata->msg->desc.dma_buf_size) {
- dev_err(easelcomm_miscdev.this_device,
- "RECVDMA descriptor buffer size %u doesn't match msg r%llu size %u\n",
- buf_desc->buf_size, buf_desc->message_id,
- msg_metadata->msg->desc.dma_buf_size);
- ret = -EINVAL;
- goto out;
- }
- /* If the message doesn't even request a DMA transfer then skip it. */
- if (!msg_metadata->msg->desc.dma_buf_size)
- goto out;
- /* If it's a user buffer, check valid range and writable. */
- if (buf_desc->buf_type == EASELCOMM_DMA_BUFFER_USER) {
- if (!access_ok(VERIFY_WRITE, buf_desc->buf,
- buf_desc->buf_size)) {
- ret = -EFAULT;
- goto out;
- }
- }
- dma_dir = easelcomm_is_client() ?
- EASELCOMM_DMA_DIR_TO_CLIENT : EASELCOMM_DMA_DIR_TO_SERVER;
- /*
- * If the DMA transfer is not being discarded locally then generate
- * the local DMA scatter-gather list.
- */
- msg_metadata->dma_xfer.sg_local_size = 0;
- msg_metadata->dma_xfer.sg_local_localdata = NULL;
- if (buf_desc->buf_size) {
- msg_metadata->dma_xfer.sg_local =
- easelcomm_create_dma_scatterlist(
- buf_desc,
- &msg_metadata->dma_xfer.sg_local_size,
- &msg_metadata->dma_xfer.sg_local_localdata,
- dma_dir);
- if (!msg_metadata->dma_xfer.sg_local) {
- ret = -ENOMEM;
- msg_metadata->dma_xfer.sg_local_size = 0;
- goto out;
- }
- }
- if (easelcomm_is_client())
- ret = easelcomm_client_handle_dma_request(
- service, msg_metadata, dma_dir,
- buf_desc->wait.timeout_ms);
- else
- ret = easelcomm_server_handle_dma_request(
- service, msg_metadata, dma_dir);
- /* Free contents of struct mnh_sg_list */
- if (msg_metadata->dma_xfer.sg_local_localdata)
- easelcomm_hw_destroy_scatterlist(
- msg_metadata->dma_xfer.sg_local_localdata);
- out:
- /* If no reply needed then done with the remote message. */
- easelcomm_drop_reference(service, msg_metadata,
- !msg_metadata->msg->desc.need_reply);
- return ret;
- }
- EXPORT_SYMBOL(easelcomm_receive_dma);
- /* SENDDMA ioctl processing. */
- int easelcomm_send_dma(
- struct easelcomm_service *service,
- struct easelcomm_kbuf_desc *buf_desc)
- {
- struct easelcomm_message_metadata *msg_metadata;
- enum easelcomm_dma_direction dma_dir;
- int ret = 0;
- dev_dbg(easelcomm_miscdev.this_device,
- "SENDDMA msg %u:l%llu buf_type=%d buf_size=%u timeout_ms=%d dma_buf_fd=%d dma_buf_off=%u dma_buf_width=%u dma_buf_stride=%u\n",
- service->service_id, buf_desc->message_id,
- buf_desc->buf_type, buf_desc->buf_size,
- buf_desc->wait.timeout_ms,
- buf_desc->dma_buf_fd, buf_desc->dma_buf_off,
- buf_desc->dma_buf_width, buf_desc->dma_buf_stride);
- msg_metadata =
- easelcomm_find_local_message(service, buf_desc->message_id);
- if (!msg_metadata) {
- dev_err(easelcomm_miscdev.this_device, "SENDDMA msg l%llu not found\n",
- buf_desc->message_id);
- return -EINVAL;
- }
- if (buf_desc->buf_size &&
- buf_desc->buf_size != msg_metadata->msg->desc.dma_buf_size) {
- dev_err(easelcomm_miscdev.this_device,
- "SENDDMA descriptor buffer size %u doesn't match message l%llu size %u\n",
- buf_desc->buf_size, buf_desc->message_id,
- msg_metadata->msg->desc.dma_buf_size);
- ret = -EINVAL;
- goto out;
- }
- /* If it's a user buffer, check valid range and readable. */
- if (buf_desc->buf_type == EASELCOMM_DMA_BUFFER_USER) {
- if (!access_ok(VERIFY_READ, buf_desc->buf,
- buf_desc->buf_size)) {
- ret = -EFAULT;
- goto out;
- }
- }
- dma_dir = easelcomm_is_client() ?
- EASELCOMM_DMA_DIR_TO_SERVER : EASELCOMM_DMA_DIR_TO_CLIENT;
- /*
- * If the DMA transfer is not being discarded locally then generate
- * the local DMA scatter-gather list.
- */
- msg_metadata->dma_xfer.sg_local_size = 0;
- msg_metadata->dma_xfer.sg_local_localdata = NULL;
- if (buf_desc->buf_size) {
- msg_metadata->dma_xfer.sg_local =
- easelcomm_create_dma_scatterlist(
- buf_desc,
- &msg_metadata->dma_xfer.sg_local_size,
- &msg_metadata->dma_xfer.sg_local_localdata,
- dma_dir);
- if (!msg_metadata->dma_xfer.sg_local) {
- ret = -ENOMEM;
- goto out;
- }
- }
- if (msg_metadata->msg->desc.dma_buf_size) {
- if (easelcomm_is_client())
- ret = easelcomm_client_handle_dma_request(
- service, msg_metadata, dma_dir,
- buf_desc->wait.timeout_ms);
- else
- ret = easelcomm_server_handle_dma_request(
- service, msg_metadata, dma_dir);
- }
- /* Free contents of struct mnh_sg_list */
- if (msg_metadata->dma_xfer.sg_local_localdata)
- easelcomm_hw_destroy_scatterlist(
- msg_metadata->dma_xfer.sg_local_localdata);
- out:
- /* If no reply needed then done with local message. */
- easelcomm_drop_reference(service, msg_metadata,
- !msg_metadata->msg->desc.need_reply);
- return ret;
- }
- EXPORT_SYMBOL(easelcomm_send_dma);
|