251 lines · 7.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only #include <linux/ethtool.h>#include <linux/sfp.h> #include "common.h"#include "netlink.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) 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; 	if (modinfo->type == ETH_MODULE_SFF_8472 &&	    request->i2c_address == 0x51)		offset += ETH_MODULE_EEPROM_PAGE_LEN * 2; 	if (offset >= modinfo->eeprom_len)		return -EINVAL; 	if (length > modinfo->eeprom_len - offset)		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 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 = kzalloc(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;} 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->ethtool->module_fw_flash_in_progress) {		NL_SET_ERR_MSG(extack,			       "Module firmware flashing is in progress");		return -EBUSY;	} 	if (dev->sfp_bus)		return sfp_get_module_eeprom_by_page(dev->sfp_bus, page_data, extack); 	if (ops->get_module_eeprom_by_page)		return ops->get_module_eeprom_by_page(dev, page_data, extack); 	return -EOPNOTSUPP;} static int eeprom_prepare_data(const struct ethnl_req_info *req_base,			       struct ethnl_reply_data *reply_base,			       const 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; 	ret = get_module_eeprom_by_page(dev, &page_data, info->extack);	if (ret < 0)		goto err_ops; 	reply->length = ret;	reply->data = page_data.data; 	ethnl_ops_complete(dev);	return 0; err_ops:	if (ret == -EOPNOTSUPP)		ret = eeprom_fallback(request, reply);	ethnl_ops_complete(dev);err_free:	kfree(page_data.data);	return ret;} static int eeprom_parse_request(struct ethnl_req_info *req_info,				const struct genl_info *info,				struct nlattr **tb,				struct netlink_ext_ack *extack){	struct eeprom_req_info *request = MODULE_EEPROM_REQINFO(req_info); 	if (GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_MODULE_EEPROM_OFFSET) ||	    GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_MODULE_EEPROM_LENGTH) ||	    GENL_REQ_ATTR_CHECK(info, ETHTOOL_A_MODULE_EEPROM_PAGE) ||	    GENL_REQ_ATTR_CHECK(info, 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),	[ETHTOOL_A_MODULE_EEPROM_OFFSET]	=		NLA_POLICY_MAX(NLA_U32, ETH_MODULE_EEPROM_PAGE_LEN * 2 - 1),	[ETHTOOL_A_MODULE_EEPROM_LENGTH]	=		NLA_POLICY_RANGE(NLA_U32, 1, ETH_MODULE_EEPROM_PAGE_LEN),	[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),};