#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sys_machdep.c,v 1.58 2022/08/20 23:49:31 riastradh Exp $");
#include "opt_mtrr.h"
#include "opt_user_ldt.h"
#include "opt_compat_netbsd.h"
#include "opt_xen.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/buf.h>
#include <sys/signal.h>
#include <sys/malloc.h>
#include <sys/kmem.h>
#include <sys/kauth.h>
#include <sys/cpu.h>
#include <sys/mount.h>
#include <sys/syscallargs.h>
#include <uvm/uvm_extern.h>
#include <machine/cpufunc.h>
#include <machine/gdt.h>
#include <machine/psl.h>
#include <machine/reg.h>
#include <machine/sysarch.h>
#include <machine/mtrr.h>
#include <machine/pmap_private.h>
#if defined(__x86_64__) || defined(XENPV)
#undef IOPERM
#else
#define IOPERM
#endif
#if defined(XENPV) && defined(USER_LDT)
#error "USER_LDT not supported on XENPV"
#endif
extern struct vm_map *kernel_map;
static int x86_get_ioperm(struct lwp *, void *, register_t *);
static int x86_set_ioperm(struct lwp *, void *, register_t *);
static int x86_set_sdbase32(void *, char, lwp_t *, bool);
int x86_set_sdbase(void *, char, lwp_t *, bool);
static int x86_get_sdbase32(void *, char);
int x86_get_sdbase(void *, char);
#ifdef i386
static int
x86_get_ldt(struct lwp *l, void *args, register_t *retval)
{
#ifndef USER_LDT
return EINVAL;
#else
struct x86_get_ldt_args ua;
union descriptor *cp;
int error;
if ((error = copyin(args, &ua, sizeof(ua))) != 0)
return error;
if (ua.num < 0 || ua.num > MAX_USERLDT_SLOTS)
return EINVAL;
cp = malloc(ua.num * sizeof(union descriptor), M_TEMP, M_WAITOK);
if (cp == NULL)
return ENOMEM;
error = x86_get_ldt1(l, &ua, cp);
*retval = ua.num;
if (error == 0)
error = copyout(cp, ua.desc, ua.num * sizeof(*cp));
free(cp, M_TEMP);
return error;
#endif
}
#endif
int
x86_get_ldt1(struct lwp *l, struct x86_get_ldt_args *ua, union descriptor *cp)
{
#ifndef USER_LDT
return EINVAL;
#else
int error;
struct proc *p = l->l_proc;
pmap_t pmap = p->p_vmspace->vm_map.pmap;
int nldt, num;
union descriptor *lp;
#ifdef __x86_64__
const size_t min_ldt_size = LDT_SIZE;
#else
const size_t min_ldt_size = NLDT * sizeof(union descriptor);
#endif
error = kauth_authorize_machdep(l->l_cred, KAUTH_MACHDEP_LDT_GET,
NULL, NULL, NULL, NULL);
if (error)
return error;
if (ua->start < 0 || ua->num < 0 ||
ua->start > MAX_USERLDT_SLOTS || ua->num > MAX_USERLDT_SLOTS ||
ua->start + ua->num > MAX_USERLDT_SLOTS)
return EINVAL;
if (ua->start * sizeof(union descriptor) < min_ldt_size)
return EINVAL;
mutex_enter(&cpu_lock);
if (pmap->pm_ldt != NULL) {
nldt = MAX_USERLDT_SIZE / sizeof(*lp);
lp = pmap->pm_ldt;
} else {
#ifdef __x86_64__
nldt = LDT_SIZE / sizeof(*lp);
#else
nldt = NLDT;
#endif
lp = (union descriptor *)ldtstore;
}
if (ua->start > nldt) {
mutex_exit(&cpu_lock);
return EINVAL;
}
lp += ua->start;
num = uimin(ua->num, nldt - ua->start);
ua->num = num;
memcpy(cp, lp, num * sizeof(union descriptor));
mutex_exit(&cpu_lock);
return 0;
#endif
}
#ifdef i386
static int
x86_set_ldt(struct lwp *l, void *args, register_t *retval)
{
#ifndef USER_LDT
return EINVAL;
#else
struct x86_set_ldt_args ua;
union descriptor *descv;
int error;
if ((error = copyin(args, &ua, sizeof(ua))) != 0)
return error;
if (ua.num < 0 || ua.num > MAX_USERLDT_SLOTS)
return EINVAL;
descv = malloc(sizeof (*descv) * ua.num, M_TEMP, M_WAITOK);
error = copyin(ua.desc, descv, sizeof (*descv) * ua.num);
if (error == 0)
error = x86_set_ldt1(l, &ua, descv);
*retval = ua.start;
free(descv, M_TEMP);
return error;
#endif
}
#endif
int
x86_set_ldt1(struct lwp *l, struct x86_set_ldt_args *ua,
union descriptor *descv)
{
#ifndef USER_LDT
return EINVAL;
#else
int error, i, n, old_sel, new_sel;
struct proc *p = l->l_proc;
pmap_t pmap = p->p_vmspace->vm_map.pmap;
union descriptor *old_ldt, *new_ldt;
#ifdef __x86_64__
const size_t min_ldt_size = LDT_SIZE;
#else
const size_t min_ldt_size = NLDT * sizeof(union descriptor);
#endif
error = kauth_authorize_machdep(l->l_cred, KAUTH_MACHDEP_LDT_SET,
NULL, NULL, NULL, NULL);
if (error)
return error;
if (ua->start < 0 || ua->num < 0 ||
ua->start > MAX_USERLDT_SLOTS || ua->num > MAX_USERLDT_SLOTS ||
ua->start + ua->num > MAX_USERLDT_SLOTS)
return EINVAL;
if (ua->start * sizeof(union descriptor) < min_ldt_size)
return EINVAL;
for (i = 0; i < ua->num; i++) {
union descriptor *desc = &descv[i];
#ifdef __x86_64__
if (desc->sd.sd_long != 0)
return EACCES;
#endif
switch (desc->sd.sd_type) {
case SDT_SYSNULL:
desc->sd.sd_p = 0;
break;
case SDT_MEMEC:
case SDT_MEMEAC:
case SDT_MEMERC:
case SDT_MEMERAC:
if (desc->sd.sd_p == 0)
return EACCES;
break;
case SDT_MEMRO:
case SDT_MEMROA:
case SDT_MEMRW:
case SDT_MEMRWA:
case SDT_MEMROD:
case SDT_MEMRODA:
case SDT_MEMRWD:
case SDT_MEMRWDA:
case SDT_MEME:
case SDT_MEMEA:
case SDT_MEMER:
case SDT_MEMERA:
break;
default:
return EACCES;
}
if (desc->sd.sd_p != 0) {
if (desc->sd.sd_dpl != SEL_UPL)
return EACCES;
}
}
new_ldt = (union descriptor *)uvm_km_alloc(kernel_map,
MAX_USERLDT_SIZE, 0, UVM_KMF_WIRED | UVM_KMF_ZERO | UVM_KMF_WAITVA);
mutex_enter(&cpu_lock);
if (pmap->pm_ldt != NULL) {
old_ldt = pmap->pm_ldt;
old_sel = pmap->pm_ldt_sel;
memcpy(new_ldt, old_ldt, MAX_USERLDT_SIZE);
} else {
old_ldt = NULL;
old_sel = -1;
memcpy(new_ldt, ldtstore, min_ldt_size);
}
for (i = 0, n = ua->start; i < ua->num; i++, n++) {
new_ldt[n] = descv[i];
}
new_sel = ldt_alloc(new_ldt, MAX_USERLDT_SIZE);
if (new_sel == -1) {
mutex_exit(&cpu_lock);
uvm_km_free(kernel_map, (vaddr_t)new_ldt, MAX_USERLDT_SIZE,
UVM_KMF_WIRED);
return ENOMEM;
}
atomic_store_relaxed(&pmap->pm_ldt_sel, new_sel);
membar_producer();
atomic_store_relaxed(&pmap->pm_ldt, new_ldt);
pmap_ldt_sync(pmap);
if (old_ldt != NULL) {
ldt_free(old_sel);
mutex_exit(&cpu_lock);
uvm_km_free(kernel_map, (vaddr_t)old_ldt, MAX_USERLDT_SIZE,
UVM_KMF_WIRED);
} else {
mutex_exit(&cpu_lock);
}
return error;
#endif
}
int
x86_iopl(struct lwp *l, void *args, register_t *retval)
{
int error;
struct x86_iopl_args ua;
#ifdef XENPV
int iopl;
#else
struct trapframe *tf = l->l_md.md_regs;
#endif
error = kauth_authorize_machdep(l->l_cred, KAUTH_MACHDEP_IOPL,
NULL, NULL, NULL, NULL);
if (error)
return error;
if ((error = copyin(args, &ua, sizeof(ua))) != 0)
return error;
#ifdef XENPV
if (ua.iopl)
iopl = SEL_UPL;
else
iopl = SEL_KPL;
{
struct pcb *pcb;
pcb = lwp_getpcb(l);
pcb->pcb_iopl = iopl;
struct physdev_set_iopl set_iopl;
set_iopl.iopl = iopl;
HYPERVISOR_physdev_op(PHYSDEVOP_set_iopl, &set_iopl);
}
#elif defined(__x86_64__)
if (ua.iopl)
tf->tf_rflags |= PSL_IOPL;
else
tf->tf_rflags &= ~PSL_IOPL;
#else
if (ua.iopl)
tf->tf_eflags |= PSL_IOPL;
else
tf->tf_eflags &= ~PSL_IOPL;
#endif
return 0;
}
static int
x86_get_ioperm(struct lwp *l, void *args, register_t *retval)
{
#ifdef IOPERM
int error;
struct pcb *pcb = lwp_getpcb(l);
struct x86_get_ioperm_args ua;
void *dummymap = NULL;
void *iomap;
error = kauth_authorize_machdep(l->l_cred, KAUTH_MACHDEP_IOPERM_GET,
NULL, NULL, NULL, NULL);
if (error)
return error;
if ((error = copyin(args, &ua, sizeof(ua))) != 0)
return error;
iomap = pcb->pcb_iomap;
if (iomap == NULL) {
iomap = dummymap = kmem_alloc(IOMAPSIZE, KM_SLEEP);
memset(dummymap, 0xff, IOMAPSIZE);
}
error = copyout(iomap, ua.iomap, IOMAPSIZE);
if (dummymap != NULL) {
kmem_free(dummymap, IOMAPSIZE);
}
return error;
#else
return EINVAL;
#endif
}
static int
x86_set_ioperm(struct lwp *l, void *args, register_t *retval)
{
#ifdef IOPERM
struct cpu_info *ci;
int error;
struct pcb *pcb = lwp_getpcb(l);
struct x86_set_ioperm_args ua;
void *new;
void *old;
error = kauth_authorize_machdep(l->l_cred, KAUTH_MACHDEP_IOPERM_SET,
NULL, NULL, NULL, NULL);
if (error)
return error;
if ((error = copyin(args, &ua, sizeof(ua))) != 0)
return error;
new = kmem_alloc(IOMAPSIZE, KM_SLEEP);
error = copyin(ua.iomap, new, IOMAPSIZE);
if (error) {
kmem_free(new, IOMAPSIZE);
return error;
}
old = pcb->pcb_iomap;
pcb->pcb_iomap = new;
if (old != NULL) {
kmem_free(old, IOMAPSIZE);
}
CTASSERT(offsetof(struct cpu_tss, iomap) -
offsetof(struct cpu_tss, tss) == IOMAP_VALIDOFF);
kpreempt_disable();
ci = curcpu();
memcpy(ci->ci_tss->iomap, pcb->pcb_iomap, IOMAPSIZE);
ci->ci_tss->tss.tss_iobase = IOMAP_VALIDOFF << 16;
kpreempt_enable();
return error;
#else
return EINVAL;
#endif
}
static int
x86_get_mtrr(struct lwp *l, void *args, register_t *retval)
{
#ifdef MTRR
struct x86_get_mtrr_args ua;
int error, n;
if (mtrr_funcs == NULL)
return ENOSYS;
error = kauth_authorize_machdep(l->l_cred, KAUTH_MACHDEP_MTRR_GET,
NULL, NULL, NULL, NULL);
if (error)
return error;
error = copyin(args, &ua, sizeof ua);
if (error != 0)
return error;
error = copyin(ua.n, &n, sizeof n);
if (error != 0)
return error;
KERNEL_LOCK(1, NULL);
error = mtrr_get(ua.mtrrp, &n, l->l_proc, MTRR_GETSET_USER);
KERNEL_UNLOCK_ONE(NULL);
copyout(&n, ua.n, sizeof (int));
return error;
#else
return EINVAL;
#endif
}
static int
x86_set_mtrr(struct lwp *l, void *args, register_t *retval)
{
#ifdef MTRR
int error, n;
struct x86_set_mtrr_args ua;
if (mtrr_funcs == NULL)
return ENOSYS;
error = kauth_authorize_machdep(l->l_cred, KAUTH_MACHDEP_MTRR_SET,
NULL, NULL, NULL, NULL);
if (error)
return error;
error = copyin(args, &ua, sizeof ua);
if (error != 0)
return error;
error = copyin(ua.n, &n, sizeof n);
if (error != 0)
return error;
KERNEL_LOCK(1, NULL);
error = mtrr_set(ua.mtrrp, &n, l->l_proc, MTRR_GETSET_USER);
if (n != 0)
mtrr_commit();
KERNEL_UNLOCK_ONE(NULL);
copyout(&n, ua.n, sizeof n);
return error;
#else
return EINVAL;
#endif
}
#ifdef __x86_64__
#define pcb_fsd pcb_fs
#define pcb_gsd pcb_gs
#define segment_descriptor mem_segment_descriptor
#endif
static int
x86_set_sdbase32(void *arg, char which, lwp_t *l, bool direct)
{
struct trapframe *tf = l->l_md.md_regs;
union descriptor usd;
struct pcb *pcb;
uint32_t base;
int error;
if (direct) {
base = (vaddr_t)arg;
} else {
error = copyin(arg, &base, sizeof(base));
if (error != 0)
return error;
}
memset(&usd, 0, sizeof(usd));
usd.sd.sd_lobase = base & 0xffffff;
usd.sd.sd_hibase = (base >> 24) & 0xff;
usd.sd.sd_lolimit = 0xffff;
usd.sd.sd_hilimit = 0xf;
usd.sd.sd_type = SDT_MEMRWA;
usd.sd.sd_dpl = SEL_UPL;
usd.sd.sd_p = 1;
usd.sd.sd_def32 = 1;
usd.sd.sd_gran = 1;
pcb = lwp_getpcb(l);
kpreempt_disable();
if (which == 'f') {
memcpy(&pcb->pcb_fsd, &usd.sd,
sizeof(struct segment_descriptor));
if (l == curlwp) {
update_descriptor(&curcpu()->ci_gdt[GUFS_SEL], &usd);
}
tf->tf_fs = GSEL(GUFS_SEL, SEL_UPL);
} else {
memcpy(&pcb->pcb_gsd, &usd.sd,
sizeof(struct segment_descriptor));
if (l == curlwp) {
update_descriptor(&curcpu()->ci_gdt[GUGS_SEL], &usd);
#if defined(__x86_64__) && defined(XENPV)
setusergs(GSEL(GUGS_SEL, SEL_UPL));
#endif
}
tf->tf_gs = GSEL(GUGS_SEL, SEL_UPL);
}
kpreempt_enable();
return 0;
}
int
x86_set_sdbase(void *arg, char which, lwp_t *l, bool direct)
{
#ifdef i386
return x86_set_sdbase32(arg, which, l, direct);
#else
struct pcb *pcb;
vaddr_t base;
if (l->l_proc->p_flag & PK_32) {
return x86_set_sdbase32(arg, which, l, direct);
}
if (direct) {
base = (vaddr_t)arg;
} else {
int error = copyin(arg, &base, sizeof(base));
if (error != 0)
return error;
}
if (base >= VM_MAXUSER_ADDRESS)
return EINVAL;
pcb = lwp_getpcb(l);
kpreempt_disable();
switch (which) {
case 'f':
pcb->pcb_fs = base;
if (l == curlwp)
wrmsr(MSR_FSBASE, pcb->pcb_fs);
break;
case 'g':
pcb->pcb_gs = base;
if (l == curlwp)
wrmsr(MSR_KERNELGSBASE, pcb->pcb_gs);
break;
default:
panic("x86_set_sdbase");
}
kpreempt_enable();
return 0;
#endif
}
static int
x86_get_sdbase32(void *arg, char which)
{
struct segment_descriptor *sd;
uint32_t base;
switch (which) {
case 'f':
sd = (void *)&curpcb->pcb_fsd;
break;
case 'g':
sd = (void *)&curpcb->pcb_gsd;
break;
default:
panic("x86_get_sdbase32");
}
base = sd->sd_hibase << 24 | sd->sd_lobase;
return copyout(&base, arg, sizeof(base));
}
int
x86_get_sdbase(void *arg, char which)
{
#ifdef i386
return x86_get_sdbase32(arg, which);
#else
vaddr_t base;
struct pcb *pcb;
if (curproc->p_flag & PK_32) {
return x86_get_sdbase32(arg, which);
}
pcb = lwp_getpcb(curlwp);
switch (which) {
case 'f':
base = pcb->pcb_fs;
break;
case 'g':
base = pcb->pcb_gs;
break;
default:
panic("x86_get_sdbase");
}
return copyout(&base, arg, sizeof(base));
#endif
}
int
sys_sysarch(struct lwp *l, const struct sys_sysarch_args *uap,
register_t *retval)
{
int error = 0;
switch (SCARG(uap, op)) {
case X86_IOPL:
error = x86_iopl(l, SCARG(uap, parms), retval);
break;
#ifdef i386
case X86_GET_LDT:
error = x86_get_ldt(l, SCARG(uap, parms), retval);
break;
case X86_SET_LDT:
error = x86_set_ldt(l, SCARG(uap, parms), retval);
break;
#endif
case X86_GET_IOPERM:
error = x86_get_ioperm(l, SCARG(uap, parms), retval);
break;
case X86_SET_IOPERM:
error = x86_set_ioperm(l, SCARG(uap, parms), retval);
break;
case X86_GET_MTRR:
error = x86_get_mtrr(l, SCARG(uap, parms), retval);
break;
case X86_SET_MTRR:
error = x86_set_mtrr(l, SCARG(uap, parms), retval);
break;
case X86_SET_FSBASE:
error = x86_set_sdbase(SCARG(uap, parms), 'f', curlwp, false);
break;
case X86_SET_GSBASE:
error = x86_set_sdbase(SCARG(uap, parms), 'g', curlwp, false);
break;
case X86_GET_FSBASE:
error = x86_get_sdbase(SCARG(uap, parms), 'f');
break;
case X86_GET_GSBASE:
error = x86_get_sdbase(SCARG(uap, parms), 'g');
break;
default:
error = EINVAL;
break;
}
return error;
}
int
cpu_lwp_setprivate(lwp_t *l, void *addr)
{
#ifdef __x86_64__
if ((l->l_proc->p_flag & PK_32) == 0) {
return x86_set_sdbase(addr, 'f', l, true);
}
#endif
return x86_set_sdbase(addr, 'g', l, true);
}