2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-07-19 04:54:20 +03:00
/*
* Copyright Gavin Shan , IBM Corporation 2016.
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/netdevice.h>
# include <linux/skbuff.h>
# include <net/ncsi.h>
# include <net/net_namespace.h>
# include <net/sock.h>
# include "internal.h"
# include "ncsi-pkt.h"
static int ncsi_validate_aen_pkt ( struct ncsi_aen_pkt_hdr * h ,
const unsigned short payload )
{
u32 checksum ;
__be32 * pchecksum ;
if ( h - > common . revision ! = NCSI_PKT_REVISION )
return - EINVAL ;
if ( ntohs ( h - > common . length ) ! = payload )
return - EINVAL ;
/* Validate checksum, which might be zeroes if the
* sender doesn ' t support checksum according to NCSI
* specification .
*/
pchecksum = ( __be32 * ) ( ( void * ) ( h + 1 ) + payload - 4 ) ;
if ( ntohl ( * pchecksum ) = = 0 )
return 0 ;
checksum = ncsi_calculate_checksum ( ( unsigned char * ) h ,
sizeof ( * h ) + payload - 4 ) ;
if ( * pchecksum ! = htonl ( checksum ) )
return - EINVAL ;
return 0 ;
}
static int ncsi_aen_handler_lsc ( struct ncsi_dev_priv * ndp ,
struct ncsi_aen_pkt_hdr * h )
{
2018-11-16 07:51:59 +03:00
struct ncsi_channel * nc , * tmp ;
2016-07-19 04:54:20 +03:00
struct ncsi_channel_mode * ncm ;
2016-10-04 03:25:47 +03:00
unsigned long old_data , data ;
2018-11-16 07:51:59 +03:00
struct ncsi_aen_lsc_pkt * lsc ;
struct ncsi_package * np ;
2018-11-16 07:51:57 +03:00
bool had_link , has_link ;
2018-11-16 07:51:59 +03:00
unsigned long flags ;
bool chained ;
int state ;
2016-07-19 04:54:20 +03:00
/* Find the NCSI channel */
ncsi_find_package_and_channel ( ndp , h - > common . channel , NULL , & nc ) ;
if ( ! nc )
return - ENODEV ;
/* Update the link status */
lsc = ( struct ncsi_aen_lsc_pkt * ) h ;
2016-10-04 03:25:47 +03:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
ncm = & nc - > modes [ NCSI_MODE_LINK ] ;
2016-07-19 04:54:20 +03:00
old_data = ncm - > data [ 2 ] ;
2016-10-04 03:25:47 +03:00
data = ntohl ( lsc - > status ) ;
ncm - > data [ 2 ] = data ;
2016-07-19 04:54:20 +03:00
ncm - > data [ 4 ] = ntohl ( lsc - > oem_status ) ;
2016-10-04 03:25:47 +03:00
2018-11-16 07:51:57 +03:00
had_link = ! ! ( old_data & 0x1 ) ;
has_link = ! ! ( data & 0x1 ) ;
2018-06-19 08:38:31 +03:00
netdev_dbg ( ndp - > ndev . dev , " NCSI: LSC AEN - channel %u state %s \n " ,
nc - > id , data & 0x1 ? " up " : " down " ) ;
2017-11-08 08:30:44 +03:00
2016-10-04 03:25:47 +03:00
chained = ! list_empty ( & nc - > link ) ;
state = nc - > state ;
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2018-11-16 07:51:57 +03:00
if ( state = = NCSI_CHANNEL_INACTIVE )
netdev_warn ( ndp - > ndev . dev ,
" NCSI: Inactive channel %u received AEN! \n " ,
nc - > id ) ;
if ( ( had_link = = has_link ) | | chained )
2016-07-19 04:54:20 +03:00
return 0 ;
2018-11-16 07:51:59 +03:00
if ( ! ndp - > multi_package & & ! nc - > package - > multi_channel ) {
if ( had_link ) {
ndp - > flags | = NCSI_DEV_RESHUFFLE ;
ncsi_stop_channel_monitor ( nc ) ;
spin_lock_irqsave ( & ndp - > lock , flags ) ;
list_add_tail_rcu ( & nc - > link , & ndp - > channel_queue ) ;
spin_unlock_irqrestore ( & ndp - > lock , flags ) ;
return ncsi_process_next_channel ( ndp ) ;
}
/* Configured channel came up */
return 0 ;
}
2016-07-19 04:54:20 +03:00
2018-11-16 07:51:59 +03:00
if ( had_link ) {
ncm = & nc - > modes [ NCSI_MODE_TX_ENABLE ] ;
if ( ncsi_channel_is_last ( ndp , nc ) ) {
/* No channels left, reconfigure */
return ncsi_reset_dev ( & ndp - > ndev ) ;
} else if ( ncm - > enable ) {
/* Need to failover Tx channel */
ncsi_update_tx_channel ( ndp , nc - > package , nc , NULL ) ;
}
} else if ( has_link & & nc - > package - > preferred_channel = = nc ) {
/* Return Tx to preferred channel */
ncsi_update_tx_channel ( ndp , nc - > package , NULL , nc ) ;
} else if ( has_link ) {
NCSI_FOR_EACH_PACKAGE ( ndp , np ) {
NCSI_FOR_EACH_CHANNEL ( np , tmp ) {
/* Enable Tx on this channel if the current Tx
* channel is down .
*/
ncm = & tmp - > modes [ NCSI_MODE_TX_ENABLE ] ;
if ( ncm - > enable & &
! ncsi_channel_has_link ( tmp ) ) {
ncsi_update_tx_channel ( ndp , nc - > package ,
tmp , nc ) ;
break ;
}
}
}
}
/* Leave configured channels active in a multi-channel scenario so
* AEN events are still received .
*/
return 0 ;
2016-07-19 04:54:20 +03:00
}
static int ncsi_aen_handler_cr ( struct ncsi_dev_priv * ndp ,
struct ncsi_aen_pkt_hdr * h )
{
struct ncsi_channel * nc ;
unsigned long flags ;
/* Find the NCSI channel */
ncsi_find_package_and_channel ( ndp , h - > common . channel , NULL , & nc ) ;
if ( ! nc )
return - ENODEV ;
2016-10-04 03:25:47 +03:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
2016-07-19 04:54:20 +03:00
if ( ! list_empty ( & nc - > link ) | |
2016-10-04 03:25:47 +03:00
nc - > state ! = NCSI_CHANNEL_ACTIVE ) {
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2016-07-19 04:54:20 +03:00
return 0 ;
2016-10-04 03:25:47 +03:00
}
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2016-07-19 04:54:20 +03:00
ncsi_stop_channel_monitor ( nc ) ;
2016-10-04 03:25:47 +03:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
nc - > state = NCSI_CHANNEL_INVISIBLE ;
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2016-07-19 04:54:20 +03:00
spin_lock_irqsave ( & ndp - > lock , flags ) ;
2016-10-04 03:25:47 +03:00
nc - > state = NCSI_CHANNEL_INACTIVE ;
2016-07-19 04:54:20 +03:00
list_add_tail_rcu ( & nc - > link , & ndp - > channel_queue ) ;
spin_unlock_irqrestore ( & ndp - > lock , flags ) ;
2023-04-26 11:13:50 +03:00
nc - > modes [ NCSI_MODE_TX_ENABLE ] . enable = 0 ;
2016-07-19 04:54:20 +03:00
return ncsi_process_next_channel ( ndp ) ;
}
static int ncsi_aen_handler_hncdsc ( struct ncsi_dev_priv * ndp ,
struct ncsi_aen_pkt_hdr * h )
{
struct ncsi_channel * nc ;
struct ncsi_channel_mode * ncm ;
struct ncsi_aen_hncdsc_pkt * hncdsc ;
unsigned long flags ;
/* Find the NCSI channel */
ncsi_find_package_and_channel ( ndp , h - > common . channel , NULL , & nc ) ;
if ( ! nc )
return - ENODEV ;
2016-10-20 03:45:52 +03:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
2016-07-19 04:54:20 +03:00
ncm = & nc - > modes [ NCSI_MODE_LINK ] ;
hncdsc = ( struct ncsi_aen_hncdsc_pkt * ) h ;
ncm - > data [ 3 ] = ntohl ( hncdsc - > status ) ;
2016-10-20 03:45:52 +03:00
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2018-06-19 08:38:33 +03:00
netdev_dbg ( ndp - > ndev . dev ,
" NCSI: host driver %srunning on channel %u \n " ,
ncm - > data [ 3 ] & 0x1 ? " " : " not " , nc - > id ) ;
2016-07-19 04:54:20 +03:00
return 0 ;
}
static struct ncsi_aen_handler {
unsigned char type ;
int payload ;
int ( * handler ) ( struct ncsi_dev_priv * ndp ,
struct ncsi_aen_pkt_hdr * h ) ;
} ncsi_aen_handlers [ ] = {
{ NCSI_PKT_AEN_LSC , 12 , ncsi_aen_handler_lsc } ,
{ NCSI_PKT_AEN_CR , 4 , ncsi_aen_handler_cr } ,
2017-10-19 05:43:05 +03:00
{ NCSI_PKT_AEN_HNCDSC , 8 , ncsi_aen_handler_hncdsc }
2016-07-19 04:54:20 +03:00
} ;
int ncsi_aen_handler ( struct ncsi_dev_priv * ndp , struct sk_buff * skb )
{
struct ncsi_aen_pkt_hdr * h ;
struct ncsi_aen_handler * nah = NULL ;
int i , ret ;
/* Find the handler */
h = ( struct ncsi_aen_pkt_hdr * ) skb_network_header ( skb ) ;
for ( i = 0 ; i < ARRAY_SIZE ( ncsi_aen_handlers ) ; i + + ) {
if ( ncsi_aen_handlers [ i ] . type = = h - > type ) {
nah = & ncsi_aen_handlers [ i ] ;
break ;
}
}
if ( ! nah ) {
netdev_warn ( ndp - > ndev . dev , " Invalid AEN (0x%x) received \n " ,
h - > type ) ;
return - ENOENT ;
}
ret = ncsi_validate_aen_pkt ( h , nah - > payload ) ;
2017-11-08 08:30:44 +03:00
if ( ret ) {
netdev_warn ( ndp - > ndev . dev ,
" NCSI: 'bad' packet ignored for AEN type 0x%x \n " ,
h - > type ) ;
2016-07-19 04:54:20 +03:00
goto out ;
2017-11-08 08:30:44 +03:00
}
2016-07-19 04:54:20 +03:00
ret = nah - > handler ( ndp , h ) ;
2017-11-08 08:30:44 +03:00
if ( ret )
netdev_err ( ndp - > ndev . dev ,
" NCSI: Handler for AEN type 0x%x returned %d \n " ,
h - > type , ret ) ;
2016-07-19 04:54:20 +03:00
out :
consume_skb ( skb ) ;
return ret ;
}