2021-04-09 11:06:34 +03:00
// SPDX-License-Identifier: GPL-2.0-only
# include <linux/ethtool.h>
2021-04-09 11:06:41 +03:00
# include <linux/sfp.h>
2021-04-09 11:06:34 +03:00
# include "netlink.h"
# include "common.h"
struct eeprom_req_info {
struct ethnl_req_info base ;
u32 offset ;
u32 length ;
u8 page ;
u8 bank ;
u8 i2c_address ;
} ;
struct eeprom_reply_data {
struct ethnl_reply_data base ;
u32 length ;
u8 * data ;
} ;
# define MODULE_EEPROM_REQINFO(__req_base) \
container_of ( __req_base , struct eeprom_req_info , base )
# define MODULE_EEPROM_REPDATA(__reply_base) \
container_of ( __reply_base , struct eeprom_reply_data , base )
2021-04-09 11:06:39 +03:00
static int fallback_set_params ( struct eeprom_req_info * request ,
struct ethtool_modinfo * modinfo ,
struct ethtool_eeprom * eeprom )
{
u32 offset = request - > offset ;
u32 length = request - > length ;
if ( request - > page )
offset = request - > page * ETH_MODULE_EEPROM_PAGE_LEN + offset ;
2022-06-16 19:08:55 +03:00
if ( modinfo - > type = = ETH_MODULE_SFF_8472 & &
2021-04-09 11:06:39 +03:00
request - > i2c_address = = 0x51 )
offset + = ETH_MODULE_EEPROM_PAGE_LEN * 2 ;
if ( offset > = modinfo - > eeprom_len )
return - EINVAL ;
eeprom - > cmd = ETHTOOL_GMODULEEEPROM ;
eeprom - > len = length ;
eeprom - > offset = offset ;
return 0 ;
}
static int eeprom_fallback ( struct eeprom_req_info * request ,
struct eeprom_reply_data * reply ,
struct genl_info * info )
{
struct net_device * dev = reply - > base . dev ;
struct ethtool_modinfo modinfo = { 0 } ;
struct ethtool_eeprom eeprom = { 0 } ;
u8 * data ;
int err ;
modinfo . cmd = ETHTOOL_GMODULEINFO ;
err = ethtool_get_module_info_call ( dev , & modinfo ) ;
if ( err < 0 )
return err ;
err = fallback_set_params ( request , & modinfo , & eeprom ) ;
if ( err < 0 )
return err ;
data = kmalloc ( eeprom . len , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
err = ethtool_get_module_eeprom_call ( dev , & eeprom , data ) ;
if ( err < 0 )
goto err_out ;
reply - > data = data ;
reply - > length = eeprom . len ;
return 0 ;
err_out :
kfree ( data ) ;
return err ;
}
2021-04-09 11:06:41 +03:00
static int get_module_eeprom_by_page ( struct net_device * dev ,
struct ethtool_module_eeprom * page_data ,
struct netlink_ext_ack * extack )
{
const struct ethtool_ops * ops = dev - > ethtool_ops ;
if ( dev - > sfp_bus )
return sfp_get_module_eeprom_by_page ( dev - > sfp_bus , page_data , extack ) ;
2021-06-06 17:24:22 +03:00
if ( ops - > get_module_eeprom_by_page )
2021-04-09 11:06:41 +03:00
return ops - > get_module_eeprom_by_page ( dev , page_data , extack ) ;
return - EOPNOTSUPP ;
}
2021-04-09 11:06:34 +03:00
static int eeprom_prepare_data ( const struct ethnl_req_info * req_base ,
struct ethnl_reply_data * reply_base ,
struct genl_info * info )
{
struct eeprom_reply_data * reply = MODULE_EEPROM_REPDATA ( reply_base ) ;
struct eeprom_req_info * request = MODULE_EEPROM_REQINFO ( req_base ) ;
struct ethtool_module_eeprom page_data = { 0 } ;
struct net_device * dev = reply_base - > dev ;
int ret ;
page_data . offset = request - > offset ;
page_data . length = request - > length ;
page_data . i2c_address = request - > i2c_address ;
page_data . page = request - > page ;
page_data . bank = request - > bank ;
page_data . data = kmalloc ( page_data . length , GFP_KERNEL ) ;
if ( ! page_data . data )
return - ENOMEM ;
ret = ethnl_ops_begin ( dev ) ;
if ( ret )
goto err_free ;
2022-10-21 17:22:47 +03:00
ret = get_module_eeprom_by_page ( dev , & page_data , info ? info - > extack : NULL ) ;
2021-04-09 11:06:34 +03:00
if ( ret < 0 )
goto err_ops ;
reply - > length = ret ;
reply - > data = page_data . data ;
ethnl_ops_complete ( dev ) ;
return 0 ;
err_ops :
ethnl_ops_complete ( dev ) ;
err_free :
kfree ( page_data . data ) ;
2021-04-09 11:06:41 +03:00
if ( ret = = - EOPNOTSUPP )
return eeprom_fallback ( request , reply , info ) ;
2021-04-09 11:06:34 +03:00
return ret ;
}
static int eeprom_parse_request ( struct ethnl_req_info * req_info , struct nlattr * * tb ,
struct netlink_ext_ack * extack )
{
struct eeprom_req_info * request = MODULE_EEPROM_REQINFO ( req_info ) ;
if ( ! tb [ ETHTOOL_A_MODULE_EEPROM_OFFSET ] | |
! tb [ ETHTOOL_A_MODULE_EEPROM_LENGTH ] | |
! tb [ ETHTOOL_A_MODULE_EEPROM_PAGE ] | |
! tb [ ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS ] )
return - EINVAL ;
request - > i2c_address = nla_get_u8 ( tb [ ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS ] ) ;
request - > offset = nla_get_u32 ( tb [ ETHTOOL_A_MODULE_EEPROM_OFFSET ] ) ;
request - > length = nla_get_u32 ( tb [ ETHTOOL_A_MODULE_EEPROM_LENGTH ] ) ;
/* The following set of conditions limit the API to only dump 1/2
* EEPROM page without crossing low page boundary located at offset 128.
* This means user may only request dumps of length limited to 128 from
* either low 128 bytes or high 128 bytes .
* For pages higher than 0 only high 128 bytes are accessible .
*/
request - > page = nla_get_u8 ( tb [ ETHTOOL_A_MODULE_EEPROM_PAGE ] ) ;
if ( request - > page & & request - > offset < ETH_MODULE_EEPROM_PAGE_LEN ) {
NL_SET_ERR_MSG_ATTR ( extack , tb [ ETHTOOL_A_MODULE_EEPROM_PAGE ] ,
" reading from lower half page is allowed for page 0 only " ) ;
return - EINVAL ;
}
if ( request - > offset < ETH_MODULE_EEPROM_PAGE_LEN & &
request - > offset + request - > length > ETH_MODULE_EEPROM_PAGE_LEN ) {
NL_SET_ERR_MSG_ATTR ( extack , tb [ ETHTOOL_A_MODULE_EEPROM_LENGTH ] ,
" reading cross half page boundary is illegal " ) ;
return - EINVAL ;
} else if ( request - > offset + request - > length > ETH_MODULE_EEPROM_PAGE_LEN * 2 ) {
NL_SET_ERR_MSG_ATTR ( extack , tb [ ETHTOOL_A_MODULE_EEPROM_LENGTH ] ,
" reading cross page boundary is illegal " ) ;
return - EINVAL ;
}
if ( tb [ ETHTOOL_A_MODULE_EEPROM_BANK ] )
request - > bank = nla_get_u8 ( tb [ ETHTOOL_A_MODULE_EEPROM_BANK ] ) ;
return 0 ;
}
static int eeprom_reply_size ( const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
const struct eeprom_req_info * request = MODULE_EEPROM_REQINFO ( req_base ) ;
return nla_total_size ( sizeof ( u8 ) * request - > length ) ; /* _EEPROM_DATA */
}
static int eeprom_fill_reply ( struct sk_buff * skb ,
const struct ethnl_req_info * req_base ,
const struct ethnl_reply_data * reply_base )
{
struct eeprom_reply_data * reply = MODULE_EEPROM_REPDATA ( reply_base ) ;
return nla_put ( skb , ETHTOOL_A_MODULE_EEPROM_DATA , reply - > length , reply - > data ) ;
}
static void eeprom_cleanup_data ( struct ethnl_reply_data * reply_base )
{
struct eeprom_reply_data * reply = MODULE_EEPROM_REPDATA ( reply_base ) ;
kfree ( reply - > data ) ;
}
const struct ethnl_request_ops ethnl_module_eeprom_request_ops = {
. request_cmd = ETHTOOL_MSG_MODULE_EEPROM_GET ,
. reply_cmd = ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY ,
. hdr_attr = ETHTOOL_A_MODULE_EEPROM_HEADER ,
. req_info_size = sizeof ( struct eeprom_req_info ) ,
. reply_data_size = sizeof ( struct eeprom_reply_data ) ,
. parse_request = eeprom_parse_request ,
. prepare_data = eeprom_prepare_data ,
. reply_size = eeprom_reply_size ,
. fill_reply = eeprom_fill_reply ,
. cleanup_data = eeprom_cleanup_data ,
} ;
const struct nla_policy ethnl_module_eeprom_get_policy [ ] = {
[ ETHTOOL_A_MODULE_EEPROM_HEADER ] = NLA_POLICY_NESTED ( ethnl_header_policy ) ,
2021-06-22 09:50:52 +03:00
[ ETHTOOL_A_MODULE_EEPROM_OFFSET ] =
NLA_POLICY_MAX ( NLA_U32 , ETH_MODULE_EEPROM_PAGE_LEN * 2 - 1 ) ,
2021-06-22 09:50:51 +03:00
[ ETHTOOL_A_MODULE_EEPROM_LENGTH ] =
NLA_POLICY_RANGE ( NLA_U32 , 1 , ETH_MODULE_EEPROM_PAGE_LEN ) ,
2021-04-09 11:06:34 +03:00
[ ETHTOOL_A_MODULE_EEPROM_PAGE ] = { . type = NLA_U8 } ,
[ ETHTOOL_A_MODULE_EEPROM_BANK ] = { . type = NLA_U8 } ,
[ ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS ] =
NLA_POLICY_RANGE ( NLA_U8 , 0 , ETH_MODULE_MAX_I2C_ADDRESS ) ,
} ;