123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661 |
- /*
- * Remote Processor Procedure Call Driver
- *
- * Copyright (C) 2012-2017 Texas Instruments Incorporated - http://www.ti.com/
- *
- * Erik Rainey <erik.rainey@ti.com>
- * Suman Anna <s-anna@ti.com>
- *
- * 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.
- *
- * 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/dma-buf.h>
- #include <linux/rpmsg_rpc.h>
- #include "rpmsg_rpc_internal.h"
- #if defined(CONFIG_ARCH_OMAP4) || defined(CONFIG_SOC_OMAP5) || \
- defined(CONFIG_SOC_DRA7XX)
- /*
- * TODO: Remove tiler_stride_from_region & rppc_recalc_off from here, and
- * rely on OMAPDRM/TILER code for OMAP dependencies
- */
- /**
- * tiler_stride_from_region() - calculate stride value for OMAP TILER
- * @localphys: The local physical address.
- *
- * Returns the stride value as seen by remote processors based on the local
- * address given to the function. This stride value is calculated based on the
- * actual bus address, and is assumed that the TILER regions are mapped in a
- * in a linear fashion.
- *
- * The physical address range decoding of local addresses is as follows:
- *
- * 0x60000000 - 0x67FFFFFF : 8-bit region (Stride is 16K bytes)
- * 0x68000000 - 0x6FFFFFFF : 16-bit region (Stride is 32K bytes)
- * 0x70000000 - 0x77FFFFFF : 32-bit region (Stride is 32K bytes)
- * 0x78000000 - 0x7FFFFFFF : Page mode region (Stride is 0 bytes)
- *
- * Return: stride value
- */
- static long tiler_stride_from_region(phys_addr_t localphys)
- {
- switch (localphys & 0xf8000000) {
- case 0x60000000:
- return 0x4000;
- case 0x68000000:
- case 0x70000000:
- return 0x8000;
- default:
- return 0;
- }
- }
- /**
- * rppc_recalc_off() - Recalculate the unsigned offset in a buffer due to
- * it's location in the TILER.
- * @lpa: local physical address
- * @uoff: unsigned offset
- *
- * Return: adjusted offset accounting for TILER region
- */
- static long rppc_recalc_off(phys_addr_t lpa, long uoff)
- {
- long stride = tiler_stride_from_region(lpa);
- return (stride != 0) ? (stride * (uoff / PAGE_SIZE)) +
- (uoff & (PAGE_SIZE - 1)) : uoff;
- }
- #else
- static inline long rppc_recalc_off(phys_addr_t lpa, long uoff)
- {
- return uoff;
- }
- #endif
- /**
- * rppc_alloc_dmabuf - import a buffer and store in a rppc buffer descriptor
- * @rpc - rppc instance handle
- * @fd - dma_buf file descriptor
- * @autoreg: flag indicating the mode of creation
- *
- * This function primarily imports a buffer into the driver and holds
- * a reference to the buffer on behalf of the remote processor. The
- * buffer to be imported is represented by a dma-buf file descriptor,
- * and as such is agnostic of the buffer allocator and/or exporter.
- * The buffer is imported using the dma-buf api, and a driver specific
- * buffer descriptor is used to store the imported buffer properties.
- * The imported buffers are all stored in a rppc instance specific
- * idr, to be used for looking up and cleaning up the driver buffer
- * descriptors.
- *
- * The @autoreg field is used to dictate the manner in which the buffer
- * is imported. The user-side can pre-register the buffers with the driver
- * (which will import the buffers) if the application is going to use
- * these repeatedly in consecutive function invocations. The buffers
- * are auto-imported if the user-side has not registered them previously
- * and are un-imported once the remote function call returns.
- *
- * This function is to be called only after checking that buffer has
- * not been imported already (see rppc_find_dmabuf).
- *
- * Return: allocated rppc_dma_buf or error
- */
- struct rppc_dma_buf *rppc_alloc_dmabuf(struct rppc_instance *rpc, int fd,
- bool autoreg)
- {
- struct rppc_dma_buf *dma;
- void *ret;
- int id;
- dma = kzalloc(sizeof(*dma), GFP_KERNEL);
- if (!dma)
- return ERR_PTR(-ENOMEM);
- dma->fd = fd;
- dma->autoreg = !!autoreg;
- dma->buf = dma_buf_get(dma->fd);
- if (IS_ERR(dma->buf)) {
- ret = dma->buf;
- goto free_dma;
- }
- dma->attach = dma_buf_attach(dma->buf, rpc->dev);
- if (IS_ERR(dma->attach)) {
- ret = dma->attach;
- goto put_buf;
- }
- dma->sgt = dma_buf_map_attachment(dma->attach, DMA_BIDIRECTIONAL);
- if (IS_ERR(dma->sgt)) {
- ret = dma->sgt;
- goto detach_buf;
- }
- dma->pa = sg_dma_address(dma->sgt->sgl);
- mutex_lock(&rpc->lock);
- id = idr_alloc(&rpc->dma_idr, dma, 0, 0, GFP_KERNEL);
- dma->id = id;
- mutex_unlock(&rpc->lock);
- if (id < 0) {
- ret = ERR_PTR(id);
- goto unmap_buf;
- }
- return dma;
- unmap_buf:
- dma_buf_unmap_attachment(dma->attach, dma->sgt, DMA_BIDIRECTIONAL);
- detach_buf:
- dma_buf_detach(dma->buf, dma->attach);
- put_buf:
- dma_buf_put(dma->buf);
- free_dma:
- kfree(dma);
- return ret;
- }
- /**
- * rppc_free_dmabuf - release the imported buffer
- * @id: idr index of the imported buffer descriptor
- * @p: imported buffer descriptor allocated during rppc_alloc_dmabuf
- * @data: rpc instance handle
- *
- * This function is used to release a buffer that has been previously
- * imported through a rppc_alloc_dmabuf call. The function can be used
- * either individually for releasing a specific buffer or in a loop iterator
- * for releasing all the buffers associated with a remote function call, or
- * during cleanup of the rpc instance.
- *
- * Return: 0 on success, and -ENOENT if invalid pointers passed in
- */
- int rppc_free_dmabuf(int id, void *p, void *data)
- {
- struct rppc_dma_buf *dma = p;
- struct rppc_instance *rpc = data;
- if (!dma || !rpc)
- return -ENOENT;
- dma_buf_unmap_attachment(dma->attach, dma->sgt, DMA_BIDIRECTIONAL);
- dma_buf_detach(dma->buf, dma->attach);
- dma_buf_put(dma->buf);
- WARN_ON(id != dma->id);
- idr_remove(&rpc->dma_idr, id);
- kfree(dma);
- return 0;
- }
- /**
- * rppc_free_auto_dmabuf - release an auto-registered imported buffer
- * @id: idr index of the imported buffer descriptor
- * @p: imported buffer descriptor allocated during the rppc_alloc_dmabuf
- * @data: rpc instance handle
- *
- * This function is used to release a buffer that has been previously
- * imported automatically in the remote function invocation path (for
- * rppc_alloc_dmabuf invocations with autoreg set as true). The function
- * is used as a loop iterator for releasing all such buffers associated
- * with a remote function call, and is called after processing the
- * translations while handling the return message of an executed function
- * call.
- *
- * Return: 0 on success or if the buffer is not auto-imported, and -ENOENT
- * if invalid pointers passed in
- */
- static int rppc_free_auto_dmabuf(int id, void *p, void *data)
- {
- struct rppc_dma_buf *dma = p;
- struct rppc_instance *rpc = data;
- if (WARN_ON(!dma || !rpc))
- return -ENOENT;
- if (!dma->autoreg)
- return 0;
- rppc_free_dmabuf(id, p, data);
- return 0;
- }
- /**
- * find_dma_by_fd - find the allocated buffer descriptor
- * @id: idr loop index
- * @p: imported buffer descriptor associated with each idr index @id
- * @data: dma-buf file descriptor of the buffer
- *
- * This is a idr iterator helper function, used for checking if a buffer
- * has been imported before and present within the rpc instance's idr.
- *
- * Return: rpc buffer descriptor if file descriptor matches, and 0 otherwise
- */
- static int find_dma_by_fd(int id, void *p, void *data)
- {
- struct rppc_dma_buf *dma = p;
- int fd = (int)data;
- if (dma->fd == fd)
- return (int)p;
- return 0;
- }
- /**
- * rppc_find_dmabuf - find and return the rppc buffer descriptor of an imported
- * buffer
- * @rpc: rpc instance
- * @fd: dma-buf file descriptor of the buffer
- *
- * This function is used to find and return the rppc buffer descriptor of an
- * imported buffer. The function is used to check if ia buffer has already
- * been imported (during manual registration to return an error), and to return
- * the rppc buffer descriptor to be used for freeing (during manual
- * deregistration). It is also used during auto-registration to see if the
- * buffer needs to be imported through a rppc_alloc_dmabuf if not found.
- *
- * Return: rppc buffer descriptor of the buffer if it has already been imported,
- * or NULL otherwise.
- */
- struct rppc_dma_buf *rppc_find_dmabuf(struct rppc_instance *rpc, int fd)
- {
- struct rppc_dma_buf *node = NULL;
- void *data = (void *)fd;
- dev_dbg(rpc->dev, "looking for fd %u\n", fd);
- mutex_lock(&rpc->lock);
- node = (struct rppc_dma_buf *)
- idr_for_each(&rpc->dma_idr, find_dma_by_fd, data);
- mutex_unlock(&rpc->lock);
- dev_dbg(rpc->dev, "returning node %p for fd %u\n",
- node, fd);
- return node;
- }
- /**
- * rppc_map_page - import and map a kernel page in a dma_buf
- * @rpc - rppc instance handle
- * @fd: file descriptor of the dma_buf to import
- * @offset: offset of the translate location within the buffer
- * @base_ptr: pointer for returning mapped kernel address
- * @dmabuf: pointer for returning the imported dma_buf
- *
- * A helper function to import the dma_buf buffer and map into kernel
- * the page containing the offset within the buffer. The function is
- * called by rppc_xlate_buffers and returns the pointers to the kernel
- * mapped address and the imported dma_buf handle in arguments. The
- * mapping is used for performing in-place translation of the user
- * provided pointer at location @offset within the buffer.
- *
- * The mapping is achieved through the appropriate dma_buf ops, and
- * the page will be unmapped after performing the translation. See
- * also rppc_unmap_page.
- *
- * Return: 0 on success, or an appropriate failure code otherwise
- */
- static int rppc_map_page(struct rppc_instance *rpc, int fd, u32 offset,
- u8 **base_ptr, struct dma_buf **dmabuf)
- {
- int ret = 0;
- u8 *ptr = NULL;
- struct dma_buf *dbuf = NULL;
- u32 pg_offset;
- unsigned long pg_num;
- size_t begin, end = PAGE_SIZE;
- struct device *dev = rpc->dev;
- if (!base_ptr || !dmabuf)
- return -EINVAL;
- pg_offset = (offset & (PAGE_SIZE - 1));
- begin = offset & PAGE_MASK;
- pg_num = offset >> PAGE_SHIFT;
- dbuf = dma_buf_get(fd);
- if (IS_ERR(dbuf)) {
- ret = PTR_ERR(dbuf);
- dev_err(dev, "invalid dma_buf file descriptor passed! fd = %d ret = %d\n",
- fd, ret);
- goto out;
- }
- ret = dma_buf_begin_cpu_access(dbuf, DMA_BIDIRECTIONAL);
- if (ret < 0) {
- dev_err(dev, "failed to acquire cpu access to the dma buf fd = %d offset = 0x%x, ret = %d\n",
- fd, offset, ret);
- goto put_dmabuf;
- }
- ptr = dma_buf_kmap(dbuf, pg_num);
- if (!ptr) {
- ret = -ENOBUFS;
- dev_err(dev, "failed to map the page containing the translation into kernel fd = %d offset = 0x%x\n",
- fd, offset);
- goto end_cpuaccess;
- }
- *base_ptr = ptr;
- *dmabuf = dbuf;
- dev_dbg(dev, "kmap'd base_ptr = %p buf = %p into kernel from %zu for %zu bytes, pg_offset = 0x%x\n",
- ptr, dbuf, begin, end, pg_offset);
- return 0;
- end_cpuaccess:
- dma_buf_end_cpu_access(dbuf, DMA_BIDIRECTIONAL);
- put_dmabuf:
- dma_buf_put(dbuf);
- out:
- return ret;
- }
- /**
- * rppc_unmap_page - unmap and release a previously mapped page
- * @rpc - rppc instance handle
- * @offset: offset of the translate location within the buffer
- * @base_ptr: kernel mapped address for the page to be unmapped
- * @dmabuf: imported dma_buf to be released
- *
- * This function is called by rppc_xlate_buffers to unmap the
- * page and release the imported buffer. It essentially undoes
- * the functionality of rppc_map_page.
- */
- static void rppc_unmap_page(struct rppc_instance *rpc, u32 offset,
- u8 *base_ptr, struct dma_buf *dmabuf)
- {
- u32 pg_offset;
- unsigned long pg_num;
- size_t begin, end = PAGE_SIZE;
- struct device *dev = rpc->dev;
- if (!base_ptr || !dmabuf)
- return;
- pg_offset = (offset & (PAGE_SIZE - 1));
- begin = offset & PAGE_MASK;
- pg_num = offset >> PAGE_SHIFT;
- dev_dbg(dev, "Unkmaping base_ptr = %p of buf = %p from %zu to %zu bytes\n",
- base_ptr, dmabuf, begin, end);
- dma_buf_kunmap(dmabuf, pg_num, base_ptr);
- dma_buf_end_cpu_access(dmabuf, DMA_BIDIRECTIONAL);
- dma_buf_put(dmabuf);
- }
- /**
- * rppc_buffer_lookup - convert a buffer pointer to a remote processor pointer
- * @rpc: rpc instance
- * @uva: buffer pointer that needs to be translated
- * @buva: base pointer of the allocated buffer
- * @fd: dma-buf file descriptor of the allocated buffer
- *
- * This function is used for converting a pointer value in the function
- * arguments to its appropriate remote processor device address value.
- * The @uva and @buva are used for identifying the offset of the function
- * argument pointer in an original allocation. This supports the cases where
- * an offset pointer (eg: alignment, packed buffers etc) needs to be passed
- * as the argument rather than the actual allocated pointer.
- *
- * The remote processor device address is done by retrieving the base physical
- * address of the buffer by importing the buffer and converting it to the
- * remote processor device address using a remoteproc api, with adjustments
- * to the offset.
- *
- * The offset is specifically adjusted for OMAP TILER to account for the stride
- * and mapping onto the remote processor.
- *
- * Return: remote processor device address, 0 on failure (implies invalid
- * arguments)
- */
- dev_addr_t rppc_buffer_lookup(struct rppc_instance *rpc, virt_addr_t uva,
- virt_addr_t buva, int fd)
- {
- phys_addr_t lpa = 0;
- dev_addr_t rda = 0;
- long uoff = uva - buva;
- struct device *dev = rpc->dev;
- struct rppc_dma_buf *buf;
- dev_dbg(dev, "buva = %p uva = %p offset = %ld [0x%016lx] fd = %d\n",
- (void *)buva, (void *)uva, uoff, (ulong)uoff, fd);
- if (uoff < 0) {
- dev_err(dev, "invalid pointer values for uva = %p from buva = %p\n",
- (void *)uva, (void *)buva);
- return rda;
- }
- buf = rppc_find_dmabuf(rpc, fd);
- if (IS_ERR_OR_NULL(buf)) {
- buf = rppc_alloc_dmabuf(rpc, fd, true);
- if (IS_ERR(buf))
- goto out;
- }
- lpa = buf->pa;
- WARN_ON(lpa != sg_dma_address(buf->sgt->sgl));
- uoff = rppc_recalc_off(lpa, uoff);
- lpa += uoff;
- rda = rppc_local_to_remote_da(rpc, lpa);
- out:
- dev_dbg(dev, "host uva %p == host pa %pa => remote da %p (fd %d)\n",
- (void *)uva, &lpa, (void *)rda, fd);
- return rda;
- }
- /**
- * rppc_xlate_buffers - translate argument pointers in the marshalled packet
- * @rpc: rppc instance
- * @func: rppc function packet being acted upon
- * @direction: direction of translation
- *
- * This function translates all the pointers within the function call packet
- * structure, based on the translation descriptor structures. The translation
- * replaces the pointers to the appropriate pointers based on the direction.
- * The function is invoked in preparing the packet to be sent to the remote
- * processor-side and replaces the pointers to the remote processor device
- * address pointers; and in processing the packet back after executing the
- * function and replacing back the remote processor device addresses with
- * the original pointers.
- *
- * Return: 0 on success, or an appropriate failure code otherwise
- */
- int rppc_xlate_buffers(struct rppc_instance *rpc, struct rppc_function *func,
- int direction)
- {
- u8 *base_ptr = NULL;
- struct dma_buf *dbuf = NULL;
- struct device *dev = rpc->dev;
- u32 ptr_idx, pri_offset, sec_offset, offset, pg_offset, size;
- int i, limit, inc = 1;
- virt_addr_t kva, uva, buva;
- dev_addr_t rda;
- int ret = 0, final_ret = 0;
- int xlate_fd;
- limit = func->num_translations;
- if (WARN_ON(!limit))
- return 0;
- dev_dbg(dev, "operating on %d pointers\n", func->num_translations);
- /* sanity check the translation elements */
- for (i = 0; i < limit; i++) {
- ptr_idx = func->translations[i].index;
- pri_offset = func->params[ptr_idx].data -
- func->params[ptr_idx].base;
- sec_offset = func->translations[i].offset;
- size = func->params[ptr_idx].size;
- if (ptr_idx >= RPPC_MAX_PARAMETERS) {
- dev_err(dev, "xlate[%d] - invalid parameter pointer index %u\n",
- i, ptr_idx);
- return -EINVAL;
- }
- if (func->params[ptr_idx].type != RPPC_PARAM_TYPE_PTR) {
- dev_err(dev, "xlate[%d] - parameter index %u is not a pointer (type %u)\n",
- i, ptr_idx, func->params[ptr_idx].type);
- return -EINVAL;
- }
- if (func->params[ptr_idx].data == 0) {
- dev_err(dev, "xlate[%d] - supplied user pointer is NULL!\n",
- i);
- return -EINVAL;
- }
- if (sec_offset > (size - sizeof(virt_addr_t))) {
- dev_err(dev, "xlate[%d] offset is larger than data area! (sec_offset = %u size = %u)\n",
- i, sec_offset, size);
- return -ENOSPC;
- }
- }
- /*
- * we may have a failure during translation, in which case use the same
- * loop to unwind the whole operation
- */
- for (i = 0; i != limit; i += inc) {
- dev_dbg(dev, "starting translation %d of %d by %d\n",
- i, limit, inc);
- ptr_idx = func->translations[i].index;
- pri_offset = func->params[ptr_idx].data -
- func->params[ptr_idx].base;
- sec_offset = func->translations[i].offset;
- offset = pri_offset + sec_offset;
- pg_offset = (offset & (PAGE_SIZE - 1));
- /*
- * map into kernel the page containing the offset, where the
- * pointer needs to be translated.
- */
- ret = rppc_map_page(rpc, func->params[ptr_idx].fd, offset,
- &base_ptr, &dbuf);
- if (ret) {
- dev_err(dev, "rppc_map_page failed, translation = %d param_index = %d fd = %d ret = %d\n",
- i, ptr_idx, func->params[ptr_idx].fd, ret);
- goto unwind;
- }
- /*
- * perform the actual translation as per the direction.
- */
- if (direction == RPPC_UVA_TO_RPA) {
- kva = (virt_addr_t)&base_ptr[pg_offset];
- if (kva & 0x3) {
- dev_err(dev, "kernel virtual address %p is not aligned for translation = %d\n",
- (void *)kva, i);
- ret = -EADDRNOTAVAIL;
- goto unmap;
- }
- uva = *(virt_addr_t *)kva;
- if (!uva) {
- dev_err(dev, "user pointer in the translated offset location is NULL for translation = %d\n",
- i);
- print_hex_dump(KERN_DEBUG, "KMAP: ",
- DUMP_PREFIX_NONE, 16, 1,
- base_ptr, PAGE_SIZE, true);
- ret = -EADDRNOTAVAIL;
- goto unmap;
- }
- buva = (virt_addr_t)func->translations[i].base;
- xlate_fd = func->translations[i].fd;
- dev_dbg(dev, "replacing UVA %p at KVA %p prt_idx = %u pg_offset = 0x%x fd = %d\n",
- (void *)uva, (void *)kva, ptr_idx,
- pg_offset, xlate_fd);
- /* compute the corresponding remote device address */
- rda = rppc_buffer_lookup(rpc, uva, buva, xlate_fd);
- if (!rda) {
- ret = -ENODATA;
- goto unmap;
- }
- /*
- * replace the pointer, save the old value for replacing
- * it back on the function return path
- */
- func->translations[i].fd = (int32_t)uva;
- *(virt_addr_t *)kva = rda;
- dev_dbg(dev, "replaced UVA %p with RDA %p at KVA %p\n",
- (void *)uva, (void *)rda, (void *)kva);
- } else if (direction == RPPC_RPA_TO_UVA) {
- kva = (virt_addr_t)&base_ptr[pg_offset];
- if (kva & 0x3) {
- ret = -EADDRNOTAVAIL;
- goto unmap;
- }
- rda = *(virt_addr_t *)kva;
- uva = (virt_addr_t)func->translations[i].fd;
- WARN_ON(!uva);
- *(virt_addr_t *)kva = uva;
- dev_dbg(dev, "replaced RDA %p with UVA %p at KVA %p\n",
- (void *)rda, (void *)uva, (void *)kva);
- }
- unmap:
- /*
- * unmap the page containing the translation from kernel, the
- * next translation acting on the same fd might be in a
- * different page altogether from the current one
- */
- rppc_unmap_page(rpc, offset, base_ptr, dbuf);
- dbuf = NULL;
- base_ptr = NULL;
- if (!ret)
- continue;
- unwind:
- /*
- * unwind all the previous translations if the failure occurs
- * while sending a message to the remote-side. There's nothing
- * to do but to continue if the failure occurs during the
- * processing of a function response.
- */
- if (direction == RPPC_UVA_TO_RPA) {
- dev_err(dev, "unwinding UVA to RDA translations! translation = %d\n",
- i);
- direction = RPPC_RPA_TO_UVA;
- inc = -1;
- limit = -1;
- } else if (direction == RPPC_RPA_TO_UVA) {
- dev_err(dev, "error during UVA to RDA translations!! current translation = %d\n",
- i);
- }
- /*
- * store away the return value to return back to caller
- * in case of an error, record only the first error
- */
- if (!final_ret)
- final_ret = ret;
- }
- /*
- * all the in-place pointer replacements are done, release all the
- * imported buffers during the remote function return path
- */
- if (direction == RPPC_RPA_TO_UVA) {
- mutex_lock(&rpc->lock);
- idr_for_each(&rpc->dma_idr, rppc_free_auto_dmabuf, rpc);
- mutex_unlock(&rpc->lock);
- }
- return final_ret;
- }
|