| // SPDX-License-Identifier: GPL-2.0 |
| /* Marvell Octeon EP (EndPoint) VF Ethernet Driver |
| * |
| * Copyright (C) 2020 Marvell. |
| * |
| */ |
| #include <linux/types.h> |
| #include <linux/pci.h> |
| #include <linux/netdevice.h> |
| #include <linux/vmalloc.h> |
| #include "octep_vf_config.h" |
| #include "octep_vf_main.h" |
| |
| /* When a new command is implemented, the below table should be updated |
| * with new command and it's version info. |
| */ |
| static u32 pfvf_cmd_versions[OCTEP_PFVF_MBOX_CMD_MAX] = { |
| [0 ... OCTEP_PFVF_MBOX_CMD_DEV_REMOVE] = OCTEP_PFVF_MBOX_VERSION_V1, |
| [OCTEP_PFVF_MBOX_CMD_GET_FW_INFO ... OCTEP_PFVF_MBOX_NOTIF_LINK_STATUS] = |
| OCTEP_PFVF_MBOX_VERSION_V2 |
| }; |
| |
| int octep_vf_setup_mbox(struct octep_vf_device *oct) |
| { |
| int ring = 0; |
| |
| oct->mbox = vzalloc(sizeof(*oct->mbox)); |
| if (!oct->mbox) |
| return -1; |
| |
| mutex_init(&oct->mbox->lock); |
| |
| oct->hw_ops.setup_mbox_regs(oct, ring); |
| INIT_WORK(&oct->mbox->wk.work, octep_vf_mbox_work); |
| oct->mbox->wk.ctxptr = oct; |
| oct->mbox_neg_ver = OCTEP_PFVF_MBOX_VERSION_CURRENT; |
| dev_info(&oct->pdev->dev, "setup vf mbox successfully\n"); |
| return 0; |
| } |
| |
| void octep_vf_delete_mbox(struct octep_vf_device *oct) |
| { |
| if (oct->mbox) { |
| if (work_pending(&oct->mbox->wk.work)) |
| cancel_work_sync(&oct->mbox->wk.work); |
| |
| mutex_destroy(&oct->mbox->lock); |
| vfree(oct->mbox); |
| oct->mbox = NULL; |
| dev_info(&oct->pdev->dev, "Deleted vf mbox successfully\n"); |
| } |
| } |
| |
| int octep_vf_mbox_version_check(struct octep_vf_device *oct) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int ret; |
| |
| cmd.u64 = 0; |
| cmd.s_version.opcode = OCTEP_PFVF_MBOX_CMD_VERSION; |
| cmd.s_version.version = OCTEP_PFVF_MBOX_VERSION_CURRENT; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret == OCTEP_PFVF_MBOX_CMD_STATUS_NACK) { |
| dev_err(&oct->pdev->dev, |
| "VF Mbox version is incompatible with PF\n"); |
| return -EINVAL; |
| } |
| oct->mbox_neg_ver = (u32)rsp.s_version.version; |
| dev_dbg(&oct->pdev->dev, |
| "VF Mbox version:%u Negotiated VF version with PF:%u\n", |
| (u32)cmd.s_version.version, |
| (u32)rsp.s_version.version); |
| return 0; |
| } |
| |
| void octep_vf_mbox_work(struct work_struct *work) |
| { |
| struct octep_vf_mbox_wk *wk = container_of(work, struct octep_vf_mbox_wk, work); |
| struct octep_vf_iface_link_info *link_info; |
| struct octep_vf_device *oct = NULL; |
| struct octep_vf_mbox *mbox = NULL; |
| union octep_pfvf_mbox_word *notif; |
| u64 pf_vf_data; |
| |
| oct = (struct octep_vf_device *)wk->ctxptr; |
| link_info = &oct->link_info; |
| mbox = oct->mbox; |
| pf_vf_data = readq(mbox->mbox_read_reg); |
| |
| notif = (union octep_pfvf_mbox_word *)&pf_vf_data; |
| |
| switch (notif->s.opcode) { |
| case OCTEP_PFVF_MBOX_NOTIF_LINK_STATUS: |
| if (notif->s_link_status.status) { |
| link_info->oper_up = OCTEP_PFVF_LINK_STATUS_UP; |
| netif_carrier_on(oct->netdev); |
| dev_info(&oct->pdev->dev, "netif_carrier_on\n"); |
| } else { |
| link_info->oper_up = OCTEP_PFVF_LINK_STATUS_DOWN; |
| netif_carrier_off(oct->netdev); |
| dev_info(&oct->pdev->dev, "netif_carrier_off\n"); |
| } |
| break; |
| default: |
| dev_err(&oct->pdev->dev, |
| "Received unsupported notif %d\n", notif->s.opcode); |
| break; |
| } |
| } |
| |
| static int __octep_vf_mbox_send_cmd(struct octep_vf_device *oct, |
| union octep_pfvf_mbox_word cmd, |
| union octep_pfvf_mbox_word *rsp) |
| { |
| struct octep_vf_mbox *mbox = oct->mbox; |
| u64 reg_val = 0ull; |
| int count; |
| |
| if (!mbox) |
| return OCTEP_PFVF_MBOX_CMD_STATUS_NOT_SETUP; |
| |
| cmd.s.type = OCTEP_PFVF_MBOX_TYPE_CMD; |
| writeq(cmd.u64, mbox->mbox_write_reg); |
| |
| /* No response for notification messages */ |
| if (!rsp) |
| return 0; |
| |
| for (count = 0; count < OCTEP_PFVF_MBOX_TIMEOUT_WAIT_COUNT; count++) { |
| usleep_range(1000, 1500); |
| reg_val = readq(mbox->mbox_write_reg); |
| if (reg_val != cmd.u64) { |
| rsp->u64 = reg_val; |
| break; |
| } |
| } |
| if (count == OCTEP_PFVF_MBOX_TIMEOUT_WAIT_COUNT) { |
| dev_err(&oct->pdev->dev, "mbox send command timed out\n"); |
| return OCTEP_PFVF_MBOX_CMD_STATUS_TIMEDOUT; |
| } |
| if (rsp->s.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "mbox_send: Received NACK\n"); |
| return OCTEP_PFVF_MBOX_CMD_STATUS_NACK; |
| } |
| rsp->u64 = reg_val; |
| return 0; |
| } |
| |
| int octep_vf_mbox_send_cmd(struct octep_vf_device *oct, union octep_pfvf_mbox_word cmd, |
| union octep_pfvf_mbox_word *rsp) |
| { |
| struct octep_vf_mbox *mbox = oct->mbox; |
| int ret; |
| |
| if (!mbox) |
| return OCTEP_PFVF_MBOX_CMD_STATUS_NOT_SETUP; |
| mutex_lock(&mbox->lock); |
| if (pfvf_cmd_versions[cmd.s.opcode] > oct->mbox_neg_ver) { |
| dev_dbg(&oct->pdev->dev, "CMD:%d not supported in Version:%d\n", |
| cmd.s.opcode, oct->mbox_neg_ver); |
| mutex_unlock(&mbox->lock); |
| return -EOPNOTSUPP; |
| } |
| ret = __octep_vf_mbox_send_cmd(oct, cmd, rsp); |
| mutex_unlock(&mbox->lock); |
| return ret; |
| } |
| |
| int octep_vf_mbox_bulk_read(struct octep_vf_device *oct, enum octep_pfvf_mbox_opcode opcode, |
| u8 *data, int *size) |
| { |
| struct octep_vf_mbox *mbox = oct->mbox; |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int data_len = 0, tmp_len = 0; |
| int read_cnt, i = 0, ret; |
| |
| if (!mbox) |
| return OCTEP_PFVF_MBOX_CMD_STATUS_NOT_SETUP; |
| |
| mutex_lock(&mbox->lock); |
| cmd.u64 = 0; |
| cmd.s_data.opcode = opcode; |
| cmd.s_data.frag = 0; |
| /* Send cmd to read data from PF */ |
| ret = __octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "send mbox cmd fail for data request\n"); |
| mutex_unlock(&mbox->lock); |
| return ret; |
| } |
| /* PF sends the data length of requested CMD |
| * in ACK |
| */ |
| data_len = *((int32_t *)rsp.s_data.data); |
| tmp_len = data_len; |
| cmd.u64 = 0; |
| rsp.u64 = 0; |
| cmd.s_data.opcode = opcode; |
| cmd.s_data.frag = 1; |
| while (data_len) { |
| ret = __octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "send mbox cmd fail for data request\n"); |
| mutex_unlock(&mbox->lock); |
| mbox->mbox_data.data_index = 0; |
| memset(mbox->mbox_data.recv_data, 0, OCTEP_PFVF_MBOX_MAX_DATA_BUF_SIZE); |
| return ret; |
| } |
| if (data_len > OCTEP_PFVF_MBOX_MAX_DATA_SIZE) { |
| data_len -= OCTEP_PFVF_MBOX_MAX_DATA_SIZE; |
| read_cnt = OCTEP_PFVF_MBOX_MAX_DATA_SIZE; |
| } else { |
| read_cnt = data_len; |
| data_len = 0; |
| } |
| for (i = 0; i < read_cnt; i++) { |
| mbox->mbox_data.recv_data[mbox->mbox_data.data_index] = |
| rsp.s_data.data[i]; |
| mbox->mbox_data.data_index++; |
| } |
| cmd.u64 = 0; |
| rsp.u64 = 0; |
| cmd.s_data.opcode = opcode; |
| cmd.s_data.frag = 1; |
| } |
| memcpy(data, mbox->mbox_data.recv_data, tmp_len); |
| *size = tmp_len; |
| mbox->mbox_data.data_index = 0; |
| memset(mbox->mbox_data.recv_data, 0, OCTEP_PFVF_MBOX_MAX_DATA_BUF_SIZE); |
| mutex_unlock(&mbox->lock); |
| return 0; |
| } |
| |
| int octep_vf_mbox_set_mtu(struct octep_vf_device *oct, int mtu) |
| { |
| int frame_size = mtu + ETH_HLEN + ETH_FCS_LEN; |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int ret = 0; |
| |
| if (mtu < ETH_MIN_MTU || frame_size > ETH_MAX_MTU) { |
| dev_err(&oct->pdev->dev, |
| "Failed to set MTU to %d MIN MTU:%d MAX MTU:%d\n", |
| mtu, ETH_MIN_MTU, ETH_MAX_MTU); |
| return -EINVAL; |
| } |
| |
| cmd.u64 = 0; |
| cmd.s_set_mtu.opcode = OCTEP_PFVF_MBOX_CMD_SET_MTU; |
| cmd.s_set_mtu.mtu = mtu; |
| |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "Mbox send failed; err=%d\n", ret); |
| return ret; |
| } |
| if (rsp.s_set_mtu.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "Received Mbox NACK from PF for MTU:%d\n", mtu); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int octep_vf_mbox_set_mac_addr(struct octep_vf_device *oct, char *mac_addr) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int i, ret; |
| |
| cmd.u64 = 0; |
| cmd.s_set_mac.opcode = OCTEP_PFVF_MBOX_CMD_SET_MAC_ADDR; |
| for (i = 0; i < ETH_ALEN; i++) |
| cmd.s_set_mac.mac_addr[i] = mac_addr[i]; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "Mbox send failed; err = %d\n", ret); |
| return ret; |
| } |
| if (rsp.s_set_mac.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "received NACK\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| int octep_vf_mbox_get_mac_addr(struct octep_vf_device *oct, char *mac_addr) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int i, ret; |
| |
| cmd.u64 = 0; |
| cmd.s_set_mac.opcode = OCTEP_PFVF_MBOX_CMD_GET_MAC_ADDR; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "get_mac: mbox send failed; err = %d\n", ret); |
| return ret; |
| } |
| if (rsp.s_set_mac.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "get_mac: received NACK\n"); |
| return -EINVAL; |
| } |
| for (i = 0; i < ETH_ALEN; i++) |
| mac_addr[i] = rsp.s_set_mac.mac_addr[i]; |
| return 0; |
| } |
| |
| int octep_vf_mbox_set_rx_state(struct octep_vf_device *oct, bool state) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int ret; |
| |
| cmd.u64 = 0; |
| cmd.s_link_state.opcode = OCTEP_PFVF_MBOX_CMD_SET_RX_STATE; |
| cmd.s_link_state.state = state; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "Set Rx state via VF Mbox send failed\n"); |
| return ret; |
| } |
| if (rsp.s_link_state.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "Set Rx state received NACK\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| int octep_vf_mbox_set_link_status(struct octep_vf_device *oct, bool status) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int ret; |
| |
| cmd.u64 = 0; |
| cmd.s_link_status.opcode = OCTEP_PFVF_MBOX_CMD_SET_LINK_STATUS; |
| cmd.s_link_status.status = status; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "Set link status via VF Mbox send failed\n"); |
| return ret; |
| } |
| if (rsp.s_link_status.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "Set link status received NACK\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| int octep_vf_mbox_get_link_status(struct octep_vf_device *oct, u8 *oper_up) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int ret; |
| |
| cmd.u64 = 0; |
| cmd.s_link_status.opcode = OCTEP_PFVF_MBOX_CMD_GET_LINK_STATUS; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "Get link status via VF Mbox send failed\n"); |
| return ret; |
| } |
| if (rsp.s_link_status.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "Get link status received NACK\n"); |
| return -EINVAL; |
| } |
| *oper_up = rsp.s_link_status.status; |
| return 0; |
| } |
| |
| int octep_vf_mbox_dev_remove(struct octep_vf_device *oct) |
| { |
| union octep_pfvf_mbox_word cmd; |
| int ret; |
| |
| cmd.u64 = 0; |
| cmd.s.opcode = OCTEP_PFVF_MBOX_CMD_DEV_REMOVE; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, NULL); |
| return ret; |
| } |
| |
| int octep_vf_mbox_get_fw_info(struct octep_vf_device *oct) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int ret; |
| |
| cmd.u64 = 0; |
| cmd.s_fw_info.opcode = OCTEP_PFVF_MBOX_CMD_GET_FW_INFO; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "Get link status via VF Mbox send failed\n"); |
| return ret; |
| } |
| if (rsp.s_fw_info.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "Get link status received NACK\n"); |
| return -EINVAL; |
| } |
| oct->fw_info.pkind = rsp.s_fw_info.pkind; |
| oct->fw_info.fsz = rsp.s_fw_info.fsz; |
| oct->fw_info.rx_ol_flags = rsp.s_fw_info.rx_ol_flags; |
| oct->fw_info.tx_ol_flags = rsp.s_fw_info.tx_ol_flags; |
| |
| return 0; |
| } |
| |
| int octep_vf_mbox_set_offloads(struct octep_vf_device *oct, u16 tx_offloads, |
| u16 rx_offloads) |
| { |
| union octep_pfvf_mbox_word cmd; |
| union octep_pfvf_mbox_word rsp; |
| int ret; |
| |
| cmd.u64 = 0; |
| cmd.s_offloads.opcode = OCTEP_PFVF_MBOX_CMD_SET_OFFLOADS; |
| cmd.s_offloads.rx_ol_flags = rx_offloads; |
| cmd.s_offloads.tx_ol_flags = tx_offloads; |
| ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp); |
| if (ret) { |
| dev_err(&oct->pdev->dev, "Set offloads via VF Mbox send failed\n"); |
| return ret; |
| } |
| if (rsp.s_link_state.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) { |
| dev_err(&oct->pdev->dev, "Set offloads received NACK\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |