2005-04-17 02:20:36 +04:00
/*
* IA32 helper functions
*
* Copyright ( C ) 1999 Arun Sharma < arun . sharma @ intel . com >
* Copyright ( C ) 2000 Asit K . Mallick < asit . k . mallick @ intel . com >
* Copyright ( C ) 2001 - 2002 Hewlett - Packard Co
* David Mosberger - Tang < davidm @ hpl . hp . com >
*
* 06 / 16 / 00 A . Mallick added csd / ssd / tssd for ia32 thread context
* 02 / 19 / 01 D . Mosberger dropped tssd ; it ' s not needed
* 09 / 14 / 01 D . Mosberger fixed memory management for gdt / tss page
* 09 / 29 / 01 D . Mosberger added ia32_load_segment_descriptors ( )
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/mm.h>
# include <linux/sched.h>
# include <asm/intrinsics.h>
# include <asm/page.h>
# include <asm/pgtable.h>
# include <asm/system.h>
# include <asm/processor.h>
# include <asm/uaccess.h>
# include "ia32priv.h"
2008-02-05 10:43:03 +03:00
extern int die_if_kernel ( char * str , struct pt_regs * regs , long err ) ;
2005-04-17 02:20:36 +04:00
struct page * ia32_shared_page [ NR_CPUS ] ;
unsigned long * ia32_boot_gdt ;
unsigned long * cpu_gdt_table [ NR_CPUS ] ;
struct page * ia32_gate_page ;
static unsigned long
load_desc ( u16 selector )
{
unsigned long * table , limit , index ;
if ( ! selector )
return 0 ;
if ( selector & IA32_SEGSEL_TI ) {
table = ( unsigned long * ) IA32_LDT_OFFSET ;
limit = IA32_LDT_ENTRIES ;
} else {
table = cpu_gdt_table [ smp_processor_id ( ) ] ;
limit = IA32_PAGE_SIZE / sizeof ( ia32_boot_gdt [ 0 ] ) ;
}
index = selector > > IA32_SEGSEL_INDEX_SHIFT ;
if ( index > = limit )
return 0 ;
return IA32_SEG_UNSCRAMBLE ( table [ index ] ) ;
}
void
ia32_load_segment_descriptors ( struct task_struct * task )
{
2006-01-12 12:06:06 +03:00
struct pt_regs * regs = task_pt_regs ( task ) ;
2005-04-17 02:20:36 +04:00
/* Setup the segment descriptors */
regs - > r24 = load_desc ( regs - > r16 > > 16 ) ; /* ESD */
regs - > r27 = load_desc ( regs - > r16 > > 0 ) ; /* DSD */
regs - > r28 = load_desc ( regs - > r16 > > 32 ) ; /* FSD */
regs - > r29 = load_desc ( regs - > r16 > > 48 ) ; /* GSD */
regs - > ar_csd = load_desc ( regs - > r17 > > 0 ) ; /* CSD */
regs - > ar_ssd = load_desc ( regs - > r17 > > 16 ) ; /* SSD */
}
int
ia32_clone_tls ( struct task_struct * child , struct pt_regs * childregs )
{
struct desc_struct * desc ;
struct ia32_user_desc info ;
int idx ;
if ( copy_from_user ( & info , ( void __user * ) ( childregs - > r14 & 0xffffffff ) , sizeof ( info ) ) )
return - EFAULT ;
if ( LDT_empty ( & info ) )
return - EINVAL ;
idx = info . entry_number ;
if ( idx < GDT_ENTRY_TLS_MIN | | idx > GDT_ENTRY_TLS_MAX )
return - EINVAL ;
desc = child - > thread . tls_array + idx - GDT_ENTRY_TLS_MIN ;
desc - > a = LDT_entry_a ( & info ) ;
desc - > b = LDT_entry_b ( & info ) ;
/* XXX: can this be done in a cleaner way ? */
load_TLS ( & child - > thread , smp_processor_id ( ) ) ;
ia32_load_segment_descriptors ( child ) ;
load_TLS ( & current - > thread , smp_processor_id ( ) ) ;
return 0 ;
}
void
ia32_save_state ( struct task_struct * t )
{
t - > thread . eflag = ia64_getreg ( _IA64_REG_AR_EFLAG ) ;
t - > thread . fsr = ia64_getreg ( _IA64_REG_AR_FSR ) ;
t - > thread . fcr = ia64_getreg ( _IA64_REG_AR_FCR ) ;
t - > thread . fir = ia64_getreg ( _IA64_REG_AR_FIR ) ;
t - > thread . fdr = ia64_getreg ( _IA64_REG_AR_FDR ) ;
ia64_set_kr ( IA64_KR_IO_BASE , t - > thread . old_iob ) ;
ia64_set_kr ( IA64_KR_TSSD , t - > thread . old_k1 ) ;
}
void
ia32_load_state ( struct task_struct * t )
{
unsigned long eflag , fsr , fcr , fir , fdr , tssd ;
2006-01-12 12:06:06 +03:00
struct pt_regs * regs = task_pt_regs ( t ) ;
2005-04-17 02:20:36 +04:00
eflag = t - > thread . eflag ;
fsr = t - > thread . fsr ;
fcr = t - > thread . fcr ;
fir = t - > thread . fir ;
fdr = t - > thread . fdr ;
tssd = load_desc ( _TSS ) ; /* TSSD */
ia64_setreg ( _IA64_REG_AR_EFLAG , eflag ) ;
ia64_setreg ( _IA64_REG_AR_FSR , fsr ) ;
ia64_setreg ( _IA64_REG_AR_FCR , fcr ) ;
ia64_setreg ( _IA64_REG_AR_FIR , fir ) ;
ia64_setreg ( _IA64_REG_AR_FDR , fdr ) ;
current - > thread . old_iob = ia64_get_kr ( IA64_KR_IO_BASE ) ;
current - > thread . old_k1 = ia64_get_kr ( IA64_KR_TSSD ) ;
ia64_set_kr ( IA64_KR_IO_BASE , IA32_IOBASE ) ;
ia64_set_kr ( IA64_KR_TSSD , tssd ) ;
regs - > r17 = ( _TSS < < 48 ) | ( _LDT < < 32 ) | ( __u32 ) regs - > r17 ;
regs - > r30 = load_desc ( _LDT ) ; /* LDTD */
load_TLS ( & t - > thread , smp_processor_id ( ) ) ;
}
/*
* Setup IA32 GDT and TSS
*/
void
ia32_gdt_init ( void )
{
int cpu = smp_processor_id ( ) ;
ia32_shared_page [ cpu ] = alloc_page ( GFP_KERNEL ) ;
if ( ! ia32_shared_page [ cpu ] )
panic ( " failed to allocate ia32_shared_page[%d] \n " , cpu ) ;
cpu_gdt_table [ cpu ] = page_address ( ia32_shared_page [ cpu ] ) ;
/* Copy from the boot cpu's GDT */
memcpy ( cpu_gdt_table [ cpu ] , ia32_boot_gdt , PAGE_SIZE ) ;
}
/*
* Setup IA32 GDT and TSS
*/
static void
ia32_boot_gdt_init ( void )
{
unsigned long ldt_size ;
ia32_shared_page [ 0 ] = alloc_page ( GFP_KERNEL ) ;
if ( ! ia32_shared_page [ 0 ] )
panic ( " failed to allocate ia32_shared_page[0] \n " ) ;
ia32_boot_gdt = page_address ( ia32_shared_page [ 0 ] ) ;
cpu_gdt_table [ 0 ] = ia32_boot_gdt ;
/* CS descriptor in IA-32 (scrambled) format */
ia32_boot_gdt [ __USER_CS > > 3 ]
= IA32_SEG_DESCRIPTOR ( 0 , ( IA32_GATE_END - 1 ) > > IA32_PAGE_SHIFT ,
0xb , 1 , 3 , 1 , 1 , 1 , 1 ) ;
/* DS descriptor in IA-32 (scrambled) format */
ia32_boot_gdt [ __USER_DS > > 3 ]
= IA32_SEG_DESCRIPTOR ( 0 , ( IA32_GATE_END - 1 ) > > IA32_PAGE_SHIFT ,
0x3 , 1 , 3 , 1 , 1 , 1 , 1 ) ;
ldt_size = PAGE_ALIGN ( IA32_LDT_ENTRIES * IA32_LDT_ENTRY_SIZE ) ;
ia32_boot_gdt [ TSS_ENTRY ] = IA32_SEG_DESCRIPTOR ( IA32_TSS_OFFSET , 235 ,
0xb , 0 , 3 , 1 , 1 , 1 , 0 ) ;
ia32_boot_gdt [ LDT_ENTRY ] = IA32_SEG_DESCRIPTOR ( IA32_LDT_OFFSET , ldt_size - 1 ,
0x2 , 0 , 3 , 1 , 1 , 1 , 0 ) ;
}
static void
ia32_gate_page_init ( void )
{
unsigned long * sr ;
ia32_gate_page = alloc_page ( GFP_KERNEL ) ;
sr = page_address ( ia32_gate_page ) ;
/* This is popl %eax ; movl $,%eax ; int $0x80 */
* sr + + = 0xb858 | ( __IA32_NR_sigreturn < < 16 ) | ( 0x80cdUL < < 48 ) ;
/* This is movl $,%eax ; int $0x80 */
* sr = 0xb8 | ( __IA32_NR_rt_sigreturn < < 8 ) | ( 0x80cdUL < < 40 ) ;
}
void
ia32_mem_init ( void )
{
ia32_boot_gdt_init ( ) ;
ia32_gate_page_init ( ) ;
}
/*
* Handle bad IA32 interrupt via syscall
*/
void
ia32_bad_interrupt ( unsigned long int_num , struct pt_regs * regs )
{
siginfo_t siginfo ;
2008-02-05 10:43:03 +03:00
if ( die_if_kernel ( " Bad IA-32 interrupt " , regs , int_num ) )
return ;
2005-04-17 02:20:36 +04:00
siginfo . si_signo = SIGTRAP ;
siginfo . si_errno = int_num ; /* XXX is it OK to abuse si_errno like this? */
siginfo . si_flags = 0 ;
siginfo . si_isr = 0 ;
siginfo . si_addr = NULL ;
siginfo . si_imm = 0 ;
siginfo . si_code = TRAP_BRKPT ;
force_sig_info ( SIGTRAP , & siginfo , current ) ;
}
void
ia32_cpu_init ( void )
{
/* initialize global ia32 state - CR0 and CR4 */
ia64_setreg ( _IA64_REG_AR_CFLAG , ( ( ( ulong ) IA32_CR4 < < 32 ) | IA32_CR0 ) ) ;
}
static int __init
ia32_init ( void )
{
# if PAGE_SHIFT > IA32_PAGE_SHIFT
{
2007-07-25 06:44:55 +04:00
extern struct kmem_cache * ia64_partial_page_cachep ;
2005-04-17 02:20:36 +04:00
2007-07-25 06:44:55 +04:00
ia64_partial_page_cachep = kmem_cache_create ( " ia64_partial_page_cache " ,
sizeof ( struct ia64_partial_page ) ,
0 , SLAB_PANIC , NULL ) ;
2005-04-17 02:20:36 +04:00
}
# endif
return 0 ;
}
__initcall ( ia32_init ) ;