2011-03-15 09:53:21 +03:00
/*
* Function Control Protocol ( IEC 61883 - 1 ) helper functions
*
* Copyright ( c ) Clemens Ladisch < clemens @ ladisch . de >
* Licensed under the terms of the GNU General Public License , version 2.
*/
# include <linux/device.h>
# include <linux/firewire.h>
# include <linux/firewire-constants.h>
# include <linux/list.h>
# include <linux/module.h>
2014-04-25 17:44:59 +04:00
# include <linux/slab.h>
2011-03-15 09:53:21 +03:00
# include <linux/sched.h>
# include <linux/spinlock.h>
# include <linux/wait.h>
2011-03-16 09:39:28 +03:00
# include <linux/delay.h>
2011-03-15 09:53:21 +03:00
# include "fcp.h"
# include "lib.h"
2015-09-19 05:21:54 +03:00
# include "amdtp-stream.h"
2011-03-15 09:53:21 +03:00
# define CTS_AVC 0x00
# define ERROR_RETRIES 3
# define ERROR_DELAY_MS 5
# define FCP_TIMEOUT_MS 125
2014-04-25 17:44:59 +04:00
int avc_general_set_sig_fmt ( struct fw_unit * unit , unsigned int rate ,
enum avc_general_plug_dir dir ,
unsigned short pid )
{
unsigned int sfc ;
u8 * buf ;
bool flag ;
int err ;
flag = false ;
for ( sfc = 0 ; sfc < CIP_SFC_COUNT ; sfc + + ) {
if ( amdtp_rate_table [ sfc ] = = rate ) {
flag = true ;
break ;
}
}
if ( ! flag )
return - EINVAL ;
buf = kzalloc ( 8 , GFP_KERNEL ) ;
if ( buf = = NULL )
return - ENOMEM ;
buf [ 0 ] = 0x00 ; /* AV/C CONTROL */
buf [ 1 ] = 0xff ; /* UNIT */
if ( dir = = AVC_GENERAL_PLUG_DIR_IN )
buf [ 2 ] = 0x19 ; /* INPUT PLUG SIGNAL FORMAT */
else
buf [ 2 ] = 0x18 ; /* OUTPUT PLUG SIGNAL FORMAT */
buf [ 3 ] = 0xff & pid ; /* plug id */
buf [ 4 ] = 0x90 ; /* EOH_1, Form_1, FMT. AM824 */
buf [ 5 ] = 0x07 & sfc ; /* FDF-hi. AM824, frequency */
buf [ 6 ] = 0xff ; /* FDF-mid. AM824, SYT hi (not used)*/
buf [ 7 ] = 0xff ; /* FDF-low. AM824, SYT lo (not used) */
/* do transaction and check buf[1-5] are the same against command */
err = fcp_avc_transaction ( unit , buf , 8 , buf , 8 ,
BIT ( 1 ) | BIT ( 2 ) | BIT ( 3 ) | BIT ( 4 ) | BIT ( 5 ) ) ;
if ( err > = 0 & & err < 8 )
err = - EIO ;
else if ( buf [ 0 ] = = 0x08 ) /* NOT IMPLEMENTED */
err = - ENOSYS ;
else if ( buf [ 0 ] = = 0x0a ) /* REJECTED */
err = - EINVAL ;
if ( err < 0 )
goto end ;
err = 0 ;
end :
kfree ( buf ) ;
return err ;
}
EXPORT_SYMBOL ( avc_general_set_sig_fmt ) ;
int avc_general_get_sig_fmt ( struct fw_unit * unit , unsigned int * rate ,
enum avc_general_plug_dir dir ,
unsigned short pid )
{
unsigned int sfc ;
u8 * buf ;
int err ;
buf = kzalloc ( 8 , GFP_KERNEL ) ;
if ( buf = = NULL )
return - ENOMEM ;
buf [ 0 ] = 0x01 ; /* AV/C STATUS */
buf [ 1 ] = 0xff ; /* Unit */
if ( dir = = AVC_GENERAL_PLUG_DIR_IN )
buf [ 2 ] = 0x19 ; /* INPUT PLUG SIGNAL FORMAT */
else
buf [ 2 ] = 0x18 ; /* OUTPUT PLUG SIGNAL FORMAT */
buf [ 3 ] = 0xff & pid ; /* plug id */
buf [ 4 ] = 0x90 ; /* EOH_1, Form_1, FMT. AM824 */
buf [ 5 ] = 0xff ; /* FDF-hi. AM824, frequency */
buf [ 6 ] = 0xff ; /* FDF-mid. AM824, SYT hi (not used) */
buf [ 7 ] = 0xff ; /* FDF-low. AM824, SYT lo (not used) */
/* do transaction and check buf[1-4] are the same against command */
err = fcp_avc_transaction ( unit , buf , 8 , buf , 8 ,
BIT ( 1 ) | BIT ( 2 ) | BIT ( 3 ) | BIT ( 4 ) ) ;
if ( err > = 0 & & err < 8 )
err = - EIO ;
else if ( buf [ 0 ] = = 0x08 ) /* NOT IMPLEMENTED */
err = - ENOSYS ;
else if ( buf [ 0 ] = = 0x0a ) /* REJECTED */
err = - EINVAL ;
else if ( buf [ 0 ] = = 0x0b ) /* IN TRANSITION */
err = - EAGAIN ;
if ( err < 0 )
goto end ;
/* check sfc field and pick up rate */
sfc = 0x07 & buf [ 5 ] ;
if ( sfc > = CIP_SFC_COUNT ) {
err = - EAGAIN ; /* also in transition */
goto end ;
}
* rate = amdtp_rate_table [ sfc ] ;
err = 0 ;
end :
kfree ( buf ) ;
return err ;
}
EXPORT_SYMBOL ( avc_general_get_sig_fmt ) ;
int avc_general_get_plug_info ( struct fw_unit * unit , unsigned int subunit_type ,
unsigned int subunit_id , unsigned int subfunction ,
u8 info [ AVC_PLUG_INFO_BUF_BYTES ] )
{
u8 * buf ;
int err ;
/* extended subunit in spec.4.2 is not supported */
if ( ( subunit_type = = 0x1E ) | | ( subunit_id = = 5 ) )
return - EINVAL ;
buf = kzalloc ( 8 , GFP_KERNEL ) ;
if ( buf = = NULL )
return - ENOMEM ;
buf [ 0 ] = 0x01 ; /* AV/C STATUS */
/* UNIT or Subunit, Functionblock */
buf [ 1 ] = ( ( subunit_type & 0x1f ) < < 3 ) | ( subunit_id & 0x7 ) ;
buf [ 2 ] = 0x02 ; /* PLUG INFO */
buf [ 3 ] = 0xff & subfunction ;
err = fcp_avc_transaction ( unit , buf , 8 , buf , 8 , BIT ( 1 ) | BIT ( 2 ) ) ;
if ( err > = 0 & & err < 8 )
err = - EIO ;
else if ( buf [ 0 ] = = 0x08 ) /* NOT IMPLEMENTED */
err = - ENOSYS ;
else if ( buf [ 0 ] = = 0x0a ) /* REJECTED */
err = - EINVAL ;
else if ( buf [ 0 ] = = 0x0b ) /* IN TRANSITION */
err = - EAGAIN ;
if ( err < 0 )
goto end ;
info [ 0 ] = buf [ 4 ] ;
info [ 1 ] = buf [ 5 ] ;
info [ 2 ] = buf [ 6 ] ;
info [ 3 ] = buf [ 7 ] ;
err = 0 ;
end :
kfree ( buf ) ;
return err ;
}
EXPORT_SYMBOL ( avc_general_get_plug_info ) ;
2011-03-15 09:53:21 +03:00
static DEFINE_SPINLOCK ( transactions_lock ) ;
static LIST_HEAD ( transactions ) ;
enum fcp_state {
STATE_PENDING ,
STATE_BUS_RESET ,
STATE_COMPLETE ,
2014-04-25 17:44:58 +04:00
STATE_DEFERRED ,
2011-03-15 09:53:21 +03:00
} ;
struct fcp_transaction {
struct list_head list ;
struct fw_unit * unit ;
void * response_buffer ;
unsigned int response_size ;
unsigned int response_match_bytes ;
enum fcp_state state ;
wait_queue_head_t wait ;
2014-04-25 17:44:58 +04:00
bool deferrable ;
2011-03-15 09:53:21 +03:00
} ;
/**
* fcp_avc_transaction - send an AV / C command and wait for its response
* @ unit : a unit on the target device
* @ command : a buffer containing the command frame ; must be DMA - able
* @ command_size : the size of @ command
* @ response : a buffer for the response frame
* @ response_size : the maximum size of @ response
* @ response_match_bytes : a bitmap specifying the bytes used to detect the
* correct response frame
*
* This function sends a FCP command frame to the target and waits for the
* corresponding response frame to be returned .
*
* Because it is possible for multiple FCP transactions to be active at the
* same time , the correct response frame is detected by the value of certain
* bytes . These bytes must be set in @ response before calling this function ,
* and the corresponding bits must be set in @ response_match_bytes .
*
* @ command and @ response can point to the same buffer .
*
* Returns the actual size of the response frame , or a negative error code .
*/
int fcp_avc_transaction ( struct fw_unit * unit ,
const void * command , unsigned int command_size ,
void * response , unsigned int response_size ,
unsigned int response_match_bytes )
{
struct fcp_transaction t ;
int tcode , ret , tries = 0 ;
t . unit = unit ;
t . response_buffer = response ;
t . response_size = response_size ;
t . response_match_bytes = response_match_bytes ;
t . state = STATE_PENDING ;
init_waitqueue_head ( & t . wait ) ;
2014-04-25 17:44:58 +04:00
if ( * ( const u8 * ) command = = 0x00 | | * ( const u8 * ) command = = 0x03 )
t . deferrable = true ;
2011-03-15 09:53:21 +03:00
spin_lock_irq ( & transactions_lock ) ;
list_add_tail ( & t . list , & transactions ) ;
spin_unlock_irq ( & transactions_lock ) ;
for ( ; ; ) {
tcode = command_size = = 4 ? TCODE_WRITE_QUADLET_REQUEST
: TCODE_WRITE_BLOCK_REQUEST ;
ret = snd_fw_transaction ( t . unit , tcode ,
CSR_REGISTER_BASE + CSR_FCP_COMMAND ,
2011-09-05 00:17:38 +04:00
( void * ) command , command_size , 0 ) ;
2011-03-15 09:53:21 +03:00
if ( ret < 0 )
break ;
2014-04-25 17:44:58 +04:00
deferred :
2011-03-15 09:53:21 +03:00
wait_event_timeout ( t . wait , t . state ! = STATE_PENDING ,
msecs_to_jiffies ( FCP_TIMEOUT_MS ) ) ;
2014-04-25 17:44:58 +04:00
if ( t . state = = STATE_DEFERRED ) {
/*
* ' AV / C General Specification ' define no time limit
* on command completion once an INTERIM response has
* been sent . but we promise to finish this function
* for a caller . Here we use FCP_TIMEOUT_MS for next
* interval . This is not in the specification .
*/
t . state = STATE_PENDING ;
goto deferred ;
} else if ( t . state = = STATE_COMPLETE ) {
2011-03-15 09:53:21 +03:00
ret = t . response_size ;
break ;
} else if ( t . state = = STATE_BUS_RESET ) {
msleep ( ERROR_DELAY_MS ) ;
} else if ( + + tries > = ERROR_RETRIES ) {
dev_err ( & t . unit - > device , " FCP command timed out \n " ) ;
ret = - EIO ;
break ;
}
}
spin_lock_irq ( & transactions_lock ) ;
list_del ( & t . list ) ;
spin_unlock_irq ( & transactions_lock ) ;
return ret ;
}
EXPORT_SYMBOL ( fcp_avc_transaction ) ;
/**
* fcp_bus_reset - inform the target handler about a bus reset
* @ unit : the unit that might be used by fcp_avc_transaction ( )
*
* This function must be called from the driver ' s . update handler to inform
* the FCP transaction handler that a bus reset has happened . Any pending FCP
* transactions are retried .
*/
void fcp_bus_reset ( struct fw_unit * unit )
{
struct fcp_transaction * t ;
spin_lock_irq ( & transactions_lock ) ;
list_for_each_entry ( t , & transactions , list ) {
if ( t - > unit = = unit & &
2014-04-25 17:44:58 +04:00
( t - > state = = STATE_PENDING | |
t - > state = = STATE_DEFERRED ) ) {
2011-03-15 09:53:21 +03:00
t - > state = STATE_BUS_RESET ;
wake_up ( & t - > wait ) ;
}
}
spin_unlock_irq ( & transactions_lock ) ;
}
EXPORT_SYMBOL ( fcp_bus_reset ) ;
/* checks whether the response matches the masked bytes in response_buffer */
static bool is_matching_response ( struct fcp_transaction * transaction ,
const void * response , size_t length )
{
const u8 * p1 , * p2 ;
unsigned int mask , i ;
p1 = response ;
p2 = transaction - > response_buffer ;
mask = transaction - > response_match_bytes ;
for ( i = 0 ; ; + + i ) {
if ( ( mask & 1 ) & & p1 [ i ] ! = p2 [ i ] )
return false ;
mask > > = 1 ;
if ( ! mask )
return true ;
if ( - - length = = 0 )
return false ;
}
}
static void fcp_response ( struct fw_card * card , struct fw_request * request ,
int tcode , int destination , int source ,
int generation , unsigned long long offset ,
void * data , size_t length , void * callback_data )
{
struct fcp_transaction * t ;
unsigned long flags ;
if ( length < 1 | | ( * ( const u8 * ) data & 0xf0 ) ! = CTS_AVC )
return ;
spin_lock_irqsave ( & transactions_lock , flags ) ;
list_for_each_entry ( t , & transactions , list ) {
struct fw_device * device = fw_parent_device ( t - > unit ) ;
if ( device - > card ! = card | |
device - > generation ! = generation )
continue ;
smp_rmb ( ) ; /* node_id vs. generation */
if ( device - > node_id ! = source )
continue ;
if ( t - > state = = STATE_PENDING & &
is_matching_response ( t , data , length ) ) {
2014-04-25 17:44:58 +04:00
if ( t - > deferrable & & * ( const u8 * ) data = = 0x0f ) {
t - > state = STATE_DEFERRED ;
} else {
t - > state = STATE_COMPLETE ;
t - > response_size = min_t ( unsigned int , length ,
t - > response_size ) ;
memcpy ( t - > response_buffer , data ,
t - > response_size ) ;
}
2011-03-15 09:53:21 +03:00
wake_up ( & t - > wait ) ;
}
}
spin_unlock_irqrestore ( & transactions_lock , flags ) ;
}
static struct fw_address_handler response_register_handler = {
. length = 0x200 ,
. address_callback = fcp_response ,
} ;
static int __init fcp_module_init ( void )
{
static const struct fw_address_region response_register_region = {
. start = CSR_REGISTER_BASE + CSR_FCP_RESPONSE ,
. end = CSR_REGISTER_BASE + CSR_FCP_END ,
} ;
fw_core_add_address_handler ( & response_register_handler ,
& response_register_region ) ;
return 0 ;
}
static void __exit fcp_module_exit ( void )
{
WARN_ON ( ! list_empty ( & transactions ) ) ;
fw_core_remove_address_handler ( & response_register_handler ) ;
}
module_init ( fcp_module_init ) ;
module_exit ( fcp_module_exit ) ;