| // SPDX-License-Identifier: GPL-2.0+ |
| // Copyright 2018 NXP |
| // Copyright (c) 2012-2013 by Tensilica Inc. |
| |
| #include <linux/fs.h> |
| #include <linux/slab.h> |
| #include <linux/elf.h> |
| |
| #include "fsl_dsp.h" |
| #include "fsl_dsp_library_load.h" |
| |
| static Elf32_Half xtlib_host_half(Elf32_Half v, int byteswap) |
| { |
| return (byteswap) ? (v >> 8) | (v << 8) : v; |
| } |
| |
| static Elf32_Word xtlib_host_word(Elf32_Word v, int byteswap) |
| { |
| if (byteswap) { |
| v = ((v & 0x00FF00FF) << 8) | ((v & 0xFF00FF00) >> 8); |
| v = (v >> 16) | (v << 16); |
| } |
| return v; |
| } |
| |
| static int xtlib_verify_magic(Elf32_Ehdr *header, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Byte magic_no; |
| |
| magic_no = header->e_ident[EI_MAG0]; |
| if (magic_no != 0x7f) |
| return -1; |
| |
| magic_no = header->e_ident[EI_MAG1]; |
| if (magic_no != 'E') |
| return -1; |
| |
| magic_no = header->e_ident[EI_MAG2]; |
| if (magic_no != 'L') |
| return -1; |
| |
| magic_no = header->e_ident[EI_MAG3]; |
| if (magic_no != 'F') |
| return -1; |
| |
| if (header->e_ident[EI_CLASS] != ELFCLASS32) |
| return -1; |
| |
| { |
| /* determine byte order */ |
| union { |
| short s; |
| char c[sizeof(short)]; |
| } u; |
| |
| u.s = 1; |
| |
| if (header->e_ident[EI_DATA] == ELFDATA2LSB) |
| xtlib_globals->byteswap = u.c[sizeof(short) - 1] == 1; |
| else if (header->e_ident[EI_DATA] == ELFDATA2MSB) |
| xtlib_globals->byteswap = u.c[0] == 1; |
| else |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void xtlib_load_seg(Elf32_Phdr *pheader, void *src_addr, xt_ptr dst_addr, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Word bytes_to_copy = xtlib_host_word(pheader->p_filesz, |
| xtlib_globals->byteswap); |
| Elf32_Word bytes_to_zero = xtlib_host_word(pheader->p_memsz, |
| xtlib_globals->byteswap) |
| - bytes_to_copy; |
| unsigned int i; |
| char *src_back, *dst_back; |
| |
| void *zero_addr = (void *)dst_addr + bytes_to_copy; |
| |
| if (bytes_to_copy > 0) { |
| // memcpy((void *)(dst_addr), src_addr, bytes_to_copy); |
| src_back = (char *)src_addr; |
| dst_back = (char *)dst_addr; |
| for (i = 0; i < bytes_to_copy; i++) |
| *dst_back++ = *src_back++; |
| } |
| |
| if (bytes_to_zero > 0) { |
| // memset(zero_addr, 0, bytes_to_zero); |
| dst_back = (char *)zero_addr; |
| for (i = 0; i < bytes_to_zero; i++) |
| *dst_back++ = 0; |
| } |
| } |
| |
| #define xtlib_xt_half xtlib_host_half |
| #define xtlib_xt_word xtlib_host_word |
| |
| static xt_ptr align_ptr(xt_ptr ptr, xt_uint align) |
| { |
| return (xt_ptr)(((xt_uint)ptr + align - 1) & ~(align - 1)); |
| } |
| |
| static xt_ptr xt_ptr_offs(xt_ptr base, Elf32_Word offs, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| |
| return (xt_ptr)xtlib_xt_word((xt_uint)base + |
| xtlib_host_word(offs, xtlib_globals->byteswap), |
| xtlib_globals->byteswap); |
| } |
| |
| static Elf32_Dyn *find_dynamic_info(Elf32_Ehdr *eheader, |
| struct lib_info *lib_info) |
| { |
| char *base_addr = (char *)eheader; |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Phdr *pheader = (Elf32_Phdr *)(base_addr + |
| xtlib_host_word(eheader->e_phoff, |
| xtlib_globals->byteswap)); |
| |
| int seg = 0; |
| int num = xtlib_host_half(eheader->e_phnum, xtlib_globals->byteswap); |
| |
| while (seg < num) { |
| if (xtlib_host_word(pheader[seg].p_type, |
| xtlib_globals->byteswap) == PT_DYNAMIC) { |
| return (Elf32_Dyn *)(base_addr + |
| xtlib_host_word(pheader[seg].p_offset, |
| xtlib_globals->byteswap)); |
| } |
| seg++; |
| } |
| return 0; |
| } |
| |
| static int find_align(Elf32_Ehdr *header, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Shdr *sheader = (Elf32_Shdr *) (((char *)header) + |
| xtlib_host_word(header->e_shoff, xtlib_globals->byteswap)); |
| |
| int sec = 0; |
| int num = xtlib_host_half(header->e_shnum, xtlib_globals->byteswap); |
| |
| int align = 0; |
| |
| while (sec < num) { |
| if (sheader[sec].sh_type != SHT_NULL && |
| xtlib_host_word(sheader[sec].sh_size, |
| xtlib_globals->byteswap) > 0) { |
| int sec_align = |
| xtlib_host_word(sheader[sec].sh_addralign, |
| xtlib_globals->byteswap); |
| if (sec_align > align) |
| align = sec_align; |
| } |
| sec++; |
| } |
| |
| return align; |
| } |
| |
| static int validate_dynamic(Elf32_Ehdr *header, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| |
| if (xtlib_verify_magic(header, lib_info) != 0) |
| return XTLIB_NOT_ELF; |
| |
| if (xtlib_host_half(header->e_type, |
| xtlib_globals->byteswap) != ET_DYN) |
| return XTLIB_NOT_DYNAMIC; |
| |
| return XTLIB_NO_ERR; |
| } |
| |
| static int validate_dynamic_splitload(Elf32_Ehdr *header, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Phdr *pheader; |
| int err = validate_dynamic(header, lib_info); |
| |
| if (err != XTLIB_NO_ERR) |
| return err; |
| |
| /* make sure it's split load pi library, expecting three headers, |
| * code, data and dynamic, for example: |
| * |
| *LOAD off 0x00000094 vaddr 0x00000000 paddr 0x00000000 align 2**0 |
| * filesz 0x00000081 memsz 0x00000081 flags r-x |
| *LOAD off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**0 |
| * filesz 0x000001ab memsz 0x000011bc flags rwx |
| *DYNAMIC off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**2 |
| * filesz 0x000000a0 memsz 0x000000a0 flags rw- |
| */ |
| |
| if (xtlib_host_half(header->e_phnum, xtlib_globals->byteswap) != 3) |
| return XTLIB_NOT_SPLITLOAD; |
| |
| pheader = (Elf32_Phdr *)((char *)header + |
| xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); |
| |
| /* LOAD R-X */ |
| if (xtlib_host_word(pheader[0].p_type, |
| xtlib_globals->byteswap) != PT_LOAD || |
| (xtlib_host_word(pheader[0].p_flags, |
| xtlib_globals->byteswap) |
| & (PF_R | PF_W | PF_X)) != (PF_R | PF_X)) |
| return XTLIB_NOT_SPLITLOAD; |
| |
| /* LOAD RWX */ |
| if (xtlib_host_word(pheader[1].p_type, |
| xtlib_globals->byteswap) != PT_LOAD || |
| (xtlib_host_word(pheader[1].p_flags, |
| xtlib_globals->byteswap) |
| & (PF_R | PF_W | PF_X)) != (PF_R | PF_W | PF_X)) |
| return XTLIB_NOT_SPLITLOAD; |
| |
| /* DYNAMIC RW- */ |
| if (xtlib_host_word(pheader[2].p_type, |
| xtlib_globals->byteswap) != PT_DYNAMIC || |
| (xtlib_host_word(pheader[2].p_flags, |
| xtlib_globals->byteswap) |
| & (PF_R | PF_W | PF_X)) != (PF_R | PF_W)) |
| return XTLIB_NOT_SPLITLOAD; |
| |
| return XTLIB_NO_ERR; |
| } |
| |
| static unsigned int |
| xtlib_split_pi_library_size(struct xtlib_packaged_library *library, |
| unsigned int *code_size, |
| unsigned int *data_size, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Phdr *pheader; |
| Elf32_Ehdr *header = (Elf32_Ehdr *)library; |
| int align; |
| int err = validate_dynamic_splitload(header, lib_info); |
| |
| if (err != XTLIB_NO_ERR) { |
| xtlib_globals->err = err; |
| return err; |
| } |
| |
| align = find_align(header, lib_info); |
| |
| pheader = (Elf32_Phdr *)((char *)library + |
| xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); |
| |
| *code_size = xtlib_host_word(pheader[0].p_memsz, |
| xtlib_globals->byteswap) + align; |
| *data_size = xtlib_host_word(pheader[1].p_memsz, |
| xtlib_globals->byteswap) + align; |
| |
| return XTLIB_NO_ERR; |
| } |
| |
| static int get_dyn_info(Elf32_Ehdr *eheader, |
| xt_ptr dst_addr, xt_uint src_offs, |
| xt_ptr dst_data_addr, xt_uint src_data_offs, |
| struct xtlib_pil_info *info, |
| struct lib_info *lib_info) |
| { |
| unsigned int jmprel = 0; |
| unsigned int pltrelsz = 0; |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Dyn *dyn_entry = find_dynamic_info(eheader, lib_info); |
| |
| if (dyn_entry == 0) |
| return XTLIB_NO_DYNAMIC_SEGMENT; |
| |
| info->dst_addr = (xt_uint)xtlib_xt_word((Elf32_Word)dst_addr, |
| xtlib_globals->byteswap); |
| info->src_offs = xtlib_xt_word(src_offs, xtlib_globals->byteswap); |
| info->dst_data_addr = (xt_uint)xtlib_xt_word( |
| (Elf32_Word)dst_data_addr + src_data_offs, |
| xtlib_globals->byteswap); |
| info->src_data_offs = xtlib_xt_word(src_data_offs, |
| xtlib_globals->byteswap); |
| |
| dst_addr -= src_offs; |
| dst_data_addr = dst_data_addr + src_data_offs - src_data_offs; |
| |
| info->start_sym = xt_ptr_offs(dst_addr, eheader->e_entry, lib_info); |
| |
| info->align = xtlib_xt_word(find_align(eheader, lib_info), |
| xtlib_globals->byteswap); |
| |
| info->text_addr = 0; |
| |
| while (dyn_entry->d_tag != DT_NULL) { |
| switch ((Elf32_Sword) xtlib_host_word( |
| (Elf32_Word)dyn_entry->d_tag, |
| xtlib_globals->byteswap)) { |
| case DT_RELA: |
| info->rel = xt_ptr_offs(dst_data_addr, |
| dyn_entry->d_un.d_ptr, lib_info); |
| break; |
| case DT_RELASZ: |
| info->rela_count = xtlib_xt_word( |
| xtlib_host_word(dyn_entry->d_un.d_val, |
| xtlib_globals->byteswap) / |
| sizeof(Elf32_Rela), |
| xtlib_globals->byteswap); |
| break; |
| case DT_INIT: |
| info->init = xt_ptr_offs(dst_addr, |
| dyn_entry->d_un.d_ptr, lib_info); |
| break; |
| case DT_FINI: |
| info->fini = xt_ptr_offs(dst_addr, |
| dyn_entry->d_un.d_ptr, lib_info); |
| break; |
| case DT_HASH: |
| info->hash = xt_ptr_offs(dst_data_addr, |
| dyn_entry->d_un.d_ptr, lib_info); |
| break; |
| case DT_SYMTAB: |
| info->symtab = xt_ptr_offs(dst_data_addr, |
| dyn_entry->d_un.d_ptr, lib_info); |
| break; |
| case DT_STRTAB: |
| info->strtab = xt_ptr_offs(dst_data_addr, |
| dyn_entry->d_un.d_ptr, lib_info); |
| break; |
| case DT_JMPREL: |
| jmprel = dyn_entry->d_un.d_val; |
| break; |
| case DT_PLTRELSZ: |
| pltrelsz = dyn_entry->d_un.d_val; |
| break; |
| case DT_LOPROC + 2: |
| info->text_addr = xt_ptr_offs(dst_addr, |
| dyn_entry->d_un.d_ptr, lib_info); |
| break; |
| |
| default: |
| /* do nothing */ |
| break; |
| } |
| dyn_entry++; |
| } |
| |
| return XTLIB_NO_ERR; |
| } |
| |
| static xt_ptr |
| xtlib_load_split_pi_library_common(struct xtlib_packaged_library *library, |
| xt_ptr destination_code_address, |
| xt_ptr destination_data_address, |
| struct xtlib_pil_info *info, |
| struct lib_info *lib_info) |
| { |
| struct xtlib_loader_globals *xtlib_globals = |
| &lib_info->xtlib_globals; |
| Elf32_Ehdr *header = (Elf32_Ehdr *)library; |
| Elf32_Phdr *pheader; |
| unsigned int align; |
| int err = validate_dynamic_splitload(header, lib_info); |
| xt_ptr destination_code_address_back; |
| xt_ptr destination_data_address_back; |
| |
| if (err != XTLIB_NO_ERR) { |
| xtlib_globals->err = err; |
| return 0; |
| } |
| |
| align = find_align(header, lib_info); |
| |
| destination_code_address_back = destination_code_address; |
| destination_data_address_back = destination_data_address; |
| |
| destination_code_address = align_ptr(destination_code_address, align); |
| destination_data_address = align_ptr(destination_data_address, align); |
| lib_info->code_buf_virt += (destination_code_address - |
| destination_code_address_back); |
| lib_info->data_buf_virt += (destination_data_address - |
| destination_data_address_back); |
| |
| pheader = (Elf32_Phdr *)((char *)library + |
| xtlib_host_word(header->e_phoff, |
| xtlib_globals->byteswap)); |
| |
| err = get_dyn_info(header, |
| destination_code_address, |
| xtlib_host_word(pheader[0].p_paddr, |
| xtlib_globals->byteswap), |
| destination_data_address, |
| xtlib_host_word(pheader[1].p_paddr, |
| xtlib_globals->byteswap), |
| info, |
| lib_info); |
| |
| if (err != XTLIB_NO_ERR) { |
| xtlib_globals->err = err; |
| return 0; |
| } |
| |
| /* loading code */ |
| xtlib_load_seg(&pheader[0], |
| (char *)library + xtlib_host_word(pheader[0].p_offset, |
| xtlib_globals->byteswap), |
| (xt_ptr)lib_info->code_buf_virt, |
| lib_info); |
| |
| if (info->text_addr == 0) |
| info->text_addr = |
| (xt_ptr)xtlib_xt_word((Elf32_Word)destination_code_address, |
| xtlib_globals->byteswap); |
| |
| /* loading data */ |
| xtlib_load_seg(&pheader[1], |
| (char *)library + xtlib_host_word(pheader[1].p_offset, |
| xtlib_globals->byteswap), |
| (xt_ptr)lib_info->data_buf_virt + |
| xtlib_host_word(pheader[1].p_paddr, |
| xtlib_globals->byteswap), |
| lib_info); |
| |
| return (xt_ptr)xtlib_host_word((Elf32_Word)info->start_sym, |
| xtlib_globals->byteswap); |
| } |
| |
| static xt_ptr |
| xtlib_host_load_split_pi_library(struct xtlib_packaged_library *library, |
| xt_ptr destination_code_address, |
| xt_ptr destination_data_address, |
| struct xtlib_pil_info *info, |
| struct lib_info *lib_info) |
| { |
| return xtlib_load_split_pi_library_common(library, |
| destination_code_address, |
| destination_data_address, |
| info, |
| lib_info); |
| } |
| |
| static long |
| load_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, |
| struct lib_info *lib_info) |
| { |
| struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); |
| unsigned char *srambuf; |
| struct lib_dnld_info_t dpulib; |
| struct file *file; |
| struct xf_buffer *buf; |
| Elf32_Phdr *pheader; |
| Elf32_Ehdr *header; |
| loff_t pos = 0; |
| unsigned int align; |
| int filesize = 0; |
| long ret_val = 0; |
| |
| file = filp_open(lib_info->filename, O_RDONLY, 0); |
| if (IS_ERR(file)) |
| return PTR_ERR(file); |
| |
| vfs_llseek(file, 0, SEEK_END); |
| filesize = (int)file->f_pos; |
| |
| srambuf = kmalloc(filesize, GFP_KERNEL); |
| if (!srambuf) |
| return -ENOMEM; |
| |
| vfs_llseek(file, 0, SEEK_SET); |
| ret_val = kernel_read(file, srambuf, filesize, &pos); |
| if (ret_val < 0) |
| return ret_val; |
| filp_close(file, NULL); |
| |
| ret_val = xtlib_split_pi_library_size( |
| (struct xtlib_packaged_library *)(srambuf), |
| (unsigned int *)&dpulib.size_code, |
| (unsigned int *)&dpulib.size_data, |
| lib_info); |
| if (ret_val != XTLIB_NO_ERR) |
| return -EINVAL; |
| |
| lib_info->code_buf_size = dpulib.size_code; |
| lib_info->data_buf_size = dpulib.size_data; |
| |
| header = (Elf32_Ehdr *)srambuf; |
| pheader = (Elf32_Phdr *)((char *)srambuf + |
| xtlib_host_word(header->e_phoff, |
| lib_info->xtlib_globals.byteswap)); |
| |
| align = find_align(header, lib_info); |
| ret_val = xf_pool_alloc(client, proxy, 1, dpulib.size_code + align, |
| XF_POOL_AUX, &lib_info->code_section_pool); |
| if (ret_val) { |
| kfree(srambuf); |
| pr_err("Allocation failure for loading code section\n"); |
| return -ENOMEM; |
| } |
| |
| ret_val = xf_pool_alloc(client, proxy, 1, |
| dpulib.size_data + pheader[1].p_paddr + align, |
| XF_POOL_AUX, &lib_info->data_section_pool); |
| if (ret_val) { |
| kfree(srambuf); |
| pr_err("Allocation failure for loading data section\n"); |
| return -ENOMEM; |
| } |
| |
| buf = xf_buffer_get(lib_info->code_section_pool); |
| lib_info->code_buf_virt = xf_buffer_data(buf); |
| lib_info->code_buf_phys = ((u64)xf_buffer_data(buf) - |
| (u64)dsp_priv->scratch_buf_virt) + |
| dsp_priv->scratch_buf_phys; |
| lib_info->code_buf_size = dpulib.size_code + align; |
| xf_buffer_put(buf); |
| |
| buf = xf_buffer_get(lib_info->data_section_pool); |
| lib_info->data_buf_virt = xf_buffer_data(buf); |
| lib_info->data_buf_phys = ((u64)xf_buffer_data(buf) - |
| (u64)dsp_priv->scratch_buf_virt) + |
| dsp_priv->scratch_buf_phys; |
| lib_info->data_buf_size = dpulib.size_data + align + pheader[1].p_paddr; |
| xf_buffer_put(buf); |
| |
| dpulib.pbuf_code = (unsigned long)lib_info->code_buf_phys; |
| dpulib.pbuf_data = (unsigned long)lib_info->data_buf_phys; |
| |
| dpulib.ppil_inf = &lib_info->pil_info; |
| xtlib_host_load_split_pi_library((struct xtlib_packaged_library *)srambuf, |
| (xt_ptr)(dpulib.pbuf_code), |
| (xt_ptr)(dpulib.pbuf_data), |
| (struct xtlib_pil_info *)dpulib.ppil_inf, |
| (void *)lib_info); |
| kfree(srambuf); |
| |
| return ret_val; |
| } |
| |
| static long |
| unload_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, |
| struct lib_info *lib_info) |
| { |
| xf_pool_free(client, lib_info->code_section_pool); |
| xf_pool_free(client, lib_info->data_section_pool); |
| |
| return 0; |
| } |
| |
| long xf_load_lib(struct xf_client *client, |
| struct xf_handle *handle, struct lib_info *lib_info) |
| { |
| void *b = xf_handle_aux(handle); |
| struct icm_xtlib_pil_info icm_info; |
| struct xf_proxy *proxy = handle->proxy; |
| struct xf_message msg; |
| struct xf_message *rmsg; |
| long ret_val; |
| |
| ret_val = load_dpu_with_library(client, proxy, lib_info); |
| if (ret_val) |
| return ret_val; |
| |
| memcpy((void *)(&icm_info.pil_info), (void *)(&lib_info->pil_info), |
| sizeof(struct xtlib_pil_info)); |
| |
| icm_info.lib_type = lib_info->lib_type; |
| |
| /* ...set message parameters */ |
| msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), __XF_PORT_SPEC2(handle->id, 0)); |
| msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); |
| msg.opcode = XF_LOAD_LIB; |
| msg.buffer = b; |
| msg.length = sizeof(struct icm_xtlib_pil_info); |
| msg.ret = 0; |
| |
| /* ...copy lib info */ |
| memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); |
| |
| /* ...execute command synchronously */ |
| rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, |
| msg.buffer, msg.length, &client->work, |
| &client->compr_complete); |
| if (IS_ERR(rmsg)) |
| return PTR_ERR(rmsg); |
| |
| // xf_msg_free(proxy, rmsg); |
| // xf_unlock(&proxy->lock); |
| |
| return 0; |
| } |
| |
| long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info) |
| { |
| void *b = xf_handle_aux(handle); |
| struct xf_proxy *proxy = handle->proxy; |
| struct xf_message msg; |
| struct xf_message *rmsg; |
| struct icm_xtlib_pil_info icm_info; |
| |
| memset((void *)&icm_info, 0, sizeof(struct icm_xtlib_pil_info)); |
| icm_info.lib_type = lib_info->lib_type; |
| |
| /* ...set message parameters */ |
| msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),__XF_PORT_SPEC2(handle->id, 0)); |
| msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); |
| msg.opcode = XF_UNLOAD_LIB; |
| msg.buffer = b; |
| msg.length = sizeof(struct icm_xtlib_pil_info); |
| msg.ret = 0; |
| |
| /* ...copy lib info */ |
| memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); |
| |
| /* ...execute command synchronously */ |
| rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, |
| msg.buffer, msg.length, &client->work, |
| &client->compr_complete); |
| if (IS_ERR(rmsg)) |
| return PTR_ERR(rmsg); |
| |
| // xf_msg_free(proxy, rmsg); |
| // xf_unlock(&proxy->lock); |
| |
| return unload_dpu_with_library(client, proxy, lib_info); |
| } |