diff options
Diffstat (limited to 'drivers/net/bonding')
-rw-r--r-- | drivers/net/bonding/Makefile | 11 | ||||
-rw-r--r-- | drivers/net/bonding/bond_3ad.c | 9 | ||||
-rw-r--r-- | drivers/net/bonding/bond_main.c | 39 | ||||
-rw-r--r-- | drivers/net/bonding/bond_sysfs.c | 24 | ||||
-rw-r--r-- | drivers/net/bonding/bonding.h | 7 | ||||
-rw-r--r-- | drivers/net/bonding/hw_distribution.c | 2276 | ||||
-rw-r--r-- | drivers/net/bonding/hw_distribution.h | 138 |
7 files changed, 2501 insertions, 3 deletions
diff --git a/drivers/net/bonding/Makefile b/drivers/net/bonding/Makefile index 5a5d720..0024f07 100644 --- a/drivers/net/bonding/Makefile +++ b/drivers/net/bonding/Makefile @@ -2,6 +2,13 @@ # Makefile for the Ethernet Bonding driver # +include $(srctree)/drivers/net/ethernet/freescale/fman/ncsw_config.mk +ccflags-y += \ + -I$(srctree)/drivers/net/ethernet/freescale/dpa \ + -I$(srctree)/drivers/net/ethernet/freescale/fman/src/wrapper \ + -I$(srctree)/drivers/net/ethernet/freescale/fman/Peripherals/FM/Pcd \ + -I$(srctree)/drivers/net/ethernet/freescale/fman/Peripherals/FM/inc + obj-$(CONFIG_BONDING) += bonding.o bonding-objs := bond_main.o bond_3ad.o bond_alb.o bond_sysfs.o bond_debugfs.o bond_netlink.o bond_options.o @@ -9,3 +16,7 @@ bonding-objs := bond_main.o bond_3ad.o bond_alb.o bond_sysfs.o bond_debugfs.o bo proc-$(CONFIG_PROC_FS) += bond_procfs.o bonding-objs += $(proc-y) +hash_pcd_based_xmit_frames_distribution-$(CONFIG_HW_DISTRIBUTION_WITH_OH) += \ + hw_distribution.o + +bonding-objs += $(hash_pcd_based_xmit_frames_distribution-y) diff --git a/drivers/net/bonding/bond_3ad.c b/drivers/net/bonding/bond_3ad.c index 187b1b7..698ac84 100644 --- a/drivers/net/bonding/bond_3ad.c +++ b/drivers/net/bonding/bond_3ad.c @@ -2402,6 +2402,15 @@ int bond_3ad_xmit_xor(struct sk_buff *skb, struct net_device *dev) goto out; } +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + hw_lag_dbg("skb->protocol:0x%0x\n", skb->protocol); + /* exclude ARP/LACP */ + if ((bond->slave_cnt == SLAVES_PER_BOND) && + are_all_slaves_linkup(bond) && + (bond->params.ohp) && (bond->params.ohp->oh_en == 1)) + return enqueue_pkt_to_oh(bond, skb, NULL); +#endif + slave_agg_no = bond_xmit_hash(bond, skb, slaves_in_agg); first_ok_slave = NULL; diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 36eab0c..77619a6 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -1607,6 +1607,10 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev) new_slave->link != BOND_LINK_DOWN ? "n up" : " down"); /* enslave is successful */ +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + fill_oh_pcd_fqs_with_slave_info(bond, new_slave); + apply_pcd(bond, NO_POLICY); +#endif return 0; /* Undo stages on error */ @@ -1848,6 +1852,9 @@ static int __bond_release_one(struct net_device *bond_dev, slave_dev->priv_flags &= ~IFF_BONDING; +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + del_oh_pcd_fqs_with_slave_info(bond, slave); +#endif kfree(slave); return 0; /* deletion OK */ @@ -3164,6 +3171,10 @@ static struct rtnl_link_stats64 *bond_get_stats(struct net_device *bond_dev, stats->tx_heartbeat_errors += sstats->tx_heartbeat_errors; stats->tx_window_errors += sstats->tx_window_errors; } + +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + add_statistics(bond, stats); +#endif read_unlock_bh(&bond->lock); return stats; @@ -3890,6 +3901,9 @@ static const struct device_type bond_type = { static void bond_destructor(struct net_device *bond_dev) { struct bonding *bond = netdev_priv(bond_dev); +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + release_pcd_mem(bond); +#endif if (bond->wq) destroy_workqueue(bond->wq); free_netdev(bond_dev); @@ -4436,7 +4450,10 @@ int bond_create(struct net *net, const char *name) res = register_netdevice(bond_dev); netif_carrier_off(bond_dev); - +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + if (res == 0) + init_status(bond_dev); +#endif rtnl_unlock(); if (res < 0) bond_destructor(bond_dev); @@ -4508,6 +4525,13 @@ static int __init bonding_init(void) } register_netdevice_notifier(&bond_netdev_notifier); +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + if (get_oh_info()) + pr_err("oh ports probe error, use software distribution\n"); + else + pr_info("get offline ports information ok.\n"); +#endif + out: return res; err: @@ -4526,6 +4550,10 @@ static void __exit bonding_exit(void) bond_netlink_fini(); unregister_pernet_subsys(&bond_net_ops); +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + kfree(poh); + hw_lag_dbg("released offline port resources\n"); +#endif #ifdef CONFIG_NET_POLL_CONTROLLER /* @@ -4534,8 +4562,13 @@ static void __exit bonding_exit(void) WARN_ON(atomic_read(&netpoll_block_tx)); #endif } - -module_init(bonding_init); +/** + * late init to wait till oh port initilization ready, + * oh port can help distribute outgoing traffics based + * on hardware (FSL DPAA Offline port and PCD). + * module_init(bonding_init); + */ +late_initcall(bonding_init); module_exit(bonding_exit); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); diff --git a/drivers/net/bonding/bond_sysfs.c b/drivers/net/bonding/bond_sysfs.c index abf5e10..1e591c4 100644 --- a/drivers/net/bonding/bond_sysfs.c +++ b/drivers/net/bonding/bond_sysfs.c @@ -329,6 +329,9 @@ static ssize_t bonding_store_xmit_hash(struct device *d, (int)strlen(buf) - 1, buf); ret = -EINVAL; } else { +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + apply_pcd(bond, new_value); +#endif bond->params.xmit_policy = new_value; pr_info("%s: setting xmit hash policy to %s (%d).\n", bond->dev->name, @@ -883,6 +886,21 @@ static ssize_t bonding_store_min_links(struct device *d, } static DEVICE_ATTR(min_links, S_IRUGO | S_IWUSR, bonding_show_min_links, bonding_store_min_links); +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH +static DEVICE_ATTR(offline_port_xmit_statistics, S_IRUGO, + bonding_show_offline_port_xmit_statistics, NULL); + +static DEVICE_ATTR(offline_ports, S_IRUGO, + bonding_show_offline_ports, NULL); + +static DEVICE_ATTR(oh_needed_for_hw_distribution, S_IRUGO | S_IWUSR, + bonding_show_oh_needed_for_hw_distribution, + bonding_store_oh_needed_for_hw_distribution); + +static DEVICE_ATTR(oh_en, S_IRUGO | S_IWUSR, + bonding_show_oh_enable, + bonding_store_oh_enable); +#endif static ssize_t bonding_show_ad_select(struct device *d, struct device_attribute *attr, @@ -1709,6 +1727,12 @@ static struct attribute *per_bond_attrs[] = { &dev_attr_min_links.attr, &dev_attr_lp_interval.attr, &dev_attr_packets_per_slave.attr, +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + &dev_attr_offline_ports.attr, + &dev_attr_oh_needed_for_hw_distribution.attr, + &dev_attr_oh_en.attr, + &dev_attr_offline_port_xmit_statistics.attr, +#endif NULL, }; diff --git a/drivers/net/bonding/bonding.h b/drivers/net/bonding/bonding.h index a9f4f9f..a606ea0 100644 --- a/drivers/net/bonding/bonding.h +++ b/drivers/net/bonding/bonding.h @@ -25,6 +25,9 @@ #include <linux/etherdevice.h> #include "bond_3ad.h" #include "bond_alb.h" +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH +#include "hw_distribution.h" +#endif #define DRV_VERSION "3.7.1" #define DRV_RELDATE "April 27, 2011" @@ -167,6 +170,10 @@ struct bond_params { int resend_igmp; int lp_interval; int packets_per_slave; +#ifdef CONFIG_HW_DISTRIBUTION_WITH_OH + struct oh_port_priv *ohp; + struct rtnl_link_stats64 oh_stats; +#endif }; struct bond_parm_tbl { diff --git a/drivers/net/bonding/hw_distribution.c b/drivers/net/bonding/hw_distribution.c new file mode 100644 index 0000000..dce0ea1 --- /dev/null +++ b/drivers/net/bonding/hw_distribution.c @@ -0,0 +1,2276 @@ +/* Copyright 2014 Freescale Semiconductor Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <linux/kthread.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/ipv6.h> +#include <linux/if_vlan.h> +#include <net/ip.h> +#include "hw_distribution.h" +#include "mac.h" +#define to_dev(obj) container_of(obj, struct device, kobj) +#define to_bond(cd) ((struct bonding *)(netdev_priv(to_net_dev(cd)))) +#define master_to_bond(net_dev) ((struct bonding *)(netdev_priv(net_dev))) +/** + * This includes L4 checksum errors, but also other errors that + * the Hard Parser can detect, such as invalid combinations of + * TCP control flags, or bad UDP lengths. + */ +#define FM_L4_PARSE_ERROR 0x10 +/* Check if the hardware parser has run */ +#define FM_L4_HXS_RUN 0xE0 +/** + * Check if the FMan Hardware Parser has run for L4 protocols. + * @parse_result_ptr must be of type (fm_prs_result_t *). + */ +#define fm_l4_hxs_has_run(parse_result_ptr) \ + ((parse_result_ptr)->l4r & FM_L4_HXS_RUN) +/** + * If the FMan Hardware Parser has run for L4 protocols, check + * error status. + * @parse_result_ptr must be of type fm_prs_result_t *). + */ +#define fm_l4_hxs_error(parse_result_ptr) \ + ((parse_result_ptr)->l4r & FM_L4_PARSE_ERROR) + +#define DPA_WRITE_SKB_PTR(skb, skbh, addr, off) \ + { \ + skbh = (struct sk_buff **)addr; \ + *(skbh + (off)) = skb; \ + } + +#define DPA_READ_SKB_PTR(skb, skbh, addr, off) \ + { \ + skbh = (struct sk_buff **)addr; \ + skb = *(skbh + (off)); \ + } + +static const struct of_device_id oh_port_match_table[] = { + { + .compatible = "fsl,dpa-oh" + }, + {} +}; + +struct oh_port_priv *poh; /* Offline port information pointer */ +int available_num_of_oh_ports; +/** + * Sysfs interfaces: + * Show the statistics information by offline port xmit. + */ +ssize_t bonding_show_offline_port_xmit_statistics(struct device *d, + struct device_attribute *attr, char *buf) +{ + int res = 0; + struct bonding *bond = to_bond(d); + + if (bond->params.mode != BOND_MODE_8023AD) { + pr_err("%s: This command only support 802.3ad mode.\n", + bond->dev->name); + return -EPERM; + } + + if (!bond->params.ohp) { + pr_err("error, have not bind an offline port\n"); + + return -EPERM; + } + + if (!bond->params.ohp->oh_en) { + pr_err("error, binded offline port is not enabled.\n"); + + return -EPERM; + } + + res += sprintf(buf + res, "offline port TX packets: %llu\n", + bond->params.oh_stats.tx_packets); + res += sprintf(buf + res, "offline port TX bytes: %llu\n", + bond->params.oh_stats.tx_bytes); + res += sprintf(buf + res, "offline port TX errors: %llu\n", + bond->params.oh_stats.tx_errors); + res += sprintf(buf + res, "offline port TX dropped: %llu\n", + bond->params.oh_stats.tx_dropped); + + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + return res; +} +/** + * Sysfs interfaces: + * Show all available offline ports can be binded to a bond. + */ +ssize_t bonding_show_offline_ports(struct device *d, + struct device_attribute *attr, char *buf) +{ + int i, res = 0; + struct bonding *bond = to_bond(d); + + if (bond->params.mode != BOND_MODE_8023AD) { + pr_err("%s: This command only support 802.3ad mode.\n", + bond->dev->name); + return -EPERM; + } + + for (i = 0; i < available_num_of_oh_ports; i++) { + if (poh[i].oh_dev) + res += sprintf(buf + res, "%s\n", poh[i].friendname); + } + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + return res; +} +/** + * Sysfs interfaces: + * Show the offline_port has already attached to the current bond, + * which can help bond to do hardware based slave selection. + */ +ssize_t bonding_show_oh_needed_for_hw_distribution(struct device *d, + struct device_attribute *attr, char *buf) +{ + int res = 0; + struct bonding *bond = to_bond(d); + + if (bond->params.mode != BOND_MODE_8023AD) { + pr_err("%s: This command only support 802.3ad mode.\n", + bond->dev->name); + return -EPERM; + } + + if (!bond->params.ohp) { + pr_err("error, have not bind an offline port\n"); + + return -EPERM; + } + + res += sprintf(buf + res, "%s\n", bond->params.ohp->friendname); + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + + return res; +} +/** + * System interface: + * Add one Offline port into the current bond for utilizing PCD to + * do TX traffic distribution based on hard ware. + * This codes firt verify the input Offline port name validation, + * then store the Offline port to the current bond->params. + */ +ssize_t bonding_store_oh_needed_for_hw_distribution(struct device *d, + struct device_attribute *attr, const char *buffer, size_t count) +{ + char command[OHFRIENDNAMSIZ + 1] = { 0, }; + int ret = count, i, errno; + struct bonding *bond = to_bond(d); + struct oh_port_priv *tmp = poh; + bool find = false; + + if (bond->params.mode != BOND_MODE_8023AD) { + pr_err("%s: This command only support 802.3ad mode.\n", + bond->dev->name); + return -EPERM; + } + if (bond->slave_cnt > 0) { + pr_err("%s: Detach slaves before change oh binding.\n", + bond->dev->name); + return -EPERM; + } + + if (!rtnl_trylock()) + return restart_syscall(); + + /* OHFRIENDNAMSIZ = 10, there is 10 chars in a command. */ + errno = sscanf(buffer, "%10s", command); + if ((strlen(command) <= 1) || (errno != 1)) + goto err_no_cmd; + + if ((bond->params.ohp) && (bond->params.ohp->friendname[0]) && + strncasecmp(command, bond->params.ohp->friendname, + OHFRIENDNAMSIZ) == 0) { + pr_err("%s: has already used %s.\n", + bond->dev->name, command); + ret = -EPERM; + goto out; + } else + for (i = 0; i < available_num_of_oh_ports; i++) { + if (tmp->oh_dev) { + if (strncasecmp(command, tmp->friendname, + OHFRIENDNAMSIZ) == 0) { + find = true; + bond->params.ohp = tmp; + break; + } else + tmp++; + } + } + + if (!find) + goto err_no_cmd; + + pr_info("bind OH port oh_needed_for_hw_distribution: %s to %s\n", + bond->params.ohp->friendname, bond->dev->name); + + goto out; + +err_no_cmd: + pr_err("%s:bad command or no such OH port,\n" + "please try other OH ports.\n" + "Eg: echo OH0 > oh_needed_for_hw_distribution.\n", + bond->dev->name); + ret = -EPERM; + +out: + rtnl_unlock(); + return ret; +} +/** + * Sysfs interfaces: + * Show whether current offline port binding to the bond is active or not. + */ +ssize_t bonding_show_oh_enable(struct device *d, + struct device_attribute *attr, char *buf) +{ + + int res = 0; + struct bonding *bond = to_bond(d); + uint16_t channel; + unsigned long fman_dcpid, oh_offset, cell_index; + + if (bond->params.mode != BOND_MODE_8023AD) { + pr_err("%s: This command only support 802.3ad mode.\n", + bond->dev->name); + return -EPERM; + } + + if (!bond->params.ohp) { + pr_err("error, have not bind a offline port\n"); + + return -EPERM; + } + + res += sprintf(buf + res, "%d\n", bond->params.ohp->oh_en); + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + + if ((bond->params.ohp->oh_en) && + (!export_oh_port_info_to_ceetm(bond, &channel, + &fman_dcpid, &oh_offset, &cell_index))) + hw_lag_dbg("offline port channel:%d\n", channel); + + return res; +} +/** + * Sysfs interfaces: + * Set current offline port which is binding to the bond active or not, + * this interface can disable or enable the offline port which is binding + * to a bond at run-time. + */ +ssize_t bonding_store_oh_enable(struct device *d, + struct device_attribute *attr, const char *buffer, + size_t count) +{ + int new_value, ret; + struct bonding *bond = to_bond(d); + + if (bond->params.mode != BOND_MODE_8023AD) { + pr_err("%s: This command only support 802.3ad mode.\n", + bond->dev->name); + return -EPERM; + } + + ret = sscanf(buffer, "%d", &new_value); + pr_info("new_value:%d, ret: %d\n", new_value, ret); + if (ret != 1) { + pr_err("%s: Bad command, use echo [1|0] > oh_en.\n", + bond->dev->name); + return -EINVAL; + } + + if (!bond->params.ohp) { + pr_err("error, have not bind a offline port\n"); + return -EPERM; + } + + if ((new_value == 0) || (new_value == 1)) { + bond->params.ohp->oh_en = new_value; + return count; + } else { + pr_err("%s: Bad value, only is 1 or 0.\n", + bond->dev->name); + return -EINVAL; + } +} + +/** + * Judge a slave net device is a dpa-eth NIC, + * return true if it is a dpa-eth NIC, + * otherwise return false. + */ +static bool is_dpa_eth_port(struct net_device *netdev) +{ + struct device *dev = (struct device *) &(netdev->dev); + + if ((strlen(dev_driver_string(dev->parent)) >= 7) && + strncmp(dev_driver_string(dev->parent), "fsl_dpa", 7) == 0) + return true; + else + return false; +} + +bool are_all_slaves_linkup(struct bonding *bond) +{ + struct slave *slave; + struct list_head *iter; + + read_lock(&bond->lock); + bond_for_each_slave(bond, slave, iter) + if (!(SLAVE_IS_OK(slave))) { + read_unlock(&bond->lock); + return false; + } + + read_unlock(&bond->lock); + return true; +} + +unsigned int to_which_oh_i_attached(struct oh_port_priv *current_poh) +{ + struct oh_port_priv *org = poh; + int i = 0; + while (current_poh - org) { + i++; + org++; + } + + return i; +} +/* Borrowed from dpa_fd_release, removed netdev params. */ +static void __attribute__((nonnull)) +dpa_oh_fd_release(const struct qm_fd *fd) +{ + struct qm_sg_entry *sgt; + struct dpa_bp *dpa_bp; + struct bm_buffer bmb; + + bmb.hi = fd->addr_hi; + bmb.lo = fd->addr_lo; + + dpa_bp = dpa_bpid2pool(fd->bpid); + DPA_BUG_ON(!dpa_bp); + + if (fd->format == qm_fd_sg) { + sgt = (phys_to_virt(bm_buf_addr(&bmb)) + dpa_fd_offset(fd)); + dpa_release_sgt(sgt); + } + + while (bman_release(dpa_bp->pool, &bmb, 1, 0)) + cpu_relax(); +} + +static void dpa_oh_drain_bp(struct dpa_bp *bp) +{ + int i, num; + struct bm_buffer bmb[8]; + dma_addr_t addr; + int *countptr = __this_cpu_ptr(bp->percpu_count); + int count = *countptr; + struct sk_buff **skbh; + + while (count >= 8) { + num = bman_acquire(bp->pool, bmb, 8, 0); + /* There may still be up to 7 buffers in the pool; + * just leave them there until more arrive + */ + if (num < 0) + break; + for (i = 0; i < num; i++) { + addr = bm_buf_addr(&bmb[i]); + /* bp->free_buf_cb(phys_to_virt(addr)); */ + skbh = (struct sk_buff **)phys_to_virt(addr); + dma_unmap_single(bp->dev, addr, bp->size, + DMA_TO_DEVICE); + dev_kfree_skb(*skbh); + } + count -= num; + } + *countptr = count; +} +static int dpa_oh_tx_bp_probe(struct device *dev, + struct fm_port *tx_port, + struct dpa_bp **draining_tx_bp, + struct dpa_buffer_layout_s **tx_buf_layout) +{ + struct fm_port_params params; + struct dpa_bp *bp = NULL; + struct dpa_buffer_layout_s *buf_layout = NULL; + + buf_layout = devm_kzalloc(dev, sizeof(*buf_layout), GFP_KERNEL); + if (!buf_layout) { + dev_err(dev, "devm_kzalloc() failed\n"); + return -ENOMEM; + } + + buf_layout->priv_data_size = DPA_TX_PRIV_DATA_SIZE; + buf_layout->parse_results = true; + buf_layout->hash_results = true; + buf_layout->time_stamp = false; + + fm_port_get_buff_layout_ext_params(tx_port, ¶ms); + buf_layout->manip_extra_space = params.manip_extra_space; + buf_layout->data_align = params.data_align ? : DPA_FD_DATA_ALIGNMENT; + + bp = devm_kzalloc(dev, sizeof(*bp), GFP_KERNEL); + if (unlikely(bp == NULL)) { + dev_err(dev, "devm_kzalloc() failed\n"); + return -ENOMEM; + } + + bp->size = dpa_bp_size(buf_layout); + bp->percpu_count = alloc_percpu(*bp->percpu_count); + bp->target_count = CONFIG_FSL_DPAA_ETH_MAX_BUF_COUNT; + + *draining_tx_bp = bp; + *tx_buf_layout = buf_layout; + + return 0; +} +static int dpa_oh_bp_create(struct oh_port_priv *ohp) +{ + int err = 0; + struct dpa_bp *draining_tx_bp; + struct dpa_buffer_layout_s *tx_buf_layout; + + err = dpa_oh_tx_bp_probe(ohp->dpa_oh_dev, ohp->oh_config->oh_port, + &draining_tx_bp, &tx_buf_layout); + if (err) { + pr_err("errors on dpa_oh_tx_bp_probe()\n"); + return err; + } + + ohp->tx_bp = draining_tx_bp; + ohp->tx_buf_layout = tx_buf_layout; + + err = dpa_bp_alloc(ohp->tx_bp); + if (err < 0) { + /* _dpa_bp_free(ohp->tx_bp); */ + pr_err("error on dpa_bp_alloc()\n"); + ohp->tx_bp = NULL; + return err; + } + hw_lag_dbg("created bp, bpid(ohp->tx_bp):%d\n", ohp->tx_bp->bpid); + + return 0; +} +/** + * Copied from DPA-Eth driver (since they have different params type): + * Cleanup function for outgoing frame descriptors that were built on Tx path, + * either contiguous frames or scatter/gather ones. + * Skb freeing is not handled here. + * + * This function may be called on error paths in the Tx function, so guard + * against cases when not all fd relevant fields were filled in. + * + * Return the skb backpointer, since for S/G frames the buffer containing it + * gets freed here. + */ +struct sk_buff *oh_cleanup_tx_fd(const struct qm_fd *fd) +{ + int i, nr_frags; + const struct qm_sg_entry *sgt; + struct sk_buff **skbh; + struct sk_buff *skb = NULL; + dma_addr_t addr = qm_fd_addr(fd); + struct dpa_bp *dpa_bp = dpa_bpid2pool(fd->bpid); + const enum dma_data_direction dma_dir = DMA_TO_DEVICE; + + DPA_BUG_ON(fd->cmd & FM_FD_CMD_FCO); + dma_unmap_single(dpa_bp->dev, addr, dpa_bp->size, dma_dir); + + /* retrieve skb back pointer */ + DPA_READ_SKB_PTR(skb, skbh, phys_to_virt(addr), 0); + nr_frags = skb_shinfo(skb)->nr_frags; + + if (fd->format == qm_fd_sg) { + /* The sgt buffer has been allocated with netdev_alloc_frag(), + * it's from lowmem. + */ + sgt = phys_to_virt(addr + dpa_fd_offset(fd)); + + /* sgt[0] is from lowmem, was dma_map_single()-ed */ + dma_unmap_single(dpa_bp->dev, sgt[0].addr, + sgt[0].length, dma_dir); + + /* remaining pages were mapped with dma_map_page() */ + for (i = 1; i < nr_frags; i++) { + DPA_BUG_ON(sgt[i].extension); + + dma_unmap_page(dpa_bp->dev, sgt[i].addr, + sgt[i].length, dma_dir); + } + + /* Free the page frag that we allocated on Tx */ + put_page(virt_to_head_page(sgt)); + } + + return skb; +} + +static void dump_parser_result(const struct qm_fd *fd) +{ +#ifdef CONFIG_HW_LAG_DEBUG + dma_addr_t addr = qm_fd_addr(fd); + void *vaddr; + const fm_prs_result_t *parse_results; + + vaddr = phys_to_virt(addr); + DPA_BUG_ON(!IS_ALIGNED((unsigned long)vaddr, SMP_CACHE_BYTES)); + + parse_results = (const fm_prs_result_t *)(vaddr + + DPA_TX_PRIV_DATA_SIZE); + + hw_lag_dbg("parse_results->l2r:0x%08x\n", parse_results->l2r); + + hw_lag_dbg("FM_L3_PARSE_RESULT_IPV4:0x%0x\n" + "FM_L3_PARSE_RESULT_IPV6:0x%0x\n" + "parse_results->l3r:0x%08x\n", + parse_results->l3r & FM_L3_PARSE_RESULT_IPV4, + parse_results->l3r & FM_L3_PARSE_RESULT_IPV6, + parse_results->l3r); + + hw_lag_dbg("fm_l4_hxs_has_run(parse_results):0x%0x\n" + "fm_l4_hxs_error(parse_results):0x%0x\n", + fm_l4_hxs_has_run(parse_results), + fm_l4_hxs_error(parse_results)); + + hw_lag_dbg("fd->status & FM_FD_STAT_L4CV:0x%x\n" + "parse_results->l4r:0x%08x\n" + "fm_l4_frame_is_tcp(parse_results):0x%0x\n", + fd->status & FM_FD_STAT_L4CV, + parse_results->l4r, + fm_l4_frame_is_tcp(parse_results)); +#endif +} + +static void show_dbg_info(const struct qm_fd *fd, const char *func_name, + struct sk_buff *skb) +{ +#ifdef CONFIG_HW_LAG_DEBUG + u32 pad, fd_status; + dma_addr_t addr; + struct ethhdr *eth; + struct iphdr *iph; + struct tcphdr *tcph; + struct udphdr *udph; + unsigned int data_start; + unsigned long skb_addr; + + fd_status = fd->status; + addr = qm_fd_addr(fd); + + /* find out the pad */ + skb_addr = virt_to_phys(skb->head); + pad = addr - skb_addr; + + /* The skb is currently pointed at head + headroom. The packet + * starts at skb->head + pad + fd offset. + */ + data_start = pad + dpa_fd_offset(fd) - skb_headroom(skb); + + skb_pull(skb, data_start); + + pr_info("[%s]:fd->status:0x%08x\n", func_name, fd_status); + pr_info("[%s]:fd tx status:0x%08x. fd rx status:0x%08x\n", + func_name, + fd_status & FM_FD_STAT_TX_ERRORS, + fd_status & FM_FD_STAT_RX_ERRORS); + + if (likely(fd_status & FM_FD_STAT_ERR_PHYSICAL)) + pr_err("FM_FD_STAT_ERR_PHYSICAL\n"); + if (fd_status & FM_PORT_FRM_ERR_DMA) + pr_err("FM_PORT_FRM_ERR_DMA\n"); + if (fd_status & FM_PORT_FRM_ERR_PHYSICAL) + pr_err("FM_PORT_FRM_ERR_PHYSICAL\n"); + if (fd_status & FM_PORT_FRM_ERR_SIZE) + pr_err("FM_PORT_FRM_ERR_SIZE\n"); + if (fd_status & FM_PORT_FRM_ERR_PRS_HDR_ERR) + pr_err("oh_pcd_confq FM_PORT_FRM_ERR_PRS_HDR_ERR\n"); + + pr_info("[%s]:fd->format - qm_fd_contig:%d\n", func_name, + fd->format - qm_fd_contig); + pr_info("[%s]:fd->bpid:%d\n", func_name, fd->bpid); + + /* get L2 info */ + skb->protocol = htons(ETH_P_802_3); + skb_reset_mac_header(skb); + skb_pull_inline(skb, ETH_HLEN); + + eth = eth_hdr(skb); + + pr_info("\n[%s]:dmac:%02x:%02x:%02x:%02x:%02x:%02x\n" + "smac:%02x:%02x:%02x:%02x:%02x:%02x\n" + "h_proto:0x%04x\n", func_name, + eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], + eth->h_dest[3], eth->h_dest[4], eth->h_dest[5], + eth->h_source[0], eth->h_source[1], eth->h_source[2], + eth->h_source[3], eth->h_source[4], eth->h_source[5], + eth->h_proto); + + if (fd_status & FM_FD_STAT_L4CV) { + skb->ip_summed = CHECKSUM_UNNECESSARY; + pr_info("[%s]:skb->ip_summed = CHECKSUM_UNNECESSARY\n", + func_name); + } else { + skb->ip_summed = CHECKSUM_NONE; + pr_info("[%s]:skb->ip_summed = CHECKSUM_NONE\n", func_name); + } + + /* get L3 and part of L4 info */ + skb_reset_network_header(skb); + skb_reset_transport_header(skb); + skb_reset_mac_len(skb); + + if (eth->h_proto == ETH_P_IP) { + iph = ip_hdr(skb); + pr_info("[%s]:L3_pro:0x%0x, dip:0x%0x, sip:0x%0x\n", func_name, + iph->protocol, iph->daddr, iph->saddr); + + skb_pull_inline(skb, ip_hdrlen(skb)); + skb_reset_transport_header(skb); + + if (iph->protocol == IPPROTO_TCP) { + tcph = tcp_hdr(skb); + pr_info("[%s]:tcp csum:0x%0x\n", + func_name, tcph->check); + } else if (iph->protocol == IPPROTO_UDP) { + udph = udp_hdr(skb); + pr_info("[%s]:udp csum:0x%0x\n", + func_name, udph->check); + } + + } else if (eth->h_proto == ETH_P_ARP) { + pr_info("[%s]:ARP.\n", func_name); + } else if (eth->h_proto == ETH_P_IPV6) { + pr_info("[%s]:IPv6.\n", func_name); + } else if (eth->h_proto == ETH_P_SLOW) { + pr_info("[%s]:802.3ad pkt.\n", func_name); + } else { + pr_info("[%s]:other pkt.\n", func_name); + } + + return; +#endif +} +/** + * When enqueue an frame from kernel module to offline port, + * once errors happeds, this callback will be entered. + */ +static enum qman_cb_dqrr_result +oh_ingress_tx_error_dqrr(struct qman_portal *portal, + struct qman_fq *fq, + const struct qm_dqrr_entry *dq) +{ + struct sk_buff *skb; + const struct qm_fd *fd = &dq->fd; + + skb = oh_cleanup_tx_fd(fd); + dump_parser_result(fd); + show_dbg_info(fd, __func__, skb); + dev_kfree_skb_any(skb); + + return qman_cb_dqrr_consume; +} +/** + * This subroutine is copied from oNIC, it should not be call + * in normal case, only for debugging outgoing traffics to oh + * tx port while no PCD applied for oh port. such as debugging + * oh port tx L4 csum. + */ +static enum qman_cb_dqrr_result __hot +oh_ingress_tx_default_dqrr(struct qman_portal *portal, + struct qman_fq *fq, + const struct qm_dqrr_entry *dq) +{ + struct net_device *netdev; + struct dpa_priv_s *priv; + struct dpa_bp *bp; + struct dpa_percpu_priv_s *percpu_priv; + struct sk_buff **skbh; + struct sk_buff *skb; + struct iphdr *iph; + const struct qm_fd *fd = &dq->fd; + u32 fd_status = fd->status; + u32 pad; + dma_addr_t addr = qm_fd_addr(fd); + unsigned int data_start; + unsigned long skb_addr; + int *countptr; + struct ethhdr *eth; + + hw_lag_dbg("fd->status:0x%08x\n", fd_status); + + hw_lag_dbg("fd tx status:0x%08x. fd rx status:0x%08x\n", + fd_status & FM_FD_STAT_TX_ERRORS, + fd_status & FM_FD_STAT_RX_ERRORS); + + if (likely(fd_status & FM_FD_STAT_ERR_PHYSICAL)) + pr_err("FM_FD_STAT_ERR_PHYSICAL\n"); + + if (fd_status & FM_PORT_FRM_ERR_DMA) + pr_err("FM_PORT_FRM_ERR_DMA\n"); + if (fd_status & FM_PORT_FRM_ERR_PHYSICAL) + pr_err("FM_PORT_FRM_ERR_PHYSICAL\n"); + if (fd_status & FM_PORT_FRM_ERR_SIZE) + pr_err("FM_PORT_FRM_ERR_SIZE\n"); + if (fd_status & FM_PORT_FRM_ERR_PRS_HDR_ERR) + pr_err("oh_tx_defq FM_PORT_FRM_ERR_PRS_HDR_ERR\n"); + + netdev = ((struct dpa_fq *)fq)->net_dev; + if (!netdev) { + pr_err("error netdev == NULL.\n"); + skbh = (struct sk_buff **)phys_to_virt(addr); + dev_kfree_skb(*skbh); + return qman_cb_dqrr_consume; + } + priv = netdev_priv(netdev); + dump_parser_result(fd); + + percpu_priv = __this_cpu_ptr(priv->percpu_priv); + countptr = __this_cpu_ptr(priv->dpa_bp->percpu_count); + + skbh = (struct sk_buff **)phys_to_virt(addr); + /* according to the last common code (bp refill) the skb pointer is set + * to another address shifted by sizeof(struct sk_buff) to the left + */ + skb = *(skbh - 1); + + if (unlikely(fd_status & FM_FD_STAT_RX_ERRORS) != 0) { + hw_lag_dbg("FD status = 0x%08x\n", + fd_status & FM_FD_STAT_RX_ERRORS); + + percpu_priv->stats.rx_errors++; + oh_cleanup_tx_fd(fd); + goto qman_consume; + } + if (unlikely(fd->format != qm_fd_contig)) { + percpu_priv->stats.rx_dropped++; + hw_lag_dbg("Dropping a SG frame\n"); + oh_cleanup_tx_fd(fd); + goto qman_consume; + } + + hw_lag_dbg("fd->bpid:%d\n", fd->bpid); + bp = dpa_bpid2pool(fd->bpid); + dma_unmap_single(bp->dev, addr, bp->size, DMA_TO_DEVICE); + + /* find out the pad */ + skb_addr = virt_to_phys(skb->head); + pad = addr - skb_addr; + + countptr = __this_cpu_ptr(bp->percpu_count); + (*countptr)--; + + /* The skb is currently pointed at head + headroom. The packet + * starts at skb->head + pad + fd offset. + */ + data_start = pad + dpa_fd_offset(fd) - skb_headroom(skb); + skb_pull(skb, data_start); + + /* get L2 info */ + skb->protocol = eth_type_trans(skb, netdev); + eth = eth_hdr(skb); + + hw_lag_dbg("dmac:%02x:%02x:%02x:%02x:%02x:%02x\n" + "smac:%02x:%02x:%02x:%02x:%02x:%02x\n" + "h_proto:0x%04x\n", + eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], + eth->h_dest[3], eth->h_dest[4], eth->h_dest[5], + eth->h_source[0], eth->h_source[1], eth->h_source[2], + eth->h_source[3], eth->h_source[4], eth->h_source[5], + eth->h_proto); + + if (unlikely(dpa_check_rx_mtu(skb, netdev->mtu))) { + percpu_priv->stats.rx_dropped++; + goto qman_consume; + } + + if (fd_status & FM_FD_STAT_L4CV) { + skb->ip_summed = CHECKSUM_UNNECESSARY; + hw_lag_dbg("skb->ip_summed = CHECKSUM_UNNECESSARY\n"); + } else { + skb->ip_summed = CHECKSUM_NONE; + hw_lag_dbg("skb->ip_summed = CHECKSUM_NONE\n"); + } + + /* get L3 and part of L4 info */ + skb_reset_network_header(skb); + skb_reset_transport_header(skb); + skb_reset_mac_len(skb); + + if (eth->h_proto == ETH_P_IP) { + iph = ip_hdr(skb); + hw_lag_dbg("L3_pro:0x%0x, dip:0x%0x, sip:0x%0x\n", + iph->protocol, iph->daddr, iph->saddr); + } else if (eth->h_proto == ETH_P_ARP) { + hw_lag_dbg("ARP.\n"); + } else if (eth->h_proto == ETH_P_IPV6) { + hw_lag_dbg("IPv6.\n"); + } else if (eth->h_proto == ETH_P_SLOW) { + hw_lag_dbg("802.3ad pkt.\n"); + } else { + hw_lag_dbg("other pkt.\n"); + } + +qman_consume: + dev_kfree_skb_any(skb); + + return qman_cb_dqrr_consume; +} +/** + * When frame leave from PCD fqs then goes final terminated physical + * ports(MAC ports),once errors happend, this callback will be entered. + * dump debugging information when HW_LAG_DEBUG enabled . + */ +static enum qman_cb_dqrr_result +oh_pcd_err_dqrr(struct qman_portal *portal, struct qman_fq *fq, + const struct qm_dqrr_entry *dq) +{ + struct sk_buff *skb; + const struct qm_fd *fd = &dq->fd; + + skb = oh_cleanup_tx_fd(fd); + dump_parser_result(fd); + show_dbg_info(fd, __func__, skb); + dev_kfree_skb_any(skb); + + return qman_cb_dqrr_consume; + +} +/** + * When frame leave from offline port tx fqs then goes into offline tx + * ports(MAC ports), it will be into confirm fq, this callback will be + * entered. + * dump debugging information when HW_LAG_DEBUG enabled. + * don't free skb, since offline port is not the final consumer. + */ +static enum qman_cb_dqrr_result __hot +oh_tx_conf_dqrr(struct qman_portal *portal, struct qman_fq *fq, + const struct qm_dqrr_entry *dq) +{ + struct sk_buff *skb; + const struct qm_fd *fd = &dq->fd; + + skb = oh_cleanup_tx_fd(fd); + dump_parser_result(fd); + show_dbg_info(fd, __func__, skb); + + return qman_cb_dqrr_consume; +} + +static void lag_public_egress_ern(struct qman_portal *portal, + struct qman_fq *fq, const struct qm_mr_entry *msg) +{ + /* will add ERN statistics in the future version. */ + const struct qm_fd *fd = &msg->ern.fd; + struct sk_buff *skb; + + if (msg->ern.fd.cmd & FM_FD_CMD_FCO) { + dpa_oh_fd_release(fd); + return; + } + + skb = oh_cleanup_tx_fd(fd); + dump_parser_result(fd); + show_dbg_info(fd, __func__, skb); + dev_kfree_skb_any(skb); +} + +/** + * This subroutine will be called when frame out of oh pcd fqs and + * consumed by (MAC ports). + * Display debugging information if HW_LAG_DEBUG on. + */ +static enum qman_cb_dqrr_result __hot +oh_pcd_conf_dqrr(struct qman_portal *portal, struct qman_fq *fq, + const struct qm_dqrr_entry *dq) +{ + struct sk_buff *skb; + const struct qm_fd *fd = &dq->fd; + + skb = oh_cleanup_tx_fd(fd); + show_dbg_info(fd, __func__, skb); + dev_kfree_skb_any(skb); + + return qman_cb_dqrr_consume; +} + +static const struct qman_fq oh_tx_defq = { + .cb = { .dqrr = oh_ingress_tx_default_dqrr} +}; +/* for OH ports Rx Error queues = Tx Error queues */ +static const struct qman_fq oh_tx_errq = { + .cb = { .dqrr = oh_ingress_tx_error_dqrr} +}; + +static const struct qman_fq oh_pcd_confq = { + .cb = { .dqrr = oh_pcd_conf_dqrr} +}; +static const struct qman_fq oh_pcd_errq = { + .cb = { .dqrr = oh_pcd_err_dqrr} +}; +static const struct qman_fq oh_tx_confq = { + .cb = { .dqrr = oh_tx_conf_dqrr} +}; +static const struct qman_fq oh_pcd_egress_ernq = { + .cb = { .ern = lag_public_egress_ern} +}; +static const struct qman_fq oh_egress_ernq = { + .cb = { .ern = lag_public_egress_ern} +}; + +static int oh_add_channel(void *__arg) +{ + int cpu; + struct qman_portal *portal; + const cpumask_t *cpus = qman_affine_cpus(); + u32 pool = QM_SDQCR_CHANNELS_POOL_CONV((u32)(unsigned long)__arg); + + for_each_cpu(cpu, cpus) { + portal = (struct qman_portal *)qman_get_affine_portal(cpu); + qman_p_static_dequeue_add(portal, pool); + } + + return 0; +} + +static int init_oh_errq_defq(struct device *dev, + uint32_t fqid_err, uint32_t fqid_def, + struct dpa_fq **oh_errq, struct dpa_fq **oh_defq, + uint16_t *priv_channel) +{ + int errno; + struct dpa_fq *errq, *defq; + /* These two vaules come from DPA-Eth driver */ + uint8_t wq_errq = 2, wq_defq = 1; + u32 channel; + struct qm_mcc_initfq initfq; + struct qm_fqd fqd; + struct task_struct *kth; + + /* Get a channel */ + errno = qman_alloc_pool(&channel); + if (errno) { + pr_err("error on getting pool channel.\n"); + return errno; + } + + if (channel < 0) { + errno = channel; + pr_err("error on dpa_get_channel().\n"); + return errno; + } + + /* Start a thread that will walk the cpus with affine portals + * and add this pool channel to each's dequeue mask. + */ + + kth = kthread_run(oh_add_channel, (void *)(unsigned long)channel, + "oh_add_channel"); + if (!kth) { + pr_warn("run kthread faild...\n"); + return -ENOMEM; + } + + /* Allocate memories for Tx ErrQ and Tx DefQ of oh port */ + errq = devm_kzalloc(dev, sizeof(struct dpa_fq), GFP_KERNEL); + if (!errq) { + pr_err("devm_kzalloc() for OH errq failed\n"); + return -ENOMEM; + } + defq = devm_kzalloc(dev, sizeof(struct dpa_fq), GFP_KERNEL); + if (!defq) { + pr_err("devm_kzalloc() for OH defq failed.\n"); + return -ENOMEM; + } + + /* Set Tx ErrQ callbacks of oh port */ + errq->fq_base = oh_tx_errq; + + /* Set the flags of the oh port Tx ErrQ/Tx DefQ and create the FQs */ + errq->fq_base.flags = QMAN_FQ_FLAG_NO_ENQUEUE; + errno = qman_create_fq(fqid_err, errq->fq_base.flags, &errq->fq_base); + if (errno) { + pr_err("error on create OH errq.\n"); + return errno; + } + + defq->fq_base = oh_tx_defq; + defq->fq_base.flags = QMAN_FQ_FLAG_NO_ENQUEUE; + errno = qman_create_fq(fqid_def, defq->fq_base.flags, &defq->fq_base); + if (errno) { + pr_err("error on create OH defq.\n"); + return errno; + } + + *priv_channel = (uint16_t)channel; + /* Set the FQs init options then init the FQs */ + initfq.we_mask = QM_INITFQ_WE_DESTWQ; + initfq.fqd.dest.channel = (uint16_t)channel; + initfq.fqd.dest.wq = wq_errq; + initfq.we_mask |= QM_INITFQ_WE_FQCTRL; + initfq.fqd.fq_ctrl = QM_FQCTRL_PREFERINCACHE; + initfq.we_mask |= QM_INITFQ_WE_CONTEXTA; + initfq.fqd.fq_ctrl |= QM_FQCTRL_CTXASTASHING | QM_FQCTRL_AVOIDBLOCK; + initfq.fqd.context_a.stashing.exclusive = QM_STASHING_EXCL_DATA | + QM_STASHING_EXCL_CTX | QM_STASHING_EXCL_ANNOTATION; + initfq.fqd.context_a.stashing.data_cl = 2; + initfq.fqd.context_a.stashing.annotation_cl = 1; + initfq.fqd.context_a.stashing.context_cl = + DIV_ROUND_UP(sizeof(struct qman_fq), 64); + + /* init oh ports errors fq */ + errno = qman_init_fq(&errq->fq_base, QMAN_INITFQ_FLAG_SCHED, &initfq); + if (errno < 0) { + pr_err("error on qman_init_fq %u = %d\n", fqid_err, errno); + qman_destroy_fq(&errq->fq_base, 0); + devm_kfree(dev, errq); + return errno; + } + + errno = qman_query_fq(&errq->fq_base, &fqd); + hw_lag_dbg("errno of qman_query_fq:0x%08x\n", errno); + if (fqd.fq_ctrl != initfq.fqd.fq_ctrl) { + pr_err("queried fq_ctrl=%x, should be=%x\n", fqd.fq_ctrl, + initfq.fqd.fq_ctrl); + panic("fail"); + } + if (memcmp(&fqd.td, &initfq.fqd.td, sizeof(fqd.td))) { + pr_err("queried td_thresh=%x:%x, should be=%x:%x\n", + fqd.td.exp, fqd.td.mant, + initfq.fqd.td.exp, initfq.fqd.td.mant); + panic("fail"); + } + + /* init oh ports default fq */ + initfq.fqd.dest.wq = wq_defq; + errno = qman_init_fq(&defq->fq_base, QMAN_INITFQ_FLAG_SCHED, &initfq); + if (errno < 0) { + pr_err("error on qman_init_fq %u = %d\n", fqid_def, errno); + qman_destroy_fq(&defq->fq_base, 0); + devm_kfree(dev, defq); + return errno; + } + + *oh_errq = errq; + *oh_defq = defq; + + hw_lag_dbg("oh port defq and oh port errq initialize OK\n"); + + return BOND_OH_SUCCESS; +} +/** + * Initialize pcd err fqs and pcd confirmation fqs. + * HW LAG uses this method rather than reuse DPA-Eth private rx err/ + * rx def/tx err/tx confirm FQs and callbacks, since HW LAG uses + * different data structure from DPA-Eth private driver. + */ +static int init_oh_pcderrq_pcdconfq(struct device *dev, + uint32_t *fqid_pcderr, uint32_t *fqid_pcdconf, + struct dpa_fq **oh_pcderrq, struct dpa_fq **oh_pcdconfq, + uint16_t priv_channel) +{ + int errno; + struct dpa_fq *pcderrq, *pcdconfq; + /* These two vaules come from DPA-Eth driver */ + uint8_t wq_errq = 2, wq_confq = 1; + struct qm_mcc_initfq initfq; + + /* Allocate memories for PCD ErrQ and PCD confirm Q of oh port */ + pcderrq = devm_kzalloc(dev, sizeof(struct dpa_fq), GFP_KERNEL); + if (!pcderrq) { + pr_err("devm_kzalloc() for OH pcderrq failed\n"); + return -ENOMEM; + } + + pcdconfq = devm_kzalloc(dev, sizeof(struct dpa_fq), GFP_KERNEL); + if (!pcdconfq) { + pr_err("devm_kzalloc() for OH pcdconfq failed.\n"); + return -ENOMEM; + } + + /* Set PCD ErrQ callbacks of oh port */ + pcderrq->fq_base = oh_pcd_errq; + + /* Set the flags of the oh port PCD ErrQ, create the FQs */ + pcderrq->fq_base.flags = QMAN_FQ_FLAG_NO_ENQUEUE | + QMAN_FQ_FLAG_DYNAMIC_FQID; + errno = qman_create_fq(0, pcderrq->fq_base.flags, &pcderrq->fq_base); + if (errno) { + pr_err("error on create OH pcderrq.\n"); + return errno; + } + *fqid_pcderr = pcderrq->fq_base.fqid; + hw_lag_dbg("*fqid_pcderr:%d\n", *fqid_pcderr); + + /* Set PCD confirm Q callbacks of oh port */ + pcdconfq->fq_base = oh_pcd_confq; + /* Set the flags of the oh port PCD confQ, create the FQs */ + pcdconfq->fq_base.flags = QMAN_FQ_FLAG_NO_ENQUEUE| + QMAN_FQ_FLAG_DYNAMIC_FQID; + errno = qman_create_fq(0, pcdconfq->fq_base.flags, &pcdconfq->fq_base); + if (errno) { + pr_err("error on create OH pcdconfq.\n"); + return errno; + } + *fqid_pcdconf = pcdconfq->fq_base.fqid; + hw_lag_dbg("*fqid_pcdconf:%d\n", *fqid_pcdconf); + + /* Set the FQs init options then init the FQs */ + initfq.we_mask = QM_INITFQ_WE_DESTWQ; + initfq.fqd.dest.channel = priv_channel; + initfq.fqd.dest.wq = wq_errq; + initfq.we_mask |= QM_INITFQ_WE_FQCTRL; + initfq.fqd.fq_ctrl = QM_FQCTRL_PREFERINCACHE; + initfq.we_mask |= QM_INITFQ_WE_CONTEXTA; + initfq.fqd.fq_ctrl |= QM_FQCTRL_CTXASTASHING | QM_FQCTRL_AVOIDBLOCK; + initfq.fqd.context_a.stashing.exclusive = QM_STASHING_EXCL_DATA | + QM_STASHING_EXCL_CTX | QM_STASHING_EXCL_ANNOTATION; + initfq.fqd.context_a.stashing.data_cl = 2; + initfq.fqd.context_a.stashing.annotation_cl = 1; + initfq.fqd.context_a.stashing.context_cl = + DIV_ROUND_UP(sizeof(struct qman_fq), 64); + + /* init pcd errors fq */ + errno = qman_init_fq(&pcderrq->fq_base, + QMAN_INITFQ_FLAG_SCHED, &initfq); + if (errno < 0) { + pr_err("error on qman_init_fq pcderrq:%u = %d\n", + *fqid_pcderr, errno); + qman_destroy_fq(&pcderrq->fq_base, 0); + devm_kfree(dev, pcderrq); + + return errno; + } + + /* init pcd confirm fq */ + initfq.fqd.dest.wq = wq_confq; + errno = qman_init_fq(&pcdconfq->fq_base, + QMAN_INITFQ_FLAG_SCHED, &initfq); + if (errno < 0) { + pr_err("error on qman_init_fq pcdconfq:%u = %d\n", + *fqid_pcdconf, errno); + qman_destroy_fq(&pcdconfq->fq_base, 0); + devm_kfree(dev, pcdconfq); + + return errno; + } + + *oh_pcderrq = pcderrq; + *oh_pcdconfq = pcdconfq; + + hw_lag_dbg("oh pcd confq and pcd errq initialize OK\n"); + + return BOND_OH_SUCCESS; +} +/** + * Initialize confirmation fq for offline port tx fqs. + * This confirmation call back is enabled in case of buffer is released + * by BM after frame entered into tx port of offline port. + */ +static int init_oh_txconfq(struct device *dev, uint32_t *fqid_ohtxconf, + struct dpa_fq **oh_txconfq, uint16_t priv_channel) +{ + int errno; + struct dpa_fq *txconfq; + /* This vaule comes from DPA-Eth driver */ + uint8_t wq_confq = 1; + struct qm_mcc_initfq initfq; + + /* Allocate memories for PCD ErrQ and PCD confirm Q of oh port */ + txconfq = devm_kzalloc(dev, sizeof(struct dpa_fq), GFP_KERNEL); + if (!txconfq) { + pr_err("devm_kzalloc() for OH tx confq failed.\n"); + return -ENOMEM; + } + + /* Set tx confirm Q callbacks of oh port */ + txconfq->fq_base = oh_tx_confq; + /* Set the flags of the oh port PCD confQ, create the FQs */ + txconfq->fq_base.flags = QMAN_FQ_FLAG_NO_ENQUEUE | + QMAN_FQ_FLAG_DYNAMIC_FQID; + errno = qman_create_fq(0, txconfq->fq_base.flags, &txconfq->fq_base); + if (errno) { + pr_err("error on create OH tx confq.\n"); + return errno; + } + *fqid_ohtxconf = txconfq->fq_base.fqid; + hw_lag_dbg("dynamic *fqid_ohtxconf:%d\n", *fqid_ohtxconf); + + /* Set the FQs init options then init the FQs */ + initfq.we_mask = QM_INITFQ_WE_DESTWQ; + initfq.fqd.dest.channel = priv_channel; + initfq.fqd.dest.wq = wq_confq; + initfq.we_mask |= QM_INITFQ_WE_FQCTRL; + initfq.fqd.fq_ctrl = QM_FQCTRL_PREFERINCACHE; + initfq.we_mask |= QM_INITFQ_WE_CONTEXTA; + initfq.fqd.fq_ctrl |= QM_FQCTRL_CTXASTASHING | QM_FQCTRL_AVOIDBLOCK; + initfq.fqd.context_a.stashing.exclusive = QM_STASHING_EXCL_DATA | + QM_STASHING_EXCL_CTX | QM_STASHING_EXCL_ANNOTATION; + initfq.fqd.context_a.stashing.data_cl = 2; + initfq.fqd.context_a.stashing.annotation_cl = 1; + initfq.fqd.context_a.stashing.context_cl = + DIV_ROUND_UP(sizeof(struct qman_fq), 64); + + /* init oh tx confirm fq */ + initfq.fqd.dest.wq = wq_confq; + errno = qman_init_fq(&txconfq->fq_base, + QMAN_INITFQ_FLAG_SCHED, &initfq); + if (errno < 0) { + pr_err("error on qman_init_fq oh tx confq:%u = %d\n", + *fqid_ohtxconf, errno); + qman_destroy_fq(&txconfq->fq_base, 0); + devm_kfree(dev, txconfq); + + return errno; + } + + *oh_txconfq = txconfq; + + hw_lag_dbg("oh tx confq initialize OK\n"); + + return BOND_OH_SUCCESS; +} +/** + * Initialize dynamic particular tx fqs of offline port for LAG xmit, + * does not reuse tx fqs initialized by offline port driver. This method + * can avoid to modify offline port driver even if the confirmation fq + * need to be enabled. + */ +static int init_oh_tx_lag_fqs(struct device *dev, + struct dpa_fq **oh_tx_lag_fqs, uint32_t fqid_ohtxconf, + uint16_t oh_tx_channel) +{ + int errno = BOND_OH_SUCCESS, i, tx_fqs_count; + uint16_t wq_id; + struct dpa_fq *lag_fqs; + struct qm_mcc_initfq fq_opts; + uint32_t create_flags, init_flags; + + tx_fqs_count = num_possible_cpus(); + /* Allocate particular tx queues of offline port for LAG xmit. */ + lag_fqs = devm_kzalloc(dev, sizeof(struct dpa_fq) * tx_fqs_count, + GFP_KERNEL); + if (!lag_fqs) { + pr_err("Can't allocate tx fqs for LAG xmit.\n"); + errno = -ENOMEM; + goto return_kfree; + } + + /* Set flags for particular tx fqs, especially for dynamic fqid. */ + create_flags = QMAN_FQ_FLAG_TO_DCPORTAL | QMAN_FQ_FLAG_DYNAMIC_FQID; + + /* Create particular tx fqs of offline port for LAG xmit */ + for (i = 0; i < tx_fqs_count; i++) { + /* set egress_ern callback for offline port tx fq */ + lag_fqs[i].fq_base = oh_egress_ernq; + errno = qman_create_fq(0, create_flags, &lag_fqs[i].fq_base); + if (errno) { + pr_err("Error on creating tx fqs for LAG xmit.\n"); + goto return_kfree; + } + } + + /* Set init flags for tx fqs of oh port */ + init_flags = QMAN_INITFQ_FLAG_SCHED; + + /* Set fq init options. Specify destination wq id and channel */ + memset(&fq_opts, 0, sizeof(fq_opts)); + fq_opts.we_mask = QM_INITFQ_WE_DESTWQ; + /* wq info from DPA-Eth driver */ + wq_id = 3; + fq_opts.fqd.dest.wq = wq_id; + fq_opts.fqd.dest.channel = oh_tx_channel; + + fq_opts.we_mask |= QM_INITFQ_WE_FQCTRL; + fq_opts.fqd.fq_ctrl = QM_FQCTRL_PREFERINCACHE; + fq_opts.fqd.fq_ctrl |= QM_FQCTRL_CTXASTASHING | QM_FQCTRL_AVOIDBLOCK; + fq_opts.fqd.context_a.stashing.exclusive = QM_STASHING_EXCL_DATA | + QM_STASHING_EXCL_CTX | QM_STASHING_EXCL_ANNOTATION; + fq_opts.fqd.context_a.stashing.data_cl = 2; + fq_opts.fqd.context_a.stashing.annotation_cl = 1; + fq_opts.fqd.context_a.stashing.context_cl = + DIV_ROUND_UP(sizeof(struct qman_fq), 64); + +#ifdef CONFIG_HW_LAG_DEBUG + fq_opts.we_mask |= QM_INITFQ_WE_CONTEXTA | QM_INITFQ_WE_CONTEXTB; + /** + * CTXA[OVFQ] = 1 + * we set particular tx own confirmation fq and their own callback + * in case of interrupt DPA-Eth private conf callback/err callback + * /def callback. + */ + + fq_opts.fqd.context_a.hi = 0x80000000; + fq_opts.fqd.context_a.lo = 0x0; + fq_opts.fqd.context_b = fqid_ohtxconf; +#endif + /* Initialize particular tx frame queue of offline port for LAG xmit */ + for (i = 0; i < tx_fqs_count; i++) { + errno = qman_init_fq(&lag_fqs[i].fq_base, init_flags, &fq_opts); + if (errno) + goto init_error; + } + + for (i = 0; i < tx_fqs_count; i++) { + hw_lag_dbg("ok, created lag_fqs: fqid:%d\n", + lag_fqs[i].fq_base.fqid); + } + + *oh_tx_lag_fqs = lag_fqs; + + return BOND_OH_SUCCESS; +init_error: + while (i-- < 0) { + hw_lag_dbg("errors on initializing tx fqs, No.:%d tx fq.\n", i); + qman_destroy_fq(&lag_fqs[i].fq_base, 0); + } + +return_kfree: + if (lag_fqs) + devm_kfree(dev, lag_fqs); + + return errno; +} +/** + * This subroutine has been copied from offline_port driver + * to get all information of all offline ports by parse DTS + * return BOND_OH_SUCCESS when get information successfully. + */ +int get_oh_info(void) +{ + struct platform_device *oh_of_dev, *of_dev; + struct device *dpa_oh_dev, *oh_dev; + struct device_node *dpa_oh_node = NULL, *oh_node; + int lenp, errno = BOND_OH_SUCCESS, i = 0; + const phandle *p_oh_port_handle; + const unsigned int *p_port_id; + const unsigned int *p_channel_id; + struct fm_port *oh_port; + unsigned long port_handle_cnt; + struct fm_port_params params; + + available_num_of_oh_ports = 0; + + /* probe offline ports and alloc memory, these codes need refining + * to save memory and need to get rid of the global variable. + */ + poh = kzalloc(sizeof(struct oh_port_priv) * FM_MAX_NUM_OF_OH_PORTS, + GFP_KERNEL); + if (!poh) + return -ENOMEM; + + for_each_matching_node(dpa_oh_node, oh_port_match_table) { + if (dpa_oh_node) { + p_oh_port_handle = of_get_property(dpa_oh_node, + "fsl,fman-oh-port", &lenp); + if (!p_oh_port_handle) { + pr_err("No OH port handle in node %s\n", + dpa_oh_node->full_name); + return -EINVAL; + } + hw_lag_dbg("dpa_oh_node->name:%s\n", + dpa_oh_node->full_name); + BUG_ON(lenp % sizeof(*p_oh_port_handle)); + if (lenp != sizeof(*p_oh_port_handle)) { + port_handle_cnt = + lenp / sizeof(*p_oh_port_handle); + + pr_err("Found %lu oh port in node %s\n" + "only 1 phandle is allowed.\n", + port_handle_cnt, + dpa_oh_node->full_name); + return -EINVAL; + } + + oh_node = of_find_node_by_phandle(*p_oh_port_handle); + if (!oh_node) { + pr_err("no oh node referenced from %s\n", + dpa_oh_node->full_name); + return -EINVAL; + } + hw_lag_dbg("Found oh_node->full_name %s.\n", + oh_node->full_name); + p_port_id = of_get_property(oh_node, + "cell-index", &lenp); + + if (!p_port_id) { + pr_err("No port id found in node %s\n", + dpa_oh_node->full_name); + return -EINVAL; + } + + hw_lag_dbg("Found port id %ud, in node %s\n", + *p_port_id, dpa_oh_node->full_name); + BUG_ON(lenp % sizeof(*p_port_id)); + + /* Read channel id for the queues */ + p_channel_id = + of_get_property(oh_node, + "fsl,qman-channel-id", &lenp); + if (!p_channel_id) { + pr_err("No channel id found in node %s\n", + dpa_oh_node->full_name); + return -EINVAL; + } + + BUG_ON(lenp % sizeof(*p_channel_id)); + + oh_of_dev = of_find_device_by_node(oh_node); + BUG_ON(!oh_of_dev); + oh_dev = &oh_of_dev->dev; + of_dev = of_find_device_by_node(dpa_oh_node); + BUG_ON(!oh_of_dev); + dpa_oh_dev = &of_dev->dev; + poh[i].of_dev = of_dev; + poh[i].oh_of_dev = oh_of_dev; + poh[i].dpa_oh_dev = dpa_oh_dev; + poh[i].oh_dev = oh_dev; + poh[i].dpa_oh_node = dpa_oh_node; + poh[i].oh_node = oh_node; + poh[i].cell_index = *p_port_id; + poh[i].oh_config = dev_get_drvdata(dpa_oh_dev); + poh[i].p_oh_port_handle = p_oh_port_handle; + poh[i].oh_channel_id = *p_channel_id; + oh_port = poh[i].oh_config->oh_port; + fm_port_get_buff_layout_ext_params(oh_port, ¶ms); + poh[i].bpid = params.pool_param[0].id; + poh[i].bp_size = params.pool_param[0].size; + /* give a friend name like "fman0-oh@1" + * rather than "/fsl,dpaa/dpa-fman0-oh@1". + * fill friendname array with dpa_oh_node->full_name, + * please don't use oh0 since documentatin says oh0 + * has bad performance. + */ + memcpy(poh[i].friendname, + dpa_oh_node->full_name + 14, 10); + + if (qman_alloc_fqid_range(&poh[i].pcd_fqids_base, + FM_MAX_NUM_OF_MACS, true, 0) + != FM_MAX_NUM_OF_MACS) { + pr_err("error on alloc continuous pcd fqid\n"); + return -EINVAL; + } + + errno = init_oh_errq_defq(poh[i].dpa_oh_dev, + poh[i].oh_config->error_fqid, + poh[i].oh_config->default_fqid, + &poh[i].oh_errq, + &poh[i].oh_defq, + &poh[i].p_oh_rcv_channel); + if (errno != BOND_OH_SUCCESS) { + pr_err("error when probe errq or defq.\n"); + return errno; + } + + errno = init_oh_pcderrq_pcdconfq(poh[i].dpa_oh_dev, + &poh[i].fqid_pcderr, + &poh[i].fqid_pcdconf, + &poh[i].oh_pcderrq, + &poh[i].oh_pcdconfq, + poh[i].p_oh_rcv_channel); + if (errno != BOND_OH_SUCCESS) { + pr_err("error on probe pcderrq or pcdconfq\n"); + return errno; + } + + errno = init_oh_txconfq(poh[i].dpa_oh_dev, + &poh[i].fqid_ohtxconf, + &poh[i].oh_txconfq, + poh[i].oh_channel_id); + if (errno != BOND_OH_SUCCESS) { + pr_err("error on init offline port tx confq\n"); + return errno; + } + + errno = init_oh_tx_lag_fqs(poh[i].dpa_oh_dev, + &poh[i].oh_tx_lag_fqs, + poh[i].fqid_ohtxconf, + poh[i].oh_channel_id); + if (errno != BOND_OH_SUCCESS) { + pr_err("error on init offline port tx confq\n"); + return errno; + } + + errno = dpa_oh_bp_create(&poh[i]); + if (errno != BOND_OH_SUCCESS) { + pr_err("error on init offline tx bp.\n"); + return errno; + } + + available_num_of_oh_ports = ++i; + } + } + + return errno; +} +/** + * Get the FM_MAC_RES_ID from a dpa-eth NIC, return 0 if it is not a dpa-eth, + * otherwise return FM_MAC_RES_ID + * this function does not process macless, LAG does not need a macless IF. + */ +static unsigned long long get_fm_mac_res_id(struct net_device *netdev) +{ + struct dpa_priv_s *priv = netdev_priv(netdev); + if (!is_dpa_eth_port(netdev)) + return 0; + + return (unsigned long long)priv->mac_dev->res->start; +} +/** + * Get the DCP_ID from a dpa-eth NIC, return 0 if it is not a dpa-eth, + * return 1 if it's fm0, return 2 if it's fm1, since there are only 2 + * FMAN in current DPA SOC. + * this function does not process macless, LAG does not need a macless IF. + */ +int get_dcp_id_from_dpa_eth_port(struct net_device *netdev) +{ + unsigned long long mac_res_start = get_fm_mac_res_id(netdev); + + if ((mac_res_start >= FM1_GB0) && (mac_res_start <= FM1_10G)) + return 1; + else if ((mac_res_start >= FM2_GB0) && (mac_res_start <= FM2_10G)) + return 2; + else + return 0; +} +/** + * Get all information of the offline port which is being used + * by a bundle, such as fman_dcpid, offline port offset, cell index, + * offline port channel. This API is required by CEETM Qos. + * Regarding fman dpcid, till sdk1.6, there is one fman in p1023, the + * offset is 0x1000000, for other dpaa socs, the offset of fman0 is + * 0x400000, the offset of fman1 is 0x500000, hence for current socs, + * the offset of fman0 <=0x4000000, 0x400000 < fman1 <=0x500000. + * return BOND_OH_SUCCESS when got all information, otherwise return + * Non-Zero. + */ +#define FMAN0_MAX_OFFSET 0x400000 +#define FMAN1_MAX_OFFSET 0x500000 +int export_oh_port_info_to_ceetm(struct bonding *bond, uint16_t *channel, + unsigned long *fman_dcpid, unsigned long *oh_offset, + unsigned long *cell_index) +{ + /** + * split str: "/soc@ffe000000/fman@400000/port@84000", then get + * the fman@ part and port@ part from them. regex is good enough + * as below: + * ret = sscanf((char *) p, "%*[^@]@%*[^@]@%[^/]/port@%s", s1, s2); + * but the kernel version does not support the method. + */ + int errno; + char s1[16] = {0}, s2[16] = {0}; + char *p, *p1; + + if (!bond->params.ohp) { + pr_err("The bundle has not binded an offline port.\n"); + return 1; + } + + if (!bond->params.ohp->oh_en) { + pr_err("The offline is disabled, to enable it, use sysfs.\n"); + return 2; + } + + if (!bond->params.ohp->oh_node) { + pr_err("The offline node error.\n"); + return 3; + } + + p = strstr(bond->params.ohp->oh_node->full_name, "fman@"); + p += strlen("fman@"); + p1 = strstr(p, "/port@"); + + memcpy(s1, p, p1 - p); + + p = strstr(p, "/port@"); + p += strlen("/port@"); + + errno = sscanf((const char *) p, "%s", s2); + if (errno != 1) { + pr_err("parser error while process offline port node\n"); + return 4; + } + + errno = kstrtoul(s1, 16, fman_dcpid) | kstrtoul(s2, 16, oh_offset); + if (errno) { + pr_err("error on kstrtoul fman_dcpid, of_offset\n"); + return 5; + } + if (*fman_dcpid <= FMAN0_MAX_OFFSET) { + *fman_dcpid = 0; + } else if ((*fman_dcpid > FMAN0_MAX_OFFSET) && + (*fman_dcpid <= FMAN1_MAX_OFFSET)) { + *fman_dcpid = 1; + } else { + pr_err("error on calculating fman dcpid, new soc appears.\n"); + return 6; + } + + *channel = bond->params.ohp->oh_channel_id; + *cell_index = bond->params.ohp->cell_index; + + hw_lag_dbg("This oh port mapped to bond has channel:0x%0x\n", *channel); + hw_lag_dbg("fman_dcpid:0x%0lx, oh_offset:0x%0lx, cell-index:%0lx\n", + *fman_dcpid, *oh_offset, *cell_index); + + return BOND_OH_SUCCESS; +} +EXPORT_SYMBOL(export_oh_port_info_to_ceetm); +/** + * Public APIs which can use for Link Aggregation and CEETM Qos + * show bond info and slave device info when they are available + */ +int show_dpa_slave_info(struct bonding *bond, struct slave *slave) +{ + struct dpa_priv_s *priv = netdev_priv(slave->dev); + if (bond) + pr_info("bond->dev->name:%s, slave_cnt:%d\n", + bond->dev->name, bond->slave_cnt); + if (slave) + pr_info("new_slave:%s\n", slave->dev->name); + + if (is_dpa_eth_port(slave->dev)) { + pr_info("priv->mac_dev->res->start:%llx\n", + (unsigned long long)priv->mac_dev->res->start); + pr_info("get_dcp_id_from_dpa_eth_port(netdev):0x%0x\n", + get_dcp_id_from_dpa_eth_port(slave->dev)); + } else + pr_info("the slave device %s is not a DPAA-Eth NIC\n", + slave->dev->name); + + return 0; +} + +int init_status(struct net_device *netdev) +{ + struct bonding *bond = master_to_bond(netdev); + memset(&bond->params.oh_stats, 0, sizeof(struct rtnl_link_stats64)); + + return BOND_OH_SUCCESS; +} + +void add_statistics(struct bonding *bond, struct rtnl_link_stats64 *stats) +{ + stats->tx_packets += bond->params.oh_stats.tx_packets; + stats->tx_bytes += bond->params.oh_stats.tx_bytes; + stats->tx_errors += bond->params.oh_stats.tx_errors; + stats->tx_dropped += bond->params.oh_stats.tx_dropped; +} +/** + * Copied from oNIC (removed priv) + * Turn on HW checksum computation for this outgoing frame. + * If the current protocol is not something we support in this regard + * (or if the stack has already computed the SW checksum), we do nothing. + * + * Returns 0 if all goes well (or HW csum doesn't apply), and a negative value + * otherwise. + * + * Note that this function may modify the fd->cmd field and the skb data buffer + * (the Parse Results area). + */ +int oh_tx_csum_enable(struct sk_buff *skb, + struct qm_fd *fd, + char *parse_results) +{ + fm_prs_result_t *parse_result; + struct iphdr *iph; + struct ipv6hdr *ipv6h = NULL; + struct tcphdr *tcph; + struct udphdr *udph; + int l4_proto; + int ethertype = ntohs(skb->protocol); + int retval = 0, i; + unsigned char *p; + + if (skb->ip_summed != CHECKSUM_PARTIAL) + return 0; + + /* Fill in some fields of the Parse Results array, so the FMan + * can find them as if they came from the FMan Parser. + */ + parse_result = (fm_prs_result_t *)parse_results; + /* If we're dealing with VLAN, get the real Ethernet type */ + if (ethertype == ETH_P_8021Q) { + /* We can't always assume the MAC header is set correctly + * by the stack, so reset to beginning of skb->data + */ + skb_reset_mac_header(skb); + ethertype = ntohs(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto); + /* below l2r need look up FMAN RM to verify */ + parse_result->l2r = FM_PR_L2_VLAN | FM_PR_L2_VLAN_STACK; + } else { + parse_result->l2r = FM_PR_L2_ETHERNET; + } + + /* Fill in the relevant L3 parse result fields + * and read the L4 protocol type + */ + switch (ethertype) { + case ETH_P_IP: + parse_result->l3r = FM_L3_PARSE_RESULT_IPV4; + iph = ip_hdr(skb); + BUG_ON(iph == NULL); + l4_proto = ntohs(iph->protocol); + break; + case ETH_P_IPV6: + parse_result->l3r = FM_L3_PARSE_RESULT_IPV6; + ipv6h = ipv6_hdr(skb); + BUG_ON(ipv6h == NULL); + l4_proto = ntohs(ipv6h->nexthdr); + break; + default: + /* We shouldn't even be here */ + hw_lag_dbg("Can't compute HW csum for L3 proto 0x%x\n", + ntohs(skb->protocol)); + retval = -EIO; + goto return_error; + } + + hw_lag_dbg("skb->protocol(L3):0x%04x, ethertype:%x\n", + ntohs(skb->protocol), ethertype); + + /* Fill in the relevant L4 parse result fields */ + switch (l4_proto) { + case IPPROTO_UDP: + parse_result->l4r = FM_L4_PARSE_RESULT_UDP; + udph = (struct udphdr *)(skb->data + skb_transport_offset(skb)); + hw_lag_dbg("udp org csum:0x%0x\n", udph->check); + skb_set_transport_header(skb, skb_checksum_start_offset(skb)); + skb_checksum_help(skb); + hw_lag_dbg("udp software csum:0x%0x\n", udph->check); + break; + case IPPROTO_TCP: + parse_result->l4r = FM_L4_PARSE_RESULT_TCP; + tcph = (struct tcphdr *)(skb->data + skb_transport_offset(skb)); + p = skb->data; + hw_lag_dbg("\ndmac:%02x:%02x:%02x:%02x:%02x:%02x\n" + "smac:%02x:%02x:%02x:%02x:%02x:%02x\n" + "h_proto:0x%04x\n", p[0], p[1], p[2], p[3], p[4], p[5], + p[6], p[7], p[8], p[9], p[10], p[11], + *((short *)(p + 12))); + + /* dump skb data info for manual calculate L4CSUM, + * jump over net header first + */ + p += skb_network_offset(skb); + for (i = 0; i < skb->len - skb_network_offset(skb) - 4; i += 4) + hw_lag_dbg("%08x\n", *((unsigned int *) (p + i))); + + for (; i < skb->len - skb_network_offset(skb); i++) + hw_lag_dbg("%02x\n", *(p + i)); + + hw_lag_dbg("tcp org csum:0x%0x.\n", tcph->check); + skb_set_transport_header(skb, skb_checksum_start_offset(skb)); + skb_checksum_help(skb); + hw_lag_dbg("tcp software csum:0x%0x,\n", tcph->check); + + break; + default: + /* This can as well be a BUG() */ + pr_err("Can't compute HW csum for L4 proto 0x%x\n", + l4_proto); + retval = -EIO; + goto return_error; + } + + hw_lag_dbg("l4_proto:0x%04x, result->l2r:0x%04x\n", + l4_proto, parse_result->l2r); + hw_lag_dbg("result->l3r:0x%04x, result->l4r:0x%02x.\n", + parse_result->l3r, parse_result->l4r); + + /* At index 0 is IPOffset_1 as defined in the Parse Results */ + parse_result->ip_off[0] = skb_network_offset(skb); + parse_result->l4_off = skb_transport_offset(skb); + + /* Enable L3 (and L4, if TCP or UDP) HW checksum. */ + fd->cmd |= FM_FD_CMD_RPD | FM_FD_CMD_DTC; + + /* On P1023 and similar platforms fd->cmd interpretation could + * be disabled by setting CONTEXT_A bit ICMD; currently this bit + * is not set so we do not need to check; in the future, if/when + * using context_a we need to check this bit + */ + +return_error: + return retval; +} + +static int __hot dpa_oh_xmit(struct qm_fd *fd, struct qman_fq *tx_fq) +{ + int err, i; + + for (i = 0; i < 100000; i++) { + err = qman_enqueue(tx_fq, fd, 0); + if (err != -EBUSY) + break; + } + + if (unlikely(err < 0)) { + /* TODO differentiate b/w -EBUSY (EQCR full) and other codes? */ + pr_err("qman_enqueue() error.\n"); + return err; + } + + return 0; +} + + +int __hot dpa_oh_tx(struct sk_buff *skb, struct bonding *bond, + struct net_device *net_dev, struct dpa_fq *tx_fq) +{ + struct dpa_priv_s *priv; + struct dpa_bp *bp = bond->params.ohp->tx_bp; + + struct sk_buff **skbh = NULL; + dma_addr_t addr; + struct qm_fd fd; + int err = 0; + int *countptr; + struct rtnl_link_stats64 *percpu_stats; + + tx_fq->net_dev = bond->params.ohp->slave[0]->dev; + priv = netdev_priv(bond->params.ohp->slave[0]->dev); + percpu_stats = &bond->params.oh_stats; + countptr = __this_cpu_ptr(bond->params.ohp->tx_bp->percpu_count); + + if (unlikely(skb_headroom(skb) < priv->tx_headroom)) { + struct sk_buff *skb_new; + + skb_new = skb_realloc_headroom(skb, priv->tx_headroom); + if (unlikely(!skb_new)) { + percpu_stats->tx_errors++; + kfree_skb(skb); + goto done; + } + kfree_skb(skb); + skb = skb_new; + } + + clear_fd(&fd); + + /* store skb backpointer to release the skb later */ + skbh = (struct sk_buff **)(skb->data - priv->tx_headroom); + *skbh = skb; + + /* TODO check if skb->len + priv->tx_headroom < bp->size */ + + /* Enable L3/L4 hardware checksum computation. + * + * We must do this before dma_map_single(), because we may + * need to write into the skb. + */ + err = oh_tx_csum_enable(skb, &fd, + ((char *)skbh) + DPA_TX_PRIV_DATA_SIZE); + + if (unlikely(err < 0)) { + pr_err("HW csum error: %d\n", err); + + return err; + } + + addr = dma_map_single(bp->dev, skbh, + skb->len + priv->tx_headroom, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(bp->dev, addr))) { + pr_err("dma_map_single() failed\n"); + goto dma_mapping_failed; + } + + fd.format = qm_fd_contig; + fd.length20 = skb->len; + fd.offset = priv->tx_headroom; + fd.addr_hi = upper_32_bits(addr); + fd.addr_lo = lower_32_bits(addr); + /* fd.cmd |= FM_FD_CMD_FCO; */ + fd.bpid = bp->bpid; + + /* (Partially) drain the Draining Buffer Pool pool; each core + * acquires at most the number of buffers it put there; since + * BMan allows for up to 8 buffers to be acquired at one time, + * work in batches of 8 for efficiency reasons + */ + dpa_oh_drain_bp(bp); + + if (unlikely(dpa_oh_xmit(&fd, &tx_fq->fq_base) < 0)) { + /* oh tx error, add statistics */ + bond->params.oh_stats.tx_packets++; + bond->params.oh_stats.tx_errors++; + hw_lag_dbg("3ad enqueue_pkt error...txerr_pkt:%llu\n", + bond->params.oh_stats.tx_packets); + goto xmit_failed; + } else { + /* oh tx OK, add statistics */ + bond->params.oh_stats.tx_packets++; + bond->params.oh_stats.tx_bytes += skb->len; + hw_lag_dbg("3ad enqueue_pkt OK...tx_pkt:%llu\n", + bond->params.oh_stats.tx_packets); + return NETDEV_TX_OK; + } + + countptr = __this_cpu_ptr(bp->percpu_count); + (*countptr)++; + + goto done; + +xmit_failed: + dma_unmap_single(bp->dev, addr, fd.offset + fd.length20, DMA_TO_DEVICE); +dma_mapping_failed: + percpu_stats->tx_errors++; + dev_kfree_skb(skb); +done: + return NETDEV_TX_OK; +} +/** + * Enqueue one skb pkt to offline port which attached to a bond. + * bond: current bond's pointer + * skb: pkt which will be enqueued to the offline port + * ceetm_fq: pkt will use this fq for xmit. if this ceetm_fq is + * pointing to NULL, will use default tx_fq for xmit. + * return BOND_OH_SUCCESS if enqueued, otherwise return errors. + */ +int enqueue_pkt_to_oh(struct bonding *bond, struct sk_buff *skb, + struct dpa_fq *ceetm_fq) +{ + struct oh_port_priv *p_oh = bond->params.ohp; + struct net_device *slave_netdev = NULL; + struct dpa_fq *tx_fq = p_oh->oh_tx_lag_fqs; + + slave_netdev = p_oh->slave[0]->dev; + + p_oh->oh_errq->net_dev = slave_netdev; + p_oh->oh_defq->net_dev = slave_netdev; + + if (!is_dpa_eth_port(slave_netdev)) { + pr_err("is not dpaa NIC or NULL pointer.\n"); + return -EINVAL; + } + + if (ceetm_fq) + return dpa_oh_tx(skb, bond, slave_netdev, ceetm_fq); + else + return dpa_oh_tx(skb, bond, slave_netdev, tx_fq); +} +EXPORT_SYMBOL(enqueue_pkt_to_oh); + +static int get_dpa_slave_info(struct slave *slave, uint16_t *tx_channel) +{ + struct dpa_priv_s *priv = netdev_priv(slave->dev); + + if (!is_dpa_eth_port(slave->dev) || !(priv->mac_dev)) + return BOND_OH_ERROR; + + *tx_channel = fm_get_tx_port_channel(priv->mac_dev->port_dev[TX]); + + return BOND_OH_SUCCESS; +} + +int get_dpa_slave_info_ex(struct slave *slave, uint16_t *tx_channel, + struct qman_fq **egress_fq, u32 *first_fqid) +{ + struct dpa_priv_s *priv = netdev_priv(slave->dev); + + if (!is_dpa_eth_port(slave->dev) || !(priv->mac_dev)) + return BOND_OH_ERROR; + + *tx_channel = fm_get_tx_port_channel(priv->mac_dev->port_dev[TX]); + *egress_fq = priv->egress_fqs[0]; + *first_fqid = priv->egress_fqs[0]->fqid; + + return BOND_OH_SUCCESS; +} + +/* Creates Frame Queues, these 2 good subroutines are completely copied from + * Bogdan Purcareata's good patch "Offline port queues initialization", HW_LAG + * need to initialize FQs for an offline port PCD usage with tx_channel/wq of + * slave devices which have already attached to a bond, HW_LAG OH port dequeue, + * then enqueue PCD FQs to DPA-Eth via these PCD FQs. + */ +static int create_oh_pcd_fq(struct qman_fq *fq, u32 fqid_pcdconf, + uint32_t fq_id, uint16_t tx_channel, uint16_t wq_id) +{ + struct qm_mcc_initfq fq_opts; + uint32_t create_flags, init_flags; + uint32_t ret = 0; + + if (!fq) + return BOND_OH_ERROR; + + /* Set flags for FQ create */ + create_flags = QMAN_FQ_FLAG_TO_DCPORTAL; + + /* set egress_ern callback for pcd fqs */ + *fq = oh_pcd_egress_ernq; + + /* Create frame queue */ + ret = qman_create_fq(fq_id, create_flags, fq); + if (ret != 0) + return BOND_OH_ERROR; + + /* Set flags for FQ init */ + init_flags = QMAN_INITFQ_FLAG_SCHED; + + /* Set FQ init options. Specify destination WQ ID and channel */ + memset(&fq_opts, 0, sizeof(fq_opts)); + fq_opts.we_mask = QM_INITFQ_WE_DESTWQ; + fq_opts.fqd.dest.wq = wq_id; + fq_opts.fqd.dest.channel = tx_channel; + + fq_opts.we_mask |= QM_INITFQ_WE_FQCTRL; + fq_opts.fqd.fq_ctrl = QM_FQCTRL_PREFERINCACHE; + fq_opts.fqd.fq_ctrl |= QM_FQCTRL_CTXASTASHING | QM_FQCTRL_AVOIDBLOCK; + fq_opts.fqd.context_a.stashing.exclusive = QM_STASHING_EXCL_DATA | + QM_STASHING_EXCL_CTX | QM_STASHING_EXCL_ANNOTATION; + fq_opts.fqd.context_a.stashing.data_cl = 2; + fq_opts.fqd.context_a.stashing.annotation_cl = 1; + fq_opts.fqd.context_a.stashing.context_cl = + DIV_ROUND_UP(sizeof(struct qman_fq), 64); + + fq_opts.we_mask |= QM_INITFQ_WE_CONTEXTA | QM_INITFQ_WE_CONTEXTB; + /** + * CTXA[OVFQ] = 1 + * we set PCD own confirmation Q and their own callback in case of + * interrupt DPA-Eth private conf callback/err callback/def callback. + */ + fq_opts.fqd.context_a.hi = 0x80000000; + fq_opts.fqd.context_a.lo = 0x0; + fq_opts.fqd.context_b = fqid_pcdconf; + + /* Initialize frame queue */ + ret = qman_init_fq(fq, init_flags, &fq_opts); + if (ret != 0) { + qman_destroy_fq(fq, 0); + return BOND_OH_ERROR; + } + hw_lag_dbg("FQ create_flags:0X%0x, init_flags:0X%0x\n", + create_flags, init_flags); + + return BOND_OH_SUCCESS; +} + +static int hw_lag_allocate_pcd_queues(struct device *dev, + struct dpa_fq **p_pcd_fq, u32 fqid_pcdconf, u32 fqid, + uint16_t tx_channel, uint16_t wq) +{ + /* Allocate pcd queues */ + int errno = BOND_OH_SUCCESS; + struct dpa_fq *pcd_fq; + hw_lag_dbg("Allocating PCD queues...p_pcd_fq:%p, fqid:%d\n", + *p_pcd_fq, fqid); + pcd_fq = devm_kzalloc(dev, sizeof(struct dpa_fq), GFP_KERNEL); + if (!pcd_fq) { + pr_err("can't allocate slave PCD FQ!\n"); + errno = -ENOMEM; + goto return_kfree; + } + + hw_lag_dbg("Allocated pcd_fq:%p, fqid:%d\n", pcd_fq, fqid); + /* Create pcd queues */ + errno = create_oh_pcd_fq(&pcd_fq->fq_base, fqid_pcdconf, + fqid, tx_channel, wq); + if (errno != BOND_OH_SUCCESS) { + pr_err("can't create lag PCD FQ:%u\n", fqid); + errno = -EINVAL; + goto return_kfree; + } + + *p_pcd_fq = pcd_fq; + hw_lag_dbg("created pcd_fq:%p, fqid:%d, *p_pcd_fq:%p\n", + pcd_fq, fqid, *p_pcd_fq); + return BOND_OH_SUCCESS; + +return_kfree: + if (pcd_fq) + devm_kfree(dev, pcd_fq); + return errno; +} + +/* Destroys Frame Queues */ +static void hw_lag_fq_destroy(struct qman_fq *fq) +{ + int errno = BOND_OH_SUCCESS; + + errno = qman_retire_fq(fq, NULL); + if (unlikely(errno < 0)) + pr_err("qman_retire_fq(%u)=%d\n", qman_fq_fqid(fq), errno); + + errno = qman_oos_fq(fq); + if (unlikely(errno < 0)) + pr_err("qman_oos_fq(%u)=%d\n", qman_fq_fqid(fq), errno); + + qman_destroy_fq(fq, 0); +} +/* release fq memory */ +static int hw_lag_release_fq(struct device *dev, struct dpa_fq *fq) +{ + + if (!fq) + return BOND_OH_ERROR; + + hw_lag_fq_destroy(&fq->fq_base); + if (!dev) + return BOND_OH_ERROR; + + devm_kfree(dev, fq); + + return BOND_OH_SUCCESS; +} +/** + * Get DPA slave device information: wq/channel_id, allocate FQID/FQ memory, + * then set FQ flags, record the slave pointer in case of remove these + * information when detaching slave in the future. + */ +int fill_oh_pcd_fqs_with_slave_info(struct bonding *bond, struct slave *slave) +{ + uint16_t tx_channel; + struct dpa_fq *pcd_fq = NULL; + struct oh_port_priv *cur; + u32 fqid; + uint16_t wq_id = 3; /* the default value in DPA-Eth private driver */ + + if (bond->params.mode != BOND_MODE_8023AD) { + pr_err("error, only support 802.3ad when fill OH FQs.\n"); + return BOND_OH_ERROR; + } + + if (is_dpa_eth_port(slave->dev) == false) { + pr_err("error, only support dpa eth nic.\n"); + return BOND_OH_ERROR; + } + + if (bond->slave_cnt > SLAVES_PER_BOND) { + pr_err("error, only support 2 dpa nic per bond.\n"); + return BOND_OH_ERROR; + } + + if (get_dpa_slave_info(slave, &tx_channel) == BOND_OH_ERROR) { + pr_err("error on getting dpa info when fill OH FQs.\n"); + return BOND_OH_ERROR; + } + + cur = bond->params.ohp; + if (!cur) { + pr_err("have not bind an OH port,\n"); + pr_err("will use software tx traffic distribution.\n"); + return BOND_OH_ERROR; + } + + hw_lag_dbg("cur->pcd_fqs[0]:%p, cur->pcd_fqs[1]:%p\n", + cur->pcd_fqs[0], cur->pcd_fqs[1]); + if (!cur->pcd_fqs[0]) + fqid = cur->pcd_fqids_base; + else + fqid = cur->pcd_fqids_base + 1; + + hw_lag_dbg("pcd_fq:%p, fqid:%d Before alloc.\n", pcd_fq, fqid); + + if (hw_lag_allocate_pcd_queues(cur->dpa_oh_dev, &pcd_fq, + cur->fqid_pcdconf, fqid, tx_channel, + wq_id) == BOND_OH_ERROR) { + pr_err("error on create pcd fqs\n"); + return BOND_OH_ERROR; + } + + hw_lag_dbg("pcd_fq:%p, fqid:%d, tx_channel:%d, wq_id:%d After alloc.\n", + pcd_fq, fqid, tx_channel, wq_id); + hw_lag_dbg("fqid:0x%0x, tx_channel:0x%0x, wq_id:0x%0x After alloc.\n", + fqid, tx_channel, wq_id); + + if (!cur->pcd_fqs[0]) { + cur->pcd_fqs[0] = pcd_fq; + cur->slave[0] = slave; + } else if (!cur->pcd_fqs[1]) { + cur->pcd_fqs[1] = pcd_fq; + cur->slave[1] = slave; + } + + return BOND_OH_SUCCESS; +} + +/* forget offline port pcd information according to slave pointer, + * then destroy fq and release the fq memory. + */ +int del_oh_pcd_fqs_with_slave_info(struct bonding *bond, struct slave *slave) +{ + struct oh_port_priv *cur; + struct dpa_fq *pcd_fq; + + if (is_dpa_eth_port(slave->dev) == false) { + pr_err("error, only support dpa eth nic.\n"); + return BOND_OH_ERROR; + } + cur = bond->params.ohp; + if (!cur) { + pr_err("have not bind an OH port,\n"); + pr_err("will use software tx traffic distribution.\n"); + return BOND_OH_ERROR; + } + if (slave == cur->slave[0]) { + pcd_fq = cur->pcd_fqs[0]; + cur->pcd_fqs[0] = NULL; + cur->slave[0] = NULL; + } else if (slave == cur->slave[1]) { + pcd_fq = cur->pcd_fqs[1]; + cur->pcd_fqs[1] = NULL; + cur->slave[1] = NULL; + } else + pcd_fq = NULL; + + return hw_lag_release_fq(cur->dpa_oh_dev, pcd_fq); +} + +int release_pcd_mem(struct bonding *bond) +{ + return BOND_OH_SUCCESS; +} + +/* get all offline port information from bond, including + * dev,oh handler, PCD FQid base and PCD FQ count, then + * get the new xmit policy, copy schemes needed from the + * cached_scheme pointer, config PCD params, init PCD dev, + * set PCD Net Env Characteristics, then set Keygen Scheme + * params to the PCD dev, disable offline port, set PCD + * params to the offline port dev, at last enable the offline + * port. + * this subroutine return true when it can apply PCD to + * the offline port, otherwise return false. + */ +bool apply_pcd(struct bonding *bond, int new_xmit_policy) +{ + return true; +} diff --git a/drivers/net/bonding/hw_distribution.h b/drivers/net/bonding/hw_distribution.h new file mode 100644 index 0000000..55e0420 --- /dev/null +++ b/drivers/net/bonding/hw_distribution.h @@ -0,0 +1,138 @@ +/** + * Copyright 2014 Freescale Semiconductor Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HARDWARE_DISTRIBUTION_H +#define __HARDWARE_DISTRIBUTION_H + +#include <linux/of_platform.h> + +#include "bonding.h" +#include "bond_3ad.h" +#include "bond_alb.h" +#include "offline_port.h" +#include "dpaa_eth.h" +#include "dpaa_eth_common.h" + +#define OHFRIENDNAMSIZ 10 /* fman0-oh@1, ... fman1-oh@6 */ +#define OHNODENAMSIZ 24 /* /fsl,dpaa/dpa-fman0-oh@1 */ +#define BOND_OH_SUCCESS 0 +#define BOND_OH_ERROR -1 +#define NO_POLICY 0xFF /* this is a magic number */ + +#define FM1_GB0 0xffe4e0000 +#define FM1_10G 0xffe4f0000 +#define FM2_GB0 0xffe5e0000 +#define FM2_10G 0xffe5f0000 + +#define DPA_FQ_TD 0x200000 + +/* There are 4 FMAN Ethernet Ports per T1040, 2 of them are for the + * Link Aggregation for the L2Swith trunk link, thus there are at + * most 2 ports left for the other Link Aggregation, this implies + * 2 MAX_BOND_CNT * SLAVES_PER_BOND = 4 FMAN Ethernet Ports. + * In fact,we only need numbers of offline port in a DTS: + * offline port count = min(FM_MAX_NUM_OF_OH_PORTS, MAX_BOND_CNT) + */ +#define MAX_BOND_CNT 2 +#define SLAVES_PER_BOND 2 + +#ifdef CONFIG_HW_LAG_DEBUG +#define hw_lag_dbg(fmt, arg...) \ + pr_info("LAG:[CPU %d ln %d fn %s] - " fmt, smp_processor_id(), \ + __LINE__, __func__, ##arg) +#else +#define hw_lag_dbg(fmt, arg...) +#endif + +struct oh_port_priv { + unsigned int oh_channel_id; + struct dpa_oh_config_s *oh_config; + struct dpa_fq *pcd_fqs[SLAVES_PER_BOND]; + struct dpa_fq *oh_defq, *oh_errq; + uint16_t p_oh_rcv_channel; + struct slave *slave[SLAVES_PER_BOND]; + u32 pcd_fqids_base; + uint32_t fqid_pcderr, fqid_pcdconf, fqid_ohtxconf; + struct dpa_fq *oh_pcderrq, *oh_pcdconfq, *oh_txconfq; + /* init dynamic particular tx fqs of offline port for LAG xmit, + * does not reuse tx fqs initialized by offline port driver. + */ + struct dpa_fq *oh_tx_lag_fqs; + const phandle *p_oh_port_handle; + struct platform_device *oh_of_dev, *of_dev; + struct device *dpa_oh_dev, *oh_dev; + struct device_node *dpa_oh_node, *oh_node; + struct dpa_bp *tx_bp; + struct dpa_buffer_layout_s *tx_buf_layout; + uint8_t bpid; /**< External buffer pool id */ + uint16_t bp_size; /**< External buffer pool buffer size */ + int oh_en; /* enable or disable offline port's help at run-time */ + unsigned char friendname[OHFRIENDNAMSIZ]; + unsigned long cell_index; +}; + +extern struct oh_port_priv *poh; +extern int available_num_of_oh_ports; + +int get_oh_info(void); +unsigned int to_which_oh_i_attached(struct oh_port_priv *current_poh); +bool are_all_slaves_linkup(struct bonding *bond); +int get_dcp_id_from_dpa_eth_port(struct net_device *netdev); +int export_oh_port_info_to_ceetm(struct bonding *bond, uint16_t *channel, + unsigned long *fman_dcpid, unsigned long *oh_offset, + unsigned long *cell_index); +int show_dpa_slave_info(struct bonding *bond, struct slave *slave); +int get_dpa_slave_info_ex(struct slave *slave, uint16_t *tx_channel, + struct qman_fq **egress_fq, u32 *first_fqid); +int enqueue_pkt_to_oh(struct bonding *bond, struct sk_buff *skb, + struct dpa_fq *ceetm_fq); +ssize_t bonding_show_offline_port_xmit_statistics(struct device *d, + struct device_attribute *attr, char *buf); +ssize_t bonding_show_offline_ports(struct device *d, + struct device_attribute *attr, char *buf); +ssize_t bonding_show_oh_needed_for_hw_distribution(struct device *d, + struct device_attribute *attr, char *buf); +ssize_t bonding_store_oh_needed_for_hw_distribution(struct device *d, + struct device_attribute *attr, const char *buffer, + size_t count); +ssize_t bonding_show_oh_enable(struct device *d, + struct device_attribute *attr, char *buf); +ssize_t bonding_store_oh_enable(struct device *d, + struct device_attribute *attr, const char *buffer, + size_t count); +int fill_oh_pcd_fqs_with_slave_info(struct bonding *bond, struct slave *slave); +int del_oh_pcd_fqs_with_slave_info(struct bonding *bond, struct slave *slave); +bool apply_pcd(struct bonding *bond, int new_xmit_policy); +int release_pcd_mem(struct bonding *bond); +int init_status(struct net_device *netdev); +void add_statistics(struct bonding *bond, struct rtnl_link_stats64 *stats); +#endif /* __HARDWARE_DISTRIBUTION_H */ |