2005-04-16 15:20:36 -07:00
/*
2007-10-16 01:27:08 -07:00
* Copyright ( C ) 2002 - 2007 Jeff Dike ( jdike @ { addtoit , linux . intel } . com )
2005-04-16 15:20:36 -07:00
* Licensed under the GPL
*/
2007-10-16 01:27:08 -07:00
# include "linux/err.h"
2005-04-16 15:20:36 -07:00
# include "linux/highmem.h"
2007-10-16 01:27:08 -07:00
# include "linux/mm.h"
# include "asm/current.h"
2005-04-16 15:20:36 -07:00
# include "asm/page.h"
# include "asm/pgtable.h"
# include "kern_util.h"
2006-01-18 17:42:41 -08:00
# include "os.h"
2005-04-16 15:20:36 -07:00
extern void * um_virt_to_phys ( struct task_struct * task , unsigned long addr ,
pte_t * pte_out ) ;
static unsigned long maybe_map ( unsigned long virt , int is_write )
{
pte_t pte ;
int err ;
void * phys = um_virt_to_phys ( current , virt , & pte ) ;
int dummy_code ;
2007-10-16 01:27:08 -07:00
if ( IS_ERR ( phys ) | | ( is_write & & ! pte_write ( pte ) ) ) {
2005-04-16 15:20:36 -07:00
err = handle_page_fault ( virt , 0 , is_write , 1 , & dummy_code ) ;
2007-10-16 01:27:08 -07:00
if ( err )
return - 1UL ;
2005-04-16 15:20:36 -07:00
phys = um_virt_to_phys ( current , virt , NULL ) ;
}
2007-10-16 01:27:08 -07:00
if ( IS_ERR ( phys ) )
phys = ( void * ) - 1 ;
2005-05-06 21:30:55 -07:00
2007-10-16 01:27:08 -07:00
return ( unsigned long ) phys ;
2005-04-16 15:20:36 -07:00
}
2006-07-01 04:36:19 -07:00
static int do_op_one_page ( unsigned long addr , int len , int is_write ,
2005-04-16 15:20:36 -07:00
int ( * op ) ( unsigned long addr , int len , void * arg ) , void * arg )
{
struct page * page ;
int n ;
addr = maybe_map ( addr , is_write ) ;
2007-10-16 01:27:08 -07:00
if ( addr = = - 1UL )
return - 1 ;
2005-04-16 15:20:36 -07:00
page = phys_to_page ( addr ) ;
2007-10-16 01:27:08 -07:00
addr = ( unsigned long ) kmap_atomic ( page , KM_UML_USERCOPY ) +
( addr & ~ PAGE_MASK ) ;
2006-07-01 04:36:19 -07:00
2005-04-16 15:20:36 -07:00
n = ( * op ) ( addr , len , arg ) ;
2006-07-01 04:36:19 -07:00
kunmap_atomic ( page , KM_UML_USERCOPY ) ;
2005-04-16 15:20:36 -07:00
2007-10-16 01:27:08 -07:00
return n ;
2005-04-16 15:20:36 -07:00
}
static void do_buffer_op ( void * jmpbuf , void * arg_ptr )
{
va_list args ;
unsigned long addr ;
int len , is_write , size , remain , n ;
int ( * op ) ( unsigned long , int , void * ) ;
void * arg ;
int * res ;
2005-05-01 08:58:54 -07:00
va_copy ( args , * ( va_list * ) arg_ptr ) ;
2005-04-16 15:20:36 -07:00
addr = va_arg ( args , unsigned long ) ;
len = va_arg ( args , int ) ;
is_write = va_arg ( args , int ) ;
op = va_arg ( args , void * ) ;
arg = va_arg ( args , void * ) ;
res = va_arg ( args , int * ) ;
va_end ( args ) ;
size = min ( PAGE_ALIGN ( addr ) - addr , ( unsigned long ) len ) ;
remain = len ;
current - > thread . fault_catcher = jmpbuf ;
2006-07-01 04:36:19 -07:00
n = do_op_one_page ( addr , size , is_write , op , arg ) ;
2007-10-16 01:27:08 -07:00
if ( n ! = 0 ) {
2005-04-16 15:20:36 -07:00
* res = ( n < 0 ? remain : 0 ) ;
goto out ;
}
addr + = size ;
remain - = size ;
2007-10-16 01:27:08 -07:00
if ( remain = = 0 ) {
2005-04-16 15:20:36 -07:00
* res = 0 ;
goto out ;
}
2007-10-16 01:27:08 -07:00
while ( addr < ( ( addr + remain ) & PAGE_MASK ) ) {
2006-07-01 04:36:19 -07:00
n = do_op_one_page ( addr , PAGE_SIZE , is_write , op , arg ) ;
2007-10-16 01:27:08 -07:00
if ( n ! = 0 ) {
2005-04-16 15:20:36 -07:00
* res = ( n < 0 ? remain : 0 ) ;
goto out ;
}
addr + = PAGE_SIZE ;
remain - = PAGE_SIZE ;
}
2007-10-16 01:27:08 -07:00
if ( remain = = 0 ) {
2005-04-16 15:20:36 -07:00
* res = 0 ;
goto out ;
}
2006-07-01 04:36:19 -07:00
n = do_op_one_page ( addr , remain , is_write , op , arg ) ;
2007-10-16 01:27:08 -07:00
if ( n ! = 0 )
2005-04-16 15:20:36 -07:00
* res = ( n < 0 ? remain : 0 ) ;
else * res = 0 ;
out :
current - > thread . fault_catcher = NULL ;
}
static int buffer_op ( unsigned long addr , int len , int is_write ,
int ( * op ) ( unsigned long addr , int len , void * arg ) ,
void * arg )
{
int faulted , res ;
faulted = setjmp_wrapper ( do_buffer_op , addr , len , is_write , op , arg ,
& res ) ;
2007-10-16 01:27:08 -07:00
if ( ! faulted )
return res ;
2005-04-16 15:20:36 -07:00
2007-10-16 01:27:08 -07:00
return addr + len - ( unsigned long ) current - > thread . fault_addr ;
2005-04-16 15:20:36 -07:00
}
static int copy_chunk_from_user ( unsigned long from , int len , void * arg )
{
unsigned long * to_ptr = arg , to = * to_ptr ;
memcpy ( ( void * ) to , ( void * ) from , len ) ;
* to_ptr + = len ;
2007-10-16 01:27:08 -07:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:26:56 -07:00
int copy_from_user ( void * to , const void __user * from , int n )
2005-04-16 15:20:36 -07:00
{
2007-10-16 01:27:08 -07:00
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
2005-04-16 15:20:36 -07:00
memcpy ( to , ( __force void * ) from , n ) ;
2007-10-16 01:27:08 -07:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:27:08 -07:00
return access_ok ( VERIFY_READ , from , n ) ?
2005-04-16 15:20:36 -07:00
buffer_op ( ( unsigned long ) from , n , 0 , copy_chunk_from_user , & to ) :
2007-10-16 01:27:08 -07:00
n ;
2005-04-16 15:20:36 -07:00
}
static int copy_chunk_to_user ( unsigned long to , int len , void * arg )
{
unsigned long * from_ptr = arg , from = * from_ptr ;
memcpy ( ( void * ) to , ( void * ) from , len ) ;
* from_ptr + = len ;
2007-10-16 01:27:08 -07:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:26:56 -07:00
int copy_to_user ( void __user * to , const void * from , int n )
2005-04-16 15:20:36 -07:00
{
2007-10-16 01:27:08 -07:00
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
memcpy ( ( __force void * ) to , from , n ) ;
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:27:08 -07:00
return access_ok ( VERIFY_WRITE , to , n ) ?
2005-04-16 15:20:36 -07:00
buffer_op ( ( unsigned long ) to , n , 1 , copy_chunk_to_user , & from ) :
2007-10-16 01:27:08 -07:00
n ;
2005-04-16 15:20:36 -07:00
}
static int strncpy_chunk_from_user ( unsigned long from , int len , void * arg )
{
char * * to_ptr = arg , * to = * to_ptr ;
int n ;
strncpy ( to , ( void * ) from , len ) ;
n = strnlen ( to , len ) ;
* to_ptr + = n ;
2007-10-16 01:27:08 -07:00
if ( n < len )
return 1 ;
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:26:56 -07:00
int strncpy_from_user ( char * dst , const char __user * src , int count )
2005-04-16 15:20:36 -07:00
{
int n ;
char * ptr = dst ;
2007-10-16 01:27:08 -07:00
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
strncpy ( dst , ( __force void * ) src , count ) ;
return strnlen ( dst , count ) ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:27:08 -07:00
if ( ! access_ok ( VERIFY_READ , src , 1 ) )
return - EFAULT ;
2005-04-16 15:20:36 -07:00
n = buffer_op ( ( unsigned long ) src , count , 0 , strncpy_chunk_from_user ,
& ptr ) ;
2007-10-16 01:27:08 -07:00
if ( n ! = 0 )
return - EFAULT ;
return strnlen ( dst , count ) ;
2005-04-16 15:20:36 -07:00
}
static int clear_chunk ( unsigned long addr , int len , void * unused )
{
memset ( ( void * ) addr , 0 , len ) ;
2007-10-16 01:27:08 -07:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:26:56 -07:00
int __clear_user ( void __user * mem , int len )
2005-04-16 15:20:36 -07:00
{
2007-10-16 01:27:08 -07:00
return buffer_op ( ( unsigned long ) mem , len , 1 , clear_chunk , NULL ) ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:26:56 -07:00
int clear_user ( void __user * mem , int len )
2005-04-16 15:20:36 -07:00
{
2007-10-16 01:27:08 -07:00
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
2005-04-16 15:20:36 -07:00
memset ( ( __force void * ) mem , 0 , len ) ;
2007-10-16 01:27:08 -07:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:27:08 -07:00
return access_ok ( VERIFY_WRITE , mem , len ) ?
buffer_op ( ( unsigned long ) mem , len , 1 , clear_chunk , NULL ) : len ;
2005-04-16 15:20:36 -07:00
}
static int strnlen_chunk ( unsigned long str , int len , void * arg )
{
int * len_ptr = arg , n ;
n = strnlen ( ( void * ) str , len ) ;
* len_ptr + = n ;
2007-10-16 01:27:08 -07:00
if ( n < len )
return 1 ;
return 0 ;
2005-04-16 15:20:36 -07:00
}
2007-10-16 01:26:56 -07:00
int strnlen_user ( const void __user * str , int len )
2005-04-16 15:20:36 -07:00
{
int count = 0 , n ;
2007-10-16 01:27:08 -07:00
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) )
return strnlen ( ( __force char * ) str , len ) + 1 ;
2005-04-16 15:20:36 -07:00
n = buffer_op ( ( unsigned long ) str , len , 0 , strnlen_chunk , & count ) ;
2007-10-16 01:27:08 -07:00
if ( n = = 0 )
return count + 1 ;
return - EFAULT ;
2005-04-16 15:20:36 -07:00
}