Source to machdep/i386/pcb.c


Enter a symbol's name here to quickly find it.

/*
 * 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@
 */

/*
 * Copyright (c) 1992 NeXT Computer, Inc.
 *
 * Intel386 Family:	Machine dependent thread module.
 *
 * HISTORY
 *
 * 6 April 1992 ? at NeXT
 *	Created.
 */

#import <mach/mach_types.h>

#import <kern/mach_param.h>

#import <architecture/i386/table.h>

#import <machdep/i386/fp_exported.h>
#import <machdep/i386/configure.h>

#import <machdep/i386/ldt.h>
#import <machdep/i386/table_inline.h>
#import <machdep/i386/desc_inline.h>
#import <machdep/i386/sel_inline.h>
#import <machdep/i386/cpu_inline.h>

/* U**X crap!! */
#import <sys/param.h>
#import <sys/proc.h>

#import <fp_emul.h>
#import <pc_support.h>

zone_t		pcb_zone;

vm_offset_t	stack_pointers[NCPUS];
boolean_t	empty_stacks[NCPUS];

/*
 * The base of the kernel stack
 * is different when a thread is
 * in v86 mode.
 */
#define KERNEL_STACK_BASE(state) \
    ((unsigned int) &(state)->frame.v_es)
    
#define KERNEL_V86_STACK_BASE(state) \
    ((unsigned int) &(&(state)->frame)[1])
    
void
stack_attach(
    thread_t		thread,
    vm_offset_t		stack,
    void		(*continuation)(void)
)
{
    tss_t		*tss;
    extern void		_stack_attach(void);
    
    thread->kernel_stack = stack;

    tss = thread->pcb->tss;
    tss->esp = tss->ebp = stack + KERNEL_STACK_SIZE;
    tss->eip = (unsigned int)_stack_attach;
    tss->ebx = (unsigned int)continuation;
}


vm_offset_t
stack_detach(
    thread_t		thread
)
{
    vm_offset_t		stack;
        
    stack = thread->kernel_stack;
    thread->kernel_stack = 0;
    
    return (stack);
}

void
stack_handoff(
    thread_t		old,
    thread_t		new
)
{
    struct pcb		*new_pcb = new->pcb,
    			*old_pcb = old->pcb;
    vm_offset_t		stack = stack_detach(old);
    
    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);
    }

    current_thread() = new;

    /*
     * Change hardware state.
     */
    if (new_pcb->tss->cr3 != old_pcb->tss->cr3)
	set_cr3(new_pcb->tss->cr3);

    if (new_pcb->ldt != old_pcb->ldt
	    || new_pcb->ldt_size != old_pcb->ldt_size) {
	map_ldt(sel_to_gdt_entry(LDT_SEL),
		    (vm_offset_t) new_pcb->ldt,
			    (vm_size_t) new_pcb->ldt_size);
	lldt();		
    }

    map_tss(sel_to_gdt_entry(TSS_SEL),
    		(vm_offset_t) new_pcb->tss
		    + KERNEL_LINEAR_BASE,
			(vm_size_t) new_pcb->tss_size);
    ltr(); setts();
}

extern
thread_t
_switch_tss(
	tss_t			*old_tss,
	tss_t			*new_tss,
	thread_t		old
);

thread_t
switch_context(
    thread_t		old,
    void		(*continuation)(void),
    thread_t		new
)
{
    struct pcb		*new_pcb = new->pcb,
    			*old_pcb = old->pcb;

    /*
     * 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);
    }

    current_thread() = new;
    current_stack() = new->kernel_stack;
    current_stack_pointer() = new->kernel_stack + KERNEL_STACK_SIZE;

    /*
     * Change hardware state.
     */
    if (new_pcb->tss->cr3 != old_pcb->tss->cr3)
	set_cr3(new_pcb->tss->cr3);

    if (new_pcb->ldt != old_pcb->ldt
	    || new_pcb->ldt_size != old_pcb->ldt_size) {
	map_ldt(sel_to_gdt_entry(LDT_SEL),
		    (vm_offset_t) new_pcb->ldt,
			    (vm_size_t) new_pcb->ldt_size);
	lldt();		
    }

    map_tss(sel_to_gdt_entry(TSS_SEL),
    		(vm_offset_t) new_pcb->tss
		    + KERNEL_LINEAR_BASE,
			(vm_size_t) new_pcb->tss_size);
    ltr(); setts();
    
    if (old->swap_func = continuation)
    	return _switch_tss(0, new_pcb->tss, old);
    else
    	return _switch_tss(old_pcb->tss, new_pcb->tss, old);
}

void
call_continuation(
    void		(*continuation)(void)
)
{
    _call_with_stack(continuation, current_stack_pointer());
    /* NOTREACHED */
}

void
pcb_module_init(void)
{
    pcb_zone = zinit(
		    sizeof (struct pcb),
		    THREAD_MAX * sizeof (struct pcb),
		    THREAD_CHUNK * sizeof (struct pcb),
		    FALSE, "pcb");
}

/*
 * Initialize a pcb for a
 * new thread.
 */
void pcb_init(
    thread_t		thread
)
{
    struct pcb			*pcb;
    tss_t			*tss;

    /*
     * Allocate a new pcb.
     */	
    thread->pcb = pcb = (void *)zalloc(pcb_zone);
    *pcb = (struct pcb) { 0 };

    /*
     * Setup with internal TSS.
     */
    pcb->tss = tss = &pcb->tss_store.internal;
    pcb->tss_size = TSS_SIZE(0);

    /*
     * Context from pmap.
     */
    tss->cr3 = vm_map_pmap(thread->task->map)->cr3;
    
    /*
     * Setup with default LDT.
     */
    pcb->ldt = (vm_offset_t)ldt + KERNEL_LINEAR_BASE;
    pcb->ldt_size = LDTSZ * sizeof (ldt_entry_t);
    
    tss->ldt = LDT_SEL;

    /*
     * Kernel state.
     */
    tss->ss0 = KDS_SEL;
    tss->eflags = EFL_IF;
    tss->ss = KDS_SEL;
    tss->cs = KCS_SEL;
    tss->ds = KDS_SEL;
    tss->es = KDS_SEL;
    tss->fs = LDATA_SEL;
    tss->gs = NULL_SEL;
    tss->io_bmap = TSS_SIZE(0);
}

void
pcb_common_init(
    task_t		task
)
{
    pcb_common_t	*common = (void *)kalloc(sizeof (*common));
    
    lock_init(&common->lock, TRUE);
    common->ldt = (vm_offset_t)ldt + KERNEL_LINEAR_BASE;
    common->ldt_size = LDTSZ * sizeof (ldt_entry_t);
    common->io_bmap.length = common->io_bmap.base = 0;
    task->pcb_common = common;
}

/*
 * Set the thread's io space to the region
 * defined by base and length.  The TSS is
 * expanded if necessary.  Thread must either
 * be the current thread, or suspended.
 * N.B. On exit, the thread is guaranteed to
 * have an external TSS.
 */
static void
thread_set_io_bmap(
    thread_t		thread,
    vm_offset_t		base,
    vm_size_t		length
)
{
    struct pcb		*pcb = thread->pcb;
    
    if (pcb->tss_size < TSS_SIZE(length + 1)) {
	struct tss_alloc	new_tss_alloc, old_tss_alloc;
	tss_t			*new_tss, *old_tss;

	/*
	 * TSS needs to be enlarged.
	 */
	new_tss_alloc.length = TSS_SIZE(length + 1);
	new_tss_alloc.base = (vm_offset_t)kalloc(new_tss_alloc.length);
		
	new_tss = (tss_t *)new_tss_alloc.base;
	old_tss = pcb->tss;

	/*
	 * Copy old TSS to new one.
	 */
	*new_tss = *old_tss;

	if (pcb->extern_tss) {
	    /*
	     * Deal with old external tss.
	     */	
	    old_tss_alloc = pcb->tss_store.external;
    
	    /*
	     * Switch to new TSS.
	     */	
	    pcb->tss_store.external = new_tss_alloc;
	    pcb->tss = new_tss;
	    pcb->tss_size = new_tss_alloc.length;
	    pcb->extern_tss = TRUE;
		
	    /*
	     * Free old TSS.
	     */
	    kfree((void *)old_tss_alloc.base, old_tss_alloc.length);
	}
	else {
	    /*
	     * Switch to new TSS.
	     */	
	    pcb->tss_store.external = new_tss_alloc;
	    pcb->tss = new_tss;
	    pcb->tss_size = new_tss_alloc.length;
	    pcb->extern_tss = TRUE;
	}

	/*
	 * If changing the current
	 * thread, remap its tss now.
	 */	
	if (thread == current_thread()) {
	    map_tss(sel_to_gdt_entry(TSS_SEL),
			(vm_offset_t) thread->pcb->tss
			    + KERNEL_LINEAR_BASE,
				(vm_size_t) thread->pcb->tss_size);
	    ltr();
	}
    }
    
    memcpy(
	(void *)pcb->tss + pcb->tss->io_bmap,
		(void *)base, length);
}

static void
task_update_io_bmap(
    task_t		task
)
{
    queue_head_t	*list;
    thread_t		thread, prev_thread;
    pcb_common_t	*common = task->pcb_common;
    
    list = &task->thread_list;
    prev_thread = THREAD_NULL;
    task_lock(task);
    thread = (thread_t)queue_first(list);
    while (!queue_end(list, (queue_entry_t)thread)) {
	thread_reference(thread);
	task_unlock(task);
	if (prev_thread != THREAD_NULL)
	    thread_deallocate(prev_thread);
	thread_set_io_bmap(thread,
			    common->io_bmap.base,
			    common->io_bmap.length);
	prev_thread = thread;
	task_lock(task);
	thread = (thread_t)queue_next(&thread->thread_list);
    }
    task_unlock(task);
    if (prev_thread != THREAD_NULL)
    	thread_deallocate(prev_thread);
}

kern_return_t
task_map_io_ports(
    task_t		task,
    unsigned int	port,
    unsigned int	length,
    boolean_t		unmap
)
{
    pcb_common_t	*common = task->pcb_common;
    vm_offset_t		new_io_bmap_base;
    vm_size_t		new_io_bmap_length;
    int			i;

    /*
     * Bad port range.
     */	
    if ((port + length) > 65536)
    	return (KERN_INVALID_ADDRESS);
	
    if (task->kernel_vm_space)
    	return (KERN_SUCCESS);

    /*
     * If task is being terminated,
     * don't bother.
     */ 
    if (task_hold(task) != KERN_SUCCESS)
    	return (KERN_INVALID_ARGUMENT);
    
    new_io_bmap_length = roundup(port + length, NBBY) / NBBY;
    
    lock_write(&common->lock);
    
    if (common->io_bmap.length < new_io_bmap_length) {
	new_io_bmap_base = (vm_offset_t)kalloc(new_io_bmap_length);
	
	/*
	 * Invalidate all of new IO bitmap.
	 */
	memset(
	    (void *)new_io_bmap_base,
		    0xff, new_io_bmap_length);
	
	/*
	 * Copy old IO bitmap.
	 */
	memcpy(
	    (void *)new_io_bmap_base,
	    	(void *)common->io_bmap.base, common->io_bmap.length);
		
	kfree((void *)common->io_bmap.base, common->io_bmap.length);
	
	common->io_bmap.base = new_io_bmap_base;
	common->io_bmap.length = new_io_bmap_length;
    }
    
    for (i = port; i < (port + length); i++)
    	if (unmap)
	    setbit(common->io_bmap.base, i);
	else
	    clrbit(common->io_bmap.base, i);
	    
    (void) task_dowait(task, TRUE);
    
    task_update_io_bmap(task);
    
    (void) task_release(task);
    
    lock_done(&common->lock);
    
    return (KERN_SUCCESS);
}

static void
thread_set_ldt(
    thread_t		thread,
    vm_offset_t		address,
    vm_size_t		size
)
{
    thread->pcb->ldt = address;
    thread->pcb->ldt_size = size;

    /*
     * If changing the current
     * thread, remap its LDT now.
     */	
    if (thread == current_thread()) {
	map_ldt(sel_to_gdt_entry(LDT_SEL),
		    (vm_offset_t) thread->pcb->ldt,
			    (vm_size_t) thread->pcb->ldt_size);
	lldt();
    }
}

static void
task_update_ldt(
    task_t		task
)
{
    queue_head_t	*list;
    thread_t		thread, prev_thread;
    pcb_common_t	*common = task->pcb_common;
    
    list = &task->thread_list;
    prev_thread = THREAD_NULL;
    task_lock(task);
    thread = (thread_t)queue_first(list);
    while (!queue_end(list, (queue_entry_t)thread)) {
	thread_reference(thread);
	task_unlock(task);
	if (prev_thread != THREAD_NULL)
	    thread_deallocate(prev_thread);
	thread_set_ldt(thread, common->ldt, common->ldt_size);
	prev_thread = thread;
	task_lock(task);
	thread = (thread_t)queue_next(&thread->thread_list);
    }
    task_unlock(task);
    if (prev_thread != THREAD_NULL)
    	thread_deallocate(prev_thread);
}

kern_return_t
task_locate_ldt(
    task_t		task,
    vm_offset_t		address,
    vm_size_t		size
)
{
    pcb_common_t	*common = task->pcb_common;
    
    if (vm_map_min(task->map) > address ||
	    vm_map_max(task->map) <= (address + size))
	return (KERN_INVALID_ADDRESS);

    /*
     * If task is being terminated,
     * don't bother.
     */ 
    if (task_hold(task) != KERN_SUCCESS)
    	return (KERN_INVALID_ARGUMENT);
    
    lock_write(&common->lock);
    
    common->ldt = address;
    common->ldt_size = size;
    
    (void) task_dowait(task, TRUE);
    
    task_update_ldt(task);
    
    (void) task_release(task);
    
    lock_done(&common->lock);
    
    return (KERN_SUCCESS);
}

kern_return_t
task_default_ldt(
    task_t		task
)
{
    pcb_common_t	*common = task->pcb_common;

    /*
     * If task is being terminated,
     * don't bother.
     */ 
    if (task_hold(task) != KERN_SUCCESS)
    	return (KERN_INVALID_ARGUMENT);
    
    lock_write(&common->lock);
    
    common->ldt = (vm_offset_t)ldt + KERNEL_LINEAR_BASE;
    common->ldt_size = LDTSZ * sizeof (ldt_entry_t);
    
    (void) task_dowait(task, TRUE);
    
    task_update_ldt(task);
    
    (void) task_release(task);
    
    lock_done(&common->lock);
    
    return (KERN_SUCCESS);
}

thread_saved_state_t *
thread_user_state(
    thread_t		thread
)
{
    thread_save_area_t	*save_area = thread->pcb->save_area;

    if (save_area == 0) {
    	thread_saved_state_t	*saved_state;

    	thread->pcb->save_area =
		save_area = (void *)kalloc(sizeof (*save_area));

	saved_state = &save_area->sa_u;
	*saved_state = (thread_saved_state_t) { 0 };
	saved_state->frame.eflags = EFL_IF;
	saved_state->frame.cs = UCODE_SEL;
	saved_state->frame.ss = UDATA_SEL;
	saved_state->regs.ds = UDATA_SEL;
	saved_state->regs.es = UDATA_SEL;
	saved_state->regs.fs = NULL_SEL;
	saved_state->regs.gs = NULL_SEL;
	
	return (saved_state);
    }
    else
	return (&save_area->sa_u);
}

/*
 * Entry point for new user
 * threads.
 */
void
thread_bootstrap_return(void)
{
    thread_t		thread = current_thread();
    pcb_common_t	*common = thread->task->pcb_common;
    struct pcb		*pcb = thread->pcb;

    lock_read(&common->lock);
    
    if (common->io_bmap.length > 0)
    	thread_set_io_bmap(thread,
			    common->io_bmap.base,
			    common->io_bmap.length);
    
    if (common->ldt != pcb->ldt)
	thread_set_ldt(thread, common->ldt, common->ldt_size);
	
    lock_done(&common->lock);
    
    thread_exception_return();
    /* NOTREACHED */
}

void
thread_exception_return(void)
{
    thread_t			thread = current_thread();
    thread_saved_state_t	*saved_state = USER_REGS(thread);

    check_for_ast(saved_state);		// *may* not return

    thread->pcb->tss->esp0 =
	    ((saved_state->frame.eflags & EFL_VM) ?
					KERNEL_V86_STACK_BASE(saved_state) :
					KERNEL_STACK_BASE(saved_state));

    _return_with_state(saved_state);
    /* NOTREACHED */
}

void __volatile__
thread_syscall_return(
    kern_return_t	result
)
{
    thread_t			thread = current_thread();
    thread_saved_state_t	*saved_state = USER_REGS(thread);
    
    saved_state->regs.eax = result;

    check_for_ast(saved_state);		// *may* not return

    thread->pcb->tss->esp0 =
	    ((saved_state->frame.eflags & EFL_VM) ?
					KERNEL_V86_STACK_BASE(saved_state) :
					KERNEL_STACK_BASE(saved_state));

    _return_with_state(saved_state);
    /* NOTREACHED */
}

void
thread_set_syscall_return(
    thread_t		thread,
    kern_return_t	result
)
{
    thread_saved_state_t	*saved_state = USER_REGS(thread);
    
    saved_state->regs.eax = result;
}

/*
 * Called from locore to
 * startup the first thread. (never returns)
 */
void
start_initial_context(
    thread_t		thread
)
{
    struct pcb		*pcb = thread->pcb;

    /*
     * Initialize the common,
     * static LDT.
     */
    ldt_init();

    /*
     * Change software state.
     */
   PMAP_ACTIVATE(vm_map_pmap(thread->task->map), thread, 0);

   current_thread() = thread;
   current_stack() = thread->kernel_stack;
   current_stack_pointer() = thread->kernel_stack + KERNEL_STACK_SIZE;

    /*
     * Change hardware state.
     */
    set_cr3(pcb->tss->cr3);

    map_ldt(sel_to_gdt_entry(LDT_SEL),
		(vm_offset_t) pcb->ldt,
			(vm_size_t) pcb->ldt_size);
    lldt();

    map_tss(sel_to_gdt_entry(TSS_SEL),
    		(vm_offset_t) pcb->tss
		    + KERNEL_LINEAR_BASE,
			(vm_size_t) pcb->tss_size);
    ltr(); setts();
			
    _switch_tss(0, pcb->tss, 0);
}

/*
 * 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 i386_THREAD_STATE:
	return (set_thread_state(thread, tstate, count));
    
    case i386_THREAD_FPSTATE:
 	return (set_thread_fpstate(thread, tstate, count));

    default:
	return (KERN_INVALID_ARGUMENT);
    }
}

static void set_thread_v86_state(
		thread_t			thread,
		i386_thread_state_t		*state);

kern_return_t
set_thread_state(
    thread_t			thread,
    thread_state_t		tstate,
    unsigned int		count
)
{
    thread_saved_state_t	*saved_state;
    i386_thread_state_t		*state;
    
    if (count < i386_THREAD_STATE_COUNT)
	return (KERN_INVALID_ARGUMENT);
	
    state = (i386_thread_state_t *)tstate;
    
    if ((state->eflags & EFL_VM) != 0)
	set_thread_v86_state(thread, state);
    else {
	if (!thread->task->kernel_privilege) {
	    /*
	     * Validate segment selector values.
	     */
	    if (
		    !valid_user_code_selector(state->cs) ||
		    !valid_user_data_selector(state->ds) ||
		    !valid_user_data_selector(state->es) ||
		    !valid_user_data_selector(state->fs) ||
		    !valid_user_data_selector(state->gs) ||
		    !valid_user_stack_selector(state->ss)
		)
		return (KERN_INVALID_ARGUMENT);
	}
	else {
	    tss_t	*tss = thread->pcb->tss;
	    /*
	     * 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);
		
	    tss->eax = state->eax;
	    tss->ebx = state->ebx;
	    tss->ecx = state->ecx;
	    tss->edx = state->edx;
	    tss->edi = state->edi;
	    tss->esi = state->esi;
		
	    thread_start(thread, state->eip);
	    
	    return (KERN_SUCCESS);
	}

	saved_state = USER_REGS(thread);
	    
	saved_state->regs.eax = state->eax;
	saved_state->regs.ebx = state->ebx;
	saved_state->regs.ecx = state->ecx;
	saved_state->regs.edx = state->edx;
	saved_state->regs.edi = state->edi;
	saved_state->regs.esi = state->esi;
	saved_state->regs.ebp = state->ebp;
	saved_state->frame.esp = state->esp;
	saved_state->frame.ss = selector_to_sel(state->ss);
	saved_state->frame.eflags = state->eflags;
	saved_state->frame.eflags &= ~( EFL_VM | EFL_NT | EFL_IOPL | EFL_CLR );
	saved_state->frame.eflags |=  ( EFL_IF | EFL_SET );
	saved_state->frame.eip = state->eip;
	saved_state->frame.cs = selector_to_sel(state->cs);
	saved_state->regs.ds = selector_to_sel(state->ds);
	saved_state->regs.es = selector_to_sel(state->es);
	saved_state->regs.fs = selector_to_sel(state->fs);
	saved_state->regs.gs = selector_to_sel(state->gs);
    }

    return (KERN_SUCCESS);
}

static
void
set_thread_v86_state(
    thread_t			thread,
    i386_thread_state_t		*state
)
{
    thread_saved_state_t	*saved_state = USER_REGS(thread);

    saved_state->regs.eax = state->eax;
    saved_state->regs.ebx = state->ebx;
    saved_state->regs.ecx = state->ecx;
    saved_state->regs.edx = state->edx;
    saved_state->regs.edi = state->edi;
    saved_state->regs.esi = state->esi;
    saved_state->regs.ebp = state->ebp;
    saved_state->frame.esp = state->esp;
    saved_state->frame.ss = selector_to_sel(state->ss);
    saved_state->frame.eflags = state->eflags;
    saved_state->frame.eflags &= ~( EFL_NT | EFL_IOPL | EFL_CLR );
    saved_state->frame.eflags |=  ( EFL_VM | EFL_IF | EFL_SET );
    saved_state->frame.eip = state->eip;
    saved_state->frame.cs = selector_to_sel(state->cs);
    saved_state->regs.ds = NULL_SEL;
    saved_state->regs.es = NULL_SEL;
    saved_state->regs.fs = NULL_SEL;
    saved_state->regs.gs = NULL_SEL;
    saved_state->frame.v_ds = state->ds;
    saved_state->frame.v_es = state->es;
    saved_state->frame.v_fs = state->fs;
    saved_state->frame.v_gs = state->gs;
}

kern_return_t
set_thread_fpstate(
    thread_t			thread,
    thread_state_t		tstate,
    unsigned int		count
)
{
    fp_state_t			*saved_state;
    i386_thread_fpstate_t	*state;
    
    if (count < i386_THREAD_FPSTATE_COUNT)
	return (KERN_INVALID_ARGUMENT);
	
    state = (i386_thread_fpstate_t *)tstate;
    saved_state = &thread->pcb->fpstate;
    
    fp_terminate(thread);
	
    saved_state->environ = state->environ;
    saved_state->stack = state->stack;
    
    thread->pcb->fpvalid = TRUE;

    return (KERN_SUCCESS);
}

kern_return_t
thread_set_cthread_self(
    int				self
)
{
   current_thread()->pcb->cthread_self = (unsigned int)self;
   
   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 i386_THREAD_STATE:
	return (get_thread_state(thread, tstate, count));
	
    case i386_THREAD_FPSTATE:
	return (get_thread_fpstate(thread, tstate, count));
	
    case i386_THREAD_EXCEPTSTATE:
	return (get_thread_exceptstate(thread, tstate, count));
	
    case i386_THREAD_CTHREADSTATE:
	return (get_thread_cthreadstate(thread, tstate, count));

    case THREAD_STATE_FLAVOR_LIST:
	return (get_thread_state_flavor_list(tstate, count));

    default:
	return (KERN_INVALID_ARGUMENT);
    }
}

kern_return_t
get_thread_state(
    thread_t			thread,
    thread_state_t		tstate,
    unsigned int		*count
)
{
    thread_saved_state_t	*saved_state;
    i386_thread_state_t		*state;
    
    if (*count < i386_THREAD_STATE_COUNT)
	return (KERN_INVALID_ARGUMENT);
	
    state = (i386_thread_state_t *)tstate;
    saved_state = USER_REGS(thread);

    state->eax = saved_state->regs.eax;
    state->ebx = saved_state->regs.ebx;
    state->ecx = saved_state->regs.ecx;
    state->edx = saved_state->regs.edx;
    state->edi = saved_state->regs.edi;
    state->esi = saved_state->regs.esi;
    state->ebp = saved_state->regs.ebp;
    state->esp = saved_state->frame.esp;
    state->ss = sel_to_selector(saved_state->frame.ss);
    state->eflags = saved_state->frame.eflags;
    state->eip = saved_state->frame.eip;
    state->cs = sel_to_selector(saved_state->frame.cs);
    if ((saved_state->frame.eflags & EFL_VM) == 0) {
	state->ds = sel_to_selector(saved_state->regs.ds);
	state->es = sel_to_selector(saved_state->regs.es);
	state->fs = sel_to_selector(saved_state->regs.fs);
	state->gs = sel_to_selector(saved_state->regs.gs);
    }
    else {
	state->ds = saved_state->frame.v_ds;
	state->es = saved_state->frame.v_es;
	state->fs = saved_state->frame.v_fs;
	state->gs = saved_state->frame.v_gs;
    }
	
    *count = i386_THREAD_STATE_COUNT;
    
    return (KERN_SUCCESS);
}

kern_return_t
get_thread_fpstate(
    thread_t			thread,
    thread_state_t		tstate,
    unsigned int		*count
)
{
    fp_state_t			*saved_state;
    i386_thread_fpstate_t	*state;
    
    if (*count < i386_THREAD_FPSTATE_COUNT)
	return (KERN_INVALID_ARGUMENT);
	
    state = (i386_thread_fpstate_t *)tstate;
    saved_state = &thread->pcb->fpstate;
    
    fp_synch(thread);
    
    state->environ = saved_state->environ;
    state->stack = saved_state->stack;
    
    *count = i386_THREAD_FPSTATE_COUNT;
    
    return (KERN_SUCCESS);
}

kern_return_t
get_thread_exceptstate(
    thread_t			thread,
    thread_state_t		tstate,
    unsigned int		*count
)
{
    thread_saved_state_t	*saved_state;
    i386_thread_exceptstate_t	*state;
    
    if (*count < i386_THREAD_EXCEPTSTATE_COUNT)
	return (KERN_INVALID_ARGUMENT);
	
    state = (i386_thread_exceptstate_t *)tstate;
    saved_state = USER_REGS(thread);
    
    state->trapno = saved_state->trapno;
    state->err = saved_state->frame.err;
    
    *count = i386_THREAD_EXCEPTSTATE_COUNT;
    
    return (KERN_SUCCESS);
}

kern_return_t
get_thread_cthreadstate(
    thread_t			thread,
    thread_state_t		tstate,
    unsigned int		*count
)
{
    i386_thread_cthreadstate_t	*state;
    
    if (*count < i386_THREAD_CTHREADSTATE_COUNT)
	return (KERN_INVALID_ARGUMENT);
	
    state = (i386_thread_cthreadstate_t *)tstate;
    state->self = thread->pcb->cthread_self;
    
    *count = i386_THREAD_CTHREADSTATE_COUNT;
    
    return (KERN_SUCCESS);
}

kern_return_t
thread_get_cthread_self(
    void
)
{
    return ((kern_return_t)current_thread()->pcb->cthread_self);
}

kern_return_t
get_thread_state_flavor_list(
    thread_state_t		tstate,
    unsigned int		*count
)
{
    struct thread_state_flavor	*state;
#define i386_THREAD_STATE_FLAVOR_COUNT	4

#define i386_THREAD_STATE_FLAVOR_LIST_COUNT			\
    ( i386_THREAD_STATE_FLAVOR_COUNT *				\
	( sizeof (struct thread_state_flavor) / sizeof (int) ) )
    
    if (*count < i386_THREAD_STATE_FLAVOR_COUNT)
	return (KERN_INVALID_ARGUMENT);
	
    state = (struct thread_state_flavor *)tstate;

    state->flavor = i386_THREAD_STATE;
    state->count = i386_THREAD_STATE_COUNT;
    
    (++state)->flavor = i386_THREAD_FPSTATE;
    state->count = i386_THREAD_FPSTATE_COUNT;
    
    (++state)->flavor = i386_THREAD_EXCEPTSTATE;
    state->count = i386_THREAD_EXCEPTSTATE_COUNT;
    
    (++state)->flavor = i386_THREAD_CTHREADSTATE;
    state->count = i386_THREAD_CTHREADSTATE_COUNT;
    
    *count = i386_THREAD_STATE_FLAVOR_LIST_COUNT;
    
    return (KERN_SUCCESS);
}

/*
 * 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
)
{
    i386_thread_state_t	*state;

    /*
     * Set a default.
     */
    if (*user_stack == 0)
	*user_stack = VM_MAX_ADDRESS;
		
    switch (flavor) {

    case i386_THREAD_STATE:
	if (count < i386_THREAD_STATE_COUNT)
	    return (KERN_INVALID_ARGUMENT);

	state = (i386_thread_state_t *) tstate;

	/*
	 * If a valid user stack is specified, use it.
	 */
	*user_stack = state->esp ? state->esp: VM_MAX_ADDRESS;
	break;
    }

    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
)
{
    i386_thread_state_t	*state;

    /*
     * Set a default.
     */
    if (*entry_point == 0)
	*entry_point = VM_MIN_ADDRESS;
		
    switch (flavor) {

    case i386_THREAD_STATE:
	if (count < i386_THREAD_STATE_COUNT)
	    return (KERN_INVALID_ARGUMENT);

	state = (i386_thread_state_t *) tstate;

	/*
	 * If a valid entry point is specified, use it.
	 */
	*entry_point = state->eip ? state->eip: VM_MIN_ADDRESS;
	break;
    }

    return (KERN_SUCCESS);
}

/*
 * Duplicate parent state in child
 * for U**X fork.
 */
thread_dup(
    thread_t		parent,
    thread_t		child
)
{
    struct thread_saved_state	*parent_state, *child_state;

    parent_state = USER_REGS(parent);
    child_state = thread_user_state(child);

    *child_state = *parent_state;

    child_state->regs.eax = child->task->proc->p_pid;

    child_state->regs.edx = 1;
    child_state->frame.eflags &= ~EFL_CF;
}

/*
 * Release resources on
 * thread termination.
 */
pcb_terminate(
    thread_t		thread
)
{
    struct pcb		*pcb = thread->pcb;

    /*
     * Give up the fpu if
     * necessary.
     */
    fp_terminate(thread);

#if	PC_SUPPORT    
    if (pcb->PCpriv)
    	PCdestroy(thread);
#endif

    /*
     * Free save area.
     */
    if (pcb->save_area)
    	kfree((void *)pcb->save_area,
		sizeof (*pcb->save_area));

    /*
     * Free external tss.
     */
    if (pcb->extern_tss)
	kfree(
	    (void *)pcb->tss_store.external.base,
	    		pcb->tss_store.external.length);
			
    thread->pcb = 0;
    zfree(pcb_zone, pcb);
}

/*
 * Synchronize pcb with
 * hardware state.
 */
pcb_synch(
    thread_t		thread
)
{
    /*
     * Write out our fp state
     * if necessary.
     */
    fp_synch(thread);
}

void
pcb_common_terminate(
    task_t		task
)
{
    pcb_common_t	*common = task->pcb_common;
    
    kfree((void *)common, sizeof (*common));
}