/********************************************************************** * Author: Cavium, Inc. * * Contact: support@cavium.com * Please include "LiquidIO" in the subject. * * Copyright (c) 2003-2017 Cavium, Inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 2, as * published by the Free Software Foundation. * * This file is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. See the GNU General Public License for more details. ***********************************************************************/ #include #include #include "liquidio_common.h" #include "octeon_droq.h" #include "octeon_iq.h" #include "response_manager.h" #include "octeon_device.h" #include "octeon_nic.h" #include "octeon_main.h" #include "octeon_network.h" #include #include "lio_vf_rep.h" #include "octeon_network.h" static int lio_vf_rep_open(struct net_device *ndev); static int lio_vf_rep_stop(struct net_device *ndev); static int lio_vf_rep_pkt_xmit(struct sk_buff *skb, struct net_device *ndev); static void lio_vf_rep_tx_timeout(struct net_device *netdev); static int lio_vf_rep_phys_port_name(struct net_device *dev, char *buf, size_t len); static void lio_vf_rep_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats64); static int lio_vf_rep_change_mtu(struct net_device *ndev, int new_mtu); static const struct net_device_ops lio_vf_rep_ndev_ops = { .ndo_open = lio_vf_rep_open, .ndo_stop = lio_vf_rep_stop, .ndo_start_xmit = lio_vf_rep_pkt_xmit, .ndo_tx_timeout = lio_vf_rep_tx_timeout, .ndo_get_phys_port_name = lio_vf_rep_phys_port_name, .ndo_get_stats64 = lio_vf_rep_get_stats64, .ndo_change_mtu = lio_vf_rep_change_mtu, }; static void lio_vf_rep_send_sc_complete(struct octeon_device *oct, u32 status, void *ptr) { struct octeon_soft_command *sc = (struct octeon_soft_command *)ptr; struct lio_vf_rep_sc_ctx *ctx = (struct lio_vf_rep_sc_ctx *)sc->ctxptr; struct lio_vf_rep_resp *resp = (struct lio_vf_rep_resp *)sc->virtrptr; if (status != OCTEON_REQUEST_TIMEOUT && READ_ONCE(resp->status)) WRITE_ONCE(resp->status, 0); complete(&ctx->complete); } static int lio_vf_rep_send_soft_command(struct octeon_device *oct, void *req, int req_size, void *resp, int resp_size) { int tot_resp_size = sizeof(struct lio_vf_rep_resp) + resp_size; int ctx_size = sizeof(struct lio_vf_rep_sc_ctx); struct octeon_soft_command *sc = NULL; struct lio_vf_rep_resp *rep_resp; struct lio_vf_rep_sc_ctx *ctx; void *sc_req; int err; sc = (struct octeon_soft_command *) octeon_alloc_soft_command(oct, req_size, tot_resp_size, ctx_size); if (!sc) return -ENOMEM; ctx = (struct lio_vf_rep_sc_ctx *)sc->ctxptr; memset(ctx, 0, ctx_size); init_completion(&ctx->complete); sc_req = (struct lio_vf_rep_req *)sc->virtdptr; memcpy(sc_req, req, req_size); rep_resp = (struct lio_vf_rep_resp *)sc->virtrptr; memset(rep_resp, 0, tot_resp_size); WRITE_ONCE(rep_resp->status, 1); sc->iq_no = 0; octeon_prepare_soft_command(oct, sc, OPCODE_NIC, OPCODE_NIC_VF_REP_CMD, 0, 0, 0); sc->callback = lio_vf_rep_send_sc_complete; sc->callback_arg = sc; sc->wait_time = LIO_VF_REP_REQ_TMO_MS; err = octeon_send_soft_command(oct, sc); if (err == IQ_SEND_FAILED) goto free_buff; wait_for_completion_timeout(&ctx->complete, msecs_to_jiffies (2 * LIO_VF_REP_REQ_TMO_MS)); err = READ_ONCE(rep_resp->status) ? -EBUSY : 0; if (err) dev_err(&oct->pci_dev->dev, "VF rep send config failed\n"); if (resp) memcpy(resp, (rep_resp + 1), resp_size); free_buff: octeon_free_soft_command(oct, sc); return err; } static int lio_vf_rep_open(struct net_device *ndev) { struct lio_vf_rep_desc *vf_rep = netdev_priv(ndev); struct lio_vf_rep_req rep_cfg; struct octeon_device *oct; int ret; oct = vf_rep->oct; memset(&rep_cfg, 0, sizeof(rep_cfg)); rep_cfg.req_type = LIO_VF_REP_REQ_STATE; rep_cfg.ifidx = vf_rep->ifidx; rep_cfg.rep_state.state = LIO_VF_REP_STATE_UP; ret = lio_vf_rep_send_soft_command(oct, &rep_cfg, sizeof(rep_cfg), NULL, 0); if (ret) { dev_err(&oct->pci_dev->dev, "VF_REP open failed with err %d\n", ret); return -EIO; } atomic_set(&vf_rep->ifstate, (atomic_read(&vf_rep->ifstate) | LIO_IFSTATE_RUNNING)); netif_carrier_on(ndev); netif_start_queue(ndev); return 0; } static int lio_vf_rep_stop(struct net_device *ndev) { struct lio_vf_rep_desc *vf_rep = netdev_priv(ndev); struct lio_vf_rep_req rep_cfg; struct octeon_device *oct; int ret; oct = vf_rep->oct; memset(&rep_cfg, 0, sizeof(rep_cfg)); rep_cfg.req_type = LIO_VF_REP_REQ_STATE; rep_cfg.ifidx = vf_rep->ifidx; rep_cfg.rep_state.state = LIO_VF_REP_STATE_DOWN; ret = lio_vf_rep_send_soft_command(oct, &rep_cfg, sizeof(rep_cfg), NULL, 0); if (ret) { dev_err(&oct->pci_dev->dev, "VF_REP dev stop failed with err %d\n", ret); return -EIO; } atomic_set(&vf_rep->ifstate, (atomic_read(&vf_rep->ifstate) & ~LIO_IFSTATE_RUNNING)); netif_tx_disable(ndev); netif_carrier_off(ndev); return 0; } static void lio_vf_rep_tx_timeout(struct net_device *ndev) { netif_trans_update(ndev); netif_wake_queue(ndev); } static void lio_vf_rep_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats64) { struct lio_vf_rep_desc *vf_rep = netdev_priv(dev); /* Swap tx and rx stats as VF rep is a switch port */ stats64->tx_packets = vf_rep->stats.rx_packets; stats64->tx_bytes = vf_rep->stats.rx_bytes; stats64->tx_dropped = vf_rep->stats.rx_dropped; stats64->rx_packets = vf_rep->stats.tx_packets; stats64->rx_bytes = vf_rep->stats.tx_bytes; stats64->rx_dropped = vf_rep->stats.tx_dropped; } static int lio_vf_rep_change_mtu(struct net_device *ndev, int new_mtu) { struct lio_vf_rep_desc *vf_rep = netdev_priv(ndev); struct lio_vf_rep_req rep_cfg; struct octeon_device *oct; int ret; oct = vf_rep->oct; memset(&rep_cfg, 0, sizeof(rep_cfg)); rep_cfg.req_type = LIO_VF_REP_REQ_MTU; rep_cfg.ifidx = vf_rep->ifidx; rep_cfg.rep_mtu.mtu = cpu_to_be32(new_mtu); ret = lio_vf_rep_send_soft_command(oct, &rep_cfg, sizeof(rep_cfg), NULL, 0); if (ret) { dev_err(&oct->pci_dev->dev, "Change MTU failed with err %d\n", ret); return -EIO; } ndev->mtu = new_mtu; return 0; } static int lio_vf_rep_phys_port_name(struct net_device *dev, char *buf, size_t len) { struct lio_vf_rep_desc *vf_rep = netdev_priv(dev); struct octeon_device *oct = vf_rep->oct; int ret; ret = snprintf(buf, len, "pf%dvf%d", oct->pf_num, vf_rep->ifidx - oct->pf_num * 64 - 1); if (ret >= len) return -EOPNOTSUPP; return 0; } static struct net_device * lio_vf_rep_get_ndev(struct octeon_device *oct, int ifidx) { int vf_id, max_vfs = CN23XX_MAX_VFS_PER_PF + 1; int vfid_mask = max_vfs - 1; if (ifidx <= oct->pf_num * max_vfs || ifidx >= oct->pf_num * max_vfs + max_vfs) return NULL; /* ifidx 1-63 for PF0 VFs * ifidx 65-127 for PF1 VFs */ vf_id = (ifidx & vfid_mask) - 1; return oct->vf_rep_list.ndev[vf_id]; } static void lio_vf_rep_copy_packet(struct octeon_device *oct, struct sk_buff *skb, int len) { if (likely(len > MIN_SKB_SIZE)) { struct octeon_skb_page_info *pg_info; unsigned char *va; pg_info = ((struct octeon_skb_page_info *)(skb->cb)); if (pg_info->page) { va = page_address(pg_info->page) + pg_info->page_offset; memcpy(skb->data, va, MIN_SKB_SIZE); skb_put(skb, MIN_SKB_SIZE); } skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, pg_info->page, pg_info->page_offset + MIN_SKB_SIZE, len - MIN_SKB_SIZE, LIO_RXBUFFER_SZ); } else { struct octeon_skb_page_info *pg_info = ((struct octeon_skb_page_info *)(skb->cb)); skb_copy_to_linear_data(skb, page_address(pg_info->page) + pg_info->page_offset, len); skb_put(skb, len); put_page(pg_info->page); } } static int lio_vf_rep_pkt_recv(struct octeon_recv_info *recv_info, void *buf) { struct octeon_recv_pkt *recv_pkt = recv_info->recv_pkt; struct lio_vf_rep_desc *vf_rep; struct net_device *vf_ndev; struct octeon_device *oct; union octeon_rh *rh; struct sk_buff *skb; int i, ifidx; oct = lio_get_device(recv_pkt->octeon_id); if (!oct) goto free_buffers; skb = recv_pkt->buffer_ptr[0]; rh = &recv_pkt->rh; ifidx = rh->r.ossp; vf_ndev = lio_vf_rep_get_ndev(oct, ifidx); if (!vf_ndev) goto free_buffers; vf_rep = netdev_priv(vf_ndev); if (!(atomic_read(&vf_rep->ifstate) & LIO_IFSTATE_RUNNING) || recv_pkt->buffer_count > 1) goto free_buffers; skb->dev = vf_ndev; /* Multiple buffers are not used for vf_rep packets. * So just buffer_size[0] is valid. */ lio_vf_rep_copy_packet(oct, skb, recv_pkt->buffer_size[0]); skb_pull(skb, rh->r_dh.len * BYTES_PER_DHLEN_UNIT); skb->protocol = eth_type_trans(skb, skb->dev); skb->ip_summed = CHECKSUM_NONE; netif_rx(skb); octeon_free_recv_info(recv_info); return 0; free_buffers: for (i = 0; i < recv_pkt->buffer_count; i++) recv_buffer_free(recv_pkt->buffer_ptr[i]); octeon_free_recv_info(recv_info); return 0; } static void lio_vf_rep_packet_sent_callback(struct octeon_device *oct, u32 status, void *buf) { struct octeon_soft_command *sc = (struct octeon_soft_command *)buf; struct sk_buff *skb = sc->ctxptr; struct net_device *ndev = skb->dev; u32 iq_no; dma_unmap_single(&oct->pci_dev->dev, sc->dmadptr, sc->datasize, DMA_TO_DEVICE); dev_kfree_skb_any(skb); iq_no = sc->iq_no; octeon_free_soft_command(oct, sc); if (octnet_iq_is_full(oct, iq_no)) return; if (netif_queue_stopped(ndev)) netif_wake_queue(ndev); } static int lio_vf_rep_pkt_xmit(struct sk_buff *skb, struct net_device *ndev) { struct lio_vf_rep_desc *vf_rep = netdev_priv(ndev); struct net_device *parent_ndev = vf_rep->parent_ndev; struct octeon_device *oct = vf_rep->oct; struct octeon_instr_pki_ih3 *pki_ih3; struct octeon_soft_command *sc; struct lio *parent_lio; int status; parent_lio = GET_LIO(parent_ndev); if (!(atomic_read(&vf_rep->ifstate) & LIO_IFSTATE_RUNNING) || skb->len <= 0) goto xmit_failed; if (octnet_iq_is_full(vf_rep->oct, parent_lio->txq)) { dev_err(&oct->pci_dev->dev, "VF rep: Device IQ full\n"); netif_stop_queue(ndev); return NETDEV_TX_BUSY; } sc = (struct octeon_soft_command *) octeon_alloc_soft_command(oct, 0, 0, 0); if (!sc) { dev_err(&oct->pci_dev->dev, "VF rep: Soft command alloc failed\n"); goto xmit_failed; } /* Multiple buffers are not used for vf_rep packets. */ if (skb_shinfo(skb)->nr_frags != 0) { dev_err(&oct->pci_dev->dev, "VF rep: nr_frags != 0. Dropping packet\n"); goto xmit_failed; } sc->dmadptr = dma_map_single(&oct->pci_dev->dev, skb->data, skb->len, DMA_TO_DEVICE); if (dma_mapping_error(&oct->pci_dev->dev, sc->dmadptr)) { dev_err(&oct->pci_dev->dev, "VF rep: DMA mapping failed\n"); goto xmit_failed; } sc->virtdptr = skb->data; sc->datasize = skb->len; sc->ctxptr = skb; sc->iq_no = parent_lio->txq; octeon_prepare_soft_command(oct, sc, OPCODE_NIC, OPCODE_NIC_VF_REP_PKT, vf_rep->ifidx, 0, 0); pki_ih3 = (struct octeon_instr_pki_ih3 *)&sc->cmd.cmd3.pki_ih3; pki_ih3->tagtype = ORDERED_TAG; sc->callback = lio_vf_rep_packet_sent_callback; sc->callback_arg = sc; status = octeon_send_soft_command(oct, sc); if (status == IQ_SEND_FAILED) { dma_unmap_single(&oct->pci_dev->dev, sc->dmadptr, sc->datasize, DMA_TO_DEVICE); goto xmit_failed; } if (status == IQ_SEND_STOP) netif_stop_queue(ndev); netif_trans_update(ndev); return NETDEV_TX_OK; xmit_failed: dev_kfree_skb_any(skb); return NETDEV_TX_OK; } static int lio_vf_rep_attr_get(struct net_device *dev, struct switchdev_attr *attr) { struct lio_vf_rep_desc *vf_rep = netdev_priv(dev); struct net_device *parent_ndev = vf_rep->parent_ndev; struct lio *lio = GET_LIO(parent_ndev); switch (attr->id) { case SWITCHDEV_ATTR_ID_PORT_PARENT_ID: attr->u.ppid.id_len = ETH_ALEN; ether_addr_copy(attr->u.ppid.id, (void *)&lio->linfo.hw_addr + 2); break; default: return -EOPNOTSUPP; } return 0; } static const struct switchdev_ops lio_vf_rep_switchdev_ops = { .switchdev_port_attr_get = lio_vf_rep_attr_get, }; static void lio_vf_rep_fetch_stats(struct work_struct *work) { struct cavium_wk *wk = (struct cavium_wk *)work; struct lio_vf_rep_desc *vf_rep = wk->ctxptr; struct lio_vf_rep_stats stats; struct lio_vf_rep_req rep_cfg; struct octeon_device *oct; int ret; oct = vf_rep->oct; memset(&rep_cfg, 0, sizeof(rep_cfg)); rep_cfg.req_type = LIO_VF_REP_REQ_STATS; rep_cfg.ifidx = vf_rep->ifidx; ret = lio_vf_rep_send_soft_command(oct, &rep_cfg, sizeof(rep_cfg), &stats, sizeof(stats)); if (!ret) { octeon_swap_8B_data((u64 *)&stats, (sizeof(stats) >> 3)); memcpy(&vf_rep->stats, &stats, sizeof(stats)); } schedule_delayed_work(&vf_rep->stats_wk.work, msecs_to_jiffies(LIO_VF_REP_STATS_POLL_TIME_MS)); } int lio_vf_rep_create(struct octeon_device *oct) { struct lio_vf_rep_desc *vf_rep; struct net_device *ndev; int i, num_vfs; if (oct->eswitch_mode != DEVLINK_ESWITCH_MODE_SWITCHDEV) return 0; if (!oct->sriov_info.sriov_enabled) return 0; num_vfs = oct->sriov_info.num_vfs_alloced; oct->vf_rep_list.num_vfs = 0; for (i = 0; i < num_vfs; i++) { ndev = alloc_etherdev(sizeof(struct lio_vf_rep_desc)); if (!ndev) { dev_err(&oct->pci_dev->dev, "VF rep device %d creation failed\n", i); goto cleanup; } ndev->min_mtu = LIO_MIN_MTU_SIZE; ndev->max_mtu = LIO_MAX_MTU_SIZE; ndev->netdev_ops = &lio_vf_rep_ndev_ops; SWITCHDEV_SET_OPS(ndev, &lio_vf_rep_switchdev_ops); vf_rep = netdev_priv(ndev); memset(vf_rep, 0, sizeof(*vf_rep)); vf_rep->ndev = ndev; vf_rep->oct = oct; vf_rep->parent_ndev = oct->props[0].netdev; vf_rep->ifidx = (oct->pf_num * 64) + i + 1; eth_hw_addr_random(ndev); if (register_netdev(ndev)) { dev_err(&oct->pci_dev->dev, "VF rep nerdev registration failed\n"); free_netdev(ndev); goto cleanup; } netif_carrier_off(ndev); INIT_DELAYED_WORK(&vf_rep->stats_wk.work, lio_vf_rep_fetch_stats); vf_rep->stats_wk.ctxptr = (void *)vf_rep; schedule_delayed_work(&vf_rep->stats_wk.work, msecs_to_jiffies (LIO_VF_REP_STATS_POLL_TIME_MS)); oct->vf_rep_list.num_vfs++; oct->vf_rep_list.ndev[i] = ndev; } if (octeon_register_dispatch_fn(oct, OPCODE_NIC, OPCODE_NIC_VF_REP_PKT, lio_vf_rep_pkt_recv, oct)) { dev_err(&oct->pci_dev->dev, "VF rep Dispatch func registration failed\n"); goto cleanup; } return 0; cleanup: for (i = 0; i < oct->vf_rep_list.num_vfs; i++) { ndev = oct->vf_rep_list.ndev[i]; oct->vf_rep_list.ndev[i] = NULL; if (ndev) { vf_rep = netdev_priv(ndev); cancel_delayed_work_sync (&vf_rep->stats_wk.work); unregister_netdev(ndev); free_netdev(ndev); } } oct->vf_rep_list.num_vfs = 0; return -1; } void lio_vf_rep_destroy(struct octeon_device *oct) { struct lio_vf_rep_desc *vf_rep; struct net_device *ndev; int i; if (oct->eswitch_mode != DEVLINK_ESWITCH_MODE_SWITCHDEV) return; if (!oct->sriov_info.sriov_enabled) return; for (i = 0; i < oct->vf_rep_list.num_vfs; i++) { ndev = oct->vf_rep_list.ndev[i]; oct->vf_rep_list.ndev[i] = NULL; if (ndev) { vf_rep = netdev_priv(ndev); cancel_delayed_work_sync (&vf_rep->stats_wk.work); netif_tx_disable(ndev); netif_carrier_off(ndev); unregister_netdev(ndev); free_netdev(ndev); } } oct->vf_rep_list.num_vfs = 0; } static int lio_vf_rep_netdev_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *ndev = netdev_notifier_info_to_dev(ptr); struct lio_vf_rep_desc *vf_rep; struct lio_vf_rep_req rep_cfg; struct octeon_device *oct; int ret; switch (event) { case NETDEV_REGISTER: case NETDEV_CHANGENAME: break; default: return NOTIFY_DONE; } if (ndev->netdev_ops != &lio_vf_rep_ndev_ops) return NOTIFY_DONE; vf_rep = netdev_priv(ndev); oct = vf_rep->oct; if (strlen(ndev->name) > LIO_IF_NAME_SIZE) { dev_err(&oct->pci_dev->dev, "Device name change sync failed as the size is > %d\n", LIO_IF_NAME_SIZE); return NOTIFY_DONE; } memset(&rep_cfg, 0, sizeof(rep_cfg)); rep_cfg.req_type = LIO_VF_REP_REQ_DEVNAME; rep_cfg.ifidx = vf_rep->ifidx; strncpy(rep_cfg.rep_name.name, ndev->name, LIO_IF_NAME_SIZE); ret = lio_vf_rep_send_soft_command(oct, &rep_cfg, sizeof(rep_cfg), NULL, 0); if (ret) dev_err(&oct->pci_dev->dev, "vf_rep netdev name change failed with err %d\n", ret); return NOTIFY_DONE; } static struct notifier_block lio_vf_rep_netdev_notifier = { .notifier_call = lio_vf_rep_netdev_event, }; int lio_vf_rep_modinit(void) { if (register_netdevice_notifier(&lio_vf_rep_netdev_notifier)) { pr_err("netdev notifier registration failed\n"); return -EFAULT; } return 0; } void lio_vf_rep_modexit(void) { if (unregister_netdevice_notifier(&lio_vf_rep_netdev_notifier)) pr_err("netdev notifier unregister failed\n"); }