| // SPDX-License-Identifier: GPL-2.0 |
| |
| #include <linux/sched.h> |
| #include <linux/elf.h> |
| #include <linux/regset.h> |
| #include <asm/user32.h> |
| #include <asm/sigcontext.h> |
| |
| #ifdef CONFIG_X86_32 |
| /* |
| * FPU tag word conversions. |
| */ |
| |
| static inline unsigned short twd_i387_to_fxsr(unsigned short twd) |
| { |
| unsigned int tmp; /* to avoid 16 bit prefixes in the code */ |
| |
| /* Transform each pair of bits into 01 (valid) or 00 (empty) */ |
| tmp = ~twd; |
| tmp = (tmp | (tmp>>1)) & 0x5555; /* 0V0V0V0V0V0V0V0V */ |
| /* and move the valid bits to the lower byte. */ |
| tmp = (tmp | (tmp >> 1)) & 0x3333; /* 00VV00VV00VV00VV */ |
| tmp = (tmp | (tmp >> 2)) & 0x0f0f; /* 0000VVVV0000VVVV */ |
| tmp = (tmp | (tmp >> 4)) & 0x00ff; /* 00000000VVVVVVVV */ |
| return tmp; |
| } |
| |
| static inline unsigned long twd_fxsr_to_i387(struct user_fxsr_struct *fxsave) |
| { |
| struct _fpxreg *st = NULL; |
| unsigned long twd = (unsigned long) fxsave->twd; |
| unsigned long tag; |
| unsigned long ret = 0xffff0000; |
| int i; |
| |
| #define FPREG_ADDR(f, n) ((char *)&(f)->st_space + (n) * 16) |
| |
| for (i = 0; i < 8; i++) { |
| if (twd & 0x1) { |
| st = (struct _fpxreg *) FPREG_ADDR(fxsave, i); |
| |
| switch (st->exponent & 0x7fff) { |
| case 0x7fff: |
| tag = 2; /* Special */ |
| break; |
| case 0x0000: |
| if (!st->significand[0] && |
| !st->significand[1] && |
| !st->significand[2] && |
| !st->significand[3]) { |
| tag = 1; /* Zero */ |
| } else { |
| tag = 2; /* Special */ |
| } |
| break; |
| default: |
| if (st->significand[3] & 0x8000) |
| tag = 0; /* Valid */ |
| else |
| tag = 2; /* Special */ |
| break; |
| } |
| } else { |
| tag = 3; /* Empty */ |
| } |
| ret |= (tag << (2 * i)); |
| twd = twd >> 1; |
| } |
| return ret; |
| } |
| |
| /* Get/set the old 32bit i387 registers (pre-FPX) */ |
| static int fpregs_legacy_get(struct task_struct *target, |
| const struct user_regset *regset, |
| struct membuf to) |
| { |
| struct user_fxsr_struct *fxsave = (void *)target->thread.regs.regs.fp; |
| int i; |
| |
| membuf_store(&to, (unsigned long)fxsave->cwd | 0xffff0000ul); |
| membuf_store(&to, (unsigned long)fxsave->swd | 0xffff0000ul); |
| membuf_store(&to, twd_fxsr_to_i387(fxsave)); |
| membuf_store(&to, fxsave->fip); |
| membuf_store(&to, fxsave->fcs | ((unsigned long)fxsave->fop << 16)); |
| membuf_store(&to, fxsave->foo); |
| membuf_store(&to, fxsave->fos); |
| |
| for (i = 0; i < 8; i++) |
| membuf_write(&to, (void *)fxsave->st_space + i * 16, 10); |
| |
| return 0; |
| } |
| |
| static int fpregs_legacy_set(struct task_struct *target, |
| const struct user_regset *regset, |
| unsigned int pos, unsigned int count, |
| const void *kbuf, const void __user *ubuf) |
| { |
| struct user_fxsr_struct *fxsave = (void *)target->thread.regs.regs.fp; |
| const struct user_i387_struct *from; |
| struct user_i387_struct buf; |
| int i; |
| |
| if (ubuf) { |
| if (copy_from_user(&buf, ubuf, sizeof(buf))) |
| return -EFAULT; |
| from = &buf; |
| } else { |
| from = kbuf; |
| } |
| |
| fxsave->cwd = (unsigned short)(from->cwd & 0xffff); |
| fxsave->swd = (unsigned short)(from->swd & 0xffff); |
| fxsave->twd = twd_i387_to_fxsr((unsigned short)(from->twd & 0xffff)); |
| fxsave->fip = from->fip; |
| fxsave->fop = (unsigned short)((from->fcs & 0xffff0000ul) >> 16); |
| fxsave->fcs = (from->fcs & 0xffff); |
| fxsave->foo = from->foo; |
| fxsave->fos = from->fos; |
| |
| for (i = 0; i < 8; i++) { |
| memcpy((void *)fxsave->st_space + i * 16, |
| (void *)from->st_space + i * 10, 10); |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int genregs_get(struct task_struct *target, |
| const struct user_regset *regset, |
| struct membuf to) |
| { |
| int reg; |
| |
| for (reg = 0; to.left; reg++) |
| membuf_store(&to, getreg(target, reg * sizeof(unsigned long))); |
| return 0; |
| } |
| |
| static int genregs_set(struct task_struct *target, |
| const struct user_regset *regset, |
| unsigned int pos, unsigned int count, |
| const void *kbuf, const void __user *ubuf) |
| { |
| int ret = 0; |
| |
| if (kbuf) { |
| const unsigned long *k = kbuf; |
| |
| while (count >= sizeof(*k) && !ret) { |
| ret = putreg(target, pos, *k++); |
| count -= sizeof(*k); |
| pos += sizeof(*k); |
| } |
| } else { |
| const unsigned long __user *u = ubuf; |
| |
| while (count >= sizeof(*u) && !ret) { |
| unsigned long word; |
| |
| ret = __get_user(word, u++); |
| if (ret) |
| break; |
| ret = putreg(target, pos, word); |
| count -= sizeof(*u); |
| pos += sizeof(*u); |
| } |
| } |
| return ret; |
| } |
| |
| static int generic_fpregs_active(struct task_struct *target, const struct user_regset *regset) |
| { |
| return regset->n; |
| } |
| |
| static int generic_fpregs_get(struct task_struct *target, |
| const struct user_regset *regset, |
| struct membuf to) |
| { |
| void *fpregs = task_pt_regs(target)->regs.fp; |
| |
| membuf_write(&to, fpregs, regset->size * regset->n); |
| return 0; |
| } |
| |
| static int generic_fpregs_set(struct task_struct *target, |
| const struct user_regset *regset, |
| unsigned int pos, unsigned int count, |
| const void *kbuf, const void __user *ubuf) |
| { |
| void *fpregs = task_pt_regs(target)->regs.fp; |
| |
| return user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| fpregs, 0, regset->size * regset->n); |
| } |
| |
| static struct user_regset uml_regsets[] __ro_after_init = { |
| [REGSET_GENERAL] = { |
| .core_note_type = NT_PRSTATUS, |
| .n = sizeof(struct user_regs_struct) / sizeof(long), |
| .size = sizeof(long), |
| .align = sizeof(long), |
| .regset_get = genregs_get, |
| .set = genregs_set |
| }, |
| #ifdef CONFIG_X86_32 |
| /* Old FP registers, they are needed in signal frames */ |
| [REGSET_FP_LEGACY] = { |
| .core_note_type = NT_PRFPREG, |
| .n = sizeof(struct user_i387_ia32_struct) / sizeof(long), |
| .size = sizeof(long), |
| .align = sizeof(long), |
| .active = generic_fpregs_active, |
| .regset_get = fpregs_legacy_get, |
| .set = fpregs_legacy_set, |
| }, |
| #endif |
| [REGSET_FP] = { |
| #ifdef CONFIG_X86_32 |
| .core_note_type = NT_PRXFPREG, |
| .n = sizeof(struct user32_fxsr_struct) / sizeof(long), |
| #else |
| .core_note_type = NT_PRFPREG, |
| .n = sizeof(struct user_i387_struct) / sizeof(long), |
| #endif |
| .size = sizeof(long), |
| .align = sizeof(long), |
| .active = generic_fpregs_active, |
| .regset_get = generic_fpregs_get, |
| .set = generic_fpregs_set, |
| }, |
| [REGSET_XSTATE] = { |
| .core_note_type = NT_X86_XSTATE, |
| .size = sizeof(long), |
| .align = sizeof(long), |
| .active = generic_fpregs_active, |
| .regset_get = generic_fpregs_get, |
| .set = generic_fpregs_set, |
| }, |
| /* TODO: Add TLS regset for 32bit */ |
| }; |
| |
| static const struct user_regset_view user_uml_view = { |
| #ifdef CONFIG_X86_32 |
| .name = "i386", .e_machine = EM_386, |
| #else |
| .name = "x86_64", .e_machine = EM_X86_64, |
| #endif |
| .regsets = uml_regsets, .n = ARRAY_SIZE(uml_regsets) |
| }; |
| |
| const struct user_regset_view * |
| task_user_regset_view(struct task_struct *tsk) |
| { |
| return &user_uml_view; |
| } |
| |
| static int __init init_regset_xstate_info(void) |
| { |
| uml_regsets[REGSET_XSTATE].n = |
| host_fp_size / uml_regsets[REGSET_XSTATE].size; |
| |
| return 0; |
| } |
| arch_initcall(init_regset_xstate_info); |