2014-06-04 00:04:00 +04:00
/*
* Thunderbolt Cactus Ridge driver - bus logic ( NHI independent )
*
* Copyright ( c ) 2014 Andreas Noever < andreas . noever @ gmail . com >
*/
# include <linux/slab.h>
# include <linux/errno.h>
# include <linux/delay.h>
# include "tb.h"
2014-06-04 00:04:01 +04:00
# include "tb_regs.h"
2014-06-04 00:04:00 +04:00
2014-06-04 00:04:05 +04:00
/* enumeration & hot plug handling */
static void tb_scan_port ( struct tb_port * port ) ;
/**
* tb_scan_switch ( ) - scan for and initialize downstream switches
*/
static void tb_scan_switch ( struct tb_switch * sw )
{
int i ;
for ( i = 1 ; i < = sw - > config . max_port_number ; i + + )
tb_scan_port ( & sw - > ports [ i ] ) ;
}
/**
* tb_scan_port ( ) - check for and initialize switches below port
*/
static void tb_scan_port ( struct tb_port * port )
{
struct tb_switch * sw ;
if ( tb_is_upstream_port ( port ) )
return ;
if ( port - > config . type ! = TB_TYPE_PORT )
return ;
if ( tb_wait_for_port ( port , false ) < = 0 )
return ;
if ( port - > remote ) {
tb_port_WARN ( port , " port already has a remote! \n " ) ;
return ;
}
sw = tb_switch_alloc ( port - > sw - > tb , tb_downstream_route ( port ) ) ;
if ( ! sw )
return ;
port - > remote = tb_upstream_port ( sw ) ;
tb_upstream_port ( sw ) - > remote = port ;
tb_scan_switch ( sw ) ;
}
2014-06-04 00:04:00 +04:00
/* hotplug handling */
struct tb_hotplug_event {
struct work_struct work ;
struct tb * tb ;
u64 route ;
u8 port ;
bool unplug ;
} ;
/**
* tb_handle_hotplug ( ) - handle hotplug event
*
* Executes on tb - > wq .
*/
static void tb_handle_hotplug ( struct work_struct * work )
{
struct tb_hotplug_event * ev = container_of ( work , typeof ( * ev ) , work ) ;
struct tb * tb = ev - > tb ;
mutex_lock ( & tb - > lock ) ;
if ( ! tb - > hotplug_active )
goto out ; /* during init, suspend or shutdown */
/* do nothing for now */
out :
mutex_unlock ( & tb - > lock ) ;
kfree ( ev ) ;
}
/**
* tb_schedule_hotplug_handler ( ) - callback function for the control channel
*
* Delegates to tb_handle_hotplug .
*/
static void tb_schedule_hotplug_handler ( void * data , u64 route , u8 port ,
bool unplug )
{
struct tb * tb = data ;
struct tb_hotplug_event * ev = kmalloc ( sizeof ( * ev ) , GFP_KERNEL ) ;
if ( ! ev )
return ;
INIT_WORK ( & ev - > work , tb_handle_hotplug ) ;
ev - > tb = tb ;
ev - > route = route ;
ev - > port = port ;
ev - > unplug = unplug ;
queue_work ( tb - > wq , & ev - > work ) ;
}
/**
* thunderbolt_shutdown_and_free ( ) - shutdown everything
*
* Free all switches and the config channel .
*
* Used in the error path of thunderbolt_alloc_and_start .
*/
void thunderbolt_shutdown_and_free ( struct tb * tb )
{
mutex_lock ( & tb - > lock ) ;
2014-06-04 00:04:02 +04:00
if ( tb - > root_switch )
tb_switch_free ( tb - > root_switch ) ;
tb - > root_switch = NULL ;
2014-06-04 00:04:00 +04:00
if ( tb - > ctl ) {
tb_ctl_stop ( tb - > ctl ) ;
tb_ctl_free ( tb - > ctl ) ;
}
tb - > ctl = NULL ;
tb - > hotplug_active = false ; /* signal tb_handle_hotplug to quit */
/* allow tb_handle_hotplug to acquire the lock */
mutex_unlock ( & tb - > lock ) ;
if ( tb - > wq ) {
flush_workqueue ( tb - > wq ) ;
destroy_workqueue ( tb - > wq ) ;
tb - > wq = NULL ;
}
mutex_destroy ( & tb - > lock ) ;
kfree ( tb ) ;
}
/**
* thunderbolt_alloc_and_start ( ) - setup the thunderbolt bus
*
* Allocates a tb_cfg control channel , initializes the root switch , enables
* plug events and activates pci devices .
*
* Return : Returns NULL on error .
*/
struct tb * thunderbolt_alloc_and_start ( struct tb_nhi * nhi )
{
struct tb * tb ;
2014-06-04 00:04:01 +04:00
BUILD_BUG_ON ( sizeof ( struct tb_regs_switch_header ) ! = 5 * 4 ) ;
BUILD_BUG_ON ( sizeof ( struct tb_regs_port_header ) ! = 8 * 4 ) ;
BUILD_BUG_ON ( sizeof ( struct tb_regs_hop ) ! = 2 * 4 ) ;
2014-06-04 00:04:00 +04:00
tb = kzalloc ( sizeof ( * tb ) , GFP_KERNEL ) ;
if ( ! tb )
return NULL ;
tb - > nhi = nhi ;
mutex_init ( & tb - > lock ) ;
mutex_lock ( & tb - > lock ) ;
tb - > wq = alloc_ordered_workqueue ( " thunderbolt " , 0 ) ;
if ( ! tb - > wq )
goto err_locked ;
tb - > ctl = tb_ctl_alloc ( tb - > nhi , tb_schedule_hotplug_handler , tb ) ;
if ( ! tb - > ctl )
goto err_locked ;
/*
* tb_schedule_hotplug_handler may be called as soon as the config
* channel is started . Thats why we have to hold the lock here .
*/
tb_ctl_start ( tb - > ctl ) ;
2014-06-04 00:04:02 +04:00
tb - > root_switch = tb_switch_alloc ( tb , 0 ) ;
if ( ! tb - > root_switch )
goto err_locked ;
2014-06-04 00:04:05 +04:00
/* Full scan to discover devices added before the driver was loaded. */
tb_scan_switch ( tb - > root_switch ) ;
2014-06-04 00:04:00 +04:00
/* Allow tb_handle_hotplug to progress events */
tb - > hotplug_active = true ;
mutex_unlock ( & tb - > lock ) ;
return tb ;
err_locked :
mutex_unlock ( & tb - > lock ) ;
thunderbolt_shutdown_and_free ( tb ) ;
return NULL ;
}