Source to machdep/ppc/pcb.c
/*
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
* Reserved. This file contains Original Code and/or Modifications of
* Original Code as defined in and that are subject to the Apple Public
* Source License Version 1.0 (the 'License'). You may not use this file
* except in compliance with the License. Please obtain a copy of the
* License at http://www.apple.com/publicsource and read it before using
* this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
* License for the specific language governing rights and limitations
* under the License."
*
* @APPLE_LICENSE_HEADER_END@
*/
/* HISTORY
1997/05/16 Rene Vega -- cleanup sync/isync usage.
*/
#import <machdep/ppc/exception.h>
#import <mach/mach_types.h>
#import <kern/mach_param.h>
#import <kern/thread.h>
#import <kern/kernel_stack.h>
#import <machdep/ppc/thread.h>
#import <bsd/ppc/psl.h>
#import <sys/time.h>
#import <bsd/ppc/vmparam.h>
#import <kernserv/ppc/spl.h>
#import <sys/param.h>
#import <sys/proc.h>
#import <kern/parallel.h>
#include <machdep/ppc/proc_reg.h>
#include <ppc/trap.h>
#ifdef KDEBUG
#include <kern/kdebug.h>
#include <mach_counters.h>
#include <kern/counters.h>
#import <machdep/ppc/frame.h>
#endif
#import <machdep/ppc/asm.h>
#import <assym.h>
pcb_t active_pcbs[NCPUS]; /* PCB belonging to the active thread */
#if DEBUG
int fpu_trap_count = 0;
int fpu_switch_count = 0;
#endif
extern struct per_proc_info per_proc_info[];
#define current_pcb() active_pcbs[cpu_number()]
zone_t pcb_zone;
/*
* Initialize pcb allocation zone.
*/
void
pcb_module_init(void)
{
int i;
pcb_zone = zinit(
sizeof (struct pcb),
THREAD_MAX * sizeof (struct pcb),
THREAD_CHUNK * sizeof (struct pcb),
FALSE, "pcb");
for (i=0; i < NCPUS; i++) {
active_pcbs[i] = 0;
}
}
/*
* Allocated and initialize a pcb for a new thread.
*/
void pcb_init(thread_t thread)
{
struct pcb *pcb = (void *)zalloc(pcb_zone);
pmap_t pmap = thread->task->map->pmap;
thread->pcb = pcb;
/* all fields default to zero */
bzero((caddr_t)pcb, sizeof (struct pcb));
/*
* User threads will pull their context from the pcb when first
* returning to user mode, so fill in all the necessary values.
* Kernel threads are initialized from the save state structure
* at the base of the kernel stack (see stack_attach()).
*/
pcb->ss.srr1 = MSR_EXPORT_MASK_SET;
pcb->sr0 = SEG_REG_PROT | (pmap->space<<4);
pcb->ss.sr_copyin = SEG_REG_PROT | SR_COPYIN + (pmap->space<<4);
}
/*
* Release machine dependent resources on
* thread termination.
*/
pcb_terminate(
thread_t thread
)
{
struct pcb *pcb = thread->pcb;
if (per_proc_info[cpu_number()].fpu_pcb == pcb)
{
per_proc_info[cpu_number()].fpu_pcb = (pcb_t)0;
}
thread->pcb = 0;
zfree(pcb_zone, pcb);
}
#define KF_SIZE (FM_SIZE+ARG_SIZE+FM_REDZONE)
/*
* stack_attach: Attach a kernel stack to a thread.
*/
void stack_attach(thread, stack, continuation)
register thread_t thread;
vm_offset_t stack;
void (*continuation)();
{
struct ppc_kernel_state *kss;
struct pcb *pcb = thread->pcb;
#ifdef KDEBUG
if (continuation) {
KERNEL_DEBUG(MACHDBG_CODE(DBG_MACH_SCHED,MACH_STACK_ATTACH),
thread, thread->priority,
thread->sched_pri, continuation,
0);
}
#endif
thread->kernel_stack = stack;
kss = STACK_IKS(stack);
/*
* Build a kernel state area + arg frame on the stack for the initial
* switch into the thread. We also store a zero into the kernel
* stack pointer so the trap code knows there is already a frame
* on the kernel stack.
*/
kss->lr = (unsigned int) continuation;
kss->r1 = (vm_offset_t) ((int)kss - KF_SIZE);
*((int*)kss->r1) = 0; /* Zero the frame backpointer */
pcb->ksp = 0;
}
vm_offset_t
stack_detach(
thread_t thread
)
{
vm_offset_t stack;
stack = thread->kernel_stack;
thread->kernel_stack = 0;
return (stack);
}
/*
* stack_handoff: Move the current threads kernel stack to the new thread.
*/
void
stack_handoff(
thread_t old,
thread_t new
)
{
vm_offset_t stack = stack_detach(old);
KERNEL_DEBUG(MACHDBG_CODE(DBG_MACH_SCHED,MACH_STACK_HANDOFF),
old, new,
old->priority, old->sched_pri,
new->sched_pri);
stack_attach(new, stack, 0);
/*
* Change software state.
*/
if (new->task != old->task) {
int mycpu = cpu_number();
PMAP_DEACTIVATE(vm_map_pmap(old->task->map), old, mycpu);
PMAP_ACTIVATE(vm_map_pmap(new->task->map), new, mycpu);
pmap_switch(new->task->map->pmap);
}
// current_thread() = new; // can't use this because it may be inlined!
cpu_data[cpu_number()].active_thread = new;
current_pcb() = new->pcb;
cpu_data[cpu_number()].flags = new->pcb->flags;
#if NCPUS > 1
/* There is no free lunch!
* save the floating point state for the old thread
* if the fpu has been used since the last context switch
*/
fp_state_save(old);
#endif /* NCPUS > 1 */
new->pcb->ksp = 0;
}
void
call_continuation(
void (*continuation)(void)
)
{
struct ppc_kernel_state *kss;
extern Call_continuation();
int tkss;
KERNEL_DEBUG(MACHDBG_CODE(DBG_MACH_SCHED,MACH_CALL_CONT),
current_thread(), current_thread()->priority,
current_thread()->sched_pri, continuation,
0);
kss = STACK_IKS(current_thread()->kernel_stack);
*((int*)((int)kss - KF_SIZE)) = 0; /* Zero the frame backpointer */
tkss = (int)kss - KF_SIZE;
Call_continuation(continuation, tkss);
}
/*
* switch_context: Switch from one thread to another.
*/
thread_t
switch_context(
thread_t old,
void (*continuation)(void),
thread_t new
)
{
thread_t ret_thread;
extern thread_t Switch_context();
#ifdef KDEBUG
struct linkage_area *frame_ptr;
uint i;
uint lr[4];
#endif
if (new->task != old->task) {
int mycpu = cpu_number();
PMAP_DEACTIVATE(vm_map_pmap(old->task->map), old, mycpu);
PMAP_ACTIVATE(vm_map_pmap(new->task->map), new, mycpu);
pmap_switch(new->task->map->pmap);
}
// current_thread() = new; // can't use this because it may be inlined!
cpu_data[cpu_number()].active_thread = new;
//current_stack() = new->kernel_stack; // assembly does this
current_pcb()= new->pcb;
cpu_data[cpu_number()].flags = new->pcb->flags;
#if NCPUS > 1
/* There is no free lunch!
* Save the floating point state for the old thread
* if the fpu has been used since the last context switch.
* Otherwise we need to broadcast an interrupt to the
* old fpu to get the state.
*/
fp_state_save(old);
#else
//disable_fpu();
#endif
#ifdef KDEBUG
KERNEL_DEBUG(MACHDBG_CODE(DBG_MACH_SCHED,MACH_SCHED) | DBG_FUNC_END,
old, new,
old->priority, old->sched_pri,
new->sched_pri);
frame_ptr = STACK_IKS(new->kernel_stack)->r1;
lr[0] = lr[1] = lr[2] = lr[3] = 0;
lr[0] = STACK_IKS(new->kernel_stack)->lr;
for (i=1;i<4;i++) {
if (frame_ptr == NULL) break;
lr[i] = frame_ptr->saved_lr;
frame_ptr = frame_ptr->saved_sp;
}
KERNEL_DEBUG(MACHDBG_CODE(DBG_MACH_SCHED,MACH_SCHED) | DBG_FUNC_START,
new, lr[0], lr[1], lr[2], lr[3]);
lr[0] = lr[1] = lr[2] = lr[3] = 0;
for (i=0;i<4;i++) {
if (frame_ptr == NULL) break;
lr[i] = frame_ptr->saved_lr;
frame_ptr = frame_ptr->saved_sp;
}
if (lr[0]) {
KERNEL_DEBUG(MACHDBG_CODE(DBG_MACH_SCHED,MACH_SCHED),
0, lr[0], lr[1], lr[2], lr[3]);
};
#endif
ret_thread = Switch_context(old, continuation, new);
return(ret_thread);
}
void
start_initial_context(
thread_t thread
)
{
struct pcb *pcb = thread->pcb;
/*
* Change software state.
*/
PMAP_ACTIVATE(vm_map_pmap(thread->task->map), thread, 0);
// current_thread() = thread; // can't use this because it may be inlined!
cpu_data[cpu_number()].active_thread = thread;
current_stack() = thread->kernel_stack;
current_pcb() = thread->pcb;
cpu_data[cpu_number()].flags = thread->pcb->flags;
/*
* Change hardware state
*/
load_context(thread);
/*NOTREACHED*/
}
/*
* Duplicate parent state in child
* for U**X fork.
*/
thread_dup(
thread_t parent,
thread_t child
)
{
struct ppc_saved_state *parent_state, *child_state;
struct ppc_float_state *parent_float_state, *child_float_state;
struct ppc_exception_state *parent_exception_state,
*child_exception_state;
/* Save the FPU state */
if (per_proc_info[cpu_number()].fpu_pcb == parent->pcb) {
fp_state_save(parent);
}
parent_state = &parent->pcb->ss;
child_state = &child->pcb->ss;
/* rely on compiler structure assignment */
*child_state = *parent_state;
parent_float_state = &parent->pcb->fs;
child_float_state = &child->pcb->fs;
/* rely on compiler structure assignment */
*child_float_state = *parent_float_state;
#if 0
parent_exception_state = &parent->pcb->es;
child_exception_state = &child->pcb->es;
/* rely on compiler structure assignment */
*child_exception_state = *parent_exception_state;
#endif
child_state->r3 = child->task->proc->p_pid;
child_state->r4 = 1;
child_state->sr_copyin = child->pcb->sr0 + SR_COPYIN;
}
/*
* Set thread integer state
*/
kern_return_t
set_thread_state(
thread_t thread,
thread_state_t tstate,
unsigned int count
)
{
struct ppc_saved_state *saved_state;
struct ppc_thread_state *state;
if (count < PPC_THREAD_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
if (thread->task->kernel_privilege) {
/*
* State for kernel threads
* can only be set before thread
* is first started.
*
* XXX This hack is due to the
* fact that the &^%$#@! kernel loader
* uses the thread_set_state() call to
* start a thread in kernel mode.
*/
if (thread->swap_func != thread_bootstrap_return)
return (KERN_INVALID_ARGUMENT);
}
state = (struct ppc_thread_state *)tstate;
saved_state = &thread->pcb->ss;
/*
* structure assignment - depends on
* ppc_thread_state being a prefix of ppc_saved_state !
*/
*((struct ppc_thread_state *)saved_state) = *state;
saved_state->sr_copyin = thread->pcb->sr0 + SR_COPYIN;
saved_state->srr1 |= MSR_EXPORT_MASK_SET;
if (thread->task->kernel_privilege) {
saved_state->srr1 &= ~ MASK(MSR_PR);
thread_start(thread, state->srr0);
}
return (KERN_SUCCESS);
}
/*
* Set thread floating point state
*/
kern_return_t
set_thread_fpstate(
thread_t thread,
thread_state_t tstate,
unsigned int count
)
{
struct ppc_float_state *state;
if (count < PPC_FLOAT_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
fpu_save();
fpu_disable();
state = (struct ppc_float_state *)tstate;
/* structure assignment */
thread->pcb->fs = *state;
return (KERN_SUCCESS);
}
/*
* Set thread exception point state
*/
kern_return_t
set_thread_exstate(
thread_t thread,
thread_state_t tstate,
unsigned int count
)
{
struct ppc_exception_state *state;
if (count < PPC_EXCEPTION_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
state = (struct ppc_exception_state *)tstate;
/* structure assignment */
thread->pcb->es = *state;
return (KERN_SUCCESS);
}
/*
* Set externally visible thread
* state.
*/
kern_return_t
thread_setstatus(
thread_t thread,
int flavor,
thread_state_t tstate,
unsigned int count
)
{
switch (flavor) {
case PPC_THREAD_STATE:
return (set_thread_state(thread, tstate, count));
case PPC_FLOAT_STATE:
return (set_thread_fpstate(thread, tstate, count));
case PPC_EXCEPTION_STATE:
return (set_thread_exstate(thread, tstate, count));
default:
return (KERN_INVALID_ARGUMENT);
}
}
/*
* Get thread state flavor list
*/
kern_return_t
get_thread_state_flavor_list(
thread_state_t tstate,
unsigned int *count
)
{
struct thread_state_flavor *state;
if (*count < PPC_THREAD_STATE_FLAVOR_LIST_COUNT)
return (KERN_INVALID_ARGUMENT);
state = (struct thread_state_flavor *)tstate;
state->flavor = PPC_THREAD_STATE;
state->count = PPC_THREAD_STATE_COUNT;
(++state)->flavor = PPC_FLOAT_STATE;
state->count = PPC_FLOAT_STATE_COUNT;
(++state)->flavor = PPC_EXCEPTION_STATE;
state->count = PPC_EXCEPTION_STATE_COUNT;
*count = PPC_THREAD_STATE_FLAVOR_LIST_COUNT;
return (KERN_SUCCESS);
}
/*
* Get thread integer state
*/
kern_return_t
get_thread_state(
thread_t thread,
thread_state_t tstate,
unsigned int *count
)
{
struct ppc_thread_state *saved_state;
struct ppc_thread_state *state;
if (*count < PPC_THREAD_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
state = (struct ppc_thread_state *)tstate;
saved_state = (struct ppc_thread_state *) &thread->pcb->ss;
/*
* structure assignment - depends on
* ppc_thread_state being a prefix of ppc_saved_state !
*/
*state = *((struct ppc_thread_state *)saved_state);
*count = PPC_THREAD_STATE_COUNT;
return (KERN_SUCCESS);
}
/*
* Get thread floating point state
*/
kern_return_t
get_thread_fpstate(
thread_t thread,
thread_state_t tstate,
unsigned int *count
)
{
struct ppc_float_state *state;
if (*count < PPC_FLOAT_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
fpu_save();
fpu_disable();
state = (struct ppc_float_state *)tstate;
/* structure assignment */
*state = thread->pcb->fs;
*count = PPC_FLOAT_STATE_COUNT;
return (KERN_SUCCESS);
}
/*
* Get thread exception point state
*/
kern_return_t
get_thread_exstate(
thread_t thread,
thread_state_t tstate,
unsigned int *count
)
{
struct ppc_exception_state *state;
if (*count < PPC_EXCEPTION_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
state = (struct ppc_exception_state *)tstate;
/* structure assignment */
*state = thread->pcb->es;
*count = PPC_EXCEPTION_STATE_COUNT;
return (KERN_SUCCESS);
}
/*
* Return externally visible
* thread status.
*/
kern_return_t
thread_getstatus(
thread_t thread,
int flavor,
thread_state_t tstate,
unsigned int *count
)
{
switch (flavor) {
case THREAD_STATE_FLAVOR_LIST:
return (get_thread_state_flavor_list(tstate, count));
case PPC_THREAD_STATE:
return (get_thread_state(thread, tstate, count));
case PPC_FLOAT_STATE:
return (get_thread_fpstate(thread, tstate, count));
case PPC_EXCEPTION_STATE:
return (get_thread_exstate(thread, tstate, count));
default:
return (KERN_INVALID_ARGUMENT);
}
}
/*
* thread_userstack:
*
* Return the user stack pointer from the machine
* dependent thread state info.
*/
kern_return_t
thread_userstack(
thread_t thread,
int flavor,
thread_state_t tstate,
unsigned int count,
vm_offset_t *user_stack
)
{
struct ppc_thread_state *state;
/*
* Set a default.
*/
if (*user_stack == 0)
*user_stack = USRSTACK;
switch (flavor) {
case PPC_THREAD_STATE:
if (count < PPC_THREAD_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
state = (struct ppc_thread_state *) tstate;
/*
* If a valid user stack is specified, use it.
*/
*user_stack = state->r1 ? state->r1: USRSTACK;
break;
default :
return (KERN_INVALID_ARGUMENT);
}
return (KERN_SUCCESS);
}
kern_return_t
thread_entrypoint(
thread_t thread,
int flavor,
thread_state_t tstate,
unsigned int count,
vm_offset_t *entry_point
)
{
struct ppc_thread_state *state;
/*
* Set a default.
*/
if (*entry_point == 0)
*entry_point = VM_MIN_ADDRESS;
switch (flavor) {
case PPC_THREAD_STATE:
if (count < PPC_THREAD_STATE_COUNT)
return (KERN_INVALID_ARGUMENT);
state = (struct ppc_thread_state *) tstate;
/*
* If a valid entry point is specified, use it.
*/
*entry_point = state->srr0 ? state->srr0: VM_MIN_ADDRESS;
break;
default:
return (KERN_INVALID_ARGUMENT);
}
return (KERN_SUCCESS);
}
void __volatile__
thread_syscall_return(
kern_return_t retval)
{
thread_t thread = current_thread();
struct ppc_saved_state *ssp = &thread->pcb->ss;
ssp->r3 = retval;
thread_exception_return();
/* NOTREACHED */
}
void
thread_set_syscall_return(
thread_t thread,
kern_return_t retval)
{
struct ppc_saved_state *ssp = &thread->pcb->ss;
ssp->r3 = retval;
}
void
pmap_switch(pmap_t map)
{
unsigned int i;
if (map->space == PPC_SID_KERNEL)
return;
/* sr value has Ks=1, Ku=1, and vsid composed of space+seg num */
i = SEG_REG_PROT | (map->space << 4);
isync(); /* context sync before */
/* mtsr(0x0, i + 0x0); SR0 is part of the kernel address space */
/* mtsr(0x1, i + 0x1); SR1 is part of the kernel address space */
/* mtsr(0x2, i + 0x2); SR2 is part of the kernel address space */
/* mtsr(0x3, i + 0x3); SR3 is part of the kernel address space */
mtsr(0x4, i + 0x4);
mtsr(0x5, i + 0x5);
mtsr(0x6, i + 0x6);
mtsr(0x7, i + 0x7);
mtsr(0x8, i + 0x8);
mtsr(0x9, i + 0x9);
mtsr(0xa, i + 0xa);
mtsr(0xb, i + 0xb);
mtsr(0xc, i + 0xc);
mtsr(0xd, i + 0xd);
mtsr(0xe, i + 0xe);
mtsr(0xf, i + 0xf);
isync(); /* context sync after */
}
/*
* task_map_io_ports() is required by the driverkit routines.
*/
kern_return_t
task_map_io_ports(
task_t task,
unsigned int port,
unsigned int length,
boolean_t unmap
)
{
return (KERN_SUCCESS);
}
#if MACH_ASSERT
void
dump_pcb(pcb_t pcb)
{
printf("pcb @ %8.8x:\n", pcb);
printf("ksp = 0x%08x\n\n",pcb->ksp);
#if DEBUG
regDump(&pcb->ss);
#endif /* DEBUG */
}
void
dump_thread(thread_t th)
{
printf(" thread @ 0x%x:\n", th);
}
#endif