2007-05-08 04:33:32 +04:00
/*
* Incremental bus scan , based on bus topology
2006-12-20 03:58:27 +03:00
*
* Copyright ( C ) 2004 - 2006 Kristian Hoegsberg < krh @ bitplanet . net >
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software Foundation ,
* Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*/
# include <linux/module.h>
# include <linux/wait.h>
# include <linux/errno.h>
2008-03-09 00:38:16 +03:00
# include <asm/bug.h>
2008-01-25 20:57:41 +03:00
# include <asm/system.h>
2006-12-20 03:58:27 +03:00
# include "fw-transaction.h"
# include "fw-topology.h"
2007-05-08 04:33:35 +04:00
# define SELF_ID_PHY_ID(q) (((q) >> 24) & 0x3f)
# define SELF_ID_EXTENDED(q) (((q) >> 23) & 0x01)
# define SELF_ID_LINK_ON(q) (((q) >> 22) & 0x01)
# define SELF_ID_GAP_COUNT(q) (((q) >> 16) & 0x3f)
# define SELF_ID_PHY_SPEED(q) (((q) >> 14) & 0x03)
# define SELF_ID_CONTENDER(q) (((q) >> 11) & 0x01)
# define SELF_ID_PHY_INITIATOR(q) (((q) >> 1) & 0x01)
# define SELF_ID_MORE_PACKETS(q) (((q) >> 0) & 0x01)
2006-12-20 03:58:27 +03:00
2007-05-08 04:33:35 +04:00
# define SELF_ID_EXT_SEQUENCE(q) (((q) >> 20) & 0x07)
2006-12-20 03:58:27 +03:00
static u32 * count_ports ( u32 * sid , int * total_port_count , int * child_port_count )
{
u32 q ;
int port_type , shift , seq ;
* total_port_count = 0 ;
* child_port_count = 0 ;
shift = 6 ;
q = * sid ;
seq = 0 ;
while ( 1 ) {
port_type = ( q > > shift ) & 0x03 ;
switch ( port_type ) {
case SELFID_PORT_CHILD :
( * child_port_count ) + + ;
case SELFID_PORT_PARENT :
case SELFID_PORT_NCONN :
( * total_port_count ) + + ;
case SELFID_PORT_NONE :
break ;
}
shift - = 2 ;
if ( shift = = 0 ) {
2007-05-08 04:33:35 +04:00
if ( ! SELF_ID_MORE_PACKETS ( q ) )
2006-12-20 03:58:27 +03:00
return sid + 1 ;
shift = 16 ;
sid + + ;
q = * sid ;
2007-05-08 04:33:32 +04:00
/*
* Check that the extra packets actually are
2006-12-20 03:58:27 +03:00
* extended self ID packets and that the
* sequence numbers in the extended self ID
2007-05-08 04:33:32 +04:00
* packets increase as expected .
*/
2006-12-20 03:58:27 +03:00
2007-05-08 04:33:35 +04:00
if ( ! SELF_ID_EXTENDED ( q ) | |
seq ! = SELF_ID_EXT_SEQUENCE ( q ) )
2006-12-20 03:58:27 +03:00
return NULL ;
seq + + ;
}
}
}
static int get_port_type ( u32 * sid , int port_index )
{
int index , shift ;
index = ( port_index + 5 ) / 8 ;
shift = 16 - ( ( port_index + 5 ) & 7 ) * 2 ;
return ( sid [ index ] > > shift ) & 0x03 ;
}
2007-01-22 21:17:37 +03:00
static struct fw_node * fw_node_create ( u32 sid , int port_count , int color )
2006-12-20 03:58:27 +03:00
{
struct fw_node * node ;
2007-05-10 03:23:14 +04:00
node = kzalloc ( sizeof ( * node ) + port_count * sizeof ( node - > ports [ 0 ] ) ,
2006-12-20 03:58:27 +03:00
GFP_ATOMIC ) ;
if ( node = = NULL )
return NULL ;
node - > color = color ;
2007-05-08 04:33:35 +04:00
node - > node_id = LOCAL_BUS | SELF_ID_PHY_ID ( sid ) ;
node - > link_on = SELF_ID_LINK_ON ( sid ) ;
node - > phy_speed = SELF_ID_PHY_SPEED ( sid ) ;
2008-03-24 22:54:28 +03:00
node - > initiated_reset = SELF_ID_PHY_INITIATOR ( sid ) ;
2006-12-20 03:58:27 +03:00
node - > port_count = port_count ;
atomic_set ( & node - > ref_count , 1 ) ;
INIT_LIST_HEAD ( & node - > link ) ;
return node ;
}
2007-05-08 04:33:32 +04:00
/*
* Compute the maximum hop count for this node and it ' s children . The
2007-01-26 08:37:50 +03:00
* maximum hop count is the maximum number of connections between any
* two nodes in the subtree rooted at this node . We need this for
* setting the gap count . As we build the tree bottom up in
* build_tree ( ) below , this is fairly easy to do : for each node we
* maintain the max hop count and the max depth , ie the number of hops
* to the furthest leaf . Computing the max hop count breaks down into
* two cases : either the path goes through this node , in which case
* the hop count is the sum of the two biggest child depths plus 2.
* Or it could be the case that the max hop path is entirely
* containted in a child tree , in which case the max hop count is just
* the max hop count of this child .
*/
static void update_hop_count ( struct fw_node * node )
{
int depths [ 2 ] = { - 1 , - 1 } ;
int max_child_hops = 0 ;
int i ;
for ( i = 0 ; i < node - > port_count ; i + + ) {
2007-06-18 01:39:58 +04:00
if ( node - > ports [ i ] = = NULL )
2007-01-26 08:37:50 +03:00
continue ;
2007-06-18 01:39:58 +04:00
if ( node - > ports [ i ] - > max_hops > max_child_hops )
max_child_hops = node - > ports [ i ] - > max_hops ;
2007-01-26 08:37:50 +03:00
2007-06-18 01:39:58 +04:00
if ( node - > ports [ i ] - > max_depth > depths [ 0 ] ) {
2007-01-26 08:37:50 +03:00
depths [ 1 ] = depths [ 0 ] ;
2007-06-18 01:39:58 +04:00
depths [ 0 ] = node - > ports [ i ] - > max_depth ;
} else if ( node - > ports [ i ] - > max_depth > depths [ 1 ] )
depths [ 1 ] = node - > ports [ i ] - > max_depth ;
2007-01-26 08:37:50 +03:00
}
node - > max_depth = depths [ 0 ] + 1 ;
node - > max_hops = max ( max_child_hops , depths [ 0 ] + depths [ 1 ] + 2 ) ;
}
2007-10-07 04:10:11 +04:00
static inline struct fw_node * fw_node ( struct list_head * l )
{
return list_entry ( l , struct fw_node , link ) ;
}
2007-01-26 08:37:50 +03:00
2006-12-20 03:58:27 +03:00
/**
* build_tree - Build the tree representation of the topology
* @ self_ids : array of self IDs to create the tree from
* @ self_id_count : the length of the self_ids array
* @ local_id : the node ID of the local node
*
* This function builds the tree representation of the topology given
* by the self IDs from the latest bus reset . During the construction
* of the tree , the function checks that the self IDs are valid and
2007-10-07 04:21:29 +04:00
* internally consistent . On succcess this function returns the
2006-12-20 03:58:27 +03:00
* fw_node corresponding to the local card otherwise NULL .
*/
2007-03-07 20:12:55 +03:00
static struct fw_node * build_tree ( struct fw_card * card ,
u32 * sid , int self_id_count )
2006-12-20 03:58:27 +03:00
{
2007-03-27 18:42:39 +04:00
struct fw_node * node , * child , * local_node , * irm_node ;
2006-12-20 03:58:27 +03:00
struct list_head stack , * h ;
2007-03-07 20:12:55 +03:00
u32 * next_sid , * end , q ;
2006-12-20 03:58:27 +03:00
int i , port_count , child_port_count , phy_id , parent_count , stack_depth ;
2007-06-18 21:44:12 +04:00
int gap_count ;
bool beta_repeaters_present ;
2006-12-20 03:58:27 +03:00
local_node = NULL ;
node = NULL ;
INIT_LIST_HEAD ( & stack ) ;
stack_depth = 0 ;
2007-03-07 20:12:55 +03:00
end = sid + self_id_count ;
2006-12-20 03:58:27 +03:00
phy_id = 0 ;
2007-03-27 18:42:39 +04:00
irm_node = NULL ;
2007-05-08 04:33:35 +04:00
gap_count = SELF_ID_GAP_COUNT ( * sid ) ;
2007-06-18 21:44:12 +04:00
beta_repeaters_present = false ;
2006-12-20 03:58:27 +03:00
while ( sid < end ) {
next_sid = count_ports ( sid , & port_count , & child_port_count ) ;
if ( next_sid = = NULL ) {
fw_error ( " Inconsistent extended self IDs. \n " ) ;
return NULL ;
}
q = * sid ;
2007-05-08 04:33:35 +04:00
if ( phy_id ! = SELF_ID_PHY_ID ( q ) ) {
2006-12-20 03:58:27 +03:00
fw_error ( " PHY ID mismatch in self ID: %d != %d. \n " ,
2007-05-08 04:33:35 +04:00
phy_id , SELF_ID_PHY_ID ( q ) ) ;
2006-12-20 03:58:27 +03:00
return NULL ;
}
if ( child_port_count > stack_depth ) {
fw_error ( " Topology stack underflow \n " ) ;
return NULL ;
}
2007-05-08 04:33:32 +04:00
/*
* Seek back from the top of our stack to find the
* start of the child nodes for this node .
*/
2006-12-20 03:58:27 +03:00
for ( i = 0 , h = & stack ; i < child_port_count ; i + + )
h = h - > prev ;
2007-10-07 04:21:29 +04:00
/*
* When the stack is empty , this yields an invalid value ,
* but that pointer will never be dereferenced .
*/
2006-12-20 03:58:27 +03:00
child = fw_node ( h ) ;
node = fw_node_create ( q , port_count , card - > color ) ;
if ( node = = NULL ) {
2007-06-09 21:26:22 +04:00
fw_error ( " Out of memory while building topology. \n " ) ;
2006-12-20 03:58:27 +03:00
return NULL ;
}
if ( phy_id = = ( card - > node_id & 0x3f ) )
local_node = node ;
2007-05-08 04:33:35 +04:00
if ( SELF_ID_CONTENDER ( q ) )
2007-03-27 18:42:39 +04:00
irm_node = node ;
2006-12-20 03:58:27 +03:00
parent_count = 0 ;
for ( i = 0 ; i < port_count ; i + + ) {
switch ( get_port_type ( sid , i ) ) {
case SELFID_PORT_PARENT :
2007-05-08 04:33:32 +04:00
/*
* Who ' s your daddy ? We dont know the
2006-12-20 03:58:27 +03:00
* parent node at this time , so we
* temporarily abuse node - > color for
* remembering the entry in the
* node - > ports array where the parent
* node should be . Later , when we
* handle the parent node , we fix up
* the reference .
*/
parent_count + + ;
node - > color = i ;
break ;
case SELFID_PORT_CHILD :
2007-06-18 01:39:58 +04:00
node - > ports [ i ] = child ;
2007-05-08 04:33:32 +04:00
/*
* Fix up parent reference for this
* child node .
*/
2007-06-18 01:39:58 +04:00
child - > ports [ child - > color ] = node ;
2006-12-20 03:58:27 +03:00
child - > color = card - > color ;
child = fw_node ( child - > link . next ) ;
break ;
}
}
2007-05-08 04:33:32 +04:00
/*
* Check that the node reports exactly one parent
2006-12-20 03:58:27 +03:00
* port , except for the root , which of course should
2007-05-08 04:33:32 +04:00
* have no parents .
*/
2006-12-20 03:58:27 +03:00
if ( ( next_sid = = end & & parent_count ! = 0 ) | |
( next_sid < end & & parent_count ! = 1 ) ) {
fw_error ( " Parent port inconsistency for node %d: "
" parent_count=%d \n " , phy_id , parent_count ) ;
return NULL ;
}
/* Pop the child nodes off the stack and push the new node. */
__list_del ( h - > prev , & stack ) ;
list_add_tail ( & node - > link , & stack ) ;
stack_depth + = 1 - child_port_count ;
2007-06-18 21:44:12 +04:00
if ( node - > phy_speed = = SCODE_BETA & &
parent_count + child_port_count > 1 )
beta_repeaters_present = true ;
2007-05-08 04:33:32 +04:00
/*
2008-03-24 22:57:12 +03:00
* If PHYs report different gap counts , set an invalid count
* which will force a gap count reconfiguration and a reset .
2007-05-08 04:33:32 +04:00
*/
2007-05-08 04:33:35 +04:00
if ( SELF_ID_GAP_COUNT ( q ) ! = gap_count )
2008-03-24 22:57:12 +03:00
gap_count = 0 ;
2007-01-26 08:37:50 +03:00
update_hop_count ( node ) ;
2006-12-20 03:58:27 +03:00
sid = next_sid ;
phy_id + + ;
}
card - > root_node = node ;
2007-03-27 18:42:39 +04:00
card - > irm_node = irm_node ;
2007-01-26 08:37:50 +03:00
card - > gap_count = gap_count ;
2007-06-18 21:44:12 +04:00
card - > beta_repeaters_present = beta_repeaters_present ;
2006-12-20 03:58:27 +03:00
return local_node ;
}
2007-05-08 04:33:34 +04:00
typedef void ( * fw_node_callback_t ) ( struct fw_card * card ,
struct fw_node * node ,
struct fw_node * parent ) ;
2006-12-20 03:58:27 +03:00
static void
for_each_fw_node ( struct fw_card * card , struct fw_node * root ,
fw_node_callback_t callback )
{
struct list_head list ;
struct fw_node * node , * next , * child , * parent ;
int i ;
INIT_LIST_HEAD ( & list ) ;
fw_node_get ( root ) ;
list_add_tail ( & root - > link , & list ) ;
parent = NULL ;
list_for_each_entry ( node , & list , link ) {
node - > color = card - > color ;
for ( i = 0 ; i < node - > port_count ; i + + ) {
2007-06-18 01:39:58 +04:00
child = node - > ports [ i ] ;
2006-12-20 03:58:27 +03:00
if ( ! child )
continue ;
if ( child - > color = = card - > color )
parent = child ;
else {
fw_node_get ( child ) ;
list_add_tail ( & child - > link , & list ) ;
}
}
callback ( card , node , parent ) ;
}
list_for_each_entry_safe ( node , next , & list , link )
fw_node_put ( node ) ;
}
static void
report_lost_node ( struct fw_card * card ,
struct fw_node * node , struct fw_node * parent )
{
fw_node_event ( card , node , FW_NODE_DESTROYED ) ;
fw_node_put ( node ) ;
2008-11-29 20:56:47 +03:00
/* Topology has changed - reset bus manager retry counter */
card - > bm_retries = 0 ;
2006-12-20 03:58:27 +03:00
}
static void
report_found_node ( struct fw_card * card ,
struct fw_node * node , struct fw_node * parent )
{
int b_path = ( node - > phy_speed = = SCODE_BETA ) ;
if ( parent ! = NULL ) {
2007-01-27 18:59:15 +03:00
/* min() macro doesn't work here with gcc 3.4 */
node - > max_speed = parent - > max_speed < node - > phy_speed ?
parent - > max_speed : node - > phy_speed ;
2006-12-20 03:58:27 +03:00
node - > b_path = parent - > b_path & & b_path ;
} else {
node - > max_speed = node - > phy_speed ;
node - > b_path = b_path ;
}
fw_node_event ( card , node , FW_NODE_CREATED ) ;
2008-11-29 20:56:47 +03:00
/* Topology has changed - reset bus manager retry counter */
card - > bm_retries = 0 ;
2006-12-20 03:58:27 +03:00
}
void fw_destroy_nodes ( struct fw_card * card )
{
unsigned long flags ;
spin_lock_irqsave ( & card - > lock , flags ) ;
card - > color + + ;
if ( card - > local_node ! = NULL )
for_each_fw_node ( card , card - > local_node , report_lost_node ) ;
2008-02-24 20:57:23 +03:00
card - > local_node = NULL ;
2006-12-20 03:58:27 +03:00
spin_unlock_irqrestore ( & card - > lock , flags ) ;
}
static void move_tree ( struct fw_node * node0 , struct fw_node * node1 , int port )
{
struct fw_node * tree ;
int i ;
2007-06-18 01:39:58 +04:00
tree = node1 - > ports [ port ] ;
node0 - > ports [ port ] = tree ;
2006-12-20 03:58:27 +03:00
for ( i = 0 ; i < tree - > port_count ; i + + ) {
2007-06-18 01:39:58 +04:00
if ( tree - > ports [ i ] = = node1 ) {
tree - > ports [ i ] = node0 ;
2006-12-20 03:58:27 +03:00
break ;
}
}
}
/**
* update_tree - compare the old topology tree for card with the new
* one specified by root . Queue the nodes and mark them as either
* found , lost or updated . Update the nodes in the card topology tree
* as we go .
*/
static void
2007-01-26 08:37:50 +03:00
update_tree ( struct fw_card * card , struct fw_node * root )
2006-12-20 03:58:27 +03:00
{
struct list_head list0 , list1 ;
2008-10-17 02:00:15 +04:00
struct fw_node * node0 , * node1 , * next1 ;
2006-12-20 03:58:27 +03:00
int i , event ;
INIT_LIST_HEAD ( & list0 ) ;
list_add_tail ( & card - > local_node - > link , & list0 ) ;
INIT_LIST_HEAD ( & list1 ) ;
list_add_tail ( & root - > link , & list1 ) ;
node0 = fw_node ( list0 . next ) ;
node1 = fw_node ( list1 . next ) ;
while ( & node0 - > link ! = & list0 ) {
2008-03-09 00:38:16 +03:00
WARN_ON ( node0 - > port_count ! = node1 - > port_count ) ;
2006-12-20 03:58:27 +03:00
if ( node0 - > link_on & & ! node1 - > link_on )
event = FW_NODE_LINK_OFF ;
else if ( ! node0 - > link_on & & node1 - > link_on )
event = FW_NODE_LINK_ON ;
2008-03-24 22:54:28 +03:00
else if ( node1 - > initiated_reset & & node1 - > link_on )
event = FW_NODE_INITIATED_RESET ;
2006-12-20 03:58:27 +03:00
else
event = FW_NODE_UPDATED ;
node0 - > node_id = node1 - > node_id ;
node0 - > color = card - > color ;
node0 - > link_on = node1 - > link_on ;
node0 - > initiated_reset = node1 - > initiated_reset ;
2007-01-26 08:37:50 +03:00
node0 - > max_hops = node1 - > max_hops ;
2006-12-20 03:58:27 +03:00
node1 - > color = card - > color ;
fw_node_event ( card , node0 , event ) ;
if ( card - > root_node = = node1 )
card - > root_node = node0 ;
if ( card - > irm_node = = node1 )
card - > irm_node = node0 ;
for ( i = 0 ; i < node0 - > port_count ; i + + ) {
2007-06-18 01:39:58 +04:00
if ( node0 - > ports [ i ] & & node1 - > ports [ i ] ) {
2007-05-08 04:33:32 +04:00
/*
* This port didn ' t change , queue the
2006-12-20 03:58:27 +03:00
* connected node for further
2007-05-08 04:33:32 +04:00
* investigation .
*/
2007-06-18 01:39:58 +04:00
if ( node0 - > ports [ i ] - > color = = card - > color )
2006-12-20 03:58:27 +03:00
continue ;
2007-06-18 01:39:58 +04:00
list_add_tail ( & node0 - > ports [ i ] - > link , & list0 ) ;
list_add_tail ( & node1 - > ports [ i ] - > link , & list1 ) ;
} else if ( node0 - > ports [ i ] ) {
2007-05-08 04:33:32 +04:00
/*
* The nodes connected here were
2006-12-20 03:58:27 +03:00
* unplugged ; unref the lost nodes and
* queue FW_NODE_LOST callbacks for
2007-05-08 04:33:32 +04:00
* them .
*/
2006-12-20 03:58:27 +03:00
2007-06-18 01:39:58 +04:00
for_each_fw_node ( card , node0 - > ports [ i ] ,
2006-12-20 03:58:27 +03:00
report_lost_node ) ;
2007-06-18 01:39:58 +04:00
node0 - > ports [ i ] = NULL ;
} else if ( node1 - > ports [ i ] ) {
2007-05-08 04:33:32 +04:00
/*
* One or more node were connected to
2006-12-20 03:58:27 +03:00
* this port . Move the new nodes into
* the tree and queue FW_NODE_CREATED
2007-05-08 04:33:32 +04:00
* callbacks for them .
*/
2006-12-20 03:58:27 +03:00
move_tree ( node0 , node1 , i ) ;
2007-06-18 01:39:58 +04:00
for_each_fw_node ( card , node0 - > ports [ i ] ,
2006-12-20 03:58:27 +03:00
report_found_node ) ;
}
}
node0 = fw_node ( node0 - > link . next ) ;
2008-10-17 02:00:15 +04:00
next1 = fw_node ( node1 - > link . next ) ;
fw_node_put ( node1 ) ;
node1 = next1 ;
2006-12-20 03:58:27 +03:00
}
}
2007-03-07 20:12:55 +03:00
static void
update_topology_map ( struct fw_card * card , u32 * self_ids , int self_id_count )
{
int node_count ;
card - > topology_map [ 1 ] + + ;
node_count = ( card - > root_node - > node_id & 0x3f ) + 1 ;
card - > topology_map [ 2 ] = ( node_count < < 16 ) | self_id_count ;
2007-05-08 04:33:31 +04:00
card - > topology_map [ 0 ] = ( self_id_count + 2 ) < < 16 ;
2007-03-07 20:12:55 +03:00
memcpy ( & card - > topology_map [ 3 ] , self_ids , self_id_count * 4 ) ;
2007-05-08 04:33:31 +04:00
fw_compute_block_crc ( card - > topology_map ) ;
2007-03-07 20:12:55 +03:00
}
2006-12-20 03:58:27 +03:00
void
fw_core_handle_bus_reset ( struct fw_card * card ,
int node_id , int generation ,
int self_id_count , u32 * self_ids )
{
struct fw_node * local_node ;
unsigned long flags ;
2008-03-20 00:02:40 +03:00
/*
* If the selfID buffer is not the immediate successor of the
* previously processed one , we cannot reliably compare the
* old and new topologies .
*/
if ( ( generation & 0xff ) ! = ( ( card - > generation + 1 ) & 0xff ) & &
card - > local_node ! = NULL ) {
fw_notify ( " skipped bus generations, destroying all nodes \n " ) ;
fw_destroy_nodes ( card ) ;
card - > bm_retries = 0 ;
}
2006-12-20 03:58:27 +03:00
spin_lock_irqsave ( & card - > lock , flags ) ;
card - > node_id = node_id ;
2008-01-25 20:57:41 +03:00
/*
* Update node_id before generation to prevent anybody from using
* a stale node_id together with a current generation .
*/
smp_wmb ( ) ;
2006-12-20 03:58:27 +03:00
card - > generation = generation ;
2007-01-26 08:38:45 +03:00
card - > reset_jiffies = jiffies ;
2008-11-29 19:44:57 +03:00
fw_schedule_bm_work ( card , 0 ) ;
2006-12-20 03:58:27 +03:00
2007-03-07 20:12:55 +03:00
local_node = build_tree ( card , self_ids , self_id_count ) ;
update_topology_map ( card , self_ids , self_id_count ) ;
2006-12-20 03:58:27 +03:00
card - > color + + ;
if ( local_node = = NULL ) {
fw_error ( " topology build failed \n " ) ;
/* FIXME: We need to issue a bus reset in this case. */
} else if ( card - > local_node = = NULL ) {
card - > local_node = local_node ;
for_each_fw_node ( card , local_node , report_found_node ) ;
} else {
2007-01-26 08:37:50 +03:00
update_tree ( card , local_node ) ;
2006-12-20 03:58:27 +03:00
}
spin_unlock_irqrestore ( & card - > lock , flags ) ;
}
EXPORT_SYMBOL ( fw_core_handle_bus_reset ) ;