diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/net/fjes | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | drivers/net/fjes/Makefile | 30 | ||||
-rw-r--r-- | drivers/net/fjes/fjes.h | 93 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_debugfs.c | 117 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_ethtool.c | 313 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_hw.c | 1314 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_hw.h | 371 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_main.c | 1603 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_regs.h | 142 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_trace.c | 30 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_trace.h | 380 |
10 files changed, 4393 insertions, 0 deletions
diff --git a/drivers/net/fjes/Makefile b/drivers/net/fjes/Makefile new file mode 100644 index 000000000..bc47b354c --- /dev/null +++ b/drivers/net/fjes/Makefile @@ -0,0 +1,30 @@ +################################################################################ +# +# FUJITSU Extended Socket Network Device driver +# Copyright (c) 2015 FUJITSU LIMITED +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, see <http://www.gnu.org/licenses/>. +# +# The full GNU General Public License is included in this distribution in +# the file called "COPYING". +# +################################################################################ + + +# +# Makefile for the FUJITSU Extended Socket network device driver +# + +obj-$(CONFIG_FUJITSU_ES) += fjes.o + +fjes-objs := fjes_main.o fjes_hw.o fjes_ethtool.o fjes_trace.o fjes_debugfs.o diff --git a/drivers/net/fjes/fjes.h b/drivers/net/fjes/fjes.h new file mode 100644 index 000000000..0372be3ad --- /dev/null +++ b/drivers/net/fjes/fjes.h @@ -0,0 +1,93 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +#ifndef FJES_H_ +#define FJES_H_ + +#include <linux/acpi.h> + +#include "fjes_hw.h" + +#define FJES_ACPI_SYMBOL "Extended Socket" +#define FJES_MAX_QUEUES 1 +#define FJES_TX_RETRY_INTERVAL (20 * HZ) +#define FJES_TX_RETRY_TIMEOUT (100) +#define FJES_TX_TX_STALL_TIMEOUT (FJES_TX_RETRY_INTERVAL / 2) +#define FJES_OPEN_ZONE_UPDATE_WAIT (300) /* msec */ +#define FJES_IRQ_WATCH_DELAY (HZ) + +/* board specific private data structure */ +struct fjes_adapter { + struct net_device *netdev; + struct platform_device *plat_dev; + + struct napi_struct napi; + struct rtnl_link_stats64 stats64; + + unsigned int tx_retry_count; + unsigned long tx_start_jiffies; + unsigned long rx_last_jiffies; + bool unset_rx_last; + + struct work_struct force_close_task; + bool force_reset; + bool open_guard; + + bool irq_registered; + + struct workqueue_struct *txrx_wq; + struct workqueue_struct *control_wq; + + struct work_struct tx_stall_task; + struct work_struct raise_intr_rxdata_task; + + struct work_struct unshare_watch_task; + unsigned long unshare_watch_bitmask; + + struct delayed_work interrupt_watch_task; + bool interrupt_watch_enable; + + struct fjes_hw hw; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dbg_adapter; +#endif +}; + +extern char fjes_driver_name[]; +extern char fjes_driver_version[]; +extern const u32 fjes_support_mtu[]; + +void fjes_set_ethtool_ops(struct net_device *); + +#ifdef CONFIG_DEBUG_FS +void fjes_dbg_adapter_init(struct fjes_adapter *adapter); +void fjes_dbg_adapter_exit(struct fjes_adapter *adapter); +void fjes_dbg_init(void); +void fjes_dbg_exit(void); +#else +static inline void fjes_dbg_adapter_init(struct fjes_adapter *adapter) {} +static inline void fjes_dbg_adapter_exit(struct fjes_adapter *adapter) {} +static inline void fjes_dbg_init(void) {} +static inline void fjes_dbg_exit(void) {} +#endif /* CONFIG_DEBUG_FS */ + +#endif /* FJES_H_ */ diff --git a/drivers/net/fjes/fjes_debugfs.c b/drivers/net/fjes/fjes_debugfs.c new file mode 100644 index 000000000..30052ebd5 --- /dev/null +++ b/drivers/net/fjes/fjes_debugfs.c @@ -0,0 +1,117 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015-2016 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +/* debugfs support for fjes driver */ + +#ifdef CONFIG_DEBUG_FS + +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> + +#include "fjes.h" + +static struct dentry *fjes_debug_root; + +static const char * const ep_status_string[] = { + "unshared", + "shared", + "waiting", + "complete", +}; + +static int fjes_dbg_status_show(struct seq_file *m, void *v) +{ + struct fjes_adapter *adapter = m->private; + struct fjes_hw *hw = &adapter->hw; + int max_epid = hw->max_epid; + int my_epid = hw->my_epid; + int epidx; + + seq_puts(m, "EPID\tSTATUS SAME_ZONE CONNECTED\n"); + for (epidx = 0; epidx < max_epid; epidx++) { + if (epidx == my_epid) { + seq_printf(m, "ep%d\t%-16c %-16c %-16c\n", + epidx, '-', '-', '-'); + } else { + seq_printf(m, "ep%d\t%-16s %-16c %-16c\n", + epidx, + ep_status_string[fjes_hw_get_partner_ep_status(hw, epidx)], + fjes_hw_epid_is_same_zone(hw, epidx) ? 'Y' : 'N', + fjes_hw_epid_is_shared(hw->hw_info.share, epidx) ? 'Y' : 'N'); + } + } + + return 0; +} + +static int fjes_dbg_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, fjes_dbg_status_show, inode->i_private); +} + +static const struct file_operations fjes_dbg_status_fops = { + .owner = THIS_MODULE, + .open = fjes_dbg_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void fjes_dbg_adapter_init(struct fjes_adapter *adapter) +{ + const char *name = dev_name(&adapter->plat_dev->dev); + struct dentry *pfile; + + adapter->dbg_adapter = debugfs_create_dir(name, fjes_debug_root); + if (!adapter->dbg_adapter) { + dev_err(&adapter->plat_dev->dev, + "debugfs entry for %s failed\n", name); + return; + } + + pfile = debugfs_create_file("status", 0444, adapter->dbg_adapter, + adapter, &fjes_dbg_status_fops); + if (!pfile) + dev_err(&adapter->plat_dev->dev, + "debugfs status for %s failed\n", name); +} + +void fjes_dbg_adapter_exit(struct fjes_adapter *adapter) +{ + debugfs_remove_recursive(adapter->dbg_adapter); + adapter->dbg_adapter = NULL; +} + +void fjes_dbg_init(void) +{ + fjes_debug_root = debugfs_create_dir(fjes_driver_name, NULL); + if (!fjes_debug_root) + pr_info("init of debugfs failed\n"); +} + +void fjes_dbg_exit(void) +{ + debugfs_remove_recursive(fjes_debug_root); + fjes_debug_root = NULL; +} + +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/net/fjes/fjes_ethtool.c b/drivers/net/fjes/fjes_ethtool.c new file mode 100644 index 000000000..7d101714c --- /dev/null +++ b/drivers/net/fjes/fjes_ethtool.c @@ -0,0 +1,313 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +/* ethtool support for fjes */ + +#include <linux/vmalloc.h> +#include <linux/netdevice.h> +#include <linux/ethtool.h> +#include <linux/platform_device.h> + +#include "fjes.h" + +struct fjes_stats { + char stat_string[ETH_GSTRING_LEN]; + int sizeof_stat; + int stat_offset; +}; + +#define FJES_STAT(name, stat) { \ + .stat_string = name, \ + .sizeof_stat = FIELD_SIZEOF(struct fjes_adapter, stat), \ + .stat_offset = offsetof(struct fjes_adapter, stat) \ +} + +static const struct fjes_stats fjes_gstrings_stats[] = { + FJES_STAT("rx_packets", stats64.rx_packets), + FJES_STAT("tx_packets", stats64.tx_packets), + FJES_STAT("rx_bytes", stats64.rx_bytes), + FJES_STAT("tx_bytes", stats64.rx_bytes), + FJES_STAT("rx_dropped", stats64.rx_dropped), + FJES_STAT("tx_dropped", stats64.tx_dropped), +}; + +#define FJES_EP_STATS_LEN 14 +#define FJES_STATS_LEN \ + (ARRAY_SIZE(fjes_gstrings_stats) + \ + ((&((struct fjes_adapter *)netdev_priv(netdev))->hw)->max_epid - 1) * \ + FJES_EP_STATS_LEN) + +static void fjes_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats *stats, u64 *data) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + int epidx; + char *p; + int i; + + for (i = 0; i < ARRAY_SIZE(fjes_gstrings_stats); i++) { + p = (char *)adapter + fjes_gstrings_stats[i].stat_offset; + data[i] = (fjes_gstrings_stats[i].sizeof_stat == sizeof(u64)) + ? *(u64 *)p : *(u32 *)p; + } + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .com_regist_buf_exec; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .com_unregist_buf_exec; + data[i++] = hw->ep_shm_info[epidx].ep_stats.send_intr_rx; + data[i++] = hw->ep_shm_info[epidx].ep_stats.send_intr_unshare; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .send_intr_zoneupdate; + data[i++] = hw->ep_shm_info[epidx].ep_stats.recv_intr_rx; + data[i++] = hw->ep_shm_info[epidx].ep_stats.recv_intr_unshare; + data[i++] = hw->ep_shm_info[epidx].ep_stats.recv_intr_stop; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .recv_intr_zoneupdate; + data[i++] = hw->ep_shm_info[epidx].ep_stats.tx_buffer_full; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .tx_dropped_not_shared; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .tx_dropped_ver_mismatch; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .tx_dropped_buf_size_mismatch; + data[i++] = hw->ep_shm_info[epidx].ep_stats + .tx_dropped_vlanid_mismatch; + } +} + +static void fjes_get_strings(struct net_device *netdev, + u32 stringset, u8 *data) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + u8 *p = data; + int i; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(fjes_gstrings_stats); i++) { + memcpy(p, fjes_gstrings_stats[i].stat_string, + ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + for (i = 0; i < hw->max_epid; i++) { + if (i == hw->my_epid) + continue; + sprintf(p, "ep%u_com_regist_buf_exec", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_com_unregist_buf_exec", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_send_intr_rx", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_send_intr_unshare", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_send_intr_zoneupdate", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_recv_intr_rx", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_recv_intr_unshare", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_recv_intr_stop", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_recv_intr_zoneupdate", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_tx_buffer_full", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_tx_dropped_not_shared", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_tx_dropped_ver_mismatch", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_tx_dropped_buf_size_mismatch", i); + p += ETH_GSTRING_LEN; + sprintf(p, "ep%u_tx_dropped_vlanid_mismatch", i); + p += ETH_GSTRING_LEN; + } + break; + } +} + +static int fjes_get_sset_count(struct net_device *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return FJES_STATS_LEN; + default: + return -EOPNOTSUPP; + } +} + +static void fjes_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct platform_device *plat_dev; + + plat_dev = adapter->plat_dev; + + strlcpy(drvinfo->driver, fjes_driver_name, sizeof(drvinfo->driver)); + strlcpy(drvinfo->version, fjes_driver_version, + sizeof(drvinfo->version)); + + strlcpy(drvinfo->fw_version, "none", sizeof(drvinfo->fw_version)); + snprintf(drvinfo->bus_info, sizeof(drvinfo->bus_info), + "platform:%s", plat_dev->name); +} + +static int fjes_get_link_ksettings(struct net_device *netdev, + struct ethtool_link_ksettings *ecmd) +{ + ethtool_link_ksettings_zero_link_mode(ecmd, supported); + ethtool_link_ksettings_zero_link_mode(ecmd, advertising); + ecmd->base.duplex = DUPLEX_FULL; + ecmd->base.autoneg = AUTONEG_DISABLE; + ecmd->base.port = PORT_NONE; + ecmd->base.speed = 20000; /* 20Gb/s */ + + return 0; +} + +static int fjes_get_regs_len(struct net_device *netdev) +{ +#define FJES_REGS_LEN 37 + return FJES_REGS_LEN * sizeof(u32); +} + +static void fjes_get_regs(struct net_device *netdev, + struct ethtool_regs *regs, void *p) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + u32 *regs_buff = p; + + memset(p, 0, FJES_REGS_LEN * sizeof(u32)); + + regs->version = 1; + + /* Information registers */ + regs_buff[0] = rd32(XSCT_OWNER_EPID); + regs_buff[1] = rd32(XSCT_MAX_EP); + + /* Device Control registers */ + regs_buff[4] = rd32(XSCT_DCTL); + + /* Command Control registers */ + regs_buff[8] = rd32(XSCT_CR); + regs_buff[9] = rd32(XSCT_CS); + regs_buff[10] = rd32(XSCT_SHSTSAL); + regs_buff[11] = rd32(XSCT_SHSTSAH); + + regs_buff[13] = rd32(XSCT_REQBL); + regs_buff[14] = rd32(XSCT_REQBAL); + regs_buff[15] = rd32(XSCT_REQBAH); + + regs_buff[17] = rd32(XSCT_RESPBL); + regs_buff[18] = rd32(XSCT_RESPBAL); + regs_buff[19] = rd32(XSCT_RESPBAH); + + /* Interrupt Control registers */ + regs_buff[32] = rd32(XSCT_IS); + regs_buff[33] = rd32(XSCT_IMS); + regs_buff[34] = rd32(XSCT_IMC); + regs_buff[35] = rd32(XSCT_IG); + regs_buff[36] = rd32(XSCT_ICTL); +} + +static int fjes_set_dump(struct net_device *netdev, struct ethtool_dump *dump) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + int ret = 0; + + if (dump->flag) { + if (hw->debug_mode) + return -EPERM; + + hw->debug_mode = dump->flag; + + /* enable debug mode */ + mutex_lock(&hw->hw_info.lock); + ret = fjes_hw_start_debug(hw); + mutex_unlock(&hw->hw_info.lock); + + if (ret) + hw->debug_mode = 0; + } else { + if (!hw->debug_mode) + return -EPERM; + + /* disable debug mode */ + mutex_lock(&hw->hw_info.lock); + ret = fjes_hw_stop_debug(hw); + mutex_unlock(&hw->hw_info.lock); + } + + return ret; +} + +static int fjes_get_dump_flag(struct net_device *netdev, + struct ethtool_dump *dump) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + + dump->len = hw->hw_info.trace_size; + dump->version = 1; + dump->flag = hw->debug_mode; + + return 0; +} + +static int fjes_get_dump_data(struct net_device *netdev, + struct ethtool_dump *dump, void *buf) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + int ret = 0; + + if (hw->hw_info.trace) + memcpy(buf, hw->hw_info.trace, hw->hw_info.trace_size); + else + ret = -EPERM; + + return ret; +} + +static const struct ethtool_ops fjes_ethtool_ops = { + .get_drvinfo = fjes_get_drvinfo, + .get_ethtool_stats = fjes_get_ethtool_stats, + .get_strings = fjes_get_strings, + .get_sset_count = fjes_get_sset_count, + .get_regs = fjes_get_regs, + .get_regs_len = fjes_get_regs_len, + .set_dump = fjes_set_dump, + .get_dump_flag = fjes_get_dump_flag, + .get_dump_data = fjes_get_dump_data, + .get_link_ksettings = fjes_get_link_ksettings, +}; + +void fjes_set_ethtool_ops(struct net_device *netdev) +{ + netdev->ethtool_ops = &fjes_ethtool_ops; +} diff --git a/drivers/net/fjes/fjes_hw.c b/drivers/net/fjes/fjes_hw.c new file mode 100644 index 000000000..9c652c043 --- /dev/null +++ b/drivers/net/fjes/fjes_hw.c @@ -0,0 +1,1314 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +#include "fjes_hw.h" +#include "fjes.h" +#include "fjes_trace.h" + +static void fjes_hw_update_zone_task(struct work_struct *); +static void fjes_hw_epstop_task(struct work_struct *); + +/* supported MTU list */ +const u32 fjes_support_mtu[] = { + FJES_MTU_DEFINE(8 * 1024), + FJES_MTU_DEFINE(16 * 1024), + FJES_MTU_DEFINE(32 * 1024), + FJES_MTU_DEFINE(64 * 1024), + 0 +}; + +u32 fjes_hw_rd32(struct fjes_hw *hw, u32 reg) +{ + u8 *base = hw->base; + u32 value = 0; + + value = readl(&base[reg]); + + return value; +} + +static u8 *fjes_hw_iomap(struct fjes_hw *hw) +{ + u8 *base; + + if (!request_mem_region(hw->hw_res.start, hw->hw_res.size, + fjes_driver_name)) { + pr_err("request_mem_region failed\n"); + return NULL; + } + + base = (u8 *)ioremap_nocache(hw->hw_res.start, hw->hw_res.size); + + return base; +} + +static void fjes_hw_iounmap(struct fjes_hw *hw) +{ + iounmap(hw->base); + release_mem_region(hw->hw_res.start, hw->hw_res.size); +} + +int fjes_hw_reset(struct fjes_hw *hw) +{ + union REG_DCTL dctl; + int timeout; + + dctl.reg = 0; + dctl.bits.reset = 1; + wr32(XSCT_DCTL, dctl.reg); + + timeout = FJES_DEVICE_RESET_TIMEOUT * 1000; + dctl.reg = rd32(XSCT_DCTL); + while ((dctl.bits.reset == 1) && (timeout > 0)) { + msleep(1000); + dctl.reg = rd32(XSCT_DCTL); + timeout -= 1000; + } + + return timeout > 0 ? 0 : -EIO; +} + +static int fjes_hw_get_max_epid(struct fjes_hw *hw) +{ + union REG_MAX_EP info; + + info.reg = rd32(XSCT_MAX_EP); + + return info.bits.maxep; +} + +static int fjes_hw_get_my_epid(struct fjes_hw *hw) +{ + union REG_OWNER_EPID info; + + info.reg = rd32(XSCT_OWNER_EPID); + + return info.bits.epid; +} + +static int fjes_hw_alloc_shared_status_region(struct fjes_hw *hw) +{ + size_t size; + + size = sizeof(struct fjes_device_shared_info) + + (sizeof(u8) * hw->max_epid); + hw->hw_info.share = kzalloc(size, GFP_KERNEL); + if (!hw->hw_info.share) + return -ENOMEM; + + hw->hw_info.share->epnum = hw->max_epid; + + return 0; +} + +static void fjes_hw_free_shared_status_region(struct fjes_hw *hw) +{ + kfree(hw->hw_info.share); + hw->hw_info.share = NULL; +} + +static int fjes_hw_alloc_epbuf(struct epbuf_handler *epbh) +{ + void *mem; + + mem = vzalloc(EP_BUFFER_SIZE); + if (!mem) + return -ENOMEM; + + epbh->buffer = mem; + epbh->size = EP_BUFFER_SIZE; + + epbh->info = (union ep_buffer_info *)mem; + epbh->ring = (u8 *)(mem + sizeof(union ep_buffer_info)); + + return 0; +} + +static void fjes_hw_free_epbuf(struct epbuf_handler *epbh) +{ + vfree(epbh->buffer); + epbh->buffer = NULL; + epbh->size = 0; + + epbh->info = NULL; + epbh->ring = NULL; +} + +void fjes_hw_setup_epbuf(struct epbuf_handler *epbh, u8 *mac_addr, u32 mtu) +{ + union ep_buffer_info *info = epbh->info; + u16 vlan_id[EP_BUFFER_SUPPORT_VLAN_MAX]; + int i; + + for (i = 0; i < EP_BUFFER_SUPPORT_VLAN_MAX; i++) + vlan_id[i] = info->v1i.vlan_id[i]; + + memset(info, 0, sizeof(union ep_buffer_info)); + + info->v1i.version = 0; /* version 0 */ + + for (i = 0; i < ETH_ALEN; i++) + info->v1i.mac_addr[i] = mac_addr[i]; + + info->v1i.head = 0; + info->v1i.tail = 1; + + info->v1i.info_size = sizeof(union ep_buffer_info); + info->v1i.buffer_size = epbh->size - info->v1i.info_size; + + info->v1i.frame_max = FJES_MTU_TO_FRAME_SIZE(mtu); + info->v1i.count_max = + EP_RING_NUM(info->v1i.buffer_size, info->v1i.frame_max); + + for (i = 0; i < EP_BUFFER_SUPPORT_VLAN_MAX; i++) + info->v1i.vlan_id[i] = vlan_id[i]; + + info->v1i.rx_status |= FJES_RX_MTU_CHANGING_DONE; +} + +void +fjes_hw_init_command_registers(struct fjes_hw *hw, + struct fjes_device_command_param *param) +{ + /* Request Buffer length */ + wr32(XSCT_REQBL, (__le32)(param->req_len)); + /* Response Buffer Length */ + wr32(XSCT_RESPBL, (__le32)(param->res_len)); + + /* Request Buffer Address */ + wr32(XSCT_REQBAL, + (__le32)(param->req_start & GENMASK_ULL(31, 0))); + wr32(XSCT_REQBAH, + (__le32)((param->req_start & GENMASK_ULL(63, 32)) >> 32)); + + /* Response Buffer Address */ + wr32(XSCT_RESPBAL, + (__le32)(param->res_start & GENMASK_ULL(31, 0))); + wr32(XSCT_RESPBAH, + (__le32)((param->res_start & GENMASK_ULL(63, 32)) >> 32)); + + /* Share status address */ + wr32(XSCT_SHSTSAL, + (__le32)(param->share_start & GENMASK_ULL(31, 0))); + wr32(XSCT_SHSTSAH, + (__le32)((param->share_start & GENMASK_ULL(63, 32)) >> 32)); +} + +static int fjes_hw_setup(struct fjes_hw *hw) +{ + u8 mac[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + struct fjes_device_command_param param; + struct ep_share_mem_info *buf_pair; + unsigned long flags; + size_t mem_size; + int result; + int epidx; + void *buf; + + hw->hw_info.max_epid = &hw->max_epid; + hw->hw_info.my_epid = &hw->my_epid; + + buf = kcalloc(hw->max_epid, sizeof(struct ep_share_mem_info), + GFP_KERNEL); + if (!buf) + return -ENOMEM; + + hw->ep_shm_info = (struct ep_share_mem_info *)buf; + + mem_size = FJES_DEV_REQ_BUF_SIZE(hw->max_epid); + hw->hw_info.req_buf = kzalloc(mem_size, GFP_KERNEL); + if (!(hw->hw_info.req_buf)) + return -ENOMEM; + + hw->hw_info.req_buf_size = mem_size; + + mem_size = FJES_DEV_RES_BUF_SIZE(hw->max_epid); + hw->hw_info.res_buf = kzalloc(mem_size, GFP_KERNEL); + if (!(hw->hw_info.res_buf)) + return -ENOMEM; + + hw->hw_info.res_buf_size = mem_size; + + result = fjes_hw_alloc_shared_status_region(hw); + if (result) + return result; + + hw->hw_info.buffer_share_bit = 0; + hw->hw_info.buffer_unshare_reserve_bit = 0; + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx != hw->my_epid) { + buf_pair = &hw->ep_shm_info[epidx]; + + result = fjes_hw_alloc_epbuf(&buf_pair->tx); + if (result) + return result; + + result = fjes_hw_alloc_epbuf(&buf_pair->rx); + if (result) + return result; + + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf(&buf_pair->tx, mac, + fjes_support_mtu[0]); + fjes_hw_setup_epbuf(&buf_pair->rx, mac, + fjes_support_mtu[0]); + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + } + } + + memset(¶m, 0, sizeof(param)); + + param.req_len = hw->hw_info.req_buf_size; + param.req_start = __pa(hw->hw_info.req_buf); + param.res_len = hw->hw_info.res_buf_size; + param.res_start = __pa(hw->hw_info.res_buf); + + param.share_start = __pa(hw->hw_info.share->ep_status); + + fjes_hw_init_command_registers(hw, ¶m); + + return 0; +} + +static void fjes_hw_cleanup(struct fjes_hw *hw) +{ + int epidx; + + if (!hw->ep_shm_info) + return; + + fjes_hw_free_shared_status_region(hw); + + kfree(hw->hw_info.req_buf); + hw->hw_info.req_buf = NULL; + + kfree(hw->hw_info.res_buf); + hw->hw_info.res_buf = NULL; + + for (epidx = 0; epidx < hw->max_epid ; epidx++) { + if (epidx == hw->my_epid) + continue; + fjes_hw_free_epbuf(&hw->ep_shm_info[epidx].tx); + fjes_hw_free_epbuf(&hw->ep_shm_info[epidx].rx); + } + + kfree(hw->ep_shm_info); + hw->ep_shm_info = NULL; +} + +int fjes_hw_init(struct fjes_hw *hw) +{ + int ret; + + hw->base = fjes_hw_iomap(hw); + if (!hw->base) + return -EIO; + + ret = fjes_hw_reset(hw); + if (ret) + return ret; + + fjes_hw_set_irqmask(hw, REG_ICTL_MASK_ALL, true); + + INIT_WORK(&hw->update_zone_task, fjes_hw_update_zone_task); + INIT_WORK(&hw->epstop_task, fjes_hw_epstop_task); + + mutex_init(&hw->hw_info.lock); + spin_lock_init(&hw->rx_status_lock); + + hw->max_epid = fjes_hw_get_max_epid(hw); + hw->my_epid = fjes_hw_get_my_epid(hw); + + if ((hw->max_epid == 0) || (hw->my_epid >= hw->max_epid)) + return -ENXIO; + + ret = fjes_hw_setup(hw); + + hw->hw_info.trace = vzalloc(FJES_DEBUG_BUFFER_SIZE); + hw->hw_info.trace_size = FJES_DEBUG_BUFFER_SIZE; + + return ret; +} + +void fjes_hw_exit(struct fjes_hw *hw) +{ + int ret; + + if (hw->base) { + + if (hw->debug_mode) { + /* disable debug mode */ + mutex_lock(&hw->hw_info.lock); + fjes_hw_stop_debug(hw); + mutex_unlock(&hw->hw_info.lock); + } + vfree(hw->hw_info.trace); + hw->hw_info.trace = NULL; + hw->hw_info.trace_size = 0; + hw->debug_mode = 0; + + ret = fjes_hw_reset(hw); + if (ret) + pr_err("%s: reset error", __func__); + + fjes_hw_iounmap(hw); + hw->base = NULL; + } + + fjes_hw_cleanup(hw); + + cancel_work_sync(&hw->update_zone_task); + cancel_work_sync(&hw->epstop_task); +} + +static enum fjes_dev_command_response_e +fjes_hw_issue_request_command(struct fjes_hw *hw, + enum fjes_dev_command_request_type type) +{ + enum fjes_dev_command_response_e ret = FJES_CMD_STATUS_UNKNOWN; + union REG_CR cr; + union REG_CS cs; + int timeout = FJES_COMMAND_REQ_TIMEOUT * 1000; + + cr.reg = 0; + cr.bits.req_start = 1; + cr.bits.req_code = type; + wr32(XSCT_CR, cr.reg); + cr.reg = rd32(XSCT_CR); + + if (cr.bits.error == 0) { + timeout = FJES_COMMAND_REQ_TIMEOUT * 1000; + cs.reg = rd32(XSCT_CS); + + while ((cs.bits.complete != 1) && timeout > 0) { + msleep(1000); + cs.reg = rd32(XSCT_CS); + timeout -= 1000; + } + + if (cs.bits.complete == 1) + ret = FJES_CMD_STATUS_NORMAL; + else if (timeout <= 0) + ret = FJES_CMD_STATUS_TIMEOUT; + + } else { + switch (cr.bits.err_info) { + case FJES_CMD_REQ_ERR_INFO_PARAM: + ret = FJES_CMD_STATUS_ERROR_PARAM; + break; + case FJES_CMD_REQ_ERR_INFO_STATUS: + ret = FJES_CMD_STATUS_ERROR_STATUS; + break; + default: + ret = FJES_CMD_STATUS_UNKNOWN; + break; + } + } + + trace_fjes_hw_issue_request_command(&cr, &cs, timeout, ret); + + return ret; +} + +int fjes_hw_request_info(struct fjes_hw *hw) +{ + union fjes_device_command_req *req_buf = hw->hw_info.req_buf; + union fjes_device_command_res *res_buf = hw->hw_info.res_buf; + enum fjes_dev_command_response_e ret; + int result; + + memset(req_buf, 0, hw->hw_info.req_buf_size); + memset(res_buf, 0, hw->hw_info.res_buf_size); + + req_buf->info.length = FJES_DEV_COMMAND_INFO_REQ_LEN; + + res_buf->info.length = 0; + res_buf->info.code = 0; + + ret = fjes_hw_issue_request_command(hw, FJES_CMD_REQ_INFO); + trace_fjes_hw_request_info(hw, res_buf); + + result = 0; + + if (FJES_DEV_COMMAND_INFO_RES_LEN((*hw->hw_info.max_epid)) != + res_buf->info.length) { + trace_fjes_hw_request_info_err("Invalid res_buf"); + result = -ENOMSG; + } else if (ret == FJES_CMD_STATUS_NORMAL) { + switch (res_buf->info.code) { + case FJES_CMD_REQ_RES_CODE_NORMAL: + result = 0; + break; + default: + result = -EPERM; + break; + } + } else { + switch (ret) { + case FJES_CMD_STATUS_UNKNOWN: + result = -EPERM; + break; + case FJES_CMD_STATUS_TIMEOUT: + trace_fjes_hw_request_info_err("Timeout"); + result = -EBUSY; + break; + case FJES_CMD_STATUS_ERROR_PARAM: + result = -EPERM; + break; + case FJES_CMD_STATUS_ERROR_STATUS: + result = -EPERM; + break; + default: + result = -EPERM; + break; + } + } + + return result; +} + +int fjes_hw_register_buff_addr(struct fjes_hw *hw, int dest_epid, + struct ep_share_mem_info *buf_pair) +{ + union fjes_device_command_req *req_buf = hw->hw_info.req_buf; + union fjes_device_command_res *res_buf = hw->hw_info.res_buf; + enum fjes_dev_command_response_e ret; + int page_count; + int timeout; + int i, idx; + void *addr; + int result; + + if (test_bit(dest_epid, &hw->hw_info.buffer_share_bit)) + return 0; + + memset(req_buf, 0, hw->hw_info.req_buf_size); + memset(res_buf, 0, hw->hw_info.res_buf_size); + + req_buf->share_buffer.length = FJES_DEV_COMMAND_SHARE_BUFFER_REQ_LEN( + buf_pair->tx.size, + buf_pair->rx.size); + req_buf->share_buffer.epid = dest_epid; + + idx = 0; + req_buf->share_buffer.buffer[idx++] = buf_pair->tx.size; + page_count = buf_pair->tx.size / EP_BUFFER_INFO_SIZE; + for (i = 0; i < page_count; i++) { + addr = ((u8 *)(buf_pair->tx.buffer)) + + (i * EP_BUFFER_INFO_SIZE); + req_buf->share_buffer.buffer[idx++] = + (__le64)(page_to_phys(vmalloc_to_page(addr)) + + offset_in_page(addr)); + } + + req_buf->share_buffer.buffer[idx++] = buf_pair->rx.size; + page_count = buf_pair->rx.size / EP_BUFFER_INFO_SIZE; + for (i = 0; i < page_count; i++) { + addr = ((u8 *)(buf_pair->rx.buffer)) + + (i * EP_BUFFER_INFO_SIZE); + req_buf->share_buffer.buffer[idx++] = + (__le64)(page_to_phys(vmalloc_to_page(addr)) + + offset_in_page(addr)); + } + + res_buf->share_buffer.length = 0; + res_buf->share_buffer.code = 0; + + trace_fjes_hw_register_buff_addr_req(req_buf, buf_pair); + + ret = fjes_hw_issue_request_command(hw, FJES_CMD_REQ_SHARE_BUFFER); + + timeout = FJES_COMMAND_REQ_BUFF_TIMEOUT * 1000; + while ((ret == FJES_CMD_STATUS_NORMAL) && + (res_buf->share_buffer.length == + FJES_DEV_COMMAND_SHARE_BUFFER_RES_LEN) && + (res_buf->share_buffer.code == FJES_CMD_REQ_RES_CODE_BUSY) && + (timeout > 0)) { + msleep(200 + hw->my_epid * 20); + timeout -= (200 + hw->my_epid * 20); + + res_buf->share_buffer.length = 0; + res_buf->share_buffer.code = 0; + + ret = fjes_hw_issue_request_command( + hw, FJES_CMD_REQ_SHARE_BUFFER); + } + + result = 0; + + trace_fjes_hw_register_buff_addr(res_buf, timeout); + + if (res_buf->share_buffer.length != + FJES_DEV_COMMAND_SHARE_BUFFER_RES_LEN) { + trace_fjes_hw_register_buff_addr_err("Invalid res_buf"); + result = -ENOMSG; + } else if (ret == FJES_CMD_STATUS_NORMAL) { + switch (res_buf->share_buffer.code) { + case FJES_CMD_REQ_RES_CODE_NORMAL: + result = 0; + set_bit(dest_epid, &hw->hw_info.buffer_share_bit); + break; + case FJES_CMD_REQ_RES_CODE_BUSY: + trace_fjes_hw_register_buff_addr_err("Busy Timeout"); + result = -EBUSY; + break; + default: + result = -EPERM; + break; + } + } else { + switch (ret) { + case FJES_CMD_STATUS_UNKNOWN: + result = -EPERM; + break; + case FJES_CMD_STATUS_TIMEOUT: + trace_fjes_hw_register_buff_addr_err("Timeout"); + result = -EBUSY; + break; + case FJES_CMD_STATUS_ERROR_PARAM: + case FJES_CMD_STATUS_ERROR_STATUS: + default: + result = -EPERM; + break; + } + } + + return result; +} + +int fjes_hw_unregister_buff_addr(struct fjes_hw *hw, int dest_epid) +{ + union fjes_device_command_req *req_buf = hw->hw_info.req_buf; + union fjes_device_command_res *res_buf = hw->hw_info.res_buf; + struct fjes_device_shared_info *share = hw->hw_info.share; + enum fjes_dev_command_response_e ret; + int timeout; + int result; + + if (!hw->base) + return -EPERM; + + if (!req_buf || !res_buf || !share) + return -EPERM; + + if (!test_bit(dest_epid, &hw->hw_info.buffer_share_bit)) + return 0; + + memset(req_buf, 0, hw->hw_info.req_buf_size); + memset(res_buf, 0, hw->hw_info.res_buf_size); + + req_buf->unshare_buffer.length = + FJES_DEV_COMMAND_UNSHARE_BUFFER_REQ_LEN; + req_buf->unshare_buffer.epid = dest_epid; + + res_buf->unshare_buffer.length = 0; + res_buf->unshare_buffer.code = 0; + + trace_fjes_hw_unregister_buff_addr_req(req_buf); + ret = fjes_hw_issue_request_command(hw, FJES_CMD_REQ_UNSHARE_BUFFER); + + timeout = FJES_COMMAND_REQ_BUFF_TIMEOUT * 1000; + while ((ret == FJES_CMD_STATUS_NORMAL) && + (res_buf->unshare_buffer.length == + FJES_DEV_COMMAND_UNSHARE_BUFFER_RES_LEN) && + (res_buf->unshare_buffer.code == + FJES_CMD_REQ_RES_CODE_BUSY) && + (timeout > 0)) { + msleep(200 + hw->my_epid * 20); + timeout -= (200 + hw->my_epid * 20); + + res_buf->unshare_buffer.length = 0; + res_buf->unshare_buffer.code = 0; + + ret = + fjes_hw_issue_request_command(hw, FJES_CMD_REQ_UNSHARE_BUFFER); + } + + result = 0; + + trace_fjes_hw_unregister_buff_addr(res_buf, timeout); + + if (res_buf->unshare_buffer.length != + FJES_DEV_COMMAND_UNSHARE_BUFFER_RES_LEN) { + trace_fjes_hw_unregister_buff_addr_err("Invalid res_buf"); + result = -ENOMSG; + } else if (ret == FJES_CMD_STATUS_NORMAL) { + switch (res_buf->unshare_buffer.code) { + case FJES_CMD_REQ_RES_CODE_NORMAL: + result = 0; + clear_bit(dest_epid, &hw->hw_info.buffer_share_bit); + break; + case FJES_CMD_REQ_RES_CODE_BUSY: + trace_fjes_hw_unregister_buff_addr_err("Busy Timeout"); + result = -EBUSY; + break; + default: + result = -EPERM; + break; + } + } else { + switch (ret) { + case FJES_CMD_STATUS_UNKNOWN: + result = -EPERM; + break; + case FJES_CMD_STATUS_TIMEOUT: + trace_fjes_hw_unregister_buff_addr_err("Timeout"); + result = -EBUSY; + break; + case FJES_CMD_STATUS_ERROR_PARAM: + case FJES_CMD_STATUS_ERROR_STATUS: + default: + result = -EPERM; + break; + } + } + + return result; +} + +int fjes_hw_raise_interrupt(struct fjes_hw *hw, int dest_epid, + enum REG_ICTL_MASK mask) +{ + u32 ig = mask | dest_epid; + + wr32(XSCT_IG, cpu_to_le32(ig)); + + return 0; +} + +u32 fjes_hw_capture_interrupt_status(struct fjes_hw *hw) +{ + u32 cur_is; + + cur_is = rd32(XSCT_IS); + + return cur_is; +} + +void fjes_hw_set_irqmask(struct fjes_hw *hw, + enum REG_ICTL_MASK intr_mask, bool mask) +{ + if (mask) + wr32(XSCT_IMS, intr_mask); + else + wr32(XSCT_IMC, intr_mask); +} + +bool fjes_hw_epid_is_same_zone(struct fjes_hw *hw, int epid) +{ + if (epid >= hw->max_epid) + return false; + + if ((hw->ep_shm_info[epid].es_status != + FJES_ZONING_STATUS_ENABLE) || + (hw->ep_shm_info[hw->my_epid].zone == + FJES_ZONING_ZONE_TYPE_NONE)) + return false; + else + return (hw->ep_shm_info[epid].zone == + hw->ep_shm_info[hw->my_epid].zone); +} + +int fjes_hw_epid_is_shared(struct fjes_device_shared_info *share, + int dest_epid) +{ + int value = false; + + if (dest_epid < share->epnum) + value = share->ep_status[dest_epid]; + + return value; +} + +static bool fjes_hw_epid_is_stop_requested(struct fjes_hw *hw, int src_epid) +{ + return test_bit(src_epid, &hw->txrx_stop_req_bit); +} + +static bool fjes_hw_epid_is_stop_process_done(struct fjes_hw *hw, int src_epid) +{ + return (hw->ep_shm_info[src_epid].tx.info->v1i.rx_status & + FJES_RX_STOP_REQ_DONE); +} + +enum ep_partner_status +fjes_hw_get_partner_ep_status(struct fjes_hw *hw, int epid) +{ + enum ep_partner_status status; + + if (fjes_hw_epid_is_shared(hw->hw_info.share, epid)) { + if (fjes_hw_epid_is_stop_requested(hw, epid)) { + status = EP_PARTNER_WAITING; + } else { + if (fjes_hw_epid_is_stop_process_done(hw, epid)) + status = EP_PARTNER_COMPLETE; + else + status = EP_PARTNER_SHARED; + } + } else { + status = EP_PARTNER_UNSHARE; + } + + return status; +} + +void fjes_hw_raise_epstop(struct fjes_hw *hw) +{ + enum ep_partner_status status; + unsigned long flags; + int epidx; + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + status = fjes_hw_get_partner_ep_status(hw, epidx); + switch (status) { + case EP_PARTNER_SHARED: + fjes_hw_raise_interrupt(hw, epidx, + REG_ICTL_MASK_TXRX_STOP_REQ); + hw->ep_shm_info[epidx].ep_stats.send_intr_unshare += 1; + break; + default: + break; + } + + set_bit(epidx, &hw->hw_info.buffer_unshare_reserve_bit); + set_bit(epidx, &hw->txrx_stop_req_bit); + + spin_lock_irqsave(&hw->rx_status_lock, flags); + hw->ep_shm_info[epidx].tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_REQUEST; + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + } +} + +int fjes_hw_wait_epstop(struct fjes_hw *hw) +{ + enum ep_partner_status status; + union ep_buffer_info *info; + int wait_time = 0; + int epidx; + + while (hw->hw_info.buffer_unshare_reserve_bit && + (wait_time < FJES_COMMAND_EPSTOP_WAIT_TIMEOUT * 1000)) { + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + status = fjes_hw_epid_is_shared(hw->hw_info.share, + epidx); + info = hw->ep_shm_info[epidx].rx.info; + if ((!status || + (info->v1i.rx_status & + FJES_RX_STOP_REQ_DONE)) && + test_bit(epidx, + &hw->hw_info.buffer_unshare_reserve_bit)) { + clear_bit(epidx, + &hw->hw_info.buffer_unshare_reserve_bit); + } + } + + msleep(100); + wait_time += 100; + } + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + if (test_bit(epidx, &hw->hw_info.buffer_unshare_reserve_bit)) + clear_bit(epidx, + &hw->hw_info.buffer_unshare_reserve_bit); + } + + return (wait_time < FJES_COMMAND_EPSTOP_WAIT_TIMEOUT * 1000) + ? 0 : -EBUSY; +} + +bool fjes_hw_check_epbuf_version(struct epbuf_handler *epbh, u32 version) +{ + union ep_buffer_info *info = epbh->info; + + return (info->common.version == version); +} + +bool fjes_hw_check_mtu(struct epbuf_handler *epbh, u32 mtu) +{ + union ep_buffer_info *info = epbh->info; + + return ((info->v1i.frame_max == FJES_MTU_TO_FRAME_SIZE(mtu)) && + info->v1i.rx_status & FJES_RX_MTU_CHANGING_DONE); +} + +bool fjes_hw_check_vlan_id(struct epbuf_handler *epbh, u16 vlan_id) +{ + union ep_buffer_info *info = epbh->info; + bool ret = false; + int i; + + if (vlan_id == 0) { + ret = true; + } else { + for (i = 0; i < EP_BUFFER_SUPPORT_VLAN_MAX; i++) { + if (vlan_id == info->v1i.vlan_id[i]) { + ret = true; + break; + } + } + } + return ret; +} + +bool fjes_hw_set_vlan_id(struct epbuf_handler *epbh, u16 vlan_id) +{ + union ep_buffer_info *info = epbh->info; + int i; + + for (i = 0; i < EP_BUFFER_SUPPORT_VLAN_MAX; i++) { + if (info->v1i.vlan_id[i] == 0) { + info->v1i.vlan_id[i] = vlan_id; + return true; + } + } + return false; +} + +void fjes_hw_del_vlan_id(struct epbuf_handler *epbh, u16 vlan_id) +{ + union ep_buffer_info *info = epbh->info; + int i; + + if (0 != vlan_id) { + for (i = 0; i < EP_BUFFER_SUPPORT_VLAN_MAX; i++) { + if (vlan_id == info->v1i.vlan_id[i]) + info->v1i.vlan_id[i] = 0; + } + } +} + +bool fjes_hw_epbuf_rx_is_empty(struct epbuf_handler *epbh) +{ + union ep_buffer_info *info = epbh->info; + + if (!(info->v1i.rx_status & FJES_RX_MTU_CHANGING_DONE)) + return true; + + if (info->v1i.count_max == 0) + return true; + + return EP_RING_EMPTY(info->v1i.head, info->v1i.tail, + info->v1i.count_max); +} + +void *fjes_hw_epbuf_rx_curpkt_get_addr(struct epbuf_handler *epbh, + size_t *psize) +{ + union ep_buffer_info *info = epbh->info; + struct esmem_frame *ring_frame; + void *frame; + + ring_frame = (struct esmem_frame *)&(epbh->ring[EP_RING_INDEX + (info->v1i.head, + info->v1i.count_max) * + info->v1i.frame_max]); + + *psize = (size_t)ring_frame->frame_size; + + frame = ring_frame->frame_data; + + return frame; +} + +void fjes_hw_epbuf_rx_curpkt_drop(struct epbuf_handler *epbh) +{ + union ep_buffer_info *info = epbh->info; + + if (fjes_hw_epbuf_rx_is_empty(epbh)) + return; + + EP_RING_INDEX_INC(epbh->info->v1i.head, info->v1i.count_max); +} + +int fjes_hw_epbuf_tx_pkt_send(struct epbuf_handler *epbh, + void *frame, size_t size) +{ + union ep_buffer_info *info = epbh->info; + struct esmem_frame *ring_frame; + + if (EP_RING_FULL(info->v1i.head, info->v1i.tail, info->v1i.count_max)) + return -ENOBUFS; + + ring_frame = (struct esmem_frame *)&(epbh->ring[EP_RING_INDEX + (info->v1i.tail - 1, + info->v1i.count_max) * + info->v1i.frame_max]); + + ring_frame->frame_size = size; + memcpy((void *)(ring_frame->frame_data), (void *)frame, size); + + EP_RING_INDEX_INC(epbh->info->v1i.tail, info->v1i.count_max); + + return 0; +} + +static void fjes_hw_update_zone_task(struct work_struct *work) +{ + struct fjes_hw *hw = container_of(work, + struct fjes_hw, update_zone_task); + + struct my_s {u8 es_status; u8 zone; } *info; + union fjes_device_command_res *res_buf; + enum ep_partner_status pstatus; + + struct fjes_adapter *adapter; + struct net_device *netdev; + unsigned long flags; + + ulong unshare_bit = 0; + ulong share_bit = 0; + ulong irq_bit = 0; + + int epidx; + int ret; + + adapter = (struct fjes_adapter *)hw->back; + netdev = adapter->netdev; + res_buf = hw->hw_info.res_buf; + info = (struct my_s *)&res_buf->info.info; + + mutex_lock(&hw->hw_info.lock); + + ret = fjes_hw_request_info(hw); + switch (ret) { + case -ENOMSG: + case -EBUSY: + default: + if (!work_pending(&adapter->force_close_task)) { + adapter->force_reset = true; + schedule_work(&adapter->force_close_task); + } + break; + + case 0: + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) { + hw->ep_shm_info[epidx].es_status = + info[epidx].es_status; + hw->ep_shm_info[epidx].zone = + info[epidx].zone; + continue; + } + + pstatus = fjes_hw_get_partner_ep_status(hw, epidx); + switch (pstatus) { + case EP_PARTNER_UNSHARE: + default: + if ((info[epidx].zone != + FJES_ZONING_ZONE_TYPE_NONE) && + (info[epidx].es_status == + FJES_ZONING_STATUS_ENABLE) && + (info[epidx].zone == + info[hw->my_epid].zone)) + set_bit(epidx, &share_bit); + else + set_bit(epidx, &unshare_bit); + break; + + case EP_PARTNER_COMPLETE: + case EP_PARTNER_WAITING: + if ((info[epidx].zone == + FJES_ZONING_ZONE_TYPE_NONE) || + (info[epidx].es_status != + FJES_ZONING_STATUS_ENABLE) || + (info[epidx].zone != + info[hw->my_epid].zone)) { + set_bit(epidx, + &adapter->unshare_watch_bitmask); + set_bit(epidx, + &hw->hw_info.buffer_unshare_reserve_bit); + } + break; + + case EP_PARTNER_SHARED: + if ((info[epidx].zone == + FJES_ZONING_ZONE_TYPE_NONE) || + (info[epidx].es_status != + FJES_ZONING_STATUS_ENABLE) || + (info[epidx].zone != + info[hw->my_epid].zone)) + set_bit(epidx, &irq_bit); + break; + } + + hw->ep_shm_info[epidx].es_status = + info[epidx].es_status; + hw->ep_shm_info[epidx].zone = info[epidx].zone; + } + break; + } + + mutex_unlock(&hw->hw_info.lock); + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + if (test_bit(epidx, &share_bit)) { + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf(&hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + + mutex_lock(&hw->hw_info.lock); + + ret = fjes_hw_register_buff_addr( + hw, epidx, &hw->ep_shm_info[epidx]); + + switch (ret) { + case 0: + break; + case -ENOMSG: + case -EBUSY: + default: + if (!work_pending(&adapter->force_close_task)) { + adapter->force_reset = true; + schedule_work( + &adapter->force_close_task); + } + break; + } + mutex_unlock(&hw->hw_info.lock); + + hw->ep_shm_info[epidx].ep_stats + .com_regist_buf_exec += 1; + } + + if (test_bit(epidx, &unshare_bit)) { + mutex_lock(&hw->hw_info.lock); + + ret = fjes_hw_unregister_buff_addr(hw, epidx); + + switch (ret) { + case 0: + break; + case -ENOMSG: + case -EBUSY: + default: + if (!work_pending(&adapter->force_close_task)) { + adapter->force_reset = true; + schedule_work( + &adapter->force_close_task); + } + break; + } + + mutex_unlock(&hw->hw_info.lock); + + hw->ep_shm_info[epidx].ep_stats + .com_unregist_buf_exec += 1; + + if (ret == 0) { + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf( + &hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + spin_unlock_irqrestore(&hw->rx_status_lock, + flags); + } + } + + if (test_bit(epidx, &irq_bit)) { + fjes_hw_raise_interrupt(hw, epidx, + REG_ICTL_MASK_TXRX_STOP_REQ); + + hw->ep_shm_info[epidx].ep_stats.send_intr_unshare += 1; + + set_bit(epidx, &hw->txrx_stop_req_bit); + spin_lock_irqsave(&hw->rx_status_lock, flags); + hw->ep_shm_info[epidx].tx. + info->v1i.rx_status |= + FJES_RX_STOP_REQ_REQUEST; + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + set_bit(epidx, &hw->hw_info.buffer_unshare_reserve_bit); + } + } + + if (irq_bit || adapter->unshare_watch_bitmask) { + if (!work_pending(&adapter->unshare_watch_task)) + queue_work(adapter->control_wq, + &adapter->unshare_watch_task); + } +} + +static void fjes_hw_epstop_task(struct work_struct *work) +{ + struct fjes_hw *hw = container_of(work, struct fjes_hw, epstop_task); + struct fjes_adapter *adapter = (struct fjes_adapter *)hw->back; + unsigned long flags; + + ulong remain_bit; + int epid_bit; + + while ((remain_bit = hw->epstop_req_bit)) { + for (epid_bit = 0; remain_bit; remain_bit >>= 1, epid_bit++) { + if (remain_bit & 1) { + spin_lock_irqsave(&hw->rx_status_lock, flags); + hw->ep_shm_info[epid_bit]. + tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_DONE; + spin_unlock_irqrestore(&hw->rx_status_lock, + flags); + + clear_bit(epid_bit, &hw->epstop_req_bit); + set_bit(epid_bit, + &adapter->unshare_watch_bitmask); + + if (!work_pending(&adapter->unshare_watch_task)) + queue_work( + adapter->control_wq, + &adapter->unshare_watch_task); + } + } + } +} + +int fjes_hw_start_debug(struct fjes_hw *hw) +{ + union fjes_device_command_req *req_buf = hw->hw_info.req_buf; + union fjes_device_command_res *res_buf = hw->hw_info.res_buf; + enum fjes_dev_command_response_e ret; + int page_count; + int result = 0; + void *addr; + int i; + + if (!hw->hw_info.trace) + return -EPERM; + memset(hw->hw_info.trace, 0, FJES_DEBUG_BUFFER_SIZE); + + memset(req_buf, 0, hw->hw_info.req_buf_size); + memset(res_buf, 0, hw->hw_info.res_buf_size); + + req_buf->start_trace.length = + FJES_DEV_COMMAND_START_DBG_REQ_LEN(hw->hw_info.trace_size); + req_buf->start_trace.mode = hw->debug_mode; + req_buf->start_trace.buffer_len = hw->hw_info.trace_size; + page_count = hw->hw_info.trace_size / FJES_DEBUG_PAGE_SIZE; + for (i = 0; i < page_count; i++) { + addr = ((u8 *)hw->hw_info.trace) + i * FJES_DEBUG_PAGE_SIZE; + req_buf->start_trace.buffer[i] = + (__le64)(page_to_phys(vmalloc_to_page(addr)) + + offset_in_page(addr)); + } + + res_buf->start_trace.length = 0; + res_buf->start_trace.code = 0; + + trace_fjes_hw_start_debug_req(req_buf); + ret = fjes_hw_issue_request_command(hw, FJES_CMD_REQ_START_DEBUG); + trace_fjes_hw_start_debug(res_buf); + + if (res_buf->start_trace.length != + FJES_DEV_COMMAND_START_DBG_RES_LEN) { + result = -ENOMSG; + trace_fjes_hw_start_debug_err("Invalid res_buf"); + } else if (ret == FJES_CMD_STATUS_NORMAL) { + switch (res_buf->start_trace.code) { + case FJES_CMD_REQ_RES_CODE_NORMAL: + result = 0; + break; + default: + result = -EPERM; + break; + } + } else { + switch (ret) { + case FJES_CMD_STATUS_UNKNOWN: + result = -EPERM; + break; + case FJES_CMD_STATUS_TIMEOUT: + trace_fjes_hw_start_debug_err("Busy Timeout"); + result = -EBUSY; + break; + case FJES_CMD_STATUS_ERROR_PARAM: + case FJES_CMD_STATUS_ERROR_STATUS: + default: + result = -EPERM; + break; + } + } + + return result; +} + +int fjes_hw_stop_debug(struct fjes_hw *hw) +{ + union fjes_device_command_req *req_buf = hw->hw_info.req_buf; + union fjes_device_command_res *res_buf = hw->hw_info.res_buf; + enum fjes_dev_command_response_e ret; + int result = 0; + + if (!hw->hw_info.trace) + return -EPERM; + + memset(req_buf, 0, hw->hw_info.req_buf_size); + memset(res_buf, 0, hw->hw_info.res_buf_size); + req_buf->stop_trace.length = FJES_DEV_COMMAND_STOP_DBG_REQ_LEN; + + res_buf->stop_trace.length = 0; + res_buf->stop_trace.code = 0; + + ret = fjes_hw_issue_request_command(hw, FJES_CMD_REQ_STOP_DEBUG); + trace_fjes_hw_stop_debug(res_buf); + + if (res_buf->stop_trace.length != FJES_DEV_COMMAND_STOP_DBG_RES_LEN) { + trace_fjes_hw_stop_debug_err("Invalid res_buf"); + result = -ENOMSG; + } else if (ret == FJES_CMD_STATUS_NORMAL) { + switch (res_buf->stop_trace.code) { + case FJES_CMD_REQ_RES_CODE_NORMAL: + result = 0; + hw->debug_mode = 0; + break; + default: + result = -EPERM; + break; + } + } else { + switch (ret) { + case FJES_CMD_STATUS_UNKNOWN: + result = -EPERM; + break; + case FJES_CMD_STATUS_TIMEOUT: + result = -EBUSY; + trace_fjes_hw_stop_debug_err("Busy Timeout"); + break; + case FJES_CMD_STATUS_ERROR_PARAM: + case FJES_CMD_STATUS_ERROR_STATUS: + default: + result = -EPERM; + break; + } + } + + return result; +} diff --git a/drivers/net/fjes/fjes_hw.h b/drivers/net/fjes/fjes_hw.h new file mode 100644 index 000000000..3a6da0996 --- /dev/null +++ b/drivers/net/fjes/fjes_hw.h @@ -0,0 +1,371 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +#ifndef FJES_HW_H_ +#define FJES_HW_H_ + +#include <linux/netdevice.h> +#include <linux/if_vlan.h> +#include <linux/vmalloc.h> + +#include "fjes_regs.h" + +struct fjes_hw; + +#define EP_BUFFER_SUPPORT_VLAN_MAX 4 +#define EP_BUFFER_INFO_SIZE 4096 + +#define FJES_DEBUG_PAGE_SIZE 4096 +#define FJES_DEBUG_BUFFER_SIZE (16 * FJES_DEBUG_PAGE_SIZE) + +#define FJES_DEVICE_RESET_TIMEOUT ((17 + 1) * 3 * 8) /* sec */ +#define FJES_COMMAND_REQ_TIMEOUT ((5 + 1) * 3 * 8) /* sec */ +#define FJES_COMMAND_REQ_BUFF_TIMEOUT (60 * 3) /* sec */ +#define FJES_COMMAND_EPSTOP_WAIT_TIMEOUT (1) /* sec */ + +#define FJES_CMD_REQ_ERR_INFO_PARAM (0x0001) +#define FJES_CMD_REQ_ERR_INFO_STATUS (0x0002) + +#define FJES_CMD_REQ_RES_CODE_NORMAL (0) +#define FJES_CMD_REQ_RES_CODE_BUSY (1) + +#define FJES_ZONING_STATUS_DISABLE (0x00) +#define FJES_ZONING_STATUS_ENABLE (0x01) +#define FJES_ZONING_STATUS_INVALID (0xFF) + +#define FJES_ZONING_ZONE_TYPE_NONE (0xFF) + +#define FJES_TX_DELAY_SEND_NONE (0) +#define FJES_TX_DELAY_SEND_PENDING (1) + +#define FJES_RX_STOP_REQ_NONE (0x0) +#define FJES_RX_STOP_REQ_DONE (0x1) +#define FJES_RX_STOP_REQ_REQUEST (0x2) +#define FJES_RX_POLL_WORK (0x4) +#define FJES_RX_MTU_CHANGING_DONE (0x8) + +#define EP_BUFFER_SIZE \ + (((sizeof(union ep_buffer_info) + (128 * (64 * 1024))) \ + / EP_BUFFER_INFO_SIZE) * EP_BUFFER_INFO_SIZE) + +#define EP_RING_NUM(buffer_size, frame_size) \ + (u32)((buffer_size) / (frame_size)) +#define EP_RING_INDEX(_num, _max) (((_num) + (_max)) % (_max)) +#define EP_RING_INDEX_INC(_num, _max) \ + ((_num) = EP_RING_INDEX((_num) + 1, (_max))) +#define EP_RING_FULL(_head, _tail, _max) \ + (0 == EP_RING_INDEX(((_tail) - (_head)), (_max))) +#define EP_RING_EMPTY(_head, _tail, _max) \ + (1 == EP_RING_INDEX(((_tail) - (_head)), (_max))) + +#define FJES_MTU_TO_BUFFER_SIZE(mtu) \ + (ETH_HLEN + VLAN_HLEN + (mtu) + ETH_FCS_LEN) +#define FJES_MTU_TO_FRAME_SIZE(mtu) \ + (sizeof(struct esmem_frame) + FJES_MTU_TO_BUFFER_SIZE(mtu)) +#define FJES_MTU_DEFINE(size) \ + ((size) - sizeof(struct esmem_frame) - \ + (ETH_HLEN + VLAN_HLEN + ETH_FCS_LEN)) + +#define FJES_DEV_COMMAND_INFO_REQ_LEN (4) +#define FJES_DEV_COMMAND_INFO_RES_LEN(epnum) (8 + 2 * (epnum)) +#define FJES_DEV_COMMAND_SHARE_BUFFER_REQ_LEN(txb, rxb) \ + (24 + (8 * ((txb) / EP_BUFFER_INFO_SIZE + (rxb) / EP_BUFFER_INFO_SIZE))) +#define FJES_DEV_COMMAND_SHARE_BUFFER_RES_LEN (8) +#define FJES_DEV_COMMAND_UNSHARE_BUFFER_REQ_LEN (8) +#define FJES_DEV_COMMAND_UNSHARE_BUFFER_RES_LEN (8) + +#define FJES_DEV_REQ_BUF_SIZE(maxep) \ + FJES_DEV_COMMAND_SHARE_BUFFER_REQ_LEN(EP_BUFFER_SIZE, EP_BUFFER_SIZE) +#define FJES_DEV_RES_BUF_SIZE(maxep) \ + FJES_DEV_COMMAND_INFO_RES_LEN(maxep) + +#define FJES_DEV_COMMAND_START_DBG_REQ_LEN(byte) \ + (16 + (8 * (byte) / FJES_DEBUG_PAGE_SIZE)) +#define FJES_DEV_COMMAND_START_DBG_RES_LEN (8) +#define FJES_DEV_COMMAND_STOP_DBG_REQ_LEN (4) +#define FJES_DEV_COMMAND_STOP_DBG_RES_LEN (8) + +/* Frame & MTU */ +struct esmem_frame { + __le32 frame_size; + u8 frame_data[]; +}; + +/* EP partner status */ +enum ep_partner_status { + EP_PARTNER_UNSHARE, + EP_PARTNER_SHARED, + EP_PARTNER_WAITING, + EP_PARTNER_COMPLETE, + EP_PARTNER_STATUS_MAX, +}; + +/* shared status region */ +struct fjes_device_shared_info { + int epnum; + u8 ep_status[]; +}; + +/* structures for command control request data*/ +union fjes_device_command_req { + struct { + __le32 length; + } info; + struct { + __le32 length; + __le32 epid; + __le64 buffer[]; + } share_buffer; + struct { + __le32 length; + __le32 epid; + } unshare_buffer; + struct { + __le32 length; + __le32 mode; + __le64 buffer_len; + __le64 buffer[]; + } start_trace; + struct { + __le32 length; + } stop_trace; +}; + +/* structures for command control response data */ +union fjes_device_command_res { + struct { + __le32 length; + __le32 code; + struct { + u8 es_status; + u8 zone; + } info[]; + } info; + struct { + __le32 length; + __le32 code; + } share_buffer; + struct { + __le32 length; + __le32 code; + } unshare_buffer; + struct { + __le32 length; + __le32 code; + } start_trace; + struct { + __le32 length; + __le32 code; + } stop_trace; +}; + +/* request command type */ +enum fjes_dev_command_request_type { + FJES_CMD_REQ_INFO = 0x0001, + FJES_CMD_REQ_SHARE_BUFFER = 0x0002, + FJES_CMD_REQ_UNSHARE_BUFFER = 0x0004, + FJES_CMD_REQ_START_DEBUG = 0x0100, + FJES_CMD_REQ_STOP_DEBUG = 0x0200, +}; + +/* parameter for command control */ +struct fjes_device_command_param { + u32 req_len; + phys_addr_t req_start; + u32 res_len; + phys_addr_t res_start; + phys_addr_t share_start; +}; + +/* error code for command control */ +enum fjes_dev_command_response_e { + FJES_CMD_STATUS_UNKNOWN, + FJES_CMD_STATUS_NORMAL, + FJES_CMD_STATUS_TIMEOUT, + FJES_CMD_STATUS_ERROR_PARAM, + FJES_CMD_STATUS_ERROR_STATUS, +}; + +/* EP buffer information */ +union ep_buffer_info { + u8 raw[EP_BUFFER_INFO_SIZE]; + + struct _ep_buffer_info_common_t { + u32 version; + } common; + + struct _ep_buffer_info_v1_t { + u32 version; + u32 info_size; + + u32 buffer_size; + u16 count_max; + + u16 _rsv_1; + + u32 frame_max; + u8 mac_addr[ETH_ALEN]; + + u16 _rsv_2; + u32 _rsv_3; + + u16 tx_status; + u16 rx_status; + + u32 head; + u32 tail; + + u16 vlan_id[EP_BUFFER_SUPPORT_VLAN_MAX]; + + } v1i; + +}; + +/* statistics of EP */ +struct fjes_drv_ep_stats { + u64 com_regist_buf_exec; + u64 com_unregist_buf_exec; + u64 send_intr_rx; + u64 send_intr_unshare; + u64 send_intr_zoneupdate; + u64 recv_intr_rx; + u64 recv_intr_unshare; + u64 recv_intr_stop; + u64 recv_intr_zoneupdate; + u64 tx_buffer_full; + u64 tx_dropped_not_shared; + u64 tx_dropped_ver_mismatch; + u64 tx_dropped_buf_size_mismatch; + u64 tx_dropped_vlanid_mismatch; +}; + +/* buffer pair for Extended Partition */ +struct ep_share_mem_info { + struct epbuf_handler { + void *buffer; + size_t size; + union ep_buffer_info *info; + u8 *ring; + } tx, rx; + + struct rtnl_link_stats64 net_stats; + struct fjes_drv_ep_stats ep_stats; + + u16 tx_status_work; + + u8 es_status; + u8 zone; +}; + +struct es_device_trace { + u32 record_num; + u32 current_record; + u32 status_flag; + u32 _rsv; + + struct { + u16 epid; + u16 dir_offset; + u32 data; + u64 tsc; + } record[]; +}; + +struct fjes_hw_info { + struct fjes_device_shared_info *share; + union fjes_device_command_req *req_buf; + u64 req_buf_size; + union fjes_device_command_res *res_buf; + u64 res_buf_size; + + int *my_epid; + int *max_epid; + + struct es_device_trace *trace; + u64 trace_size; + + struct mutex lock; /* buffer lock*/ + + unsigned long buffer_share_bit; + unsigned long buffer_unshare_reserve_bit; +}; + +struct fjes_hw { + void *back; + + unsigned long txrx_stop_req_bit; + unsigned long epstop_req_bit; + struct work_struct update_zone_task; + struct work_struct epstop_task; + + int my_epid; + int max_epid; + + struct ep_share_mem_info *ep_shm_info; + + struct fjes_hw_resource { + u64 start; + u64 size; + int irq; + } hw_res; + + u8 *base; + + struct fjes_hw_info hw_info; + + spinlock_t rx_status_lock; /* spinlock for rx_status */ + + u32 debug_mode; +}; + +int fjes_hw_init(struct fjes_hw *); +void fjes_hw_exit(struct fjes_hw *); +int fjes_hw_reset(struct fjes_hw *); +int fjes_hw_request_info(struct fjes_hw *); +int fjes_hw_register_buff_addr(struct fjes_hw *, int, + struct ep_share_mem_info *); +int fjes_hw_unregister_buff_addr(struct fjes_hw *, int); +void fjes_hw_init_command_registers(struct fjes_hw *, + struct fjes_device_command_param *); +void fjes_hw_setup_epbuf(struct epbuf_handler *, u8 *, u32); +int fjes_hw_raise_interrupt(struct fjes_hw *, int, enum REG_ICTL_MASK); +void fjes_hw_set_irqmask(struct fjes_hw *, enum REG_ICTL_MASK, bool); +u32 fjes_hw_capture_interrupt_status(struct fjes_hw *); +void fjes_hw_raise_epstop(struct fjes_hw *); +int fjes_hw_wait_epstop(struct fjes_hw *); +enum ep_partner_status + fjes_hw_get_partner_ep_status(struct fjes_hw *, int); + +bool fjes_hw_epid_is_same_zone(struct fjes_hw *, int); +int fjes_hw_epid_is_shared(struct fjes_device_shared_info *, int); +bool fjes_hw_check_epbuf_version(struct epbuf_handler *, u32); +bool fjes_hw_check_mtu(struct epbuf_handler *, u32); +bool fjes_hw_check_vlan_id(struct epbuf_handler *, u16); +bool fjes_hw_set_vlan_id(struct epbuf_handler *, u16); +void fjes_hw_del_vlan_id(struct epbuf_handler *, u16); +bool fjes_hw_epbuf_rx_is_empty(struct epbuf_handler *); +void *fjes_hw_epbuf_rx_curpkt_get_addr(struct epbuf_handler *, size_t *); +void fjes_hw_epbuf_rx_curpkt_drop(struct epbuf_handler *); +int fjes_hw_epbuf_tx_pkt_send(struct epbuf_handler *, void *, size_t); + +int fjes_hw_start_debug(struct fjes_hw *); +int fjes_hw_stop_debug(struct fjes_hw *); +#endif /* FJES_HW_H_ */ diff --git a/drivers/net/fjes/fjes_main.c b/drivers/net/fjes/fjes_main.c new file mode 100644 index 000000000..89b3bc389 --- /dev/null +++ b/drivers/net/fjes/fjes_main.c @@ -0,0 +1,1603 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/nls.h> +#include <linux/platform_device.h> +#include <linux/netdevice.h> +#include <linux/interrupt.h> + +#include "fjes.h" +#include "fjes_trace.h" + +#define MAJ 1 +#define MIN 2 +#define DRV_VERSION __stringify(MAJ) "." __stringify(MIN) +#define DRV_NAME "fjes" +char fjes_driver_name[] = DRV_NAME; +char fjes_driver_version[] = DRV_VERSION; +static const char fjes_driver_string[] = + "FUJITSU Extended Socket Network Device Driver"; +static const char fjes_copyright[] = + "Copyright (c) 2015 FUJITSU LIMITED"; + +MODULE_AUTHOR("Taku Izumi <izumi.taku@jp.fujitsu.com>"); +MODULE_DESCRIPTION("FUJITSU Extended Socket Network Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); + +#define ACPI_MOTHERBOARD_RESOURCE_HID "PNP0C02" + +static int fjes_request_irq(struct fjes_adapter *); +static void fjes_free_irq(struct fjes_adapter *); + +static int fjes_open(struct net_device *); +static int fjes_close(struct net_device *); +static int fjes_setup_resources(struct fjes_adapter *); +static void fjes_free_resources(struct fjes_adapter *); +static netdev_tx_t fjes_xmit_frame(struct sk_buff *, struct net_device *); +static void fjes_raise_intr_rxdata_task(struct work_struct *); +static void fjes_tx_stall_task(struct work_struct *); +static void fjes_force_close_task(struct work_struct *); +static irqreturn_t fjes_intr(int, void*); +static void fjes_get_stats64(struct net_device *, struct rtnl_link_stats64 *); +static int fjes_change_mtu(struct net_device *, int); +static int fjes_vlan_rx_add_vid(struct net_device *, __be16 proto, u16); +static int fjes_vlan_rx_kill_vid(struct net_device *, __be16 proto, u16); +static void fjes_tx_retry(struct net_device *); + +static int fjes_acpi_add(struct acpi_device *); +static int fjes_acpi_remove(struct acpi_device *); +static acpi_status fjes_get_acpi_resource(struct acpi_resource *, void*); + +static int fjes_probe(struct platform_device *); +static int fjes_remove(struct platform_device *); + +static int fjes_sw_init(struct fjes_adapter *); +static void fjes_netdev_setup(struct net_device *); +static void fjes_irq_watch_task(struct work_struct *); +static void fjes_watch_unshare_task(struct work_struct *); +static void fjes_rx_irq(struct fjes_adapter *, int); +static int fjes_poll(struct napi_struct *, int); + +static const struct acpi_device_id fjes_acpi_ids[] = { + {ACPI_MOTHERBOARD_RESOURCE_HID, 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, fjes_acpi_ids); + +static struct acpi_driver fjes_acpi_driver = { + .name = DRV_NAME, + .class = DRV_NAME, + .owner = THIS_MODULE, + .ids = fjes_acpi_ids, + .ops = { + .add = fjes_acpi_add, + .remove = fjes_acpi_remove, + }, +}; + +static struct platform_driver fjes_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = fjes_probe, + .remove = fjes_remove, +}; + +static struct resource fjes_resource[] = { + { + .flags = IORESOURCE_MEM, + .start = 0, + .end = 0, + }, + { + .flags = IORESOURCE_IRQ, + .start = 0, + .end = 0, + }, +}; + +static bool is_extended_socket_device(struct acpi_device *device) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL}; + char str_buf[sizeof(FJES_ACPI_SYMBOL) + 1]; + union acpi_object *str; + acpi_status status; + int result; + + status = acpi_evaluate_object(device->handle, "_STR", NULL, &buffer); + if (ACPI_FAILURE(status)) + return false; + + str = buffer.pointer; + result = utf16s_to_utf8s((wchar_t *)str->string.pointer, + str->string.length, UTF16_LITTLE_ENDIAN, + str_buf, sizeof(str_buf) - 1); + str_buf[result] = 0; + + if (strncmp(FJES_ACPI_SYMBOL, str_buf, strlen(FJES_ACPI_SYMBOL)) != 0) { + kfree(buffer.pointer); + return false; + } + kfree(buffer.pointer); + + return true; +} + +static int acpi_check_extended_socket_status(struct acpi_device *device) +{ + unsigned long long sta; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "_STA", NULL, &sta); + if (ACPI_FAILURE(status)) + return -ENODEV; + + if (!((sta & ACPI_STA_DEVICE_PRESENT) && + (sta & ACPI_STA_DEVICE_ENABLED) && + (sta & ACPI_STA_DEVICE_UI) && + (sta & ACPI_STA_DEVICE_FUNCTIONING))) + return -ENODEV; + + return 0; +} + +static int fjes_acpi_add(struct acpi_device *device) +{ + struct platform_device *plat_dev; + acpi_status status; + + if (!is_extended_socket_device(device)) + return -ENODEV; + + if (acpi_check_extended_socket_status(device)) + return -ENODEV; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + fjes_get_acpi_resource, fjes_resource); + if (ACPI_FAILURE(status)) + return -ENODEV; + + /* create platform_device */ + plat_dev = platform_device_register_simple(DRV_NAME, 0, fjes_resource, + ARRAY_SIZE(fjes_resource)); + if (IS_ERR(plat_dev)) + return PTR_ERR(plat_dev); + + device->driver_data = plat_dev; + + return 0; +} + +static int fjes_acpi_remove(struct acpi_device *device) +{ + struct platform_device *plat_dev; + + plat_dev = (struct platform_device *)acpi_driver_data(device); + platform_device_unregister(plat_dev); + + return 0; +} + +static acpi_status +fjes_get_acpi_resource(struct acpi_resource *acpi_res, void *data) +{ + struct acpi_resource_address32 *addr; + struct acpi_resource_irq *irq; + struct resource *res = data; + + switch (acpi_res->type) { + case ACPI_RESOURCE_TYPE_ADDRESS32: + addr = &acpi_res->data.address32; + res[0].start = addr->address.minimum; + res[0].end = addr->address.minimum + + addr->address.address_length - 1; + break; + + case ACPI_RESOURCE_TYPE_IRQ: + irq = &acpi_res->data.irq; + if (irq->interrupt_count != 1) + return AE_ERROR; + res[1].start = irq->interrupts[0]; + res[1].end = irq->interrupts[0]; + break; + + default: + break; + } + + return AE_OK; +} + +static int fjes_request_irq(struct fjes_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + int result = -1; + + adapter->interrupt_watch_enable = true; + if (!delayed_work_pending(&adapter->interrupt_watch_task)) { + queue_delayed_work(adapter->control_wq, + &adapter->interrupt_watch_task, + FJES_IRQ_WATCH_DELAY); + } + + if (!adapter->irq_registered) { + result = request_irq(adapter->hw.hw_res.irq, fjes_intr, + IRQF_SHARED, netdev->name, adapter); + if (result) + adapter->irq_registered = false; + else + adapter->irq_registered = true; + } + + return result; +} + +static void fjes_free_irq(struct fjes_adapter *adapter) +{ + struct fjes_hw *hw = &adapter->hw; + + adapter->interrupt_watch_enable = false; + cancel_delayed_work_sync(&adapter->interrupt_watch_task); + + fjes_hw_set_irqmask(hw, REG_ICTL_MASK_ALL, true); + + if (adapter->irq_registered) { + free_irq(adapter->hw.hw_res.irq, adapter); + adapter->irq_registered = false; + } +} + +static const struct net_device_ops fjes_netdev_ops = { + .ndo_open = fjes_open, + .ndo_stop = fjes_close, + .ndo_start_xmit = fjes_xmit_frame, + .ndo_get_stats64 = fjes_get_stats64, + .ndo_change_mtu = fjes_change_mtu, + .ndo_tx_timeout = fjes_tx_retry, + .ndo_vlan_rx_add_vid = fjes_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = fjes_vlan_rx_kill_vid, +}; + +/* fjes_open - Called when a network interface is made active */ +static int fjes_open(struct net_device *netdev) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + int result; + + if (adapter->open_guard) + return -ENXIO; + + result = fjes_setup_resources(adapter); + if (result) + goto err_setup_res; + + hw->txrx_stop_req_bit = 0; + hw->epstop_req_bit = 0; + + napi_enable(&adapter->napi); + + fjes_hw_capture_interrupt_status(hw); + + result = fjes_request_irq(adapter); + if (result) + goto err_req_irq; + + fjes_hw_set_irqmask(hw, REG_ICTL_MASK_ALL, false); + + netif_tx_start_all_queues(netdev); + netif_carrier_on(netdev); + + return 0; + +err_req_irq: + fjes_free_irq(adapter); + napi_disable(&adapter->napi); + +err_setup_res: + fjes_free_resources(adapter); + return result; +} + +/* fjes_close - Disables a network interface */ +static int fjes_close(struct net_device *netdev) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + unsigned long flags; + int epidx; + + netif_tx_stop_all_queues(netdev); + netif_carrier_off(netdev); + + fjes_hw_raise_epstop(hw); + + napi_disable(&adapter->napi); + + spin_lock_irqsave(&hw->rx_status_lock, flags); + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + if (fjes_hw_get_partner_ep_status(hw, epidx) == + EP_PARTNER_SHARED) + adapter->hw.ep_shm_info[epidx] + .tx.info->v1i.rx_status &= + ~FJES_RX_POLL_WORK; + } + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + + fjes_free_irq(adapter); + + cancel_delayed_work_sync(&adapter->interrupt_watch_task); + cancel_work_sync(&adapter->unshare_watch_task); + adapter->unshare_watch_bitmask = 0; + cancel_work_sync(&adapter->raise_intr_rxdata_task); + cancel_work_sync(&adapter->tx_stall_task); + + cancel_work_sync(&hw->update_zone_task); + cancel_work_sync(&hw->epstop_task); + + fjes_hw_wait_epstop(hw); + + fjes_free_resources(adapter); + + return 0; +} + +static int fjes_setup_resources(struct fjes_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + struct ep_share_mem_info *buf_pair; + struct fjes_hw *hw = &adapter->hw; + unsigned long flags; + int result; + int epidx; + + mutex_lock(&hw->hw_info.lock); + result = fjes_hw_request_info(hw); + switch (result) { + case 0: + for (epidx = 0; epidx < hw->max_epid; epidx++) { + hw->ep_shm_info[epidx].es_status = + hw->hw_info.res_buf->info.info[epidx].es_status; + hw->ep_shm_info[epidx].zone = + hw->hw_info.res_buf->info.info[epidx].zone; + } + break; + default: + case -ENOMSG: + case -EBUSY: + adapter->force_reset = true; + + mutex_unlock(&hw->hw_info.lock); + return result; + } + mutex_unlock(&hw->hw_info.lock); + + for (epidx = 0; epidx < (hw->max_epid); epidx++) { + if ((epidx != hw->my_epid) && + (hw->ep_shm_info[epidx].es_status == + FJES_ZONING_STATUS_ENABLE)) { + fjes_hw_raise_interrupt(hw, epidx, + REG_ICTL_MASK_INFO_UPDATE); + hw->ep_shm_info[epidx].ep_stats + .send_intr_zoneupdate += 1; + } + } + + msleep(FJES_OPEN_ZONE_UPDATE_WAIT * hw->max_epid); + + for (epidx = 0; epidx < (hw->max_epid); epidx++) { + if (epidx == hw->my_epid) + continue; + + buf_pair = &hw->ep_shm_info[epidx]; + + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf(&buf_pair->tx, netdev->dev_addr, + netdev->mtu); + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + + if (fjes_hw_epid_is_same_zone(hw, epidx)) { + mutex_lock(&hw->hw_info.lock); + result = + fjes_hw_register_buff_addr(hw, epidx, buf_pair); + mutex_unlock(&hw->hw_info.lock); + + switch (result) { + case 0: + break; + case -ENOMSG: + case -EBUSY: + default: + adapter->force_reset = true; + return result; + } + + hw->ep_shm_info[epidx].ep_stats + .com_regist_buf_exec += 1; + } + } + + return 0; +} + +static void fjes_free_resources(struct fjes_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + struct fjes_device_command_param param; + struct ep_share_mem_info *buf_pair; + struct fjes_hw *hw = &adapter->hw; + bool reset_flag = false; + unsigned long flags; + int result; + int epidx; + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + mutex_lock(&hw->hw_info.lock); + result = fjes_hw_unregister_buff_addr(hw, epidx); + mutex_unlock(&hw->hw_info.lock); + + hw->ep_shm_info[epidx].ep_stats.com_unregist_buf_exec += 1; + + if (result) + reset_flag = true; + + buf_pair = &hw->ep_shm_info[epidx]; + + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf(&buf_pair->tx, + netdev->dev_addr, netdev->mtu); + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + + clear_bit(epidx, &hw->txrx_stop_req_bit); + } + + if (reset_flag || adapter->force_reset) { + result = fjes_hw_reset(hw); + + adapter->force_reset = false; + + if (result) + adapter->open_guard = true; + + hw->hw_info.buffer_share_bit = 0; + + memset((void *)¶m, 0, sizeof(param)); + + param.req_len = hw->hw_info.req_buf_size; + param.req_start = __pa(hw->hw_info.req_buf); + param.res_len = hw->hw_info.res_buf_size; + param.res_start = __pa(hw->hw_info.res_buf); + param.share_start = __pa(hw->hw_info.share->ep_status); + + fjes_hw_init_command_registers(hw, ¶m); + } +} + +static void fjes_tx_stall_task(struct work_struct *work) +{ + struct fjes_adapter *adapter = container_of(work, + struct fjes_adapter, tx_stall_task); + struct net_device *netdev = adapter->netdev; + struct fjes_hw *hw = &adapter->hw; + int all_queue_available, sendable; + enum ep_partner_status pstatus; + int max_epid, my_epid, epid; + union ep_buffer_info *info; + int i; + + if (((long)jiffies - + dev_trans_start(netdev)) > FJES_TX_TX_STALL_TIMEOUT) { + netif_wake_queue(netdev); + return; + } + + my_epid = hw->my_epid; + max_epid = hw->max_epid; + + for (i = 0; i < 5; i++) { + all_queue_available = 1; + + for (epid = 0; epid < max_epid; epid++) { + if (my_epid == epid) + continue; + + pstatus = fjes_hw_get_partner_ep_status(hw, epid); + sendable = (pstatus == EP_PARTNER_SHARED); + if (!sendable) + continue; + + info = adapter->hw.ep_shm_info[epid].tx.info; + + if (!(info->v1i.rx_status & FJES_RX_MTU_CHANGING_DONE)) + return; + + if (EP_RING_FULL(info->v1i.head, info->v1i.tail, + info->v1i.count_max)) { + all_queue_available = 0; + break; + } + } + + if (all_queue_available) { + netif_wake_queue(netdev); + return; + } + } + + usleep_range(50, 100); + + queue_work(adapter->txrx_wq, &adapter->tx_stall_task); +} + +static void fjes_force_close_task(struct work_struct *work) +{ + struct fjes_adapter *adapter = container_of(work, + struct fjes_adapter, force_close_task); + struct net_device *netdev = adapter->netdev; + + rtnl_lock(); + dev_close(netdev); + rtnl_unlock(); +} + +static void fjes_raise_intr_rxdata_task(struct work_struct *work) +{ + struct fjes_adapter *adapter = container_of(work, + struct fjes_adapter, raise_intr_rxdata_task); + struct fjes_hw *hw = &adapter->hw; + enum ep_partner_status pstatus; + int max_epid, my_epid, epid; + + my_epid = hw->my_epid; + max_epid = hw->max_epid; + + for (epid = 0; epid < max_epid; epid++) + hw->ep_shm_info[epid].tx_status_work = 0; + + for (epid = 0; epid < max_epid; epid++) { + if (epid == my_epid) + continue; + + pstatus = fjes_hw_get_partner_ep_status(hw, epid); + if (pstatus == EP_PARTNER_SHARED) { + hw->ep_shm_info[epid].tx_status_work = + hw->ep_shm_info[epid].tx.info->v1i.tx_status; + + if (hw->ep_shm_info[epid].tx_status_work == + FJES_TX_DELAY_SEND_PENDING) { + hw->ep_shm_info[epid].tx.info->v1i.tx_status = + FJES_TX_DELAY_SEND_NONE; + } + } + } + + for (epid = 0; epid < max_epid; epid++) { + if (epid == my_epid) + continue; + + pstatus = fjes_hw_get_partner_ep_status(hw, epid); + if ((hw->ep_shm_info[epid].tx_status_work == + FJES_TX_DELAY_SEND_PENDING) && + (pstatus == EP_PARTNER_SHARED) && + !(hw->ep_shm_info[epid].rx.info->v1i.rx_status & + FJES_RX_POLL_WORK)) { + fjes_hw_raise_interrupt(hw, epid, + REG_ICTL_MASK_RX_DATA); + hw->ep_shm_info[epid].ep_stats.send_intr_rx += 1; + } + } + + usleep_range(500, 1000); +} + +static int fjes_tx_send(struct fjes_adapter *adapter, int dest, + void *data, size_t len) +{ + int retval; + + retval = fjes_hw_epbuf_tx_pkt_send(&adapter->hw.ep_shm_info[dest].tx, + data, len); + if (retval) + return retval; + + adapter->hw.ep_shm_info[dest].tx.info->v1i.tx_status = + FJES_TX_DELAY_SEND_PENDING; + if (!work_pending(&adapter->raise_intr_rxdata_task)) + queue_work(adapter->txrx_wq, + &adapter->raise_intr_rxdata_task); + + retval = 0; + return retval; +} + +static netdev_tx_t +fjes_xmit_frame(struct sk_buff *skb, struct net_device *netdev) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + + int max_epid, my_epid, dest_epid; + enum ep_partner_status pstatus; + struct netdev_queue *cur_queue; + char shortpkt[VLAN_ETH_HLEN]; + bool is_multi, vlan; + struct ethhdr *eth; + u16 queue_no = 0; + u16 vlan_id = 0; + netdev_tx_t ret; + char *data; + int len; + + ret = NETDEV_TX_OK; + is_multi = false; + cur_queue = netdev_get_tx_queue(netdev, queue_no); + + eth = (struct ethhdr *)skb->data; + my_epid = hw->my_epid; + + vlan = (vlan_get_tag(skb, &vlan_id) == 0) ? true : false; + + data = skb->data; + len = skb->len; + + if (is_multicast_ether_addr(eth->h_dest)) { + dest_epid = 0; + max_epid = hw->max_epid; + is_multi = true; + } else if (is_local_ether_addr(eth->h_dest)) { + dest_epid = eth->h_dest[ETH_ALEN - 1]; + max_epid = dest_epid + 1; + + if ((eth->h_dest[0] == 0x02) && + (0x00 == (eth->h_dest[1] | eth->h_dest[2] | + eth->h_dest[3] | eth->h_dest[4])) && + (dest_epid < hw->max_epid)) { + ; + } else { + dest_epid = 0; + max_epid = 0; + ret = NETDEV_TX_OK; + + adapter->stats64.tx_packets += 1; + hw->ep_shm_info[my_epid].net_stats.tx_packets += 1; + adapter->stats64.tx_bytes += len; + hw->ep_shm_info[my_epid].net_stats.tx_bytes += len; + } + } else { + dest_epid = 0; + max_epid = 0; + ret = NETDEV_TX_OK; + + adapter->stats64.tx_packets += 1; + hw->ep_shm_info[my_epid].net_stats.tx_packets += 1; + adapter->stats64.tx_bytes += len; + hw->ep_shm_info[my_epid].net_stats.tx_bytes += len; + } + + for (; dest_epid < max_epid; dest_epid++) { + if (my_epid == dest_epid) + continue; + + pstatus = fjes_hw_get_partner_ep_status(hw, dest_epid); + if (pstatus != EP_PARTNER_SHARED) { + if (!is_multi) + hw->ep_shm_info[dest_epid].ep_stats + .tx_dropped_not_shared += 1; + ret = NETDEV_TX_OK; + } else if (!fjes_hw_check_epbuf_version( + &adapter->hw.ep_shm_info[dest_epid].rx, 0)) { + /* version is NOT 0 */ + adapter->stats64.tx_carrier_errors += 1; + hw->ep_shm_info[dest_epid].net_stats + .tx_carrier_errors += 1; + hw->ep_shm_info[dest_epid].ep_stats + .tx_dropped_ver_mismatch += 1; + + ret = NETDEV_TX_OK; + } else if (!fjes_hw_check_mtu( + &adapter->hw.ep_shm_info[dest_epid].rx, + netdev->mtu)) { + adapter->stats64.tx_dropped += 1; + hw->ep_shm_info[dest_epid].net_stats.tx_dropped += 1; + adapter->stats64.tx_errors += 1; + hw->ep_shm_info[dest_epid].net_stats.tx_errors += 1; + hw->ep_shm_info[dest_epid].ep_stats + .tx_dropped_buf_size_mismatch += 1; + + ret = NETDEV_TX_OK; + } else if (vlan && + !fjes_hw_check_vlan_id( + &adapter->hw.ep_shm_info[dest_epid].rx, + vlan_id)) { + hw->ep_shm_info[dest_epid].ep_stats + .tx_dropped_vlanid_mismatch += 1; + ret = NETDEV_TX_OK; + } else { + if (len < VLAN_ETH_HLEN) { + memset(shortpkt, 0, VLAN_ETH_HLEN); + memcpy(shortpkt, skb->data, skb->len); + len = VLAN_ETH_HLEN; + data = shortpkt; + } + + if (adapter->tx_retry_count == 0) { + adapter->tx_start_jiffies = jiffies; + adapter->tx_retry_count = 1; + } else { + adapter->tx_retry_count++; + } + + if (fjes_tx_send(adapter, dest_epid, data, len)) { + if (is_multi) { + ret = NETDEV_TX_OK; + } else if ( + ((long)jiffies - + (long)adapter->tx_start_jiffies) >= + FJES_TX_RETRY_TIMEOUT) { + adapter->stats64.tx_fifo_errors += 1; + hw->ep_shm_info[dest_epid].net_stats + .tx_fifo_errors += 1; + adapter->stats64.tx_errors += 1; + hw->ep_shm_info[dest_epid].net_stats + .tx_errors += 1; + + ret = NETDEV_TX_OK; + } else { + netif_trans_update(netdev); + hw->ep_shm_info[dest_epid].ep_stats + .tx_buffer_full += 1; + netif_tx_stop_queue(cur_queue); + + if (!work_pending(&adapter->tx_stall_task)) + queue_work(adapter->txrx_wq, + &adapter->tx_stall_task); + + ret = NETDEV_TX_BUSY; + } + } else { + if (!is_multi) { + adapter->stats64.tx_packets += 1; + hw->ep_shm_info[dest_epid].net_stats + .tx_packets += 1; + adapter->stats64.tx_bytes += len; + hw->ep_shm_info[dest_epid].net_stats + .tx_bytes += len; + } + + adapter->tx_retry_count = 0; + ret = NETDEV_TX_OK; + } + } + } + + if (ret == NETDEV_TX_OK) { + dev_kfree_skb(skb); + if (is_multi) { + adapter->stats64.tx_packets += 1; + hw->ep_shm_info[my_epid].net_stats.tx_packets += 1; + adapter->stats64.tx_bytes += 1; + hw->ep_shm_info[my_epid].net_stats.tx_bytes += len; + } + } + + return ret; +} + +static void fjes_tx_retry(struct net_device *netdev) +{ + struct netdev_queue *queue = netdev_get_tx_queue(netdev, 0); + + netif_tx_wake_queue(queue); +} + +static void +fjes_get_stats64(struct net_device *netdev, struct rtnl_link_stats64 *stats) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + + memcpy(stats, &adapter->stats64, sizeof(struct rtnl_link_stats64)); +} + +static int fjes_change_mtu(struct net_device *netdev, int new_mtu) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + bool running = netif_running(netdev); + struct fjes_hw *hw = &adapter->hw; + unsigned long flags; + int ret = -EINVAL; + int idx, epidx; + + for (idx = 0; fjes_support_mtu[idx] != 0; idx++) { + if (new_mtu <= fjes_support_mtu[idx]) { + new_mtu = fjes_support_mtu[idx]; + if (new_mtu == netdev->mtu) + return 0; + + ret = 0; + break; + } + } + + if (ret) + return ret; + + if (running) { + spin_lock_irqsave(&hw->rx_status_lock, flags); + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + hw->ep_shm_info[epidx].tx.info->v1i.rx_status &= + ~FJES_RX_MTU_CHANGING_DONE; + } + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + + netif_tx_stop_all_queues(netdev); + netif_carrier_off(netdev); + cancel_work_sync(&adapter->tx_stall_task); + napi_disable(&adapter->napi); + + msleep(1000); + + netif_tx_stop_all_queues(netdev); + } + + netdev->mtu = new_mtu; + + if (running) { + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf(&hw->ep_shm_info[epidx].tx, + netdev->dev_addr, + netdev->mtu); + + hw->ep_shm_info[epidx].tx.info->v1i.rx_status |= + FJES_RX_MTU_CHANGING_DONE; + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + } + + netif_tx_wake_all_queues(netdev); + netif_carrier_on(netdev); + napi_enable(&adapter->napi); + napi_schedule(&adapter->napi); + } + + return ret; +} + +static int fjes_vlan_rx_add_vid(struct net_device *netdev, + __be16 proto, u16 vid) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + bool ret = true; + int epid; + + for (epid = 0; epid < adapter->hw.max_epid; epid++) { + if (epid == adapter->hw.my_epid) + continue; + + if (!fjes_hw_check_vlan_id( + &adapter->hw.ep_shm_info[epid].tx, vid)) + ret = fjes_hw_set_vlan_id( + &adapter->hw.ep_shm_info[epid].tx, vid); + } + + return ret ? 0 : -ENOSPC; +} + +static int fjes_vlan_rx_kill_vid(struct net_device *netdev, + __be16 proto, u16 vid) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + int epid; + + for (epid = 0; epid < adapter->hw.max_epid; epid++) { + if (epid == adapter->hw.my_epid) + continue; + + fjes_hw_del_vlan_id(&adapter->hw.ep_shm_info[epid].tx, vid); + } + + return 0; +} + +static void fjes_txrx_stop_req_irq(struct fjes_adapter *adapter, + int src_epid) +{ + struct fjes_hw *hw = &adapter->hw; + enum ep_partner_status status; + unsigned long flags; + + status = fjes_hw_get_partner_ep_status(hw, src_epid); + trace_fjes_txrx_stop_req_irq_pre(hw, src_epid, status); + switch (status) { + case EP_PARTNER_UNSHARE: + case EP_PARTNER_COMPLETE: + default: + break; + case EP_PARTNER_WAITING: + if (src_epid < hw->my_epid) { + spin_lock_irqsave(&hw->rx_status_lock, flags); + hw->ep_shm_info[src_epid].tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_DONE; + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + + clear_bit(src_epid, &hw->txrx_stop_req_bit); + set_bit(src_epid, &adapter->unshare_watch_bitmask); + + if (!work_pending(&adapter->unshare_watch_task)) + queue_work(adapter->control_wq, + &adapter->unshare_watch_task); + } + break; + case EP_PARTNER_SHARED: + if (hw->ep_shm_info[src_epid].rx.info->v1i.rx_status & + FJES_RX_STOP_REQ_REQUEST) { + set_bit(src_epid, &hw->epstop_req_bit); + if (!work_pending(&hw->epstop_task)) + queue_work(adapter->control_wq, + &hw->epstop_task); + } + break; + } + trace_fjes_txrx_stop_req_irq_post(hw, src_epid); +} + +static void fjes_stop_req_irq(struct fjes_adapter *adapter, int src_epid) +{ + struct fjes_hw *hw = &adapter->hw; + enum ep_partner_status status; + unsigned long flags; + + set_bit(src_epid, &hw->hw_info.buffer_unshare_reserve_bit); + + status = fjes_hw_get_partner_ep_status(hw, src_epid); + trace_fjes_stop_req_irq_pre(hw, src_epid, status); + switch (status) { + case EP_PARTNER_WAITING: + spin_lock_irqsave(&hw->rx_status_lock, flags); + hw->ep_shm_info[src_epid].tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_DONE; + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + clear_bit(src_epid, &hw->txrx_stop_req_bit); + /* fall through */ + case EP_PARTNER_UNSHARE: + case EP_PARTNER_COMPLETE: + default: + set_bit(src_epid, &adapter->unshare_watch_bitmask); + if (!work_pending(&adapter->unshare_watch_task)) + queue_work(adapter->control_wq, + &adapter->unshare_watch_task); + break; + case EP_PARTNER_SHARED: + set_bit(src_epid, &hw->epstop_req_bit); + + if (!work_pending(&hw->epstop_task)) + queue_work(adapter->control_wq, &hw->epstop_task); + break; + } + trace_fjes_stop_req_irq_post(hw, src_epid); +} + +static void fjes_update_zone_irq(struct fjes_adapter *adapter, + int src_epid) +{ + struct fjes_hw *hw = &adapter->hw; + + if (!work_pending(&hw->update_zone_task)) + queue_work(adapter->control_wq, &hw->update_zone_task); +} + +static irqreturn_t fjes_intr(int irq, void *data) +{ + struct fjes_adapter *adapter = data; + struct fjes_hw *hw = &adapter->hw; + irqreturn_t ret; + u32 icr; + + icr = fjes_hw_capture_interrupt_status(hw); + + if (icr & REG_IS_MASK_IS_ASSERT) { + if (icr & REG_ICTL_MASK_RX_DATA) { + fjes_rx_irq(adapter, icr & REG_IS_MASK_EPID); + hw->ep_shm_info[icr & REG_IS_MASK_EPID].ep_stats + .recv_intr_rx += 1; + } + + if (icr & REG_ICTL_MASK_DEV_STOP_REQ) { + fjes_stop_req_irq(adapter, icr & REG_IS_MASK_EPID); + hw->ep_shm_info[icr & REG_IS_MASK_EPID].ep_stats + .recv_intr_stop += 1; + } + + if (icr & REG_ICTL_MASK_TXRX_STOP_REQ) { + fjes_txrx_stop_req_irq(adapter, icr & REG_IS_MASK_EPID); + hw->ep_shm_info[icr & REG_IS_MASK_EPID].ep_stats + .recv_intr_unshare += 1; + } + + if (icr & REG_ICTL_MASK_TXRX_STOP_DONE) + fjes_hw_set_irqmask(hw, + REG_ICTL_MASK_TXRX_STOP_DONE, true); + + if (icr & REG_ICTL_MASK_INFO_UPDATE) { + fjes_update_zone_irq(adapter, icr & REG_IS_MASK_EPID); + hw->ep_shm_info[icr & REG_IS_MASK_EPID].ep_stats + .recv_intr_zoneupdate += 1; + } + + ret = IRQ_HANDLED; + } else { + ret = IRQ_NONE; + } + + return ret; +} + +static int fjes_rxframe_search_exist(struct fjes_adapter *adapter, + int start_epid) +{ + struct fjes_hw *hw = &adapter->hw; + enum ep_partner_status pstatus; + int max_epid, cur_epid; + int i; + + max_epid = hw->max_epid; + start_epid = (start_epid + 1 + max_epid) % max_epid; + + for (i = 0; i < max_epid; i++) { + cur_epid = (start_epid + i) % max_epid; + if (cur_epid == hw->my_epid) + continue; + + pstatus = fjes_hw_get_partner_ep_status(hw, cur_epid); + if (pstatus == EP_PARTNER_SHARED) { + if (!fjes_hw_epbuf_rx_is_empty( + &hw->ep_shm_info[cur_epid].rx)) + return cur_epid; + } + } + return -1; +} + +static void *fjes_rxframe_get(struct fjes_adapter *adapter, size_t *psize, + int *cur_epid) +{ + void *frame; + + *cur_epid = fjes_rxframe_search_exist(adapter, *cur_epid); + if (*cur_epid < 0) + return NULL; + + frame = + fjes_hw_epbuf_rx_curpkt_get_addr( + &adapter->hw.ep_shm_info[*cur_epid].rx, psize); + + return frame; +} + +static void fjes_rxframe_release(struct fjes_adapter *adapter, int cur_epid) +{ + fjes_hw_epbuf_rx_curpkt_drop(&adapter->hw.ep_shm_info[cur_epid].rx); +} + +static void fjes_rx_irq(struct fjes_adapter *adapter, int src_epid) +{ + struct fjes_hw *hw = &adapter->hw; + + fjes_hw_set_irqmask(hw, REG_ICTL_MASK_RX_DATA, true); + + adapter->unset_rx_last = true; + napi_schedule(&adapter->napi); +} + +static int fjes_poll(struct napi_struct *napi, int budget) +{ + struct fjes_adapter *adapter = + container_of(napi, struct fjes_adapter, napi); + struct net_device *netdev = napi->dev; + struct fjes_hw *hw = &adapter->hw; + struct sk_buff *skb; + int work_done = 0; + int cur_epid = 0; + int epidx; + size_t frame_len; + void *frame; + + spin_lock(&hw->rx_status_lock); + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + if (fjes_hw_get_partner_ep_status(hw, epidx) == + EP_PARTNER_SHARED) + adapter->hw.ep_shm_info[epidx] + .tx.info->v1i.rx_status |= FJES_RX_POLL_WORK; + } + spin_unlock(&hw->rx_status_lock); + + while (work_done < budget) { + prefetch(&adapter->hw); + frame = fjes_rxframe_get(adapter, &frame_len, &cur_epid); + + if (frame) { + skb = napi_alloc_skb(napi, frame_len); + if (!skb) { + adapter->stats64.rx_dropped += 1; + hw->ep_shm_info[cur_epid].net_stats + .rx_dropped += 1; + adapter->stats64.rx_errors += 1; + hw->ep_shm_info[cur_epid].net_stats + .rx_errors += 1; + } else { + skb_put_data(skb, frame, frame_len); + skb->protocol = eth_type_trans(skb, netdev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + netif_receive_skb(skb); + + work_done++; + + adapter->stats64.rx_packets += 1; + hw->ep_shm_info[cur_epid].net_stats + .rx_packets += 1; + adapter->stats64.rx_bytes += frame_len; + hw->ep_shm_info[cur_epid].net_stats + .rx_bytes += frame_len; + + if (is_multicast_ether_addr( + ((struct ethhdr *)frame)->h_dest)) { + adapter->stats64.multicast += 1; + hw->ep_shm_info[cur_epid].net_stats + .multicast += 1; + } + } + + fjes_rxframe_release(adapter, cur_epid); + adapter->unset_rx_last = true; + } else { + break; + } + } + + if (work_done < budget) { + napi_complete_done(napi, work_done); + + if (adapter->unset_rx_last) { + adapter->rx_last_jiffies = jiffies; + adapter->unset_rx_last = false; + } + + if (((long)jiffies - (long)adapter->rx_last_jiffies) < 3) { + napi_reschedule(napi); + } else { + spin_lock(&hw->rx_status_lock); + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + if (fjes_hw_get_partner_ep_status(hw, epidx) == + EP_PARTNER_SHARED) + adapter->hw.ep_shm_info[epidx].tx + .info->v1i.rx_status &= + ~FJES_RX_POLL_WORK; + } + spin_unlock(&hw->rx_status_lock); + + fjes_hw_set_irqmask(hw, REG_ICTL_MASK_RX_DATA, false); + } + } + + return work_done; +} + +/* fjes_probe - Device Initialization Routine */ +static int fjes_probe(struct platform_device *plat_dev) +{ + struct fjes_adapter *adapter; + struct net_device *netdev; + struct resource *res; + struct fjes_hw *hw; + int err; + + err = -ENOMEM; + netdev = alloc_netdev_mq(sizeof(struct fjes_adapter), "es%d", + NET_NAME_UNKNOWN, fjes_netdev_setup, + FJES_MAX_QUEUES); + + if (!netdev) + goto err_out; + + SET_NETDEV_DEV(netdev, &plat_dev->dev); + + dev_set_drvdata(&plat_dev->dev, netdev); + adapter = netdev_priv(netdev); + adapter->netdev = netdev; + adapter->plat_dev = plat_dev; + hw = &adapter->hw; + hw->back = adapter; + + /* setup the private structure */ + err = fjes_sw_init(adapter); + if (err) + goto err_free_netdev; + + INIT_WORK(&adapter->force_close_task, fjes_force_close_task); + adapter->force_reset = false; + adapter->open_guard = false; + + adapter->txrx_wq = alloc_workqueue(DRV_NAME "/txrx", WQ_MEM_RECLAIM, 0); + if (unlikely(!adapter->txrx_wq)) { + err = -ENOMEM; + goto err_free_netdev; + } + + adapter->control_wq = alloc_workqueue(DRV_NAME "/control", + WQ_MEM_RECLAIM, 0); + if (unlikely(!adapter->control_wq)) { + err = -ENOMEM; + goto err_free_txrx_wq; + } + + INIT_WORK(&adapter->tx_stall_task, fjes_tx_stall_task); + INIT_WORK(&adapter->raise_intr_rxdata_task, + fjes_raise_intr_rxdata_task); + INIT_WORK(&adapter->unshare_watch_task, fjes_watch_unshare_task); + adapter->unshare_watch_bitmask = 0; + + INIT_DELAYED_WORK(&adapter->interrupt_watch_task, fjes_irq_watch_task); + adapter->interrupt_watch_enable = false; + + res = platform_get_resource(plat_dev, IORESOURCE_MEM, 0); + if (!res) { + err = -EINVAL; + goto err_free_control_wq; + } + hw->hw_res.start = res->start; + hw->hw_res.size = resource_size(res); + hw->hw_res.irq = platform_get_irq(plat_dev, 0); + if (hw->hw_res.irq < 0) { + err = hw->hw_res.irq; + goto err_free_control_wq; + } + + err = fjes_hw_init(&adapter->hw); + if (err) + goto err_free_control_wq; + + /* setup MAC address (02:00:00:00:00:[epid])*/ + netdev->dev_addr[0] = 2; + netdev->dev_addr[1] = 0; + netdev->dev_addr[2] = 0; + netdev->dev_addr[3] = 0; + netdev->dev_addr[4] = 0; + netdev->dev_addr[5] = hw->my_epid; /* EPID */ + + err = register_netdev(netdev); + if (err) + goto err_hw_exit; + + netif_carrier_off(netdev); + + fjes_dbg_adapter_init(adapter); + + return 0; + +err_hw_exit: + fjes_hw_exit(&adapter->hw); +err_free_control_wq: + destroy_workqueue(adapter->control_wq); +err_free_txrx_wq: + destroy_workqueue(adapter->txrx_wq); +err_free_netdev: + free_netdev(netdev); +err_out: + return err; +} + +/* fjes_remove - Device Removal Routine */ +static int fjes_remove(struct platform_device *plat_dev) +{ + struct net_device *netdev = dev_get_drvdata(&plat_dev->dev); + struct fjes_adapter *adapter = netdev_priv(netdev); + struct fjes_hw *hw = &adapter->hw; + + fjes_dbg_adapter_exit(adapter); + + cancel_delayed_work_sync(&adapter->interrupt_watch_task); + cancel_work_sync(&adapter->unshare_watch_task); + cancel_work_sync(&adapter->raise_intr_rxdata_task); + cancel_work_sync(&adapter->tx_stall_task); + if (adapter->control_wq) + destroy_workqueue(adapter->control_wq); + if (adapter->txrx_wq) + destroy_workqueue(adapter->txrx_wq); + + unregister_netdev(netdev); + + fjes_hw_exit(hw); + + netif_napi_del(&adapter->napi); + + free_netdev(netdev); + + return 0; +} + +static int fjes_sw_init(struct fjes_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + netif_napi_add(netdev, &adapter->napi, fjes_poll, 64); + + return 0; +} + +/* fjes_netdev_setup - netdevice initialization routine */ +static void fjes_netdev_setup(struct net_device *netdev) +{ + ether_setup(netdev); + + netdev->watchdog_timeo = FJES_TX_RETRY_INTERVAL; + netdev->netdev_ops = &fjes_netdev_ops; + fjes_set_ethtool_ops(netdev); + netdev->mtu = fjes_support_mtu[3]; + netdev->min_mtu = fjes_support_mtu[0]; + netdev->max_mtu = fjes_support_mtu[3]; + netdev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; +} + +static void fjes_irq_watch_task(struct work_struct *work) +{ + struct fjes_adapter *adapter = container_of(to_delayed_work(work), + struct fjes_adapter, interrupt_watch_task); + + local_irq_disable(); + fjes_intr(adapter->hw.hw_res.irq, adapter); + local_irq_enable(); + + if (fjes_rxframe_search_exist(adapter, 0) >= 0) + napi_schedule(&adapter->napi); + + if (adapter->interrupt_watch_enable) { + if (!delayed_work_pending(&adapter->interrupt_watch_task)) + queue_delayed_work(adapter->control_wq, + &adapter->interrupt_watch_task, + FJES_IRQ_WATCH_DELAY); + } +} + +static void fjes_watch_unshare_task(struct work_struct *work) +{ + struct fjes_adapter *adapter = + container_of(work, struct fjes_adapter, unshare_watch_task); + + struct net_device *netdev = adapter->netdev; + struct fjes_hw *hw = &adapter->hw; + + int unshare_watch, unshare_reserve; + int max_epid, my_epid, epidx; + int stop_req, stop_req_done; + ulong unshare_watch_bitmask; + unsigned long flags; + int wait_time = 0; + int is_shared; + int ret; + + my_epid = hw->my_epid; + max_epid = hw->max_epid; + + unshare_watch_bitmask = adapter->unshare_watch_bitmask; + adapter->unshare_watch_bitmask = 0; + + while ((unshare_watch_bitmask || hw->txrx_stop_req_bit) && + (wait_time < 3000)) { + for (epidx = 0; epidx < max_epid; epidx++) { + if (epidx == my_epid) + continue; + + is_shared = fjes_hw_epid_is_shared(hw->hw_info.share, + epidx); + + stop_req = test_bit(epidx, &hw->txrx_stop_req_bit); + + stop_req_done = hw->ep_shm_info[epidx].rx.info->v1i.rx_status & + FJES_RX_STOP_REQ_DONE; + + unshare_watch = test_bit(epidx, &unshare_watch_bitmask); + + unshare_reserve = test_bit(epidx, + &hw->hw_info.buffer_unshare_reserve_bit); + + if ((!stop_req || + (is_shared && (!is_shared || !stop_req_done))) && + (is_shared || !unshare_watch || !unshare_reserve)) + continue; + + mutex_lock(&hw->hw_info.lock); + ret = fjes_hw_unregister_buff_addr(hw, epidx); + switch (ret) { + case 0: + break; + case -ENOMSG: + case -EBUSY: + default: + if (!work_pending( + &adapter->force_close_task)) { + adapter->force_reset = true; + schedule_work( + &adapter->force_close_task); + } + break; + } + mutex_unlock(&hw->hw_info.lock); + hw->ep_shm_info[epidx].ep_stats + .com_unregist_buf_exec += 1; + + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf(&hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + spin_unlock_irqrestore(&hw->rx_status_lock, flags); + + clear_bit(epidx, &hw->txrx_stop_req_bit); + clear_bit(epidx, &unshare_watch_bitmask); + clear_bit(epidx, + &hw->hw_info.buffer_unshare_reserve_bit); + } + + msleep(100); + wait_time += 100; + } + + if (hw->hw_info.buffer_unshare_reserve_bit) { + for (epidx = 0; epidx < max_epid; epidx++) { + if (epidx == my_epid) + continue; + + if (test_bit(epidx, + &hw->hw_info.buffer_unshare_reserve_bit)) { + mutex_lock(&hw->hw_info.lock); + + ret = fjes_hw_unregister_buff_addr(hw, epidx); + switch (ret) { + case 0: + break; + case -ENOMSG: + case -EBUSY: + default: + if (!work_pending( + &adapter->force_close_task)) { + adapter->force_reset = true; + schedule_work( + &adapter->force_close_task); + } + break; + } + mutex_unlock(&hw->hw_info.lock); + + hw->ep_shm_info[epidx].ep_stats + .com_unregist_buf_exec += 1; + + spin_lock_irqsave(&hw->rx_status_lock, flags); + fjes_hw_setup_epbuf( + &hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + spin_unlock_irqrestore(&hw->rx_status_lock, + flags); + + clear_bit(epidx, &hw->txrx_stop_req_bit); + clear_bit(epidx, &unshare_watch_bitmask); + clear_bit(epidx, &hw->hw_info.buffer_unshare_reserve_bit); + } + + if (test_bit(epidx, &unshare_watch_bitmask)) { + spin_lock_irqsave(&hw->rx_status_lock, flags); + hw->ep_shm_info[epidx].tx.info->v1i.rx_status &= + ~FJES_RX_STOP_REQ_DONE; + spin_unlock_irqrestore(&hw->rx_status_lock, + flags); + } + } + } +} + +static acpi_status +acpi_find_extended_socket_device(acpi_handle obj_handle, u32 level, + void *context, void **return_value) +{ + struct acpi_device *device; + bool *found = context; + int result; + + result = acpi_bus_get_device(obj_handle, &device); + if (result) + return AE_OK; + + if (strcmp(acpi_device_hid(device), ACPI_MOTHERBOARD_RESOURCE_HID)) + return AE_OK; + + if (!is_extended_socket_device(device)) + return AE_OK; + + if (acpi_check_extended_socket_status(device)) + return AE_OK; + + *found = true; + return AE_CTRL_TERMINATE; +} + +/* fjes_init_module - Driver Registration Routine */ +static int __init fjes_init_module(void) +{ + bool found = false; + int result; + + acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, + acpi_find_extended_socket_device, NULL, &found, + NULL); + + if (!found) + return -ENODEV; + + pr_info("%s - version %s - %s\n", + fjes_driver_string, fjes_driver_version, fjes_copyright); + + fjes_dbg_init(); + + result = platform_driver_register(&fjes_driver); + if (result < 0) { + fjes_dbg_exit(); + return result; + } + + result = acpi_bus_register_driver(&fjes_acpi_driver); + if (result < 0) + goto fail_acpi_driver; + + return 0; + +fail_acpi_driver: + platform_driver_unregister(&fjes_driver); + fjes_dbg_exit(); + return result; +} + +module_init(fjes_init_module); + +/* fjes_exit_module - Driver Exit Cleanup Routine */ +static void __exit fjes_exit_module(void) +{ + acpi_bus_unregister_driver(&fjes_acpi_driver); + platform_driver_unregister(&fjes_driver); + fjes_dbg_exit(); +} + +module_exit(fjes_exit_module); diff --git a/drivers/net/fjes/fjes_regs.h b/drivers/net/fjes/fjes_regs.h new file mode 100644 index 000000000..029c924dc --- /dev/null +++ b/drivers/net/fjes/fjes_regs.h @@ -0,0 +1,142 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +#ifndef FJES_REGS_H_ +#define FJES_REGS_H_ + +#include <linux/bitops.h> + +#define XSCT_DEVICE_REGISTER_SIZE 0x1000 + +/* register offset */ +/* Information registers */ +#define XSCT_OWNER_EPID 0x0000 /* Owner EPID */ +#define XSCT_MAX_EP 0x0004 /* Maximum EP */ + +/* Device Control registers */ +#define XSCT_DCTL 0x0010 /* Device Control */ + +/* Command Control registers */ +#define XSCT_CR 0x0020 /* Command request */ +#define XSCT_CS 0x0024 /* Command status */ +#define XSCT_SHSTSAL 0x0028 /* Share status address Low */ +#define XSCT_SHSTSAH 0x002C /* Share status address High */ + +#define XSCT_REQBL 0x0034 /* Request Buffer length */ +#define XSCT_REQBAL 0x0038 /* Request Buffer Address Low */ +#define XSCT_REQBAH 0x003C /* Request Buffer Address High */ + +#define XSCT_RESPBL 0x0044 /* Response Buffer Length */ +#define XSCT_RESPBAL 0x0048 /* Response Buffer Address Low */ +#define XSCT_RESPBAH 0x004C /* Response Buffer Address High */ + +/* Interrupt Control registers */ +#define XSCT_IS 0x0080 /* Interrupt status */ +#define XSCT_IMS 0x0084 /* Interrupt mask set */ +#define XSCT_IMC 0x0088 /* Interrupt mask clear */ +#define XSCT_IG 0x008C /* Interrupt generator */ +#define XSCT_ICTL 0x0090 /* Interrupt control */ + +/* register structure */ +/* Information registers */ +union REG_OWNER_EPID { + struct { + __le32 epid:16; + __le32:16; + } bits; + __le32 reg; +}; + +union REG_MAX_EP { + struct { + __le32 maxep:16; + __le32:16; + } bits; + __le32 reg; +}; + +/* Device Control registers */ +union REG_DCTL { + struct { + __le32 reset:1; + __le32 rsv0:15; + __le32 rsv1:16; + } bits; + __le32 reg; +}; + +/* Command Control registers */ +union REG_CR { + struct { + __le32 req_code:16; + __le32 err_info:14; + __le32 error:1; + __le32 req_start:1; + } bits; + __le32 reg; +}; + +union REG_CS { + struct { + __le32 req_code:16; + __le32 rsv0:14; + __le32 busy:1; + __le32 complete:1; + } bits; + __le32 reg; +}; + +/* Interrupt Control registers */ +union REG_ICTL { + struct { + __le32 automak:1; + __le32 rsv0:31; + } bits; + __le32 reg; +}; + +enum REG_ICTL_MASK { + REG_ICTL_MASK_INFO_UPDATE = 1 << 20, + REG_ICTL_MASK_DEV_STOP_REQ = 1 << 19, + REG_ICTL_MASK_TXRX_STOP_REQ = 1 << 18, + REG_ICTL_MASK_TXRX_STOP_DONE = 1 << 17, + REG_ICTL_MASK_RX_DATA = 1 << 16, + REG_ICTL_MASK_ALL = GENMASK(20, 16), +}; + +enum REG_IS_MASK { + REG_IS_MASK_IS_ASSERT = 1 << 31, + REG_IS_MASK_EPID = GENMASK(15, 0), +}; + +struct fjes_hw; + +u32 fjes_hw_rd32(struct fjes_hw *hw, u32 reg); + +#define wr32(reg, val) \ +do { \ + u8 *base = hw->base; \ + writel((val), &base[(reg)]); \ +} while (0) + +#define rd32(reg) (fjes_hw_rd32(hw, reg)) + +#endif /* FJES_REGS_H_ */ diff --git a/drivers/net/fjes/fjes_trace.c b/drivers/net/fjes/fjes_trace.c new file mode 100644 index 000000000..066fa765d --- /dev/null +++ b/drivers/net/fjes/fjes_trace.c @@ -0,0 +1,30 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015-2016 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +#include <linux/module.h> + +#ifndef __CHECKER__ +#include "fjes_hw.h" + +#define CREATE_TRACE_POINTS +#include "fjes_trace.h" + +#endif /* __CHECKER__ */ diff --git a/drivers/net/fjes/fjes_trace.h b/drivers/net/fjes/fjes_trace.h new file mode 100644 index 000000000..cca01a1b3 --- /dev/null +++ b/drivers/net/fjes/fjes_trace.h @@ -0,0 +1,380 @@ +/* + * FUJITSU Extended Socket Network Device driver + * Copyright (c) 2015-2016 FUJITSU LIMITED + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, see <http://www.gnu.org/licenses/>. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + */ + +#if !defined(FJES_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define FJES_TRACE_H_ + +#include <linux/types.h> +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM fjes + +/* tracepoints for fjes_hw.c */ + +TRACE_EVENT(fjes_hw_issue_request_command, + TP_PROTO(union REG_CR *cr, union REG_CS *cs, int timeout, + enum fjes_dev_command_response_e ret), + TP_ARGS(cr, cs, timeout, ret), + TP_STRUCT__entry( + __field(u16, cr_req) + __field(u8, cr_error) + __field(u16, cr_err_info) + __field(u8, cr_req_start) + __field(u16, cs_req) + __field(u8, cs_busy) + __field(u8, cs_complete) + __field(int, timeout) + __field(int, ret); + ), + TP_fast_assign( + __entry->cr_req = cr->bits.req_code; + __entry->cr_error = cr->bits.error; + __entry->cr_err_info = cr->bits.err_info; + __entry->cr_req_start = cr->bits.req_start; + __entry->cs_req = cs->bits.req_code; + __entry->cs_busy = cs->bits.busy; + __entry->cs_complete = cs->bits.complete; + __entry->timeout = timeout; + __entry->ret = ret; + ), + TP_printk("CR=[req=%04x, error=%u, err_info=%04x, req_start=%u], CS=[req=%04x, busy=%u, complete=%u], timeout=%d, ret=%d", + __entry->cr_req, __entry->cr_error, __entry->cr_err_info, + __entry->cr_req_start, __entry->cs_req, __entry->cs_busy, + __entry->cs_complete, __entry->timeout, __entry->ret) +); + +TRACE_EVENT(fjes_hw_request_info, + TP_PROTO(struct fjes_hw *hw, union fjes_device_command_res *res_buf), + TP_ARGS(hw, res_buf), + TP_STRUCT__entry( + __field(int, length) + __field(int, code) + __dynamic_array(u8, zone, hw->max_epid) + __dynamic_array(u8, status, hw->max_epid) + ), + TP_fast_assign( + int x; + + __entry->length = res_buf->info.length; + __entry->code = res_buf->info.code; + for (x = 0; x < hw->max_epid; x++) { + *((u8 *)__get_dynamic_array(zone) + x) = + res_buf->info.info[x].zone; + *((u8 *)__get_dynamic_array(status) + x) = + res_buf->info.info[x].es_status; + } + ), + TP_printk("res_buf=[length=%d, code=%d, es_zones=%s, es_status=%s]", + __entry->length, __entry->code, + __print_array(__get_dynamic_array(zone), + __get_dynamic_array_len(zone) / sizeof(u8), + sizeof(u8)), + __print_array(__get_dynamic_array(status), + __get_dynamic_array_len(status) / sizeof(u8), + sizeof(u8))) +); + +TRACE_EVENT(fjes_hw_request_info_err, + TP_PROTO(char *err), + TP_ARGS(err), + TP_STRUCT__entry( + __string(err, err) + ), + TP_fast_assign( + __assign_str(err, err); + ), + TP_printk("%s", __get_str(err)) +); + +TRACE_EVENT(fjes_hw_register_buff_addr_req, + TP_PROTO(union fjes_device_command_req *req_buf, + struct ep_share_mem_info *buf_pair), + TP_ARGS(req_buf, buf_pair), + TP_STRUCT__entry( + __field(int, length) + __field(int, epid) + __field(u64, tx) + __field(size_t, tx_size) + __field(u64, rx) + __field(size_t, rx_size) + ), + TP_fast_assign( + void *tx, *rx; + + tx = (void *)buf_pair->tx.buffer; + rx = (void *)buf_pair->rx.buffer; + __entry->length = req_buf->share_buffer.length; + __entry->epid = req_buf->share_buffer.epid; + __entry->tx_size = buf_pair->tx.size; + __entry->rx_size = buf_pair->rx.size; + __entry->tx = page_to_phys(vmalloc_to_page(tx)) + + offset_in_page(tx); + __entry->rx = page_to_phys(vmalloc_to_page(rx)) + + offset_in_page(rx); + ), + TP_printk("req_buf=[length=%d, epid=%d], TX=[phy=0x%016llx, size=%zu], RX=[phy=0x%016llx, size=%zu]", + __entry->length, __entry->epid, __entry->tx, __entry->tx_size, + __entry->rx, __entry->rx_size) +); + +TRACE_EVENT(fjes_hw_register_buff_addr, + TP_PROTO(union fjes_device_command_res *res_buf, int timeout), + TP_ARGS(res_buf, timeout), + TP_STRUCT__entry( + __field(int, length) + __field(int, code) + __field(int, timeout) + ), + TP_fast_assign( + __entry->length = res_buf->share_buffer.length; + __entry->code = res_buf->share_buffer.code; + __entry->timeout = timeout; + ), + TP_printk("res_buf=[length=%d, code=%d], timeout=%d", + __entry->length, __entry->code, __entry->timeout) +); + +TRACE_EVENT(fjes_hw_register_buff_addr_err, + TP_PROTO(char *err), + TP_ARGS(err), + TP_STRUCT__entry( + __string(err, err) + ), + TP_fast_assign( + __assign_str(err, err); + ), + TP_printk("%s", __get_str(err)) +); + +TRACE_EVENT(fjes_hw_unregister_buff_addr_req, + TP_PROTO(union fjes_device_command_req *req_buf), + TP_ARGS(req_buf), + TP_STRUCT__entry( + __field(int, length) + __field(int, epid) + ), + TP_fast_assign( + __entry->length = req_buf->unshare_buffer.length; + __entry->epid = req_buf->unshare_buffer.epid; + ), + TP_printk("req_buf=[length=%d, epid=%d]", + __entry->length, __entry->epid) +); + +TRACE_EVENT(fjes_hw_unregister_buff_addr, + TP_PROTO(union fjes_device_command_res *res_buf, int timeout), + TP_ARGS(res_buf, timeout), + TP_STRUCT__entry( + __field(int, length) + __field(int, code) + __field(int, timeout) + ), + TP_fast_assign( + __entry->length = res_buf->unshare_buffer.length; + __entry->code = res_buf->unshare_buffer.code; + __entry->timeout = timeout; + ), + TP_printk("res_buf=[length=%d, code=%d], timeout=%d", + __entry->length, __entry->code, __entry->timeout) +); + +TRACE_EVENT(fjes_hw_unregister_buff_addr_err, + TP_PROTO(char *err), + TP_ARGS(err), + TP_STRUCT__entry( + __string(err, err) + ), + TP_fast_assign( + __assign_str(err, err); + ), + TP_printk("%s", __get_str(err)) +); + +TRACE_EVENT(fjes_hw_start_debug_req, + TP_PROTO(union fjes_device_command_req *req_buf), + TP_ARGS(req_buf), + TP_STRUCT__entry( + __field(int, length) + __field(int, mode) + __field(phys_addr_t, buffer) + ), + TP_fast_assign( + __entry->length = req_buf->start_trace.length; + __entry->mode = req_buf->start_trace.mode; + __entry->buffer = req_buf->start_trace.buffer[0]; + ), + TP_printk("req_buf=[length=%d, mode=%d, buffer=%pap]", + __entry->length, __entry->mode, &__entry->buffer) +); + +TRACE_EVENT(fjes_hw_start_debug, + TP_PROTO(union fjes_device_command_res *res_buf), + TP_ARGS(res_buf), + TP_STRUCT__entry( + __field(int, length) + __field(int, code) + ), + TP_fast_assign( + __entry->length = res_buf->start_trace.length; + __entry->code = res_buf->start_trace.code; + ), + TP_printk("res_buf=[length=%d, code=%d]", __entry->length, __entry->code) +); + +TRACE_EVENT(fjes_hw_start_debug_err, + TP_PROTO(char *err), + TP_ARGS(err), + TP_STRUCT__entry( + __string(err, err) + ), + TP_fast_assign( + __assign_str(err, err) + ), + TP_printk("%s", __get_str(err)) +); + +TRACE_EVENT(fjes_hw_stop_debug, + TP_PROTO(union fjes_device_command_res *res_buf), + TP_ARGS(res_buf), + TP_STRUCT__entry( + __field(int, length) + __field(int, code) + ), + TP_fast_assign( + __entry->length = res_buf->stop_trace.length; + __entry->code = res_buf->stop_trace.code; + ), + TP_printk("res_buf=[length=%d, code=%d]", __entry->length, __entry->code) +); + +TRACE_EVENT(fjes_hw_stop_debug_err, + TP_PROTO(char *err), + TP_ARGS(err), + TP_STRUCT__entry( + __string(err, err) + ), + TP_fast_assign( + __assign_str(err, err) + ), + TP_printk("%s", __get_str(err)) +); + +/* tracepoints for fjes_main.c */ + +TRACE_EVENT(fjes_txrx_stop_req_irq_pre, + TP_PROTO(struct fjes_hw *hw, int src_epid, + enum ep_partner_status status), + TP_ARGS(hw, src_epid, status), + TP_STRUCT__entry( + __field(int, src_epid) + __field(enum ep_partner_status, status) + __field(u8, ep_status) + __field(unsigned long, txrx_stop_req_bit) + __field(u16, rx_status) + ), + TP_fast_assign( + __entry->src_epid = src_epid; + __entry->status = status; + __entry->ep_status = hw->hw_info.share->ep_status[src_epid]; + __entry->txrx_stop_req_bit = hw->txrx_stop_req_bit; + __entry->rx_status = + hw->ep_shm_info[src_epid].tx.info->v1i.rx_status; + ), + TP_printk("epid=%d, partner_status=%d, ep_status=%x, txrx_stop_req_bit=%016lx, tx.rx_status=%08x", + __entry->src_epid, __entry->status, __entry->ep_status, + __entry->txrx_stop_req_bit, __entry->rx_status) +); + +TRACE_EVENT(fjes_txrx_stop_req_irq_post, + TP_PROTO(struct fjes_hw *hw, int src_epid), + TP_ARGS(hw, src_epid), + TP_STRUCT__entry( + __field(int, src_epid) + __field(u8, ep_status) + __field(unsigned long, txrx_stop_req_bit) + __field(u16, rx_status) + ), + TP_fast_assign( + __entry->src_epid = src_epid; + __entry->ep_status = hw->hw_info.share->ep_status[src_epid]; + __entry->txrx_stop_req_bit = hw->txrx_stop_req_bit; + __entry->rx_status = hw->ep_shm_info[src_epid].tx.info->v1i.rx_status; + ), + TP_printk("epid=%d, ep_status=%x, txrx_stop_req_bit=%016lx, tx.rx_status=%08x", + __entry->src_epid, __entry->ep_status, + __entry->txrx_stop_req_bit, __entry->rx_status) +); + +TRACE_EVENT(fjes_stop_req_irq_pre, + TP_PROTO(struct fjes_hw *hw, int src_epid, + enum ep_partner_status status), + TP_ARGS(hw, src_epid, status), + TP_STRUCT__entry( + __field(int, src_epid) + __field(enum ep_partner_status, status) + __field(u8, ep_status) + __field(unsigned long, txrx_stop_req_bit) + __field(u16, rx_status) + ), + TP_fast_assign( + __entry->src_epid = src_epid; + __entry->status = status; + __entry->ep_status = hw->hw_info.share->ep_status[src_epid]; + __entry->txrx_stop_req_bit = hw->txrx_stop_req_bit; + __entry->rx_status = + hw->ep_shm_info[src_epid].tx.info->v1i.rx_status; + ), + TP_printk("epid=%d, partner_status=%d, ep_status=%x, txrx_stop_req_bit=%016lx, tx.rx_status=%08x", + __entry->src_epid, __entry->status, __entry->ep_status, + __entry->txrx_stop_req_bit, __entry->rx_status) +); + +TRACE_EVENT(fjes_stop_req_irq_post, + TP_PROTO(struct fjes_hw *hw, int src_epid), + TP_ARGS(hw, src_epid), + TP_STRUCT__entry( + __field(int, src_epid) + __field(u8, ep_status) + __field(unsigned long, txrx_stop_req_bit) + __field(u16, rx_status) + ), + TP_fast_assign( + __entry->src_epid = src_epid; + __entry->ep_status = hw->hw_info.share->ep_status[src_epid]; + __entry->txrx_stop_req_bit = hw->txrx_stop_req_bit; + __entry->rx_status = + hw->ep_shm_info[src_epid].tx.info->v1i.rx_status; + ), + TP_printk("epid=%d, ep_status=%x, txrx_stop_req_bit=%016lx, tx.rx_status=%08x", + __entry->src_epid, __entry->ep_status, + __entry->txrx_stop_req_bit, __entry->rx_status) +); + +#endif /* FJES_TRACE_H_ */ + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../../drivers/net/fjes +#define TRACE_INCLUDE_FILE fjes_trace + +/* This part must be outside protection */ +#include <trace/define_trace.h> |