DragonFlyBSD Kernel Audit
sys/platform/pc64/x86_64/npx.c
← back
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
/*
 * Copyright (c) 1990 William Jolitz.
 * Copyright (c) 1991 The Regents of the University of California.
 * Copyright (c) 2006 The DragonFly Project.
 * Copyright (c) 2006 Matthew Dillon.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * from: @(#)npx.c	7.2 (Berkeley) 5/12/91
 * $FreeBSD: src/sys/i386/isa/npx.c,v 1.80.2.3 2001/10/20 19:04:38 tegge Exp $
 */

#include "opt_cpu.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <sys/signalvar.h>

#include <sys/thread2.h>

#include <machine/cpufunc.h>
#include <machine/cputypes.h>
#include <machine/frame.h>
#include <machine/md_var.h>
#include <machine/pcb.h>
#include <machine/psl.h>
#include <machine/specialreg.h>
#include <machine/segments.h>
#include <machine/globaldata.h>

#define	fldcw(addr)		__asm("fldcw %0" : : "m" (*(addr)))
#define	fnclex()		__asm("fnclex")
#define	fninit()		__asm("fninit")
#define	fnop()			__asm("fnop")
#define	fnsave(addr)		__asm __volatile("fnsave %0" : "=m" (*(addr)))
#define	fnstcw(addr)		__asm __volatile("fnstcw %0" : "=m" (*(addr)))
#define	fnstsw(addr)		__asm __volatile("fnstsw %0" : "=m" (*(addr)))
#define	frstor(addr)		__asm("frstor %0" : : "m" (*(addr)))
#define	fxrstor(addr)		__asm("fxrstor64 %0" : : "m" (*(addr)))
#define	fxsave(addr)		__asm __volatile("fxsave64 %0" : "=m" (*(addr)))
#define	ldmxcsr(csr)		__asm __volatile("ldmxcsr %0" : : "m" (csr))

#define	start_emulating()	load_cr0(rcr0() | CR0_TS)
#define	stop_emulating()	clts()

#ifndef CPU_DISABLE_AVX
static inline void
xrstor(const void *addr, uint64_t mask)
{
	const uint8_t *area = addr;
	uint32_t low, high;

	low = mask;
	high = mask >> 32;

	__asm __volatile("xrstor64 %[area]"
			 :
			 : [area] "m" (*area), "a" (low), "d" (high));
}

static inline void
xsave(void *addr, uint64_t mask)
{
	uint8_t *area = addr;
	uint32_t low, high;

	low = mask;
	high = mask >> 32;

	__asm __volatile("xsave64 %[area]"
			 : [area] "=m" (*area)
			 : "a" (low), "d" (high)
			 : "memory");
}
#endif /* !CPU_DISABLE_AVX */

static	void	fpu_clean_state(void);

static struct krate badfprate = { 1 };

__read_mostly uint32_t npx_mxcsr_mask = 0xFFBF;	/* this is the default */
__read_mostly uint64_t npx_xcr0_mask = 0;

/*
 * Probe the npx_mxcsr_mask as described in the intel document
 * "Intel processor identification and the CPUID instruction" Section 7
 * "Denormals are Zero".
 * Note that for fxsave to work reliably, the os support bit for
 * FXSAVE/FXRESTORE operations in CR4 has to be set as per
 * Intel 64 and IA-32 Architectures Developer's Manual: Vol. 1,
 * 10.5.1.2.
 */
void npxprobemask(void)
{
	static union savefpu dummy __aligned(64);

	crit_enter();
	stop_emulating();
	load_cr4(rcr4() | CR4_OSFXSR);
	fxsave(&dummy);
	npx_mxcsr_mask = ((uint32_t *)&dummy)[7];
	start_emulating();
	crit_exit();
}

/*
 * Initialize the floating point unit.
 */
void
npxinit(void)
{
	static union savefpu dummy __aligned(64);
	u_short control = __INITIAL_FPUCW__;
	u_int mxcsr = __INITIAL_MXCSR__;

	/*
	 * fninit has the same h/w bugs as fnsave.  Use the detoxified
	 * fnsave to throw away any junk in the fpu.  npxsave() initializes
	 * the fpu and sets npxthread = NULL as important side effects.
	 */
	npxsave(&dummy);
	crit_enter();
	stop_emulating();
	fldcw(&control);
	ldmxcsr(mxcsr);
	fpusave(curthread->td_savefpu, npx_xcr0_mask);
	mdcpu->gd_npxthread = NULL;
	start_emulating();
	crit_exit();
}

/*
 * Free coprocessor (if we have it).
 */
void
npxexit(void)
{
	if (curthread == mdcpu->gd_npxthread)
		npxsave(curthread->td_savefpu);
}


/*
 * Implement the device not available (DNA) exception.  gd_npxthread had
 * better be NULL.  Restore the current thread's FP state and set gd_npxthread
 * to curthread.
 *
 * Interrupts are enabled and preemption can occur.  Enter a critical
 * section to stabilize the FP state.
 */
int
npxdna(void)
{
	struct mdglobaldata *md = mdcpu;
	thread_t td;
	int didinit = 0;

	td = md->mi.gd_curthread;

	/*
	 * npxthread is almost always NULL.  When it isn't NULL it can
	 * only be exactly equal to 'td'.  This case occurs when the switch
	 * code pro-actively restores the FPU state due to the trap() code
	 * being interruptable (e.g. such as by an interrupt thread).
	 */
	if (__predict_false(md->gd_npxthread != NULL)) {
		if (md->gd_npxthread == td) {
			return 1;
		}
		kprintf("npxdna: npxthread = %p, curthread = %p\n",
		       md->gd_npxthread, td);
		panic("npxdna");
	}

	/*
	 * Setup the initial saved state if the thread has never before
	 * used the FP unit.  This also occurs when a thread pushes a
	 * signal handler and uses FP in the handler.
	 */
	crit_enter();
	if ((td->td_flags & TDF_USINGFP) == 0) {
		td->td_flags |= TDF_USINGFP;
		npxinit();
		didinit = 1;
	}

	/*
	 * The setting of gd_npxthread and the call to fpurstor() must not
	 * be preempted by an interrupt thread or we will take an npxdna
	 * trap and potentially save our current fpstate (which is garbage)
	 * and then restore the garbage rather then the originally saved
	 * fpstate.
	 */
	stop_emulating();

	/*
	 * Record new context early in case frstor causes an IRQ13.
	 */
	md->gd_npxthread = td;

	/*
	 * The following frstor may cause an IRQ13 when the state being
	 * restored has a pending error.  The error will appear to have been
	 * triggered by the current (npx) user instruction even when that
	 * instruction is a no-wait instruction that should not trigger an
	 * error (e.g., fnclex).  On at least one 486 system all of the
	 * no-wait instructions are broken the same as frstor, so our
	 * treatment does not amplify the breakage.  On at least one
	 * 386/Cyrix 387 system, fnclex works correctly while frstor and
	 * fnsave are broken, so our treatment breaks fnclex if it is the
	 * first FPU instruction after a context switch.
	 */
	if ((td->td_savefpu->sv_xmm.sv_env.en_mxcsr & ~npx_mxcsr_mask) &&
	    cpu_fxsr) {
		krateprintf(&badfprate,
			    "%s: FXRSTOR: illegal FP MXCSR %08x didinit = %d\n",
			    td->td_comm, td->td_savefpu->sv_xmm.sv_env.en_mxcsr,
			    didinit);
		td->td_savefpu->sv_xmm.sv_env.en_mxcsr &= npx_mxcsr_mask;
		lwpsignal(td->td_proc, td->td_lwp, SIGFPE);
	}
	fpurstor(td->td_savefpu, npx_xcr0_mask);
	crit_exit();

	return (1);
}

/*
 * From cpu heavy restore (already in critical section, gd_npxthread is NULL),
 * and TDF_USINGFP is already set.  Actively restore the FPU state to avoid
 * excessive npxdna traps.
 */
void
npxdna_quick(thread_t newtd)
{
	stop_emulating();
	mdcpu->gd_npxthread = newtd;
	if ((newtd->td_savefpu->sv_xmm.sv_env.en_mxcsr & ~npx_mxcsr_mask) &&
	    cpu_fxsr) {
		krateprintf(&badfprate,
			    "%s: FXRSTOR: illegal FP MXCSR %08x\n",
			    newtd->td_comm,
			    newtd->td_savefpu->sv_xmm.sv_env.en_mxcsr);
		newtd->td_savefpu->sv_xmm.sv_env.en_mxcsr &= npx_mxcsr_mask;
		lwpsignal(newtd->td_proc, newtd->td_lwp, SIGFPE);
	}
	fpurstor(newtd->td_savefpu, npx_xcr0_mask);
}

/*
 * Wrapper for the fnsave instruction to handle h/w bugs.  If there is an error
 * pending, then fnsave generates a bogus IRQ13 on some systems.  Force
 * any IRQ13 to be handled immediately, and then ignore it.  This routine is
 * often called at splhigh so it must not use many system services.  In
 * particular, it's much easier to install a special handler than to
 * guarantee that it's safe to use npxintr() and its supporting code.
 *
 * WARNING!  This call is made during a switch and the MP lock will be
 * setup for the new target thread rather then the current thread, so we
 * cannot do anything here that depends on the *_mplock() functions as
 * we may trip over their assertions.
 *
 * WARNING!  When using fxsave we MUST fninit after saving the FP state.  The
 * kernel will always assume that the FP state is 'safe' (will not cause
 * exceptions) for mmx/xmm use if npxthread is NULL.  The kernel must still
 * setup a custom save area before actually using the FP unit, but it will
 * not bother calling fninit.  This greatly improves kernel performance when
 * it wishes to use the FP unit.
 */
void
npxsave(union savefpu *addr)
{
	struct mdglobaldata *md;

	md = mdcpu;
	crit_enter();
	stop_emulating();
	fpusave(addr, npx_xcr0_mask);
	md->gd_npxthread = NULL;
	fninit();
	fpurstor(&md->gd_zerofpu, npx_xcr0_mask);	/* security wipe */
	start_emulating();
	crit_exit();
}

void
fpusave(union savefpu *addr, uint64_t mask)
{
#ifndef CPU_DISABLE_AVX
	if (cpu_xsave)
		xsave(addr, mask);
	else
#endif
	if (cpu_fxsr)
		fxsave(addr);
	else
		fnsave(addr);
}

/*
 * Save the FP state to the mcontext structure.
 *
 * WARNING: If you want to try to npxsave() directly to mctx->mc_fpregs,
 * then it MUST be 16-byte aligned.  Currently this is not guarenteed.
 */
void
npxpush(mcontext_t *mctx)
{
	thread_t td = curthread;

	if (td->td_flags & TDF_USINGFP) {
		if (mdcpu->gd_npxthread == td) {
			/*
			 * XXX Note: This is a bit inefficient if the signal
			 * handler uses floating point, extra faults will
			 * occur.
			 */
			mctx->mc_ownedfp = _MC_FPOWNED_FPU;
			npxsave(td->td_savefpu);
		} else {
			mctx->mc_ownedfp = _MC_FPOWNED_PCB;
		}
		KKASSERT(sizeof(*td->td_savefpu) <= sizeof(mctx->mc_fpregs));
		bcopy(td->td_savefpu, mctx->mc_fpregs, sizeof(*td->td_savefpu));
		td->td_flags &= ~TDF_USINGFP;
#ifndef CPU_DISABLE_AVX
		if (npx_xcr0_mask & CPU_XFEATURE_YMM)
			mctx->mc_fpformat = _MC_FPFMT_YMM;
		else
#endif
		{
			if (cpu_fxsr)
				mctx->mc_fpformat = _MC_FPFMT_XMM;
			else
				mctx->mc_fpformat = _MC_FPFMT_387;
		}
	} else {
		mctx->mc_ownedfp = _MC_FPOWNED_NONE;
		mctx->mc_fpformat = _MC_FPFMT_NODEV;
	}
}

/*
 * Restore the FP state from the mcontext structure.
 */
void
npxpop(mcontext_t *mctx)
{
	thread_t td = curthread;

	switch (mctx->mc_ownedfp) {
	case _MC_FPOWNED_NONE:
		/*
		 * If the signal handler used the FP unit but the interrupted
		 * code did not, release the FP unit.  Clear TDF_USINGFP will
		 * force the FP unit to reinit so the interrupted code sees
		 * a clean slate.
		 */
		if (td->td_flags & TDF_USINGFP) {
			if (td == mdcpu->gd_npxthread)
				npxsave(td->td_savefpu);
			td->td_flags &= ~TDF_USINGFP;
		}
		break;
	case _MC_FPOWNED_FPU:
	case _MC_FPOWNED_PCB:
		/*
		 * Clear ownership of the FP unit and restore our saved state.
		 *
		 * NOTE: The signal handler may have set-up some FP state and
		 * enabled the FP unit, so we have to restore no matter what.
		 *
		 * XXX: This is bit inefficient, if the code being returned
		 * to is actively using the FP this results in multiple
		 * kernel faults.
		 *
		 * WARNING: The saved state was exposed to userland and may
		 * have to be sanitized to avoid a GP fault in the kernel.
		 */
		if (td == mdcpu->gd_npxthread)
			npxsave(td->td_savefpu);
		KKASSERT(sizeof(*td->td_savefpu) <= sizeof(mctx->mc_fpregs));
		bcopy(mctx->mc_fpregs, td->td_savefpu, sizeof(*td->td_savefpu));
		if ((td->td_savefpu->sv_xmm.sv_env.en_mxcsr & ~npx_mxcsr_mask) &&
		    cpu_fxsr) {
			krateprintf(&badfprate,
				    "pid %d (%s) signal return from user: "
				    "illegal FP MXCSR %08x\n",
				    td->td_proc->p_pid,
				    td->td_proc->p_comm,
				    td->td_savefpu->sv_xmm.sv_env.en_mxcsr);
		}
		td->td_flags |= TDF_USINGFP;
		break;
	}
}

/*
 * Allow kernel to use FP unit.  This function is not re-entrant.
 * Saves the current FP state and reinitializes the FP unit.
 *
 * XXX really not well optimized, goes through a lot unecessarily.
 */
void
kernel_fpu_begin(void)
{
	thread_t td = curthread;

	KASSERT((td->td_flags & TDF_KERNELFP) == 0,
		("Recursive call to kernel_fpu_begin()"));
	atomic_set_int(&td->td_flags, TDF_KERNELFP);
	if (td->td_kfpuctx == NULL) {
		td->td_kfpuctx = kmalloc(sizeof(*td->td_kfpuctx), M_FPUCTX,
					 M_INTWAIT | M_ZERO | M_POWEROF2);
	}
	npxpush(td->td_kfpuctx);
	npxdna();
}

/*
 * Indicate that the kernel is no longer using the FP unit.  Restores
 * the previous FP state.
 */
void
kernel_fpu_end(void)
{
	thread_t td = curthread;

	KASSERT((td->td_flags & TDF_KERNELFP) != 0,
		("kernel_fpu_end() without kernel_fpu_begin()"));
	npxpop(td->td_kfpuctx);
	atomic_clear_int(&td->td_flags, TDF_KERNELFP);
}

/*
 * On AuthenticAMD processors, the fxrstor instruction does not restore
 * the x87's stored last instruction pointer, last data pointer, and last
 * opcode values, except in the rare case in which the exception summary
 * (ES) bit in the x87 status word is set to 1.
 *
 * In order to avoid leaking this information across processes, we clean
 * these values by performing a dummy load before executing fxrstor().
 */
static void
fpu_clean_state(void)
{
	u_short status;

	/*
	 * Clear the ES bit in the x87 status word if it is currently
	 * set, in order to avoid causing a fault in the upcoming load.
	 */
	fnstsw(&status);
	if (status & 0x80)
		fnclex();

	/*
	 * Load the dummy variable into the x87 stack.  This mangles
	 * the x87 stack, but we don't care since we're about to call
	 * fxrstor() anyway.
	 */
	__asm __volatile("ffree %st(7); fldz");
}

void
fpurstor(union savefpu *addr, uint64_t mask)
{
#ifndef CPU_DISABLE_AVX
	if (cpu_xsave)
		xrstor(addr, mask);
	else
#endif
	if (cpu_fxsr) {
		fpu_clean_state();
		fxrstor(addr);
	} else {
		frstor(addr);
	}
}