2016-07-19 11:54:20 +10:00
/*
* Copyright Gavin Shan , IBM Corporation 2016.
*
* 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 .
*/
# 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 )
{
struct ncsi_aen_lsc_pkt * lsc ;
struct ncsi_channel * nc ;
struct ncsi_channel_mode * ncm ;
2016-10-04 11:25:47 +11:00
bool chained ;
int state ;
unsigned long old_data , data ;
2016-07-19 11:54:20 +10:00
unsigned long flags ;
/* 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 11:25:47 +11:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
ncm = & nc - > modes [ NCSI_MODE_LINK ] ;
2016-07-19 11:54:20 +10:00
old_data = ncm - > data [ 2 ] ;
2016-10-04 11:25:47 +11:00
data = ntohl ( lsc - > status ) ;
ncm - > data [ 2 ] = data ;
2016-07-19 11:54:20 +10:00
ncm - > data [ 4 ] = ntohl ( lsc - > oem_status ) ;
2016-10-04 11:25:47 +11:00
2017-11-08 16:30:44 +11:00
netdev_info ( ndp - > ndev . dev , " NCSI: LSC AEN - channel %u state %s \n " ,
nc - > id , data & 0x1 ? " up " : " down " ) ;
2016-10-04 11:25:47 +11:00
chained = ! list_empty ( & nc - > link ) ;
state = nc - > state ;
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
if ( ! ( ( old_data ^ data ) & 0x1 ) | | chained )
2016-07-19 11:54:20 +10:00
return 0 ;
2016-10-04 11:25:47 +11:00
if ( ! ( state = = NCSI_CHANNEL_INACTIVE & & ( data & 0x1 ) ) & &
! ( state = = NCSI_CHANNEL_ACTIVE & & ! ( data & 0x1 ) ) )
2016-07-19 11:54:20 +10:00
return 0 ;
if ( ! ( ndp - > flags & NCSI_DEV_HWA ) & &
2016-10-04 11:25:47 +11:00
state = = NCSI_CHANNEL_ACTIVE )
2016-07-19 11:54:20 +10:00
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 ) ;
}
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 11:25:47 +11:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
2016-07-19 11:54:20 +10:00
if ( ! list_empty ( & nc - > link ) | |
2016-10-04 11:25:47 +11:00
nc - > state ! = NCSI_CHANNEL_ACTIVE ) {
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2016-07-19 11:54:20 +10:00
return 0 ;
2016-10-04 11:25:47 +11:00
}
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2016-07-19 11:54:20 +10:00
ncsi_stop_channel_monitor ( nc ) ;
2016-10-04 11:25:47 +11:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
nc - > state = NCSI_CHANNEL_INVISIBLE ;
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2016-07-19 11:54:20 +10:00
spin_lock_irqsave ( & ndp - > lock , flags ) ;
2016-10-04 11:25:47 +11:00
nc - > state = NCSI_CHANNEL_INACTIVE ;
2016-07-19 11:54:20 +10:00
list_add_tail_rcu ( & nc - > link , & ndp - > channel_queue ) ;
spin_unlock_irqrestore ( & ndp - > lock , flags ) ;
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 11:45:52 +11:00
spin_lock_irqsave ( & nc - > lock , flags ) ;
2016-07-19 11:54:20 +10:00
ncm = & nc - > modes [ NCSI_MODE_LINK ] ;
hncdsc = ( struct ncsi_aen_hncdsc_pkt * ) h ;
ncm - > data [ 3 ] = ntohl ( hncdsc - > status ) ;
2016-10-20 11:45:52 +11:00
spin_unlock_irqrestore ( & nc - > lock , flags ) ;
2017-12-15 16:16:40 +11:00
netdev_printk ( KERN_DEBUG , ndp - > ndev . dev ,
" NCSI: host driver %srunning on channel %u \n " ,
ncm - > data [ 3 ] & 0x1 ? " " : " not " , nc - > id ) ;
2016-07-19 11:54:20 +10: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 13:43:05 +11:00
{ NCSI_PKT_AEN_HNCDSC , 8 , ncsi_aen_handler_hncdsc }
2016-07-19 11:54:20 +10: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 16:30:44 +11:00
if ( ret ) {
netdev_warn ( ndp - > ndev . dev ,
" NCSI: 'bad' packet ignored for AEN type 0x%x \n " ,
h - > type ) ;
2016-07-19 11:54:20 +10:00
goto out ;
2017-11-08 16:30:44 +11:00
}
2016-07-19 11:54:20 +10:00
ret = nah - > handler ( ndp , h ) ;
2017-11-08 16:30:44 +11:00
if ( ret )
netdev_err ( ndp - > ndev . dev ,
" NCSI: Handler for AEN type 0x%x returned %d \n " ,
h - > type , ret ) ;
2016-07-19 11:54:20 +10:00
out :
consume_skb ( skb ) ;
return ret ;
}