2008-09-17 16:34:16 +01:00
/*
* WiMedia Logical Link Control Protocol ( WLP )
*
* Copyright ( C ) 2005 - 2006 Intel Corporation
* Reinette Chatre < reinette . chatre @ intel . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation .
*
* 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 . , 51 Franklin Street , Fifth Floor , Boston , MA
* 02110 - 1301 , USA .
*
*
* FIXME : docs
*/
# include <linux/wlp.h>
2008-12-22 18:22:50 +00:00
# include "wlp-internal.h"
2008-09-17 16:34:16 +01:00
static
void wlp_neighbor_init ( struct wlp_neighbor_e * neighbor )
{
INIT_LIST_HEAD ( & neighbor - > wssid ) ;
}
/**
* Create area for device information storage
*
* wlp - > mutex must be held
*/
int __wlp_alloc_device_info ( struct wlp * wlp )
{
struct device * dev = & wlp - > rc - > uwb_dev . dev ;
BUG_ON ( wlp - > dev_info ! = NULL ) ;
wlp - > dev_info = kzalloc ( sizeof ( struct wlp_device_info ) , GFP_KERNEL ) ;
if ( wlp - > dev_info = = NULL ) {
dev_err ( dev , " WLP: Unable to allocate memory for "
" device information. \n " ) ;
return - ENOMEM ;
}
return 0 ;
}
/**
* Fill in device information using function provided by driver
*
* wlp - > mutex must be held
*/
static
void __wlp_fill_device_info ( struct wlp * wlp )
{
wlp - > fill_device_info ( wlp , wlp - > dev_info ) ;
}
/**
* Setup device information
*
* Allocate area for device information and populate it .
*
* wlp - > mutex must be held
*/
int __wlp_setup_device_info ( struct wlp * wlp )
{
int result ;
struct device * dev = & wlp - > rc - > uwb_dev . dev ;
result = __wlp_alloc_device_info ( wlp ) ;
if ( result < 0 ) {
dev_err ( dev , " WLP: Unable to allocate area for "
" device information. \n " ) ;
return result ;
}
__wlp_fill_device_info ( wlp ) ;
return 0 ;
}
/**
* Remove information about neighbor stored temporarily
*
* Information learned during discovey should only be stored when the
* device enrolls in the neighbor ' s WSS . We do need to store this
* information temporarily in order to present it to the user .
*
* We are only interested in keeping neighbor WSS information if that
* neighbor is accepting enrollment .
*
* should be called with wlp - > nbmutex held
*/
void wlp_remove_neighbor_tmp_info ( struct wlp_neighbor_e * neighbor )
{
struct wlp_wssid_e * wssid_e , * next ;
u8 keep ;
if ( ! list_empty ( & neighbor - > wssid ) ) {
list_for_each_entry_safe ( wssid_e , next , & neighbor - > wssid ,
node ) {
if ( wssid_e - > info ! = NULL ) {
keep = wssid_e - > info - > accept_enroll ;
kfree ( wssid_e - > info ) ;
wssid_e - > info = NULL ;
if ( ! keep ) {
list_del ( & wssid_e - > node ) ;
kfree ( wssid_e ) ;
}
}
}
}
if ( neighbor - > info ! = NULL ) {
kfree ( neighbor - > info ) ;
neighbor - > info = NULL ;
}
}
2008-12-22 18:22:50 +00:00
/*
2008-09-17 16:34:16 +01:00
* Populate WLP neighborhood cache with neighbor information
*
* A new neighbor is found . If it is discoverable then we add it to the
* neighborhood cache .
*
*/
static
int wlp_add_neighbor ( struct wlp * wlp , struct uwb_dev * dev )
{
int result = 0 ;
int discoverable ;
struct wlp_neighbor_e * neighbor ;
2008-12-22 18:22:50 +00:00
/*
2008-09-17 16:34:16 +01:00
* FIXME :
* Use contents of WLP IE found in beacon cache to determine if
* neighbor is discoverable .
* The device does not support WLP IE yet so this still needs to be
* done . Until then we assume all devices are discoverable .
*/
discoverable = 1 ; /* will be changed when FIXME disappears */
if ( discoverable ) {
/* Add neighbor to cache for discovery */
neighbor = kzalloc ( sizeof ( * neighbor ) , GFP_KERNEL ) ;
if ( neighbor = = NULL ) {
dev_err ( & dev - > dev , " Unable to create memory for "
" new neighbor. \n " ) ;
result = - ENOMEM ;
goto error_no_mem ;
}
wlp_neighbor_init ( neighbor ) ;
uwb_dev_get ( dev ) ;
neighbor - > uwb_dev = dev ;
list_add ( & neighbor - > node , & wlp - > neighbors ) ;
}
error_no_mem :
return result ;
}
/**
* Remove one neighbor from cache
*/
static
void __wlp_neighbor_release ( struct wlp_neighbor_e * neighbor )
{
struct wlp_wssid_e * wssid_e , * next_wssid_e ;
list_for_each_entry_safe ( wssid_e , next_wssid_e ,
& neighbor - > wssid , node ) {
list_del ( & wssid_e - > node ) ;
kfree ( wssid_e ) ;
}
uwb_dev_put ( neighbor - > uwb_dev ) ;
list_del ( & neighbor - > node ) ;
kfree ( neighbor ) ;
}
/**
* Clear entire neighborhood cache .
*/
static
void __wlp_neighbors_release ( struct wlp * wlp )
{
struct wlp_neighbor_e * neighbor , * next ;
if ( list_empty ( & wlp - > neighbors ) )
return ;
list_for_each_entry_safe ( neighbor , next , & wlp - > neighbors , node ) {
__wlp_neighbor_release ( neighbor ) ;
}
}
static
void wlp_neighbors_release ( struct wlp * wlp )
{
mutex_lock ( & wlp - > nbmutex ) ;
__wlp_neighbors_release ( wlp ) ;
mutex_unlock ( & wlp - > nbmutex ) ;
}
/**
* Send D1 message to neighbor , receive D2 message
*
* @ neighbor : neighbor to which D1 message will be sent
* @ wss : if not NULL , it is an enrollment request for this WSS
* @ wssid : if wss not NULL , this is the wssid of the WSS in which we
* want to enroll
*
* A D1 / D2 exchange is done for one of two reasons : discovery or
* enrollment . If done for discovery the D1 message is sent to the neighbor
* and the contents of the D2 response is stored in a temporary cache .
* If done for enrollment the @ wss and @ wssid are provided also . In this
* case the D1 message is sent to the neighbor , the D2 response is parsed
* for enrollment of the WSS with wssid .
*
* & wss - > mutex is held
*/
static
int wlp_d1d2_exchange ( struct wlp * wlp , struct wlp_neighbor_e * neighbor ,
struct wlp_wss * wss , struct wlp_uuid * wssid )
{
int result ;
struct device * dev = & wlp - > rc - > uwb_dev . dev ;
DECLARE_COMPLETION_ONSTACK ( completion ) ;
struct wlp_session session ;
struct sk_buff * skb ;
struct wlp_frame_assoc * resp ;
struct uwb_dev_addr * dev_addr = & neighbor - > uwb_dev - > dev_addr ;
mutex_lock ( & wlp - > mutex ) ;
if ( ! wlp_uuid_is_set ( & wlp - > uuid ) ) {
dev_err ( dev , " WLP: UUID is not set. Set via sysfs to "
" proceed. \n " ) ;
result = - ENXIO ;
goto out ;
}
/* Send D1 association frame */
result = wlp_send_assoc_frame ( wlp , wss , dev_addr , WLP_ASSOC_D1 ) ;
if ( result < 0 ) {
dev_err ( dev , " Unable to send D1 frame to neighbor "
" %02x:%02x (%d) \n " , dev_addr - > data [ 1 ] ,
dev_addr - > data [ 0 ] , result ) ;
goto out ;
}
/* Create session, wait for response */
session . exp_message = WLP_ASSOC_D2 ;
session . cb = wlp_session_cb ;
session . cb_priv = & completion ;
session . neighbor_addr = * dev_addr ;
BUG_ON ( wlp - > session ! = NULL ) ;
wlp - > session = & session ;
/* Wait for D2/F0 frame */
result = wait_for_completion_interruptible_timeout ( & completion ,
WLP_PER_MSG_TIMEOUT * HZ ) ;
if ( result = = 0 ) {
result = - ETIMEDOUT ;
dev_err ( dev , " Timeout while sending D1 to neighbor "
" %02x:%02x. \n " , dev_addr - > data [ 1 ] ,
dev_addr - > data [ 0 ] ) ;
goto error_session ;
}
if ( result < 0 ) {
dev_err ( dev , " Unable to discover/enroll neighbor %02x:%02x. \n " ,
dev_addr - > data [ 1 ] , dev_addr - > data [ 0 ] ) ;
goto error_session ;
}
/* Parse message in session->data: it will be either D2 or F0 */
skb = session . data ;
resp = ( void * ) skb - > data ;
if ( resp - > type = = WLP_ASSOC_F0 ) {
result = wlp_parse_f0 ( wlp , skb ) ;
if ( result < 0 )
dev_err ( dev , " WLP: Unable to parse F0 from neighbor "
" %02x:%02x. \n " , dev_addr - > data [ 1 ] ,
dev_addr - > data [ 0 ] ) ;
result = - EINVAL ;
goto error_resp_parse ;
}
if ( wss = = NULL ) {
/* Discovery */
result = wlp_parse_d2_frame_to_cache ( wlp , skb , neighbor ) ;
if ( result < 0 ) {
dev_err ( dev , " WLP: Unable to parse D2 message from "
" neighbor %02x:%02x for discovery. \n " ,
dev_addr - > data [ 1 ] , dev_addr - > data [ 0 ] ) ;
goto error_resp_parse ;
}
} else {
/* Enrollment */
result = wlp_parse_d2_frame_to_enroll ( wss , skb , neighbor ,
wssid ) ;
if ( result < 0 ) {
dev_err ( dev , " WLP: Unable to parse D2 message from "
" neighbor %02x:%02x for enrollment. \n " ,
dev_addr - > data [ 1 ] , dev_addr - > data [ 0 ] ) ;
goto error_resp_parse ;
}
}
error_resp_parse :
kfree_skb ( skb ) ;
error_session :
wlp - > session = NULL ;
out :
mutex_unlock ( & wlp - > mutex ) ;
return result ;
}
/**
* Enroll into WSS of provided WSSID by using neighbor as registrar
*
* & wss - > mutex is held
*/
int wlp_enroll_neighbor ( struct wlp * wlp , struct wlp_neighbor_e * neighbor ,
struct wlp_wss * wss , struct wlp_uuid * wssid )
{
int result = 0 ;
struct device * dev = & wlp - > rc - > uwb_dev . dev ;
char buf [ WLP_WSS_UUID_STRSIZE ] ;
struct uwb_dev_addr * dev_addr = & neighbor - > uwb_dev - > dev_addr ;
2008-12-22 18:22:50 +00:00
2008-09-17 16:34:16 +01:00
wlp_wss_uuid_print ( buf , sizeof ( buf ) , wssid ) ;
2008-12-22 18:22:50 +00:00
2008-09-17 16:34:16 +01:00
result = wlp_d1d2_exchange ( wlp , neighbor , wss , wssid ) ;
if ( result < 0 ) {
dev_err ( dev , " WLP: D1/D2 message exchange for enrollment "
" failed. result = %d \n " , result ) ;
goto out ;
}
if ( wss - > state ! = WLP_WSS_STATE_PART_ENROLLED ) {
dev_err ( dev , " WLP: Unable to enroll into WSS %s using "
" neighbor %02x:%02x. \n " , buf ,
dev_addr - > data [ 1 ] , dev_addr - > data [ 0 ] ) ;
result = - EINVAL ;
goto out ;
}
if ( wss - > secure_status = = WLP_WSS_SECURE ) {
dev_err ( dev , " FIXME: need to complete secure enrollment. \n " ) ;
result = - EINVAL ;
goto error ;
} else {
wss - > state = WLP_WSS_STATE_ENROLLED ;
2008-12-22 18:22:50 +00:00
dev_dbg ( dev , " WLP: Success Enrollment into unsecure WSS "
" %s using neighbor %02x:%02x. \n " ,
buf , dev_addr - > data [ 1 ] , dev_addr - > data [ 0 ] ) ;
2008-09-17 16:34:16 +01:00
}
out :
return result ;
error :
wlp_wss_reset ( wss ) ;
return result ;
}
/**
* Discover WSS information of neighbor ' s active WSS
*/
static
int wlp_discover_neighbor ( struct wlp * wlp ,
struct wlp_neighbor_e * neighbor )
{
return wlp_d1d2_exchange ( wlp , neighbor , NULL , NULL ) ;
}
/**
* Each neighbor in the neighborhood cache is discoverable . Discover it .
*
* Discovery is done through sending of D1 association frame and parsing
* the D2 association frame response . Only wssid from D2 will be included
* in neighbor cache , rest is just displayed to user and forgotten .
*
* The discovery is not done in parallel . This is simple and enables us to
* maintain only one association context .
*
* The discovery of one neighbor does not affect the other , but if the
* discovery of a neighbor fails it is removed from the neighborhood cache .
*/
static
int wlp_discover_all_neighbors ( struct wlp * wlp )
{
int result = 0 ;
struct device * dev = & wlp - > rc - > uwb_dev . dev ;
struct wlp_neighbor_e * neighbor , * next ;
list_for_each_entry_safe ( neighbor , next , & wlp - > neighbors , node ) {
result = wlp_discover_neighbor ( wlp , neighbor ) ;
if ( result < 0 ) {
dev_err ( dev , " WLP: Unable to discover neighbor "
" %02x:%02x, removing from neighborhood. \n " ,
neighbor - > uwb_dev - > dev_addr . data [ 1 ] ,
neighbor - > uwb_dev - > dev_addr . data [ 0 ] ) ;
__wlp_neighbor_release ( neighbor ) ;
}
}
return result ;
}
static int wlp_add_neighbor_helper ( struct device * dev , void * priv )
{
struct wlp * wlp = priv ;
struct uwb_dev * uwb_dev = to_uwb_dev ( dev ) ;
return wlp_add_neighbor ( wlp , uwb_dev ) ;
}
/**
* Discover WLP neighborhood
*
* Will send D1 association frame to all devices in beacon group that have
* discoverable bit set in WLP IE . D2 frames will be received , information
* displayed to user in @ buf . Partial information ( from D2 association
* frame ) will be cached to assist with future association
* requests .
*
* The discovery of the WLP neighborhood is triggered by the user . This
* should occur infrequently and we thus free current cache and re - allocate
* memory if needed .
*
* If one neighbor fails during initial discovery ( determining if it is a
* neighbor or not ) , we fail all - note that interaction with neighbor has
* not occured at this point so if a failure occurs we know something went wrong
* locally . We thus undo everything .
*/
ssize_t wlp_discover ( struct wlp * wlp )
{
int result = 0 ;
struct device * dev = & wlp - > rc - > uwb_dev . dev ;
mutex_lock ( & wlp - > nbmutex ) ;
/* Clear current neighborhood cache. */
__wlp_neighbors_release ( wlp ) ;
/* Determine which devices in neighborhood. Repopulate cache. */
result = uwb_dev_for_each ( wlp - > rc , wlp_add_neighbor_helper , wlp ) ;
if ( result < 0 ) {
/* May have partial neighbor information, release all. */
__wlp_neighbors_release ( wlp ) ;
goto error_dev_for_each ;
}
/* Discover the properties of devices in neighborhood. */
result = wlp_discover_all_neighbors ( wlp ) ;
/* In case of failure we still print our partial results. */
if ( result < 0 ) {
dev_err ( dev , " Unable to fully discover neighborhood. \n " ) ;
result = 0 ;
}
error_dev_for_each :
mutex_unlock ( & wlp - > nbmutex ) ;
return result ;
}
/**
* Handle events from UWB stack
*
* We handle events conservatively . If a neighbor goes off the air we
* remove it from the neighborhood . If an association process is in
* progress this function will block waiting for the nbmutex to become
* free . The association process will thus be allowed to complete before it
* is removed .
*/
static
void wlp_uwb_notifs_cb ( void * _wlp , struct uwb_dev * uwb_dev ,
enum uwb_notifs event )
{
struct wlp * wlp = _wlp ;
struct device * dev = & wlp - > rc - > uwb_dev . dev ;
struct wlp_neighbor_e * neighbor , * next ;
int result ;
switch ( event ) {
case UWB_NOTIF_ONAIR :
result = wlp_eda_create_node ( & wlp - > eda ,
uwb_dev - > mac_addr . data ,
& uwb_dev - > dev_addr ) ;
if ( result < 0 )
dev_err ( dev , " WLP: Unable to add new neighbor "
" %02x:%02x to EDA cache. \n " ,
uwb_dev - > dev_addr . data [ 1 ] ,
uwb_dev - > dev_addr . data [ 0 ] ) ;
break ;
case UWB_NOTIF_OFFAIR :
wlp_eda_rm_node ( & wlp - > eda , & uwb_dev - > dev_addr ) ;
mutex_lock ( & wlp - > nbmutex ) ;
2008-12-22 18:22:50 +00:00
list_for_each_entry_safe ( neighbor , next , & wlp - > neighbors , node ) {
if ( neighbor - > uwb_dev = = uwb_dev )
2008-09-17 16:34:16 +01:00
__wlp_neighbor_release ( neighbor ) ;
}
mutex_unlock ( & wlp - > nbmutex ) ;
break ;
default :
dev_err ( dev , " don't know how to handle event %d from uwb \n " ,
event ) ;
}
}
2008-11-17 16:16:51 +00:00
static void wlp_channel_changed ( struct uwb_pal * pal , int channel )
{
struct wlp * wlp = container_of ( pal , struct wlp , pal ) ;
if ( channel < 0 )
netif_carrier_off ( wlp - > ndev ) ;
else
netif_carrier_on ( wlp - > ndev ) ;
}
int wlp_setup ( struct wlp * wlp , struct uwb_rc * rc , struct net_device * ndev )
2008-09-17 16:34:16 +01:00
{
int result ;
BUG_ON ( wlp - > fill_device_info = = NULL ) ;
BUG_ON ( wlp - > xmit_frame = = NULL ) ;
BUG_ON ( wlp - > stop_queue = = NULL ) ;
BUG_ON ( wlp - > start_queue = = NULL ) ;
2008-12-22 18:22:50 +00:00
2008-09-17 16:34:16 +01:00
wlp - > rc = rc ;
2008-11-17 16:16:51 +00:00
wlp - > ndev = ndev ;
2008-09-17 16:34:16 +01:00
wlp_eda_init ( & wlp - > eda ) ; /* Set up address cache */
wlp - > uwb_notifs_handler . cb = wlp_uwb_notifs_cb ;
wlp - > uwb_notifs_handler . data = wlp ;
uwb_notifs_register ( rc , & wlp - > uwb_notifs_handler ) ;
uwb_pal_init ( & wlp - > pal ) ;
2008-11-17 15:53:42 +00:00
wlp - > pal . rc = rc ;
2008-11-17 16:16:51 +00:00
wlp - > pal . channel_changed = wlp_channel_changed ;
2008-11-17 15:53:42 +00:00
result = uwb_pal_register ( & wlp - > pal ) ;
2008-09-17 16:34:16 +01:00
if ( result < 0 )
uwb_notifs_deregister ( wlp - > rc , & wlp - > uwb_notifs_handler ) ;
return result ;
}
EXPORT_SYMBOL_GPL ( wlp_setup ) ;
void wlp_remove ( struct wlp * wlp )
{
wlp_neighbors_release ( wlp ) ;
2008-11-17 15:53:42 +00:00
uwb_pal_unregister ( & wlp - > pal ) ;
2008-09-17 16:34:16 +01:00
uwb_notifs_deregister ( wlp - > rc , & wlp - > uwb_notifs_handler ) ;
wlp_eda_release ( & wlp - > eda ) ;
mutex_lock ( & wlp - > mutex ) ;
if ( wlp - > dev_info ! = NULL )
kfree ( wlp - > dev_info ) ;
mutex_unlock ( & wlp - > mutex ) ;
wlp - > rc = NULL ;
}
EXPORT_SYMBOL_GPL ( wlp_remove ) ;
/**
* wlp_reset_all - reset the WLP hardware
* @ wlp : the WLP device to reset .
*
* This schedules a full hardware reset of the WLP device . The radio
* controller and any other PALs will also be reset .
*/
void wlp_reset_all ( struct wlp * wlp )
{
uwb_rc_reset_all ( wlp - > rc ) ;
}
EXPORT_SYMBOL_GPL ( wlp_reset_all ) ;