linux/drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_pf.c
Rajesh Borundia f197a7aa62 qlcnic: VF-PF communication channel implementation
o Adapter provides communication channel between VF and PF.
  Any control commands from the VF driver are sent to the PF driver
  through this communication channel. PF driver validates the
  commands before sending them to the adapter. Similarly PF driver
  forwards any control command responses  to the VF driver
  through this communication channel.  Adapter sends message pending
  event to VF or PF when there is an outstanding response or a command
  for VF or PF respectively. When a command or a response is sent over
  a channel VF or PF cannot send another command or a response
  until adapter sends a channel free event. Adapter allocates 1K area to
  VF and  PF each for this communication.
o Commands and  responses are encapsulated in a header. Header determines
  sequence id, number of fragments, fragment number etc.

Signed-off-by: Manish Chopra <manish.chopra@qlogic.com>
Signed-off-by: Sucheta Chakraborty <sucheta.chakraborty@qlogic.com>
Signed-off-by: Rajesh Borundia <rajesh.borundia@qlogic.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2013-03-29 15:51:05 -04:00

568 lines
15 KiB
C

/*
* QLogic qlcnic NIC Driver
* Copyright (c) 2009-2013 QLogic Corporation
*
* See LICENSE.qlcnic for copyright and licensing details.
*/
#include "qlcnic_sriov.h"
#include "qlcnic.h"
#include <linux/types.h>
#define QLCNIC_SRIOV_VF_MAX_MAC 1
static int qlcnic_sriov_pf_get_vport_handle(struct qlcnic_adapter *, u8);
struct qlcnic_sriov_cmd_handler {
int (*fn) (struct qlcnic_bc_trans *, struct qlcnic_cmd_args *);
};
static int qlcnic_sriov_pf_set_vport_info(struct qlcnic_adapter *adapter,
struct qlcnic_info *npar_info,
u16 vport_id)
{
struct qlcnic_cmd_args cmd;
int err;
if (qlcnic_alloc_mbx_args(&cmd, adapter, QLCNIC_CMD_SET_NIC_INFO))
return -ENOMEM;
cmd.req.arg[1] = (vport_id << 16) | 0x1;
cmd.req.arg[2] = npar_info->bit_offsets;
cmd.req.arg[2] |= npar_info->min_tx_bw << 16;
cmd.req.arg[3] = npar_info->max_tx_bw | (npar_info->max_tx_ques << 16);
cmd.req.arg[4] = npar_info->max_tx_mac_filters;
cmd.req.arg[4] |= npar_info->max_rx_mcast_mac_filters << 16;
cmd.req.arg[5] = npar_info->max_rx_ucast_mac_filters |
(npar_info->max_rx_ip_addr << 16);
cmd.req.arg[6] = npar_info->max_rx_lro_flow |
(npar_info->max_rx_status_rings << 16);
cmd.req.arg[7] = npar_info->max_rx_buf_rings |
(npar_info->max_rx_ques << 16);
cmd.req.arg[8] = npar_info->max_tx_vlan_keys;
cmd.req.arg[8] |= npar_info->max_local_ipv6_addrs << 16;
cmd.req.arg[9] = npar_info->max_remote_ipv6_addrs;
err = qlcnic_issue_cmd(adapter, &cmd);
if (err)
dev_err(&adapter->pdev->dev,
"Failed to set vport info, err=%d\n", err);
qlcnic_free_mbx_args(&cmd);
return err;
}
static int qlcnic_sriov_pf_cal_res_limit(struct qlcnic_adapter *adapter,
struct qlcnic_info *info, u16 func)
{
struct qlcnic_sriov *sriov = adapter->ahw->sriov;
struct qlcnic_resources *res = &sriov->ff_max;
int ret = -EIO, vpid;
u32 temp, num_vf_macs, num_vfs, max;
vpid = qlcnic_sriov_pf_get_vport_handle(adapter, func);
if (vpid < 0)
return -EINVAL;
num_vfs = sriov->num_vfs;
max = num_vfs + 1;
info->bit_offsets = 0xffff;
info->min_tx_bw = 0;
info->max_tx_bw = MAX_BW;
info->max_tx_ques = res->num_tx_queues / max;
info->max_rx_mcast_mac_filters = res->num_rx_mcast_mac_filters;
num_vf_macs = QLCNIC_SRIOV_VF_MAX_MAC;
if (adapter->ahw->pci_func == func) {
temp = res->num_rx_mcast_mac_filters - (num_vfs * num_vf_macs);
info->max_rx_ucast_mac_filters = temp;
temp = res->num_tx_mac_filters - (num_vfs * num_vf_macs);
info->max_tx_mac_filters = temp;
} else {
info->max_rx_ucast_mac_filters = num_vf_macs;
info->max_tx_mac_filters = num_vf_macs;
}
info->max_rx_ip_addr = res->num_destip / max;
info->max_rx_status_rings = res->num_rx_status_rings / max;
info->max_rx_buf_rings = res->num_rx_buf_rings / max;
info->max_rx_ques = res->num_rx_queues / max;
info->max_rx_lro_flow = res->num_lro_flows_supported / max;
info->max_tx_vlan_keys = res->num_txvlan_keys;
info->max_local_ipv6_addrs = res->max_local_ipv6_addrs;
info->max_remote_ipv6_addrs = res->max_remote_ipv6_addrs;
ret = qlcnic_sriov_pf_set_vport_info(adapter, info, vpid);
if (ret)
return ret;
return 0;
}
static void qlcnic_sriov_pf_set_ff_max_res(struct qlcnic_adapter *adapter,
struct qlcnic_info *info)
{
struct qlcnic_resources *ff_max = &adapter->ahw->sriov->ff_max;
ff_max->num_tx_mac_filters = info->max_tx_mac_filters;
ff_max->num_rx_ucast_mac_filters = info->max_rx_ucast_mac_filters;
ff_max->num_rx_mcast_mac_filters = info->max_rx_mcast_mac_filters;
ff_max->num_txvlan_keys = info->max_tx_vlan_keys;
ff_max->num_rx_queues = info->max_rx_ques;
ff_max->num_tx_queues = info->max_tx_ques;
ff_max->num_lro_flows_supported = info->max_rx_lro_flow;
ff_max->num_destip = info->max_rx_ip_addr;
ff_max->num_rx_buf_rings = info->max_rx_buf_rings;
ff_max->num_rx_status_rings = info->max_rx_status_rings;
ff_max->max_remote_ipv6_addrs = info->max_remote_ipv6_addrs;
ff_max->max_local_ipv6_addrs = info->max_local_ipv6_addrs;
}
static int qlcnic_sriov_get_pf_info(struct qlcnic_adapter *adapter,
struct qlcnic_info *npar_info)
{
int err;
struct qlcnic_cmd_args cmd;
if (qlcnic_alloc_mbx_args(&cmd, adapter, QLCNIC_CMD_GET_NIC_INFO))
return -ENOMEM;
cmd.req.arg[1] = 0x2;
err = qlcnic_issue_cmd(adapter, &cmd);
if (err) {
dev_err(&adapter->pdev->dev,
"Failed to get PF info, err=%d\n", err);
goto out;
}
npar_info->total_pf = cmd.rsp.arg[2] & 0xff;
npar_info->total_rss_engines = (cmd.rsp.arg[2] >> 8) & 0xff;
npar_info->max_vports = MSW(cmd.rsp.arg[2]);
npar_info->max_tx_ques = LSW(cmd.rsp.arg[3]);
npar_info->max_tx_mac_filters = MSW(cmd.rsp.arg[3]);
npar_info->max_rx_mcast_mac_filters = LSW(cmd.rsp.arg[4]);
npar_info->max_rx_ucast_mac_filters = MSW(cmd.rsp.arg[4]);
npar_info->max_rx_ip_addr = LSW(cmd.rsp.arg[5]);
npar_info->max_rx_lro_flow = MSW(cmd.rsp.arg[5]);
npar_info->max_rx_status_rings = LSW(cmd.rsp.arg[6]);
npar_info->max_rx_buf_rings = MSW(cmd.rsp.arg[6]);
npar_info->max_rx_ques = LSW(cmd.rsp.arg[7]);
npar_info->max_tx_vlan_keys = MSW(cmd.rsp.arg[7]);
npar_info->max_local_ipv6_addrs = LSW(cmd.rsp.arg[8]);
npar_info->max_remote_ipv6_addrs = MSW(cmd.rsp.arg[8]);
dev_info(&adapter->pdev->dev,
"\n\ttotal_pf: %d,\n"
"\n\ttotal_rss_engines: %d max_vports: %d max_tx_ques %d,\n"
"\tmax_tx_mac_filters: %d max_rx_mcast_mac_filters: %d,\n"
"\tmax_rx_ucast_mac_filters: 0x%x, max_rx_ip_addr: %d,\n"
"\tmax_rx_lro_flow: %d max_rx_status_rings: %d,\n"
"\tmax_rx_buf_rings: %d, max_rx_ques: %d, max_tx_vlan_keys %d\n"
"\tmax_local_ipv6_addrs: %d, max_remote_ipv6_addrs: %d\n",
npar_info->total_pf, npar_info->total_rss_engines,
npar_info->max_vports, npar_info->max_tx_ques,
npar_info->max_tx_mac_filters,
npar_info->max_rx_mcast_mac_filters,
npar_info->max_rx_ucast_mac_filters, npar_info->max_rx_ip_addr,
npar_info->max_rx_lro_flow, npar_info->max_rx_status_rings,
npar_info->max_rx_buf_rings, npar_info->max_rx_ques,
npar_info->max_tx_vlan_keys, npar_info->max_local_ipv6_addrs,
npar_info->max_remote_ipv6_addrs);
out:
qlcnic_free_mbx_args(&cmd);
return err;
}
static void qlcnic_sriov_pf_reset_vport_handle(struct qlcnic_adapter *adapter,
u8 func)
{
struct qlcnic_sriov *sriov = adapter->ahw->sriov;
struct qlcnic_vport *vp;
int index;
if (adapter->ahw->pci_func == func) {
sriov->vp_handle = 0;
} else {
index = qlcnic_sriov_func_to_index(adapter, func);
if (index < 0)
return;
vp = sriov->vf_info[index].vp;
vp->handle = 0;
}
}
static void qlcnic_sriov_pf_set_vport_handle(struct qlcnic_adapter *adapter,
u16 vport_handle, u8 func)
{
struct qlcnic_sriov *sriov = adapter->ahw->sriov;
struct qlcnic_vport *vp;
int index;
if (adapter->ahw->pci_func == func) {
sriov->vp_handle = vport_handle;
} else {
index = qlcnic_sriov_func_to_index(adapter, func);
if (index < 0)
return;
vp = sriov->vf_info[index].vp;
vp->handle = vport_handle;
}
}
static int qlcnic_sriov_pf_get_vport_handle(struct qlcnic_adapter *adapter,
u8 func)
{
struct qlcnic_sriov *sriov = adapter->ahw->sriov;
struct qlcnic_vf_info *vf_info;
int index;
if (adapter->ahw->pci_func == func) {
return sriov->vp_handle;
} else {
index = qlcnic_sriov_func_to_index(adapter, func);
if (index >= 0) {
vf_info = &sriov->vf_info[index];
return vf_info->vp->handle;
}
}
return -EINVAL;
}
static int qlcnic_sriov_pf_config_vport(struct qlcnic_adapter *adapter,
u8 flag, u16 func)
{
struct qlcnic_cmd_args cmd;
int ret;
int vpid;
if (qlcnic_alloc_mbx_args(&cmd, adapter, QLCNIC_CMD_CONFIG_VPORT))
return -ENOMEM;
if (flag) {
cmd.req.arg[3] = func << 8;
} else {
vpid = qlcnic_sriov_pf_get_vport_handle(adapter, func);
if (vpid < 0) {
ret = -EINVAL;
goto out;
}
cmd.req.arg[3] = ((vpid & 0xffff) << 8) | 1;
}
ret = qlcnic_issue_cmd(adapter, &cmd);
if (ret) {
dev_err(&adapter->pdev->dev,
"Failed %s vport, err %d for func 0x%x\n",
(flag ? "enable" : "disable"), ret, func);
goto out;
}
if (flag) {
vpid = cmd.rsp.arg[2] & 0xffff;
qlcnic_sriov_pf_set_vport_handle(adapter, vpid, func);
} else {
qlcnic_sriov_pf_reset_vport_handle(adapter, func);
}
out:
qlcnic_free_mbx_args(&cmd);
return ret;
}
static int qlcnic_sriov_pf_cfg_eswitch(struct qlcnic_adapter *adapter,
u8 func, u8 enable)
{
struct qlcnic_cmd_args cmd;
int err = -EIO;
if (qlcnic_alloc_mbx_args(&cmd, adapter, QLCNIC_CMD_TOGGLE_ESWITCH))
return -ENOMEM;
cmd.req.arg[0] |= (3 << 29);
cmd.req.arg[1] = ((func & 0xf) << 2) | BIT_6 | BIT_1;
if (enable)
cmd.req.arg[1] |= BIT_0;
err = qlcnic_issue_cmd(adapter, &cmd);
if (err != QLCNIC_RCODE_SUCCESS) {
dev_err(&adapter->pdev->dev,
"Failed to enable sriov eswitch%d\n", err);
err = -EIO;
}
qlcnic_free_mbx_args(&cmd);
return err;
}
void qlcnic_sriov_pf_cleanup(struct qlcnic_adapter *adapter)
{
u8 func = adapter->ahw->pci_func;
if (!qlcnic_sriov_enable_check(adapter))
return;
qlcnic_sriov_cfg_bc_intr(adapter, 0);
qlcnic_sriov_pf_config_vport(adapter, 0, func);
qlcnic_sriov_pf_cfg_eswitch(adapter, func, 0);
__qlcnic_sriov_cleanup(adapter);
adapter->ahw->op_mode = QLCNIC_MGMT_FUNC;
clear_bit(__QLCNIC_SRIOV_ENABLE, &adapter->state);
}
void qlcnic_sriov_pf_disable(struct qlcnic_adapter *adapter)
{
if (!qlcnic_sriov_pf_check(adapter))
return;
if (!qlcnic_sriov_enable_check(adapter))
return;
pci_disable_sriov(adapter->pdev);
netdev_info(adapter->netdev,
"SR-IOV is disabled successfully on port %d\n",
adapter->portnum);
}
static int qlcnic_pci_sriov_disable(struct qlcnic_adapter *adapter)
{
struct net_device *netdev = adapter->netdev;
if (netif_running(netdev))
__qlcnic_down(adapter, netdev);
qlcnic_sriov_pf_disable(adapter);
qlcnic_sriov_pf_cleanup(adapter);
/* After disabling SRIOV re-init the driver in default mode
configure opmode based on op_mode of function
*/
if (qlcnic_83xx_configure_opmode(adapter))
return -EIO;
if (netif_running(netdev))
__qlcnic_up(adapter, netdev);
return 0;
}
static int qlcnic_sriov_pf_init(struct qlcnic_adapter *adapter)
{
struct qlcnic_hardware_context *ahw = adapter->ahw;
struct qlcnic_info nic_info, pf_info, vp_info;
int err;
u8 func = ahw->pci_func;
if (!qlcnic_sriov_enable_check(adapter))
return 0;
err = qlcnic_sriov_pf_cfg_eswitch(adapter, func, 1);
if (err)
goto clear_sriov_enable;
err = qlcnic_sriov_pf_config_vport(adapter, 1, func);
if (err)
goto disable_eswitch;
err = qlcnic_sriov_get_pf_info(adapter, &pf_info);
if (err)
goto delete_vport;
qlcnic_sriov_pf_set_ff_max_res(adapter, &pf_info);
err = qlcnic_get_nic_info(adapter, &nic_info, func);
if (err)
goto delete_vport;
err = qlcnic_sriov_pf_cal_res_limit(adapter, &vp_info, func);
if (err)
goto delete_vport;
err = qlcnic_sriov_cfg_bc_intr(adapter, 1);
if (err)
goto delete_vport;
ahw->physical_port = (u8) nic_info.phys_port;
ahw->switch_mode = nic_info.switch_mode;
ahw->max_mtu = nic_info.max_mtu;
ahw->capabilities = nic_info.capabilities;
ahw->nic_mode = QLC_83XX_SRIOV_MODE;
return err;
delete_vport:
qlcnic_sriov_pf_config_vport(adapter, 0, func);
disable_eswitch:
qlcnic_sriov_pf_cfg_eswitch(adapter, func, 0);
clear_sriov_enable:
__qlcnic_sriov_cleanup(adapter);
adapter->ahw->op_mode = QLCNIC_MGMT_FUNC;
clear_bit(__QLCNIC_SRIOV_ENABLE, &adapter->state);
return err;
}
static int qlcnic_sriov_pf_enable(struct qlcnic_adapter *adapter, int num_vfs)
{
int err;
if (!qlcnic_sriov_enable_check(adapter))
return 0;
err = pci_enable_sriov(adapter->pdev, num_vfs);
if (err)
qlcnic_sriov_pf_cleanup(adapter);
return err;
}
static int __qlcnic_pci_sriov_enable(struct qlcnic_adapter *adapter,
int num_vfs)
{
int err = 0;
set_bit(__QLCNIC_SRIOV_ENABLE, &adapter->state);
adapter->ahw->op_mode = QLCNIC_SRIOV_PF_FUNC;
if (qlcnic_sriov_init(adapter, num_vfs)) {
clear_bit(__QLCNIC_SRIOV_ENABLE, &adapter->state);
adapter->ahw->op_mode = QLCNIC_MGMT_FUNC;
return -EIO;
}
if (qlcnic_sriov_pf_init(adapter))
return -EIO;
err = qlcnic_sriov_pf_enable(adapter, num_vfs);
return err;
}
static int qlcnic_pci_sriov_enable(struct qlcnic_adapter *adapter, int num_vfs)
{
struct net_device *netdev = adapter->netdev;
int err;
if (!(adapter->flags & QLCNIC_MSIX_ENABLED)) {
netdev_err(netdev,
"SR-IOV cannot be enabled, when legacy interrupts are enabled\n");
return -EIO;
}
if (netif_running(netdev))
__qlcnic_down(adapter, netdev);
err = __qlcnic_pci_sriov_enable(adapter, num_vfs);
if (err) {
netdev_info(netdev, "Failed to enable SR-IOV on port %d\n",
adapter->portnum);
if (qlcnic_83xx_configure_opmode(adapter))
goto error;
} else {
netdev_info(adapter->netdev,
"SR-IOV is enabled successfully on port %d\n",
adapter->portnum);
}
if (netif_running(netdev))
__qlcnic_up(adapter, netdev);
error:
return err;
}
int qlcnic_pci_sriov_configure(struct pci_dev *dev, int num_vfs)
{
struct qlcnic_adapter *adapter = pci_get_drvdata(dev);
int err;
if (test_and_set_bit(__QLCNIC_RESETTING, &adapter->state))
return -EBUSY;
if (num_vfs == 0)
err = qlcnic_pci_sriov_disable(adapter);
else
err = qlcnic_pci_sriov_enable(adapter, num_vfs);
clear_bit(__QLCNIC_RESETTING, &adapter->state);
return err;
}
static int qlcnic_sriov_set_vf_vport_info(struct qlcnic_adapter *adapter,
u16 func)
{
struct qlcnic_info defvp_info;
int err;
err = qlcnic_sriov_pf_cal_res_limit(adapter, &defvp_info, func);
if (err)
return -EIO;
return 0;
}
static int qlcnic_sriov_pf_channel_cfg_cmd(struct qlcnic_bc_trans *trans,
struct qlcnic_cmd_args *cmd)
{
struct qlcnic_vf_info *vf = trans->vf;
struct qlcnic_adapter *adapter = vf->adapter;
int err;
u16 func = vf->pci_func;
cmd->rsp.arg[0] = trans->req_hdr->cmd_op;
cmd->rsp.arg[0] |= (1 << 16);
if (trans->req_hdr->cmd_op == QLCNIC_BC_CMD_CHANNEL_INIT) {
err = qlcnic_sriov_pf_config_vport(adapter, 1, func);
if (!err) {
err = qlcnic_sriov_set_vf_vport_info(adapter, func);
if (err)
qlcnic_sriov_pf_config_vport(adapter, 0, func);
}
} else {
err = qlcnic_sriov_pf_config_vport(adapter, 0, func);
}
if (err)
goto err_out;
cmd->rsp.arg[0] |= (1 << 25);
if (trans->req_hdr->cmd_op == QLCNIC_BC_CMD_CHANNEL_INIT)
set_bit(QLC_BC_VF_STATE, &vf->state);
else
clear_bit(QLC_BC_VF_STATE, &vf->state);
return err;
err_out:
cmd->rsp.arg[0] |= (2 << 25);
return err;
}
static const struct qlcnic_sriov_cmd_handler qlcnic_pf_bc_cmd_hdlr[] = {
[QLCNIC_BC_CMD_CHANNEL_INIT] = {&qlcnic_sriov_pf_channel_cfg_cmd},
[QLCNIC_BC_CMD_CHANNEL_TERM] = {&qlcnic_sriov_pf_channel_cfg_cmd},
};
void qlcnic_sriov_pf_process_bc_cmd(struct qlcnic_adapter *adapter,
struct qlcnic_bc_trans *trans,
struct qlcnic_cmd_args *cmd)
{
u8 size, cmd_op;
cmd_op = trans->req_hdr->cmd_op;
if (trans->req_hdr->op_type == QLC_BC_CMD) {
size = ARRAY_SIZE(qlcnic_pf_bc_cmd_hdlr);
if (cmd_op < size) {
qlcnic_pf_bc_cmd_hdlr[cmd_op].fn(trans, cmd);
return;
}
}
cmd->rsp.arg[0] |= (0x9 << 25);
}