summaryrefslogtreecommitdiff
path: root/setup/fpsupport_i86pc.c
blob: 3979d01ac28cae2f5be3f6239b3f35069d89d18a (plain)
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
#ifndef lint
/*
 * Floating point support routines for x86 and x86_64
 *
 * Copyright (C) 4Front Technologies 2006. Released under GPLv2/CDDL.
 *
 * Must be compiled to .asm with gcc -S -O6.
 */
#define BLOCK_INTERRUPTS

#define FP_SAVE(envbuf)		asm ("fnsave %0":"=m" (*envbuf));
#define FP_RESTORE(envbuf)		asm ("frstor %0":"=m" (*envbuf));

/* SSE/SSE2 compatible macros */
#define FX_SAVE(envbuf)		asm ("fxsave %0":"=m" (*envbuf));
#define FX_RESTORE(envbuf)		asm ("fxrstor %0":"=m" (*envbuf));

#if defined(amd64) || defined(__x86_64__)
#define ARCH64
#endif

static int old_arch = 0;	/* No SSE/SSE2 instructions */

/*
 * Generic CPUID function
 * clear %ecx since some cpus (Cyrix MII) do not set or clear %ecx
 * resulting in stale register contents being returned.
 */
static inline void
cpuid (int op, int *eax, int *ebx, int *ecx, int *edx)
{
__asm__ ("cpuid": "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx):"0" (op), "c"
	   (0));
}

#ifdef ARCH64
#  define local_save_flags(x)     asm volatile("pushfq ; popq %0":"=g" (x):)
#  define local_restore_flags(x)  asm volatile("pushq %0 ; popfq"::"g" (x):"memory", "cc")
#else
#  define local_save_flags(x)     asm volatile("pushfl ; popl %0":"=g" (x):)
#  define local_restore_flags(x)  asm volatile("pushl %0 ; popfl"::"g" (x):"memory", "cc")
#endif

static inline unsigned long
read_cr0 (void)
{
  unsigned long cr0;
#ifdef ARCH64
  asm volatile ("movq %%cr0,%0":"=r" (cr0));
#else
  asm volatile ("movl %%cr0,%0":"=r" (cr0));
#endif
  return cr0;
}

static inline void
write_cr0 (unsigned long val)
{
#ifdef ARCH64
  asm volatile ("movq %0,%%cr0"::"r" (val));
#else
  asm volatile ("movl %0,%%cr0"::"r" (val));
#endif
}

static inline unsigned long
read_cr4 (void)
{
  unsigned long cr4;
#ifdef ARCH64
  asm volatile ("movq %%cr4,%0":"=r" (cr4));
#else
  asm volatile ("movl %%cr4,%0":"=r" (cr4));
#endif
  return cr4;
}

static inline void
write_cr4 (unsigned long val)
{
#ifdef ARCH64
  asm volatile ("movq %0,%%cr4"::"r" (val));
#else
  asm volatile ("movl %0,%%cr4"::"r" (val));
#endif
}

static inline unsigned long long
read_mxcsr (void)
{
  unsigned long long mxcsr;
  asm volatile ("stmxcsr %0":"=m" (mxcsr));
  return mxcsr;
}

static inline void
write_mxcsr (unsigned long long val)
{
  asm volatile ("ldmxcsr %0"::"m" (val));
}

int
oss_fp_check (void)
{
/*
 * oss_fp_check returns 1 if the CPU architecture supports floating point
 * in kernel space. Otherwise 0 will be returned.
 */
  int eax, ebx, ecx, edx;
#define FLAGS_ID (1<<21)

#ifdef ARCH64
  unsigned long long flags_reg;
#else
  unsigned long flags_reg;
#endif

/*
 * First probe if the CPU supports CPUID by checking if the ID bit
 * can be changed.
 */

  local_save_flags (flags_reg);
  flags_reg &= ~FLAGS_ID;	/* Clear */
  local_restore_flags (flags_reg);

  local_save_flags (flags_reg);
  if (flags_reg & FLAGS_ID)
    return -1;

  flags_reg |= FLAGS_ID;	/* Set */
  local_restore_flags (flags_reg);

  local_save_flags (flags_reg);
  if (!(flags_reg & FLAGS_ID))
    return -2;

/*
 * Now we know that the CPU supports CPUID. Ensure that FXSAVE and FXRSTOR
 * are supported.
 */
#define CPUID_FXSR	(1<<24)
#define CPUID_SSE	(1<<25)
#define CPUID_SSE2	(1<<26)

  cpuid (1, &eax, &ebx, &ecx, &edx);

  if (!(edx & CPUID_FXSR))
    return -3;			/* No */

  /*
   * Older machines require different FP handling. Use the SSE
   * instruction set as an indicator.
   */
  if (!(edx & CPUID_SSE))
    old_arch = 1;		/* No */

  return 1;
}

#define local_irq_disable() 	asm volatile("cli": : :"memory")

void
oss_fp_save (short *envbuf, unsigned int flags[])
{
/*
 * oss_fp_save saves the floating point environment (registers) and
 * enables floating point operations if necessary.
 */

#ifdef BLOCK_INTERRUPTS
#  ifdef ARCH64
  unsigned long long flags_reg;
#  else
  unsigned long flags_reg;
#  endif
  local_save_flags (flags_reg);
  flags[3] = flags_reg;
  local_irq_disable ();
#endif

  /*
   * Prepare the FP related control register bits to disable all kind of
   * FP related exceptions.
   */
  flags[0] = read_cr0 ();
  write_cr0 (flags[0] & ~0x0e);	/* Clear CR0.TS/EM/MP */

  /*
   * Save FPU/SSE/XMM registers and init the hardware.
   */
  if (old_arch)
    {
      FP_SAVE (envbuf);
    }
  else
    {
      flags[1] = read_cr4 ();
      write_cr4 (flags[1] | 0x600);	/* Set OSFXSR & OSXMMEXCEPT */
      FX_SAVE (envbuf);
      asm ("fninit");
      asm ("fwait");
      write_mxcsr (0x1f80);
    }
  flags[2] = read_cr0 ();
}

void
oss_fp_restore (short *envbuf, unsigned int flags[])
{
/*
 * oss_fp_restore reverses any changes made by oss_fp_save and restores
 * the floating point environment (registers) back to the original state.
 */
  asm ("fwait");
  if (old_arch)
    {
      FP_RESTORE (envbuf);
    }
  else
    {
      FX_RESTORE (envbuf);
      write_cr4 (flags[1]);	/* Restore cr4 */
    }
  write_cr0 (flags[0]);		/* Restore cr0 */
#ifdef BLOCK_INTERRUPTS
  local_restore_flags (flags[3]);
#endif
}
#else
int
oss_fp_check (void)
{
  return 0;
}
#endif