2005-04-17 02:20:36 +04:00
/*
* Copyright ( C ) 1992 Krishna Balasubramanian and Linus Torvalds
* Copyright ( C ) 1999 Ingo Molnar < mingo @ redhat . com >
* Copyright ( C ) 2002 Andi Kleen
2008-01-30 15:30:13 +03:00
*
2005-04-17 02:20:36 +04:00
* This handles calls from both 32 bit and 64 bit mode .
*/
# include <linux/errno.h>
# include <linux/sched.h>
# include <linux/string.h>
# include <linux/mm.h>
# include <linux/smp.h>
# include <linux/vmalloc.h>
# include <asm/uaccess.h>
# include <asm/system.h>
# include <asm/ldt.h>
# include <asm/desc.h>
2008-01-30 15:30:13 +03:00
# include <asm/mmu_context.h>
2005-04-17 02:20:36 +04:00
2008-01-30 15:30:13 +03:00
# ifdef CONFIG_SMP
2005-04-17 02:20:36 +04:00
static void flush_ldt ( void * null )
{
if ( current - > active_mm )
2008-01-30 15:30:13 +03:00
load_LDT ( & current - > active_mm - > context ) ;
2005-04-17 02:20:36 +04:00
}
# endif
2008-01-30 15:30:13 +03:00
static int alloc_ldt ( mm_context_t * pc , int mincount , int reload )
2005-04-17 02:20:36 +04:00
{
2008-01-30 15:30:13 +03:00
void * oldldt , * newldt ;
int oldsize ;
2005-04-17 02:20:36 +04:00
2008-01-30 15:30:13 +03:00
if ( mincount < = pc - > size )
2005-04-17 02:20:36 +04:00
return 0 ;
oldsize = pc - > size ;
2008-01-30 15:30:13 +03:00
mincount = ( mincount + 511 ) & ( ~ 511 ) ;
if ( mincount * LDT_ENTRY_SIZE > PAGE_SIZE )
newldt = vmalloc ( mincount * LDT_ENTRY_SIZE ) ;
2005-04-17 02:20:36 +04:00
else
2008-01-30 15:33:14 +03:00
newldt = ( void * ) __get_free_page ( GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! newldt )
return - ENOMEM ;
if ( oldsize )
2008-01-30 15:30:13 +03:00
memcpy ( newldt , pc - > ldt , oldsize * LDT_ENTRY_SIZE ) ;
2005-04-17 02:20:36 +04:00
oldldt = pc - > ldt ;
2008-01-30 15:30:13 +03:00
memset ( newldt + oldsize * LDT_ENTRY_SIZE , 0 ,
( mincount - oldsize ) * LDT_ENTRY_SIZE ) ;
2008-01-30 15:30:14 +03:00
# ifdef CONFIG_X86_64
/* CHECKME: Do we really need this ? */
2005-04-17 02:20:36 +04:00
wmb ( ) ;
2008-01-30 15:30:14 +03:00
# endif
2005-04-17 02:20:36 +04:00
pc - > ldt = newldt ;
wmb ( ) ;
pc - > size = mincount ;
wmb ( ) ;
2008-01-30 15:30:13 +03:00
2005-04-17 02:20:36 +04:00
if ( reload ) {
# ifdef CONFIG_SMP
cpumask_t mask ;
preempt_disable ( ) ;
load_LDT ( pc ) ;
2008-01-30 15:30:13 +03:00
mask = cpumask_of_cpu ( smp_processor_id ( ) ) ;
2005-04-17 02:20:36 +04:00
if ( ! cpus_equal ( current - > mm - > cpu_vm_mask , mask ) )
smp_call_function ( flush_ldt , NULL , 1 , 1 ) ;
preempt_enable ( ) ;
# else
load_LDT ( pc ) ;
# endif
}
if ( oldsize ) {
2008-01-30 15:30:13 +03:00
if ( oldsize * LDT_ENTRY_SIZE > PAGE_SIZE )
2005-04-17 02:20:36 +04:00
vfree ( oldldt ) ;
else
2008-01-30 15:33:14 +03:00
put_page ( virt_to_page ( oldldt ) ) ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
static inline int copy_ldt ( mm_context_t * new , mm_context_t * old )
{
int err = alloc_ldt ( new , old - > size , 0 ) ;
2008-01-30 15:30:13 +03:00
2005-04-17 02:20:36 +04:00
if ( err < 0 )
return err ;
2008-01-30 15:30:13 +03:00
memcpy ( new - > ldt , old - > ldt , old - > size * LDT_ENTRY_SIZE ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/*
* we do not have to muck with descriptors here , that is
* done in switch_mm ( ) as needed .
*/
int init_new_context ( struct task_struct * tsk , struct mm_struct * mm )
{
2008-01-30 15:30:13 +03:00
struct mm_struct * old_mm ;
2005-04-17 02:20:36 +04:00
int retval = 0 ;
2007-10-17 20:04:41 +04:00
mutex_init ( & mm - > context . lock ) ;
2005-04-17 02:20:36 +04:00
mm - > context . size = 0 ;
old_mm = current - > mm ;
if ( old_mm & & old_mm - > context . size > 0 ) {
2007-10-17 20:04:41 +04:00
mutex_lock ( & old_mm - > context . lock ) ;
2005-04-17 02:20:36 +04:00
retval = copy_ldt ( & mm - > context , & old_mm - > context ) ;
2007-10-17 20:04:41 +04:00
mutex_unlock ( & old_mm - > context . lock ) ;
2005-04-17 02:20:36 +04:00
}
return retval ;
}
/*
2008-01-30 15:30:14 +03:00
* No need to lock the MM as we are the last user
*
* 64 bit : Don ' t touch the LDT register - we ' re already in the next thread .
2005-04-17 02:20:36 +04:00
*/
void destroy_context ( struct mm_struct * mm )
{
if ( mm - > context . size ) {
2008-01-30 15:30:14 +03:00
# ifdef CONFIG_X86_32
/* CHECKME: Can this ever happen ? */
if ( mm = = current - > active_mm )
clear_LDT ( ) ;
# endif
2008-01-30 15:30:13 +03:00
if ( mm - > context . size * LDT_ENTRY_SIZE > PAGE_SIZE )
2005-04-17 02:20:36 +04:00
vfree ( mm - > context . ldt ) ;
else
2008-01-30 15:33:14 +03:00
put_page ( virt_to_page ( mm - > context . ldt ) ) ;
2005-04-17 02:20:36 +04:00
mm - > context . size = 0 ;
}
}
2008-01-30 15:30:13 +03:00
static int read_ldt ( void __user * ptr , unsigned long bytecount )
2005-04-17 02:20:36 +04:00
{
int err ;
unsigned long size ;
2008-01-30 15:30:13 +03:00
struct mm_struct * mm = current - > mm ;
2005-04-17 02:20:36 +04:00
if ( ! mm - > context . size )
return 0 ;
2008-01-30 15:30:13 +03:00
if ( bytecount > LDT_ENTRY_SIZE * LDT_ENTRIES )
bytecount = LDT_ENTRY_SIZE * LDT_ENTRIES ;
2005-04-17 02:20:36 +04:00
2007-10-17 20:04:41 +04:00
mutex_lock ( & mm - > context . lock ) ;
2008-01-30 15:30:13 +03:00
size = mm - > context . size * LDT_ENTRY_SIZE ;
2005-04-17 02:20:36 +04:00
if ( size > bytecount )
size = bytecount ;
err = 0 ;
if ( copy_to_user ( ptr , mm - > context . ldt , size ) )
err = - EFAULT ;
2007-10-17 20:04:41 +04:00
mutex_unlock ( & mm - > context . lock ) ;
2005-04-17 02:20:36 +04:00
if ( err < 0 )
goto error_return ;
if ( size ! = bytecount ) {
/* zero-fill the rest */
2008-01-30 15:30:13 +03:00
if ( clear_user ( ptr + size , bytecount - size ) ! = 0 ) {
2005-04-17 02:20:36 +04:00
err = - EFAULT ;
goto error_return ;
}
}
return bytecount ;
error_return :
return err ;
}
2008-01-30 15:30:13 +03:00
static int read_default_ldt ( void __user * ptr , unsigned long bytecount )
2005-04-17 02:20:36 +04:00
{
2008-01-30 15:30:14 +03:00
/* CHECKME: Can we use _one_ random number ? */
# ifdef CONFIG_X86_32
unsigned long size = 5 * sizeof ( struct desc_struct ) ;
# else
unsigned long size = 128 ;
# endif
if ( bytecount > size )
bytecount = size ;
2005-04-17 02:20:36 +04:00
if ( clear_user ( ptr , bytecount ) )
return - EFAULT ;
2008-01-30 15:30:13 +03:00
return bytecount ;
2005-04-17 02:20:36 +04:00
}
2008-01-30 15:30:13 +03:00
static int write_ldt ( void __user * ptr , unsigned long bytecount , int oldmode )
2005-04-17 02:20:36 +04:00
{
2008-01-30 15:30:13 +03:00
struct mm_struct * mm = current - > mm ;
2008-01-30 15:31:13 +03:00
struct desc_struct ldt ;
2005-04-17 02:20:36 +04:00
int error ;
struct user_desc ldt_info ;
error = - EINVAL ;
if ( bytecount ! = sizeof ( ldt_info ) )
goto out ;
2008-01-30 15:30:13 +03:00
error = - EFAULT ;
2008-01-30 15:30:13 +03:00
if ( copy_from_user ( & ldt_info , ptr , sizeof ( ldt_info ) ) )
2005-04-17 02:20:36 +04:00
goto out ;
error = - EINVAL ;
if ( ldt_info . entry_number > = LDT_ENTRIES )
goto out ;
if ( ldt_info . contents = = 3 ) {
if ( oldmode )
goto out ;
if ( ldt_info . seg_not_present = = 0 )
goto out ;
}
2007-10-17 20:04:41 +04:00
mutex_lock ( & mm - > context . lock ) ;
2008-01-30 15:30:13 +03:00
if ( ldt_info . entry_number > = mm - > context . size ) {
2008-01-30 15:30:13 +03:00
error = alloc_ldt ( & current - > mm - > context ,
ldt_info . entry_number + 1 , 1 ) ;
2005-04-17 02:20:36 +04:00
if ( error < 0 )
goto out_unlock ;
}
2008-01-30 15:30:13 +03:00
/* Allow LDTs to be cleared by the user. */
if ( ldt_info . base_addr = = 0 & & ldt_info . limit = = 0 ) {
2005-04-17 02:20:36 +04:00
if ( oldmode | | LDT_empty ( & ldt_info ) ) {
2008-01-30 15:31:13 +03:00
memset ( & ldt , 0 , sizeof ( ldt ) ) ;
2005-04-17 02:20:36 +04:00
goto install ;
}
}
2008-01-30 15:31:13 +03:00
fill_ldt ( & ldt , & ldt_info ) ;
2005-04-17 02:20:36 +04:00
if ( oldmode )
2008-01-30 15:31:13 +03:00
ldt . avl = 0 ;
2005-04-17 02:20:36 +04:00
/* Install the new entry ... */
install :
2008-01-30 15:31:13 +03:00
write_ldt_entry ( mm - > context . ldt , ldt_info . entry_number , & ldt ) ;
2005-04-17 02:20:36 +04:00
error = 0 ;
out_unlock :
2007-10-17 20:04:41 +04:00
mutex_unlock ( & mm - > context . lock ) ;
2005-04-17 02:20:36 +04:00
out :
return error ;
}
2008-01-30 15:30:13 +03:00
asmlinkage int sys_modify_ldt ( int func , void __user * ptr ,
unsigned long bytecount )
2005-04-17 02:20:36 +04:00
{
int ret = - ENOSYS ;
switch ( func ) {
case 0 :
ret = read_ldt ( ptr , bytecount ) ;
break ;
case 1 :
ret = write_ldt ( ptr , bytecount , 1 ) ;
break ;
case 2 :
ret = read_default_ldt ( ptr , bytecount ) ;
break ;
case 0x11 :
ret = write_ldt ( ptr , bytecount , 0 ) ;
break ;
}
return ret ;
}