| /* |
| * Copyright (C) 2014 Imagination Technologies |
| * Author: Paul Burton <paul.burton@imgtec.com> |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| */ |
| |
| #include <linux/elf.h> |
| #include <linux/sched.h> |
| |
| enum { |
| FP_ERROR = -1, |
| FP_DOUBLE_64A = -2, |
| }; |
| |
| int arch_elf_pt_proc(void *_ehdr, void *_phdr, struct file *elf, |
| bool is_interp, struct arch_elf_state *state) |
| { |
| struct elf32_hdr *ehdr = _ehdr; |
| struct elf32_phdr *phdr = _phdr; |
| struct mips_elf_abiflags_v0 abiflags; |
| int ret; |
| |
| if (config_enabled(CONFIG_64BIT) && |
| (ehdr->e_ident[EI_CLASS] != ELFCLASS32)) |
| return 0; |
| if (phdr->p_type != PT_MIPS_ABIFLAGS) |
| return 0; |
| if (phdr->p_filesz < sizeof(abiflags)) |
| return -EINVAL; |
| |
| ret = kernel_read(elf, phdr->p_offset, (char *)&abiflags, |
| sizeof(abiflags)); |
| if (ret < 0) |
| return ret; |
| if (ret != sizeof(abiflags)) |
| return -EIO; |
| |
| /* Record the required FP ABIs for use by mips_check_elf */ |
| if (is_interp) |
| state->interp_fp_abi = abiflags.fp_abi; |
| else |
| state->fp_abi = abiflags.fp_abi; |
| |
| return 0; |
| } |
| |
| static inline unsigned get_fp_abi(struct elf32_hdr *ehdr, int in_abi) |
| { |
| /* If the ABI requirement is provided, simply return that */ |
| if (in_abi != -1) |
| return in_abi; |
| |
| /* If the EF_MIPS_FP64 flag was set, return MIPS_ABI_FP_64 */ |
| if (ehdr->e_flags & EF_MIPS_FP64) |
| return MIPS_ABI_FP_64; |
| |
| /* Default to MIPS_ABI_FP_DOUBLE */ |
| return MIPS_ABI_FP_DOUBLE; |
| } |
| |
| int arch_check_elf(void *_ehdr, bool has_interpreter, |
| struct arch_elf_state *state) |
| { |
| struct elf32_hdr *ehdr = _ehdr; |
| unsigned fp_abi, interp_fp_abi, abi0, abi1; |
| |
| /* Ignore non-O32 binaries */ |
| if (config_enabled(CONFIG_64BIT) && |
| (ehdr->e_ident[EI_CLASS] != ELFCLASS32)) |
| return 0; |
| |
| fp_abi = get_fp_abi(ehdr, state->fp_abi); |
| |
| if (has_interpreter) { |
| interp_fp_abi = get_fp_abi(ehdr, state->interp_fp_abi); |
| |
| abi0 = min(fp_abi, interp_fp_abi); |
| abi1 = max(fp_abi, interp_fp_abi); |
| } else { |
| abi0 = abi1 = fp_abi; |
| } |
| |
| state->overall_abi = FP_ERROR; |
| |
| if (abi0 == abi1) { |
| state->overall_abi = abi0; |
| } else if (abi0 == MIPS_ABI_FP_ANY) { |
| state->overall_abi = abi1; |
| } else if (abi0 == MIPS_ABI_FP_DOUBLE) { |
| switch (abi1) { |
| case MIPS_ABI_FP_XX: |
| state->overall_abi = MIPS_ABI_FP_DOUBLE; |
| break; |
| |
| case MIPS_ABI_FP_64A: |
| state->overall_abi = FP_DOUBLE_64A; |
| break; |
| } |
| } else if (abi0 == MIPS_ABI_FP_SINGLE || |
| abi0 == MIPS_ABI_FP_SOFT) { |
| /* Cannot link with other ABIs */ |
| } else if (abi0 == MIPS_ABI_FP_OLD_64) { |
| switch (abi1) { |
| case MIPS_ABI_FP_XX: |
| case MIPS_ABI_FP_64: |
| case MIPS_ABI_FP_64A: |
| state->overall_abi = MIPS_ABI_FP_64; |
| break; |
| } |
| } else if (abi0 == MIPS_ABI_FP_XX || |
| abi0 == MIPS_ABI_FP_64 || |
| abi0 == MIPS_ABI_FP_64A) { |
| state->overall_abi = MIPS_ABI_FP_64; |
| } |
| |
| switch (state->overall_abi) { |
| case MIPS_ABI_FP_64: |
| case MIPS_ABI_FP_64A: |
| case FP_DOUBLE_64A: |
| if (!config_enabled(CONFIG_MIPS_O32_FP64_SUPPORT)) |
| return -ELIBBAD; |
| break; |
| |
| case FP_ERROR: |
| return -ELIBBAD; |
| } |
| |
| return 0; |
| } |
| |
| void mips_set_personality_fp(struct arch_elf_state *state) |
| { |
| if (config_enabled(CONFIG_FP32XX_HYBRID_FPRS)) { |
| /* |
| * Use hybrid FPRs for all code which can correctly execute |
| * with that mode. |
| */ |
| switch (state->overall_abi) { |
| case MIPS_ABI_FP_DOUBLE: |
| case MIPS_ABI_FP_SINGLE: |
| case MIPS_ABI_FP_SOFT: |
| case MIPS_ABI_FP_XX: |
| case MIPS_ABI_FP_ANY: |
| /* FR=1, FRE=1 */ |
| clear_thread_flag(TIF_32BIT_FPREGS); |
| set_thread_flag(TIF_HYBRID_FPREGS); |
| return; |
| } |
| } |
| |
| switch (state->overall_abi) { |
| case MIPS_ABI_FP_DOUBLE: |
| case MIPS_ABI_FP_SINGLE: |
| case MIPS_ABI_FP_SOFT: |
| /* FR=0 */ |
| set_thread_flag(TIF_32BIT_FPREGS); |
| clear_thread_flag(TIF_HYBRID_FPREGS); |
| break; |
| |
| case FP_DOUBLE_64A: |
| /* FR=1, FRE=1 */ |
| clear_thread_flag(TIF_32BIT_FPREGS); |
| set_thread_flag(TIF_HYBRID_FPREGS); |
| break; |
| |
| case MIPS_ABI_FP_64: |
| case MIPS_ABI_FP_64A: |
| /* FR=1, FRE=0 */ |
| clear_thread_flag(TIF_32BIT_FPREGS); |
| clear_thread_flag(TIF_HYBRID_FPREGS); |
| break; |
| |
| case MIPS_ABI_FP_XX: |
| case MIPS_ABI_FP_ANY: |
| if (!config_enabled(CONFIG_MIPS_O32_FP64_SUPPORT)) |
| set_thread_flag(TIF_32BIT_FPREGS); |
| else |
| clear_thread_flag(TIF_32BIT_FPREGS); |
| |
| clear_thread_flag(TIF_HYBRID_FPREGS); |
| break; |
| |
| default: |
| case FP_ERROR: |
| BUG(); |
| } |
| } |