2007-08-12 16:23:50 +04:00
/*
* linux / drivers / mmc / card / mmc_test . c
*
2008-06-28 19:51:27 +04:00
* Copyright 2007 - 2008 Pierre Ossman
2007-08-12 16:23:50 +04:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or ( at
* your option ) any later version .
*/
# include <linux/mmc/core.h>
# include <linux/mmc/card.h>
# include <linux/mmc/host.h>
# include <linux/mmc/mmc.h>
# include <linux/scatterlist.h>
# define RESULT_OK 0
# define RESULT_FAIL 1
# define RESULT_UNSUP_HOST 2
# define RESULT_UNSUP_CARD 3
2008-07-04 20:17:13 +04:00
# define BUFFER_ORDER 2
# define BUFFER_SIZE (PAGE_SIZE << BUFFER_ORDER)
2007-08-12 16:23:50 +04:00
struct mmc_test_card {
struct mmc_card * card ;
2008-06-30 11:09:27 +04:00
u8 scratch [ BUFFER_SIZE ] ;
2007-08-12 16:23:50 +04:00
u8 * buffer ;
2008-07-04 20:17:13 +04:00
# ifdef CONFIG_HIGHMEM
struct page * highmem ;
# endif
2007-08-12 16:23:50 +04:00
} ;
/*******************************************************************/
2008-06-30 11:09:27 +04:00
/* General helper functions */
2007-08-12 16:23:50 +04:00
/*******************************************************************/
2008-06-30 11:09:27 +04:00
/*
* Configure correct block size in card
*/
2007-08-12 16:23:50 +04:00
static int mmc_test_set_blksize ( struct mmc_test_card * test , unsigned size )
{
struct mmc_command cmd ;
int ret ;
cmd . opcode = MMC_SET_BLOCKLEN ;
cmd . arg = size ;
cmd . flags = MMC_RSP_R1 | MMC_CMD_AC ;
ret = mmc_wait_for_cmd ( test - > card - > host , & cmd , 0 ) ;
if ( ret )
return ret ;
return 0 ;
}
2008-06-30 11:09:27 +04:00
/*
* Fill in the mmc_request structure given a set of transfer parameters .
*/
static void mmc_test_prepare_mrq ( struct mmc_test_card * test ,
struct mmc_request * mrq , struct scatterlist * sg , unsigned sg_len ,
unsigned dev_addr , unsigned blocks , unsigned blksz , int write )
2007-08-12 16:23:50 +04:00
{
2008-06-30 11:09:27 +04:00
BUG_ON ( ! mrq | | ! mrq - > cmd | | ! mrq - > data | | ! mrq - > stop ) ;
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
if ( blocks > 1 ) {
mrq - > cmd - > opcode = write ?
MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK ;
2007-08-12 16:23:50 +04:00
} else {
2008-06-30 11:09:27 +04:00
mrq - > cmd - > opcode = write ?
MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK ;
2007-08-12 16:23:50 +04:00
}
2008-06-30 11:09:27 +04:00
mrq - > cmd - > arg = dev_addr ;
mrq - > cmd - > flags = MMC_RSP_R1 | MMC_CMD_ADTC ;
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
if ( blocks = = 1 )
mrq - > stop = NULL ;
else {
mrq - > stop - > opcode = MMC_STOP_TRANSMISSION ;
mrq - > stop - > arg = 0 ;
mrq - > stop - > flags = MMC_RSP_R1B | MMC_CMD_AC ;
2007-08-12 16:23:50 +04:00
}
2008-06-30 11:09:27 +04:00
mrq - > data - > blksz = blksz ;
mrq - > data - > blocks = blocks ;
mrq - > data - > flags = write ? MMC_DATA_WRITE : MMC_DATA_READ ;
mrq - > data - > sg = sg ;
mrq - > data - > sg_len = sg_len ;
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
mmc_set_data_timeout ( mrq - > data , test - > card ) ;
}
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
/*
* Wait for the card to finish the busy state
*/
static int mmc_test_wait_busy ( struct mmc_test_card * test )
{
int ret , busy ;
struct mmc_command cmd ;
2007-08-12 16:23:50 +04:00
busy = 0 ;
do {
memset ( & cmd , 0 , sizeof ( struct mmc_command ) ) ;
cmd . opcode = MMC_SEND_STATUS ;
cmd . arg = test - > card - > rca < < 16 ;
cmd . flags = MMC_RSP_R1 | MMC_CMD_AC ;
2008-06-30 11:09:27 +04:00
ret = mmc_wait_for_cmd ( test - > card - > host , & cmd , 0 ) ;
if ( ret )
2007-08-12 16:23:50 +04:00
break ;
if ( ! busy & & ! ( cmd . resp [ 0 ] & R1_READY_FOR_DATA ) ) {
busy = 1 ;
printk ( KERN_INFO " %s: Warning: Host did not "
" wait for busy state to end. \n " ,
mmc_hostname ( test - > card - > host ) ) ;
}
} while ( ! ( cmd . resp [ 0 ] & R1_READY_FOR_DATA ) ) ;
return ret ;
}
2008-06-30 11:09:27 +04:00
/*
* Transfer a single sector of kernel addressable data
*/
static int mmc_test_buffer_transfer ( struct mmc_test_card * test ,
u8 * buffer , unsigned addr , unsigned blksz , int write )
2007-08-12 16:23:50 +04:00
{
2008-06-30 11:09:27 +04:00
int ret ;
struct mmc_request mrq ;
struct mmc_command cmd ;
struct mmc_command stop ;
struct mmc_data data ;
struct scatterlist sg ;
memset ( & mrq , 0 , sizeof ( struct mmc_request ) ) ;
memset ( & cmd , 0 , sizeof ( struct mmc_command ) ) ;
memset ( & data , 0 , sizeof ( struct mmc_data ) ) ;
memset ( & stop , 0 , sizeof ( struct mmc_command ) ) ;
mrq . cmd = & cmd ;
mrq . data = & data ;
mrq . stop = & stop ;
sg_init_one ( & sg , buffer , blksz ) ;
mmc_test_prepare_mrq ( test , & mrq , & sg , 1 , addr , 1 , blksz , write ) ;
mmc_wait_for_req ( test - > card - > host , & mrq ) ;
if ( cmd . error )
return cmd . error ;
if ( data . error )
return data . error ;
ret = mmc_test_wait_busy ( test ) ;
if ( ret )
return ret ;
return 0 ;
2007-08-12 16:23:50 +04:00
}
2008-06-30 11:09:27 +04:00
/*******************************************************************/
/* Test preparation and cleanup */
/*******************************************************************/
/*
* Fill the first couple of sectors of the card with known data
* so that bad reads / writes can be detected
*/
static int __mmc_test_prepare ( struct mmc_test_card * test , int write )
2007-08-12 16:23:50 +04:00
{
int ret , i ;
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
if ( write )
2008-06-30 11:09:27 +04:00
memset ( test - > buffer , 0xDF , 512 ) ;
2007-08-12 16:23:50 +04:00
else {
2008-06-30 11:09:27 +04:00
for ( i = 0 ; i < 512 ; i + + )
2007-08-12 16:23:50 +04:00
test - > buffer [ i ] = i ;
}
for ( i = 0 ; i < BUFFER_SIZE / 512 ; i + + ) {
2008-06-30 11:09:27 +04:00
ret = mmc_test_buffer_transfer ( test , test - > buffer , i * 512 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
2008-06-30 11:09:27 +04:00
static int mmc_test_prepare_write ( struct mmc_test_card * test )
{
return __mmc_test_prepare ( test , 1 ) ;
}
static int mmc_test_prepare_read ( struct mmc_test_card * test )
{
return __mmc_test_prepare ( test , 0 ) ;
}
static int mmc_test_cleanup ( struct mmc_test_card * test )
{
int ret , i ;
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
memset ( test - > buffer , 0 , 512 ) ;
for ( i = 0 ; i < BUFFER_SIZE / 512 ; i + + ) {
ret = mmc_test_buffer_transfer ( test , test - > buffer , i * 512 , 512 , 1 ) ;
if ( ret )
return ret ;
}
return 0 ;
}
/*******************************************************************/
/* Test execution helpers */
/*******************************************************************/
/*
* Modifies the mmc_request to perform the " short transfer " tests
*/
static void mmc_test_prepare_broken_mrq ( struct mmc_test_card * test ,
struct mmc_request * mrq , int write )
{
BUG_ON ( ! mrq | | ! mrq - > cmd | | ! mrq - > data ) ;
if ( mrq - > data - > blocks > 1 ) {
mrq - > cmd - > opcode = write ?
MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK ;
mrq - > stop = NULL ;
} else {
mrq - > cmd - > opcode = MMC_SEND_STATUS ;
mrq - > cmd - > arg = test - > card - > rca < < 16 ;
}
}
/*
* Checks that a normal transfer didn ' t have any errors
*/
static int mmc_test_check_result ( struct mmc_test_card * test ,
struct mmc_request * mrq )
2007-08-12 16:23:50 +04:00
{
2008-06-30 11:09:27 +04:00
int ret ;
BUG_ON ( ! mrq | | ! mrq - > cmd | | ! mrq - > data ) ;
ret = 0 ;
if ( ! ret & & mrq - > cmd - > error )
ret = mrq - > cmd - > error ;
if ( ! ret & & mrq - > data - > error )
ret = mrq - > data - > error ;
if ( ! ret & & mrq - > stop & & mrq - > stop - > error )
ret = mrq - > stop - > error ;
if ( ! ret & & mrq - > data - > bytes_xfered ! =
mrq - > data - > blocks * mrq - > data - > blksz )
ret = RESULT_FAIL ;
if ( ret = = - EINVAL )
ret = RESULT_UNSUP_HOST ;
return ret ;
2007-08-12 16:23:50 +04:00
}
2008-06-30 11:09:27 +04:00
/*
* Checks that a " short transfer " behaved as expected
*/
static int mmc_test_check_broken_result ( struct mmc_test_card * test ,
struct mmc_request * mrq )
2007-08-12 16:23:50 +04:00
{
2008-06-30 11:09:27 +04:00
int ret ;
BUG_ON ( ! mrq | | ! mrq - > cmd | | ! mrq - > data ) ;
ret = 0 ;
if ( ! ret & & mrq - > cmd - > error )
ret = mrq - > cmd - > error ;
if ( ! ret & & mrq - > data - > error = = 0 )
ret = RESULT_FAIL ;
if ( ! ret & & mrq - > data - > error ! = - ETIMEDOUT )
ret = mrq - > data - > error ;
if ( ! ret & & mrq - > stop & & mrq - > stop - > error )
ret = mrq - > stop - > error ;
if ( mrq - > data - > blocks > 1 ) {
if ( ! ret & & mrq - > data - > bytes_xfered > mrq - > data - > blksz )
ret = RESULT_FAIL ;
} else {
if ( ! ret & & mrq - > data - > bytes_xfered > 0 )
ret = RESULT_FAIL ;
}
if ( ret = = - EINVAL )
ret = RESULT_UNSUP_HOST ;
return ret ;
2007-08-12 16:23:50 +04:00
}
2008-06-30 11:09:27 +04:00
/*
* Tests a basic transfer with certain parameters
*/
static int mmc_test_simple_transfer ( struct mmc_test_card * test ,
struct scatterlist * sg , unsigned sg_len , unsigned dev_addr ,
unsigned blocks , unsigned blksz , int write )
2007-08-12 16:23:50 +04:00
{
2008-06-30 11:09:27 +04:00
struct mmc_request mrq ;
struct mmc_command cmd ;
struct mmc_command stop ;
struct mmc_data data ;
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
memset ( & mrq , 0 , sizeof ( struct mmc_request ) ) ;
memset ( & cmd , 0 , sizeof ( struct mmc_command ) ) ;
memset ( & data , 0 , sizeof ( struct mmc_data ) ) ;
memset ( & stop , 0 , sizeof ( struct mmc_command ) ) ;
mrq . cmd = & cmd ;
mrq . data = & data ;
mrq . stop = & stop ;
mmc_test_prepare_mrq ( test , & mrq , sg , sg_len , dev_addr ,
blocks , blksz , write ) ;
mmc_wait_for_req ( test - > card - > host , & mrq ) ;
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
mmc_test_wait_busy ( test ) ;
return mmc_test_check_result ( test , & mrq ) ;
}
/*
* Tests a transfer where the card will fail completely or partly
*/
static int mmc_test_broken_transfer ( struct mmc_test_card * test ,
unsigned blocks , unsigned blksz , int write )
{
struct mmc_request mrq ;
struct mmc_command cmd ;
struct mmc_command stop ;
struct mmc_data data ;
struct scatterlist sg ;
memset ( & mrq , 0 , sizeof ( struct mmc_request ) ) ;
memset ( & cmd , 0 , sizeof ( struct mmc_command ) ) ;
memset ( & data , 0 , sizeof ( struct mmc_data ) ) ;
memset ( & stop , 0 , sizeof ( struct mmc_command ) ) ;
mrq . cmd = & cmd ;
mrq . data = & data ;
mrq . stop = & stop ;
sg_init_one ( & sg , test - > buffer , blocks * blksz ) ;
mmc_test_prepare_mrq ( test , & mrq , & sg , 1 , 0 , blocks , blksz , write ) ;
mmc_test_prepare_broken_mrq ( test , & mrq , write ) ;
mmc_wait_for_req ( test - > card - > host , & mrq ) ;
mmc_test_wait_busy ( test ) ;
return mmc_test_check_broken_result ( test , & mrq ) ;
}
/*
* Does a complete transfer test where data is also validated
*
* Note : mmc_test_prepare ( ) must have been done before this call
*/
static int mmc_test_transfer ( struct mmc_test_card * test ,
struct scatterlist * sg , unsigned sg_len , unsigned dev_addr ,
unsigned blocks , unsigned blksz , int write )
{
int ret , i ;
unsigned long flags ;
2007-08-12 16:23:50 +04:00
if ( write ) {
for ( i = 0 ; i < blocks * blksz ; i + + )
2008-06-30 11:09:27 +04:00
test - > scratch [ i ] = i ;
} else {
2008-07-29 03:05:22 +04:00
memset ( test - > scratch , 0 , BUFFER_SIZE ) ;
2007-08-12 16:23:50 +04:00
}
2008-06-30 11:09:27 +04:00
local_irq_save ( flags ) ;
2008-07-29 03:05:22 +04:00
sg_copy_from_buffer ( sg , sg_len , test - > scratch , BUFFER_SIZE ) ;
2008-06-30 11:09:27 +04:00
local_irq_restore ( flags ) ;
2007-08-12 16:23:50 +04:00
ret = mmc_test_set_blksize ( test , blksz ) ;
if ( ret )
return ret ;
2008-06-30 11:09:27 +04:00
ret = mmc_test_simple_transfer ( test , sg , sg_len , dev_addr ,
blocks , blksz , write ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
if ( write ) {
2008-06-30 11:09:27 +04:00
int sectors ;
2007-08-12 16:23:50 +04:00
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
sectors = ( blocks * blksz + 511 ) / 512 ;
if ( ( sectors * 512 ) = = ( blocks * blksz ) )
sectors + + ;
if ( ( sectors * 512 ) > BUFFER_SIZE )
return - EINVAL ;
memset ( test - > buffer , 0 , sectors * 512 ) ;
for ( i = 0 ; i < sectors ; i + + ) {
2008-06-30 11:09:27 +04:00
ret = mmc_test_buffer_transfer ( test ,
2007-08-12 16:23:50 +04:00
test - > buffer + i * 512 ,
2008-06-30 11:09:27 +04:00
dev_addr + i * 512 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
for ( i = 0 ; i < blocks * blksz ; i + + ) {
if ( test - > buffer [ i ] ! = ( u8 ) i )
return RESULT_FAIL ;
}
for ( ; i < sectors * 512 ; i + + ) {
if ( test - > buffer [ i ] ! = 0xDF )
return RESULT_FAIL ;
}
} else {
2008-06-30 11:09:27 +04:00
local_irq_save ( flags ) ;
2008-07-29 03:05:22 +04:00
sg_copy_to_buffer ( sg , sg_len , test - > scratch , BUFFER_SIZE ) ;
2008-06-30 11:09:27 +04:00
local_irq_restore ( flags ) ;
2007-08-12 16:23:50 +04:00
for ( i = 0 ; i < blocks * blksz ; i + + ) {
2008-06-30 11:09:27 +04:00
if ( test - > scratch [ i ] ! = ( u8 ) i )
2007-08-12 16:23:50 +04:00
return RESULT_FAIL ;
}
}
return 0 ;
}
/*******************************************************************/
/* Tests */
/*******************************************************************/
struct mmc_test_case {
const char * name ;
int ( * prepare ) ( struct mmc_test_card * ) ;
int ( * run ) ( struct mmc_test_card * ) ;
int ( * cleanup ) ( struct mmc_test_card * ) ;
} ;
static int mmc_test_basic_write ( struct mmc_test_card * test )
{
int ret ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , 512 ) ;
ret = mmc_test_simple_transfer ( test , & sg , 1 , 0 , 1 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_basic_read ( struct mmc_test_card * test )
{
int ret ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , 512 ) ;
2009-02-13 20:25:26 +03:00
ret = mmc_test_simple_transfer ( test , & sg , 1 , 0 , 1 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_verify_write ( struct mmc_test_card * test )
{
int ret ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
sg_init_one ( & sg , test - > buffer , 512 ) ;
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_verify_read ( struct mmc_test_card * test )
{
int ret ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
sg_init_one ( & sg , test - > buffer , 512 ) ;
2007-08-12 16:23:50 +04:00
2008-06-30 11:09:27 +04:00
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_multi_write ( struct mmc_test_card * test )
{
int ret ;
unsigned int size ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
size = PAGE_SIZE * 2 ;
size = min ( size , test - > card - > host - > max_req_size ) ;
size = min ( size , test - > card - > host - > max_seg_size ) ;
size = min ( size , test - > card - > host - > max_blk_count * 512 ) ;
if ( size < 1024 )
return RESULT_UNSUP_HOST ;
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , size ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , size / 512 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_multi_read ( struct mmc_test_card * test )
{
int ret ;
unsigned int size ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
size = PAGE_SIZE * 2 ;
size = min ( size , test - > card - > host - > max_req_size ) ;
size = min ( size , test - > card - > host - > max_seg_size ) ;
size = min ( size , test - > card - > host - > max_blk_count * 512 ) ;
if ( size < 1024 )
return RESULT_UNSUP_HOST ;
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , size ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , size / 512 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_pow2_write ( struct mmc_test_card * test )
{
int ret , i ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( ! test - > card - > csd . write_partial )
return RESULT_UNSUP_CARD ;
for ( i = 1 ; i < 512 ; i < < = 1 ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , i ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , i , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_pow2_read ( struct mmc_test_card * test )
{
int ret , i ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( ! test - > card - > csd . read_partial )
return RESULT_UNSUP_CARD ;
for ( i = 1 ; i < 512 ; i < < = 1 ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , i ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , i , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_weird_write ( struct mmc_test_card * test )
{
int ret , i ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( ! test - > card - > csd . write_partial )
return RESULT_UNSUP_CARD ;
for ( i = 3 ; i < 512 ; i + = 7 ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , i ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , i , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_weird_read ( struct mmc_test_card * test )
{
int ret , i ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( ! test - > card - > csd . read_partial )
return RESULT_UNSUP_CARD ;
for ( i = 3 ; i < 512 ; i + = 7 ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer , i ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , i , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_align_write ( struct mmc_test_card * test )
{
int ret , i ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
for ( i = 1 ; i < 4 ; i + + ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer + i , 512 ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_align_read ( struct mmc_test_card * test )
{
int ret , i ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
for ( i = 1 ; i < 4 ; i + + ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer + i , 512 ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_align_multi_write ( struct mmc_test_card * test )
{
int ret , i ;
unsigned int size ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
size = PAGE_SIZE * 2 ;
size = min ( size , test - > card - > host - > max_req_size ) ;
size = min ( size , test - > card - > host - > max_seg_size ) ;
size = min ( size , test - > card - > host - > max_blk_count * 512 ) ;
if ( size < 1024 )
return RESULT_UNSUP_HOST ;
for ( i = 1 ; i < 4 ; i + + ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer + i , size ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , size / 512 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_align_multi_read ( struct mmc_test_card * test )
{
int ret , i ;
unsigned int size ;
2008-06-30 11:09:27 +04:00
struct scatterlist sg ;
2007-08-12 16:23:50 +04:00
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
size = PAGE_SIZE * 2 ;
size = min ( size , test - > card - > host - > max_req_size ) ;
size = min ( size , test - > card - > host - > max_seg_size ) ;
size = min ( size , test - > card - > host - > max_blk_count * 512 ) ;
if ( size < 1024 )
return RESULT_UNSUP_HOST ;
for ( i = 1 ; i < 4 ; i + + ) {
2008-06-30 11:09:27 +04:00
sg_init_one ( & sg , test - > buffer + i , size ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , size / 512 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
}
return 0 ;
}
static int mmc_test_xfersize_write ( struct mmc_test_card * test )
{
int ret ;
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
2008-06-30 11:09:27 +04:00
ret = mmc_test_broken_transfer ( test , 1 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_xfersize_read ( struct mmc_test_card * test )
{
int ret ;
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
2008-06-30 11:09:27 +04:00
ret = mmc_test_broken_transfer ( test , 1 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_multi_xfersize_write ( struct mmc_test_card * test )
{
int ret ;
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
2008-06-30 11:09:27 +04:00
ret = mmc_test_broken_transfer ( test , 2 , 512 , 1 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_multi_xfersize_read ( struct mmc_test_card * test )
{
int ret ;
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
ret = mmc_test_set_blksize ( test , 512 ) ;
if ( ret )
return ret ;
2008-06-30 11:09:27 +04:00
ret = mmc_test_broken_transfer ( test , 2 , 512 , 0 ) ;
2007-08-12 16:23:50 +04:00
if ( ret )
return ret ;
return 0 ;
}
2008-07-04 20:17:13 +04:00
# ifdef CONFIG_HIGHMEM
static int mmc_test_write_high ( struct mmc_test_card * test )
{
int ret ;
struct scatterlist sg ;
sg_init_table ( & sg , 1 ) ;
sg_set_page ( & sg , test - > highmem , 512 , 0 ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , 512 , 1 ) ;
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_read_high ( struct mmc_test_card * test )
{
int ret ;
struct scatterlist sg ;
sg_init_table ( & sg , 1 ) ;
sg_set_page ( & sg , test - > highmem , 512 , 0 ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , 1 , 512 , 0 ) ;
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_multi_write_high ( struct mmc_test_card * test )
{
int ret ;
unsigned int size ;
struct scatterlist sg ;
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
size = PAGE_SIZE * 2 ;
size = min ( size , test - > card - > host - > max_req_size ) ;
size = min ( size , test - > card - > host - > max_seg_size ) ;
size = min ( size , test - > card - > host - > max_blk_count * 512 ) ;
if ( size < 1024 )
return RESULT_UNSUP_HOST ;
sg_init_table ( & sg , 1 ) ;
sg_set_page ( & sg , test - > highmem , size , 0 ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , size / 512 , 512 , 1 ) ;
if ( ret )
return ret ;
return 0 ;
}
static int mmc_test_multi_read_high ( struct mmc_test_card * test )
{
int ret ;
unsigned int size ;
struct scatterlist sg ;
if ( test - > card - > host - > max_blk_count = = 1 )
return RESULT_UNSUP_HOST ;
size = PAGE_SIZE * 2 ;
size = min ( size , test - > card - > host - > max_req_size ) ;
size = min ( size , test - > card - > host - > max_seg_size ) ;
size = min ( size , test - > card - > host - > max_blk_count * 512 ) ;
if ( size < 1024 )
return RESULT_UNSUP_HOST ;
sg_init_table ( & sg , 1 ) ;
sg_set_page ( & sg , test - > highmem , size , 0 ) ;
ret = mmc_test_transfer ( test , & sg , 1 , 0 , size / 512 , 512 , 0 ) ;
if ( ret )
return ret ;
return 0 ;
}
# endif /* CONFIG_HIGHMEM */
2007-08-12 16:23:50 +04:00
static const struct mmc_test_case mmc_test_cases [ ] = {
{
. name = " Basic write (no data verification) " ,
. run = mmc_test_basic_write ,
} ,
{
. name = " Basic read (no data verification) " ,
. run = mmc_test_basic_read ,
} ,
{
. name = " Basic write (with data verification) " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_write ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_verify_write ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Basic read (with data verification) " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_read ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_verify_read ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Multi-block write " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_write ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_multi_write ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Multi-block read " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_read ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_multi_read ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Power of two block writes " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_write ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_pow2_write ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Power of two block reads " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_read ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_pow2_read ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Weird sized block writes " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_write ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_weird_write ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Weird sized block reads " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_read ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_weird_read ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Badly aligned write " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_write ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_align_write ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Badly aligned read " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_read ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_align_read ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Badly aligned multi-block write " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_write ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_align_multi_write ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Badly aligned multi-block read " ,
2008-06-30 11:09:27 +04:00
. prepare = mmc_test_prepare_read ,
2007-08-12 16:23:50 +04:00
. run = mmc_test_align_multi_read ,
2008-06-30 11:09:27 +04:00
. cleanup = mmc_test_cleanup ,
2007-08-12 16:23:50 +04:00
} ,
{
. name = " Correct xfer_size at write (start failure) " ,
. run = mmc_test_xfersize_write ,
} ,
{
. name = " Correct xfer_size at read (start failure) " ,
. run = mmc_test_xfersize_read ,
} ,
{
. name = " Correct xfer_size at write (midway failure) " ,
. run = mmc_test_multi_xfersize_write ,
} ,
{
. name = " Correct xfer_size at read (midway failure) " ,
. run = mmc_test_multi_xfersize_read ,
} ,
2008-07-04 20:17:13 +04:00
# ifdef CONFIG_HIGHMEM
{
. name = " Highmem write " ,
. prepare = mmc_test_prepare_write ,
. run = mmc_test_write_high ,
. cleanup = mmc_test_cleanup ,
} ,
{
. name = " Highmem read " ,
. prepare = mmc_test_prepare_read ,
. run = mmc_test_read_high ,
. cleanup = mmc_test_cleanup ,
} ,
{
. name = " Multi-block highmem write " ,
. prepare = mmc_test_prepare_write ,
. run = mmc_test_multi_write_high ,
. cleanup = mmc_test_cleanup ,
} ,
{
. name = " Multi-block highmem read " ,
. prepare = mmc_test_prepare_read ,
. run = mmc_test_multi_read_high ,
. cleanup = mmc_test_cleanup ,
} ,
# endif /* CONFIG_HIGHMEM */
2007-08-12 16:23:50 +04:00
} ;
2008-09-13 14:03:32 +04:00
static DEFINE_MUTEX ( mmc_test_lock ) ;
2007-08-12 16:23:50 +04:00
2008-05-25 00:36:31 +04:00
static void mmc_test_run ( struct mmc_test_card * test , int testcase )
2007-08-12 16:23:50 +04:00
{
int i , ret ;
printk ( KERN_INFO " %s: Starting tests of card %s... \n " ,
mmc_hostname ( test - > card - > host ) , mmc_card_id ( test - > card ) ) ;
mmc_claim_host ( test - > card - > host ) ;
for ( i = 0 ; i < ARRAY_SIZE ( mmc_test_cases ) ; i + + ) {
2008-05-25 00:36:31 +04:00
if ( testcase & & ( ( i + 1 ) ! = testcase ) )
continue ;
2007-08-12 16:23:50 +04:00
printk ( KERN_INFO " %s: Test case %d. %s... \n " ,
mmc_hostname ( test - > card - > host ) , i + 1 ,
mmc_test_cases [ i ] . name ) ;
if ( mmc_test_cases [ i ] . prepare ) {
ret = mmc_test_cases [ i ] . prepare ( test ) ;
if ( ret ) {
printk ( KERN_INFO " %s: Result: Prepare "
" stage failed! (%d) \n " ,
mmc_hostname ( test - > card - > host ) ,
ret ) ;
continue ;
}
}
ret = mmc_test_cases [ i ] . run ( test ) ;
switch ( ret ) {
case RESULT_OK :
printk ( KERN_INFO " %s: Result: OK \n " ,
mmc_hostname ( test - > card - > host ) ) ;
break ;
case RESULT_FAIL :
printk ( KERN_INFO " %s: Result: FAILED \n " ,
mmc_hostname ( test - > card - > host ) ) ;
break ;
case RESULT_UNSUP_HOST :
printk ( KERN_INFO " %s: Result: UNSUPPORTED "
" (by host) \n " ,
mmc_hostname ( test - > card - > host ) ) ;
break ;
case RESULT_UNSUP_CARD :
printk ( KERN_INFO " %s: Result: UNSUPPORTED "
" (by card) \n " ,
mmc_hostname ( test - > card - > host ) ) ;
break ;
default :
printk ( KERN_INFO " %s: Result: ERROR (%d) \n " ,
mmc_hostname ( test - > card - > host ) , ret ) ;
}
if ( mmc_test_cases [ i ] . cleanup ) {
ret = mmc_test_cases [ i ] . cleanup ( test ) ;
if ( ret ) {
printk ( KERN_INFO " %s: Warning: Cleanup "
" stage failed! (%d) \n " ,
mmc_hostname ( test - > card - > host ) ,
ret ) ;
}
}
}
mmc_release_host ( test - > card - > host ) ;
printk ( KERN_INFO " %s: Tests completed. \n " ,
mmc_hostname ( test - > card - > host ) ) ;
}
static ssize_t mmc_test_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
mutex_lock ( & mmc_test_lock ) ;
mutex_unlock ( & mmc_test_lock ) ;
return 0 ;
}
static ssize_t mmc_test_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct mmc_card * card ;
struct mmc_test_card * test ;
2008-05-25 00:36:31 +04:00
int testcase ;
2007-08-12 16:23:50 +04:00
card = container_of ( dev , struct mmc_card , dev ) ;
2008-05-25 00:36:31 +04:00
testcase = simple_strtol ( buf , NULL , 10 ) ;
2007-08-12 16:23:50 +04:00
test = kzalloc ( sizeof ( struct mmc_test_card ) , GFP_KERNEL ) ;
if ( ! test )
return - ENOMEM ;
test - > card = card ;
test - > buffer = kzalloc ( BUFFER_SIZE , GFP_KERNEL ) ;
2008-07-04 20:17:13 +04:00
# ifdef CONFIG_HIGHMEM
test - > highmem = alloc_pages ( GFP_KERNEL | __GFP_HIGHMEM , BUFFER_ORDER ) ;
# endif
# ifdef CONFIG_HIGHMEM
if ( test - > buffer & & test - > highmem ) {
# else
2007-08-12 16:23:50 +04:00
if ( test - > buffer ) {
2008-07-04 20:17:13 +04:00
# endif
2007-08-12 16:23:50 +04:00
mutex_lock ( & mmc_test_lock ) ;
2008-05-25 00:36:31 +04:00
mmc_test_run ( test , testcase ) ;
2007-08-12 16:23:50 +04:00
mutex_unlock ( & mmc_test_lock ) ;
}
2008-07-04 20:17:13 +04:00
# ifdef CONFIG_HIGHMEM
__free_pages ( test - > highmem , BUFFER_ORDER ) ;
# endif
2007-08-12 16:23:50 +04:00
kfree ( test - > buffer ) ;
kfree ( test ) ;
return count ;
}
static DEVICE_ATTR ( test , S_IWUSR | S_IRUGO , mmc_test_show , mmc_test_store ) ;
static int mmc_test_probe ( struct mmc_card * card )
{
int ret ;
2008-06-28 19:51:27 +04:00
if ( ( card - > type ! = MMC_TYPE_MMC ) & & ( card - > type ! = MMC_TYPE_SD ) )
return - ENODEV ;
2007-08-12 16:23:50 +04:00
ret = device_create_file ( & card - > dev , & dev_attr_test ) ;
if ( ret )
return ret ;
2008-07-22 16:38:35 +04:00
dev_info ( & card - > dev , " Card claimed for testing. \n " ) ;
2007-08-12 16:23:50 +04:00
return 0 ;
}
static void mmc_test_remove ( struct mmc_card * card )
{
device_remove_file ( & card - > dev , & dev_attr_test ) ;
}
static struct mmc_driver mmc_driver = {
. drv = {
. name = " mmc_test " ,
} ,
. probe = mmc_test_probe ,
. remove = mmc_test_remove ,
} ;
static int __init mmc_test_init ( void )
{
return mmc_register_driver ( & mmc_driver ) ;
}
static void __exit mmc_test_exit ( void )
{
mmc_unregister_driver ( & mmc_driver ) ;
}
module_init ( mmc_test_init ) ;
module_exit ( mmc_test_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Multimedia Card (MMC) host test driver " ) ;
MODULE_AUTHOR ( " Pierre Ossman " ) ;