diff options
author | Chris Zankel <chris@zankel.net> | 2008-02-12 16:17:07 -0500 |
---|---|---|
committer | Chris Zankel <chris@zankel.net> | 2008-02-13 20:41:43 -0500 |
commit | c658eac628aa8df040dfe614556d95e6da3a9ffb (patch) | |
tree | e2211e1d5c894c29e92d4c744f504b38410efe41 /arch/xtensa/kernel/process.c | |
parent | 71d28e6c285548106f551fde13ca6d589433d843 (diff) |
[XTENSA] Add support for configurable registers and coprocessors
The Xtensa architecture allows to define custom instructions and
registers. Registers that are bound to a coprocessor are only
accessible if the corresponding enable bit is set, which allows
to implement a 'lazy' context switch mechanism. Other registers
needs to be saved and restore at the time of the context switch
or during interrupt handling.
This patch adds support for these additional states:
- save and restore registers that are used by the compiler upon
interrupt entry and exit.
- context switch additional registers unbound to any coprocessor
- 'lazy' context switch of registers bound to a coprocessor
- ptrace interface to provide access to additional registers
- update configuration files in include/asm-xtensa/variant-fsf
Signed-off-by: Chris Zankel <chris@zankel.net>
Diffstat (limited to 'arch/xtensa/kernel/process.c')
-rw-r--r-- | arch/xtensa/kernel/process.c | 261 |
1 files changed, 102 insertions, 159 deletions
diff --git a/arch/xtensa/kernel/process.c b/arch/xtensa/kernel/process.c index 026138d641a4..9185597eb6a0 100644 --- a/arch/xtensa/kernel/process.c +++ b/arch/xtensa/kernel/process.c | |||
@@ -52,6 +52,55 @@ void (*pm_power_off)(void) = NULL; | |||
52 | EXPORT_SYMBOL(pm_power_off); | 52 | EXPORT_SYMBOL(pm_power_off); |
53 | 53 | ||
54 | 54 | ||
55 | #if XTENSA_HAVE_COPROCESSORS | ||
56 | |||
57 | void coprocessor_release_all(struct thread_info *ti) | ||
58 | { | ||
59 | unsigned long cpenable; | ||
60 | int i; | ||
61 | |||
62 | /* Make sure we don't switch tasks during this operation. */ | ||
63 | |||
64 | preempt_disable(); | ||
65 | |||
66 | /* Walk through all cp owners and release it for the requested one. */ | ||
67 | |||
68 | cpenable = ti->cpenable; | ||
69 | |||
70 | for (i = 0; i < XCHAL_CP_MAX; i++) { | ||
71 | if (coprocessor_owner[i] == ti) { | ||
72 | coprocessor_owner[i] = 0; | ||
73 | cpenable &= ~(1 << i); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | ti->cpenable = cpenable; | ||
78 | coprocessor_clear_cpenable(); | ||
79 | |||
80 | preempt_enable(); | ||
81 | } | ||
82 | |||
83 | void coprocessor_flush_all(struct thread_info *ti) | ||
84 | { | ||
85 | unsigned long cpenable; | ||
86 | int i; | ||
87 | |||
88 | preempt_disable(); | ||
89 | |||
90 | cpenable = ti->cpenable; | ||
91 | |||
92 | for (i = 0; i < XCHAL_CP_MAX; i++) { | ||
93 | if ((cpenable & 1) != 0 && coprocessor_owner[i] == ti) | ||
94 | coprocessor_flush(ti, i); | ||
95 | cpenable >>= 1; | ||
96 | } | ||
97 | |||
98 | preempt_enable(); | ||
99 | } | ||
100 | |||
101 | #endif | ||
102 | |||
103 | |||
55 | /* | 104 | /* |
56 | * Powermanagement idle function, if any is provided by the platform. | 105 | * Powermanagement idle function, if any is provided by the platform. |
57 | */ | 106 | */ |
@@ -71,15 +120,36 @@ void cpu_idle(void) | |||
71 | } | 120 | } |
72 | 121 | ||
73 | /* | 122 | /* |
74 | * Free current thread data structures etc.. | 123 | * This is called when the thread calls exit(). |
75 | */ | 124 | */ |
76 | |||
77 | void exit_thread(void) | 125 | void exit_thread(void) |
78 | { | 126 | { |
127 | #if XTENSA_HAVE_COPROCESSORS | ||
128 | coprocessor_release_all(current_thread_info()); | ||
129 | #endif | ||
79 | } | 130 | } |
80 | 131 | ||
132 | /* | ||
133 | * Flush thread state. This is called when a thread does an execve() | ||
134 | * Note that we flush coprocessor registers for the case execve fails. | ||
135 | */ | ||
81 | void flush_thread(void) | 136 | void flush_thread(void) |
82 | { | 137 | { |
138 | #if XTENSA_HAVE_COPROCESSORS | ||
139 | struct thread_info *ti = current_thread_info(); | ||
140 | coprocessor_flush_all(ti); | ||
141 | coprocessor_release_all(ti); | ||
142 | #endif | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * This is called before the thread is copied. | ||
147 | */ | ||
148 | void prepare_to_copy(struct task_struct *tsk) | ||
149 | { | ||
150 | #if XTENSA_HAVE_COPROCESSORS | ||
151 | coprocessor_flush_all(task_thread_info(tsk)); | ||
152 | #endif | ||
83 | } | 153 | } |
84 | 154 | ||
85 | /* | 155 | /* |
@@ -107,6 +177,7 @@ int copy_thread(int nr, unsigned long clone_flags, unsigned long usp, | |||
107 | struct task_struct * p, struct pt_regs * regs) | 177 | struct task_struct * p, struct pt_regs * regs) |
108 | { | 178 | { |
109 | struct pt_regs *childregs; | 179 | struct pt_regs *childregs; |
180 | struct thread_info *ti; | ||
110 | unsigned long tos; | 181 | unsigned long tos; |
111 | int user_mode = user_mode(regs); | 182 | int user_mode = user_mode(regs); |
112 | 183 | ||
@@ -128,13 +199,14 @@ int copy_thread(int nr, unsigned long clone_flags, unsigned long usp, | |||
128 | p->set_child_tid = p->clear_child_tid = NULL; | 199 | p->set_child_tid = p->clear_child_tid = NULL; |
129 | p->thread.ra = MAKE_RA_FOR_CALL((unsigned long)ret_from_fork, 0x1); | 200 | p->thread.ra = MAKE_RA_FOR_CALL((unsigned long)ret_from_fork, 0x1); |
130 | p->thread.sp = (unsigned long)childregs; | 201 | p->thread.sp = (unsigned long)childregs; |
202 | |||
131 | if (user_mode(regs)) { | 203 | if (user_mode(regs)) { |
132 | 204 | ||
133 | int len = childregs->wmask & ~0xf; | 205 | int len = childregs->wmask & ~0xf; |
134 | childregs->areg[1] = usp; | 206 | childregs->areg[1] = usp; |
135 | memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], | 207 | memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], |
136 | ®s->areg[XCHAL_NUM_AREGS - len/4], len); | 208 | ®s->areg[XCHAL_NUM_AREGS - len/4], len); |
137 | 209 | // FIXME: we need to set THREADPTR in thread_info... | |
138 | if (clone_flags & CLONE_SETTLS) | 210 | if (clone_flags & CLONE_SETTLS) |
139 | childregs->areg[2] = childregs->areg[6]; | 211 | childregs->areg[2] = childregs->areg[6]; |
140 | 212 | ||
@@ -142,6 +214,12 @@ int copy_thread(int nr, unsigned long clone_flags, unsigned long usp, | |||
142 | /* In kernel space, we start a new thread with a new stack. */ | 214 | /* In kernel space, we start a new thread with a new stack. */ |
143 | childregs->wmask = 1; | 215 | childregs->wmask = 1; |
144 | } | 216 | } |
217 | |||
218 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) | ||
219 | ti = task_thread_info(p); | ||
220 | ti->cpenable = 0; | ||
221 | #endif | ||
222 | |||
145 | return 0; | 223 | return 0; |
146 | } | 224 | } |
147 | 225 | ||
@@ -179,10 +257,6 @@ unsigned long get_wchan(struct task_struct *p) | |||
179 | } | 257 | } |
180 | 258 | ||
181 | /* | 259 | /* |
182 | * do_copy_regs() gathers information from 'struct pt_regs' and | ||
183 | * 'current->thread.areg[]' to fill in the xtensa_gregset_t | ||
184 | * structure. | ||
185 | * | ||
186 | * xtensa_gregset_t and 'struct pt_regs' are vastly different formats | 260 | * xtensa_gregset_t and 'struct pt_regs' are vastly different formats |
187 | * of processor registers. Besides different ordering, | 261 | * of processor registers. Besides different ordering, |
188 | * xtensa_gregset_t contains non-live register information that | 262 | * xtensa_gregset_t contains non-live register information that |
@@ -191,9 +265,20 @@ unsigned long get_wchan(struct task_struct *p) | |||
191 | * | 265 | * |
192 | */ | 266 | */ |
193 | 267 | ||
194 | void do_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs, | 268 | void xtensa_elf_core_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs) |
195 | struct task_struct *tsk) | ||
196 | { | 269 | { |
270 | unsigned long wb, ws, wm; | ||
271 | int live, last; | ||
272 | |||
273 | wb = regs->windowbase; | ||
274 | ws = regs->windowstart; | ||
275 | wm = regs->wmask; | ||
276 | ws = ((ws >> wb) | (ws << (WSBITS - wb))) & ((1 << WSBITS) - 1); | ||
277 | |||
278 | /* Don't leak any random bits. */ | ||
279 | |||
280 | memset(elfregs, 0, sizeof (elfregs)); | ||
281 | |||
197 | /* Note: PS.EXCM is not set while user task is running; its | 282 | /* Note: PS.EXCM is not set while user task is running; its |
198 | * being set in regs->ps is for exception handling convenience. | 283 | * being set in regs->ps is for exception handling convenience. |
199 | */ | 284 | */ |
@@ -204,159 +289,18 @@ void do_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs, | |||
204 | elfregs->lend = regs->lend; | 289 | elfregs->lend = regs->lend; |
205 | elfregs->lcount = regs->lcount; | 290 | elfregs->lcount = regs->lcount; |
206 | elfregs->sar = regs->sar; | 291 | elfregs->sar = regs->sar; |
292 | elfregs->windowstart = ws; | ||
207 | 293 | ||
208 | memcpy (elfregs->a, regs->areg, sizeof(elfregs->a)); | 294 | live = (wm & 2) ? 4 : (wm & 4) ? 8 : (wm & 8) ? 12 : 16; |
209 | } | 295 | last = XCHAL_NUM_AREGS - (wm >> 4) * 4; |
210 | 296 | memcpy(elfregs->a, regs->areg, live * 4); | |
211 | void xtensa_elf_core_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs) | 297 | memcpy(elfregs->a + last, regs->areg + last, (wm >> 4) * 16); |
212 | { | ||
213 | do_copy_regs ((xtensa_gregset_t *)elfregs, regs, current); | ||
214 | } | ||
215 | |||
216 | |||
217 | /* The inverse of do_copy_regs(). No error or sanity checking. */ | ||
218 | |||
219 | void do_restore_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs, | ||
220 | struct task_struct *tsk) | ||
221 | { | ||
222 | const unsigned long ps_mask = PS_CALLINC_MASK | PS_OWB_MASK; | ||
223 | unsigned long ps; | ||
224 | |||
225 | /* Note: PS.EXCM is not set while user task is running; it | ||
226 | * needs to be set in regs->ps is for exception handling convenience. | ||
227 | */ | ||
228 | |||
229 | ps = (regs->ps & ~ps_mask) | (elfregs->ps & ps_mask) | (1<<PS_EXCM_BIT); | ||
230 | regs->ps = ps; | ||
231 | regs->pc = elfregs->pc; | ||
232 | regs->lbeg = elfregs->lbeg; | ||
233 | regs->lend = elfregs->lend; | ||
234 | regs->lcount = elfregs->lcount; | ||
235 | regs->sar = elfregs->sar; | ||
236 | |||
237 | memcpy (regs->areg, elfregs->a, sizeof(regs->areg)); | ||
238 | } | ||
239 | |||
240 | /* | ||
241 | * do_save_fpregs() gathers information from 'struct pt_regs' and | ||
242 | * 'current->thread' to fill in the elf_fpregset_t structure. | ||
243 | * | ||
244 | * Core files and ptrace use elf_fpregset_t. | ||
245 | */ | ||
246 | |||
247 | void do_save_fpregs (elf_fpregset_t *fpregs, struct pt_regs *regs, | ||
248 | struct task_struct *tsk) | ||
249 | { | ||
250 | #if XCHAL_HAVE_CP | ||
251 | |||
252 | extern unsigned char _xtensa_reginfo_tables[]; | ||
253 | extern unsigned _xtensa_reginfo_table_size; | ||
254 | int i; | ||
255 | unsigned long flags; | ||
256 | |||
257 | /* Before dumping coprocessor state from memory, | ||
258 | * ensure any live coprocessor contents for this | ||
259 | * task are first saved to memory: | ||
260 | */ | ||
261 | local_irq_save(flags); | ||
262 | |||
263 | for (i = 0; i < XCHAL_CP_MAX; i++) { | ||
264 | if (tsk == coprocessor_info[i].owner) { | ||
265 | enable_coprocessor(i); | ||
266 | save_coprocessor_registers( | ||
267 | tsk->thread.cp_save+coprocessor_info[i].offset,i); | ||
268 | disable_coprocessor(i); | ||
269 | } | ||
270 | } | ||
271 | |||
272 | local_irq_restore(flags); | ||
273 | |||
274 | /* Now dump coprocessor & extra state: */ | ||
275 | memcpy((unsigned char*)fpregs, | ||
276 | _xtensa_reginfo_tables, _xtensa_reginfo_table_size); | ||
277 | memcpy((unsigned char*)fpregs + _xtensa_reginfo_table_size, | ||
278 | tsk->thread.cp_save, XTENSA_CP_EXTRA_SIZE); | ||
279 | #endif | ||
280 | } | 298 | } |
281 | 299 | ||
282 | /* | 300 | int dump_fpu(void) |
283 | * The inverse of do_save_fpregs(). | ||
284 | * Copies coprocessor and extra state from fpregs into regs and tsk->thread. | ||
285 | * Returns 0 on success, non-zero if layout doesn't match. | ||
286 | */ | ||
287 | |||
288 | int do_restore_fpregs (elf_fpregset_t *fpregs, struct pt_regs *regs, | ||
289 | struct task_struct *tsk) | ||
290 | { | 301 | { |
291 | #if XCHAL_HAVE_CP | ||
292 | |||
293 | extern unsigned char _xtensa_reginfo_tables[]; | ||
294 | extern unsigned _xtensa_reginfo_table_size; | ||
295 | int i; | ||
296 | unsigned long flags; | ||
297 | |||
298 | /* Make sure save area layouts match. | ||
299 | * FIXME: in the future we could allow restoring from | ||
300 | * a different layout of the same registers, by comparing | ||
301 | * fpregs' table with _xtensa_reginfo_tables and matching | ||
302 | * entries and copying registers one at a time. | ||
303 | * Not too sure yet whether that's very useful. | ||
304 | */ | ||
305 | |||
306 | if( memcmp((unsigned char*)fpregs, | ||
307 | _xtensa_reginfo_tables, _xtensa_reginfo_table_size) ) { | ||
308 | return -1; | ||
309 | } | ||
310 | |||
311 | /* Before restoring coprocessor state from memory, | ||
312 | * ensure any live coprocessor contents for this | ||
313 | * task are first invalidated. | ||
314 | */ | ||
315 | |||
316 | local_irq_save(flags); | ||
317 | |||
318 | for (i = 0; i < XCHAL_CP_MAX; i++) { | ||
319 | if (tsk == coprocessor_info[i].owner) { | ||
320 | enable_coprocessor(i); | ||
321 | save_coprocessor_registers( | ||
322 | tsk->thread.cp_save+coprocessor_info[i].offset,i); | ||
323 | coprocessor_info[i].owner = 0; | ||
324 | disable_coprocessor(i); | ||
325 | } | ||
326 | } | ||
327 | |||
328 | local_irq_restore(flags); | ||
329 | |||
330 | /* Now restore coprocessor & extra state: */ | ||
331 | |||
332 | memcpy(tsk->thread.cp_save, | ||
333 | (unsigned char*)fpregs + _xtensa_reginfo_table_size, | ||
334 | XTENSA_CP_EXTRA_SIZE); | ||
335 | #endif | ||
336 | return 0; | 302 | return 0; |
337 | } | 303 | } |
338 | /* | ||
339 | * Fill in the CP structure for a core dump for a particular task. | ||
340 | */ | ||
341 | |||
342 | int | ||
343 | dump_task_fpu(struct pt_regs *regs, struct task_struct *task, elf_fpregset_t *r) | ||
344 | { | ||
345 | return 0; /* no coprocessors active on this processor */ | ||
346 | } | ||
347 | |||
348 | /* | ||
349 | * Fill in the CP structure for a core dump. | ||
350 | * This includes any FPU coprocessor. | ||
351 | * Here, we dump all coprocessors, and other ("extra") custom state. | ||
352 | * | ||
353 | * This function is called by elf_core_dump() in fs/binfmt_elf.c | ||
354 | * (in which case 'regs' comes from calls to do_coredump, see signals.c). | ||
355 | */ | ||
356 | int dump_fpu(struct pt_regs *regs, elf_fpregset_t *r) | ||
357 | { | ||
358 | return dump_task_fpu(regs, current, r); | ||
359 | } | ||
360 | 304 | ||
361 | asmlinkage | 305 | asmlinkage |
362 | long xtensa_clone(unsigned long clone_flags, unsigned long newsp, | 306 | long xtensa_clone(unsigned long clone_flags, unsigned long newsp, |
@@ -370,8 +314,8 @@ long xtensa_clone(unsigned long clone_flags, unsigned long newsp, | |||
370 | } | 314 | } |
371 | 315 | ||
372 | /* | 316 | /* |
373 | * * xtensa_execve() executes a new program. | 317 | * xtensa_execve() executes a new program. |
374 | * */ | 318 | */ |
375 | 319 | ||
376 | asmlinkage | 320 | asmlinkage |
377 | long xtensa_execve(char __user *name, char __user * __user *argv, | 321 | long xtensa_execve(char __user *name, char __user * __user *argv, |
@@ -386,7 +330,6 @@ long xtensa_execve(char __user *name, char __user * __user *argv, | |||
386 | error = PTR_ERR(filename); | 330 | error = PTR_ERR(filename); |
387 | if (IS_ERR(filename)) | 331 | if (IS_ERR(filename)) |
388 | goto out; | 332 | goto out; |
389 | // FIXME: release coprocessor?? | ||
390 | error = do_execve(filename, argv, envp, regs); | 333 | error = do_execve(filename, argv, envp, regs); |
391 | if (error == 0) { | 334 | if (error == 0) { |
392 | task_lock(current); | 335 | task_lock(current); |