diff options
Diffstat (limited to 'drivers/net/fjes')
-rw-r--r-- | drivers/net/fjes/Makefile | 30 | ||||
-rw-r--r-- | drivers/net/fjes/fjes.h | 77 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_ethtool.c | 137 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_hw.c | 1125 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_hw.h | 334 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_main.c | 1383 | ||||
-rw-r--r-- | drivers/net/fjes/fjes_regs.h | 142 |
7 files changed, 3228 insertions, 0 deletions
diff --git a/drivers/net/fjes/Makefile b/drivers/net/fjes/Makefile new file mode 100644 index 000000000000..523e3d7cf7aa --- /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 diff --git a/drivers/net/fjes/fjes.h b/drivers/net/fjes/fjes.h new file mode 100644 index 000000000000..a592fe21c698 --- /dev/null +++ b/drivers/net/fjes/fjes.h @@ -0,0 +1,77 @@ +/* + * 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; +}; + +extern char fjes_driver_name[]; +extern char fjes_driver_version[]; +extern const u32 fjes_support_mtu[]; + +void fjes_set_ethtool_ops(struct net_device *); + +#endif /* FJES_H_ */ diff --git a/drivers/net/fjes/fjes_ethtool.c b/drivers/net/fjes/fjes_ethtool.c new file mode 100644 index 000000000000..0119dd199276 --- /dev/null +++ b/drivers/net/fjes/fjes_ethtool.c @@ -0,0 +1,137 @@ +/* + * 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), +}; + +static void fjes_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats *stats, u64 *data) +{ + struct fjes_adapter *adapter = netdev_priv(netdev); + 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; + } +} + +static void fjes_get_strings(struct net_device *netdev, + u32 stringset, u8 *data) +{ + 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; + } + break; + } +} + +static int fjes_get_sset_count(struct net_device *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(fjes_gstrings_stats); + 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); + drvinfo->regdump_len = 0; + drvinfo->eedump_len = 0; +} + +static int fjes_get_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + ecmd->supported = 0; + ecmd->advertising = 0; + ecmd->duplex = DUPLEX_FULL; + ecmd->autoneg = AUTONEG_DISABLE; + ecmd->transceiver = XCVR_DUMMY1; + ecmd->port = PORT_NONE; + ethtool_cmd_speed_set(ecmd, 20000); /* 20Gb/s */ + + return 0; +} + +static const struct ethtool_ops fjes_ethtool_ops = { + .get_settings = fjes_get_settings, + .get_drvinfo = fjes_get_drvinfo, + .get_ethtool_stats = fjes_get_ethtool_stats, + .get_strings = fjes_get_strings, + .get_sset_count = fjes_get_sset_count, +}; + +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 000000000000..b5f4a78da828 --- /dev/null +++ b/drivers/net/fjes/fjes_hw.c @@ -0,0 +1,1125 @@ +/* + * 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" + +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) +{ + if (epbh->buffer) + 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]; +} + +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; + 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; + + fjes_hw_setup_epbuf(&buf_pair->tx, mac, + fjes_support_mtu[0]); + fjes_hw_setup_epbuf(&buf_pair->rx, mac, + fjes_support_mtu[0]); + } + } + + 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); + + 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); + + return ret; +} + +void fjes_hw_exit(struct fjes_hw *hw) +{ + int ret; + + if (hw->base) { + 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; + + 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; + } + } + + 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); + + result = 0; + + if (FJES_DEV_COMMAND_INFO_RES_LEN((*hw->hw_info.max_epid)) != + res_buf->info.length) { + 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: + 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; + + 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; + + if (res_buf->share_buffer.length != + FJES_DEV_COMMAND_SHARE_BUFFER_RES_LEN) + 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: + result = -EBUSY; + break; + default: + result = -EPERM; + break; + } + } else { + switch (ret) { + case FJES_CMD_STATUS_UNKNOWN: + result = -EPERM; + break; + case FJES_CMD_STATUS_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; + + 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; + + if (res_buf->unshare_buffer.length != + FJES_DEV_COMMAND_UNSHARE_BUFFER_RES_LEN) { + 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: + result = -EBUSY; + break; + default: + result = -EPERM; + break; + } + } else { + switch (ret) { + case FJES_CMD_STATUS_UNKNOWN: + result = -EPERM; + break; + case FJES_CMD_STATUS_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; + 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); + break; + default: + break; + } + + set_bit(epidx, &hw->hw_info.buffer_unshare_reserve_bit); + set_bit(epidx, &hw->txrx_stop_req_bit); + + hw->ep_shm_info[epidx].tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_REQUEST; + } +} + +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)); +} + +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.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; + + 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)) { + fjes_hw_setup_epbuf(&hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + + 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); + } + + 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); + + if (ret == 0) + fjes_hw_setup_epbuf( + &hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + } + + if (test_bit(epidx, &irq_bit)) { + fjes_hw_raise_interrupt(hw, epidx, + REG_ICTL_MASK_TXRX_STOP_REQ); + + set_bit(epidx, &hw->txrx_stop_req_bit); + hw->ep_shm_info[epidx].tx. + info->v1i.rx_status |= + FJES_RX_STOP_REQ_REQUEST; + 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; + + 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) { + hw->ep_shm_info[epid_bit]. + tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_DONE; + + 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); + } + } + } +} diff --git a/drivers/net/fjes/fjes_hw.h b/drivers/net/fjes/fjes_hw.h new file mode 100644 index 000000000000..6d57b89a0ee8 --- /dev/null +++ b/drivers/net/fjes/fjes_hw.h @@ -0,0 +1,334 @@ +/* + * 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_DEVICE_RESET_TIMEOUT ((17 + 1) * 3) /* sec */ +#define FJES_COMMAND_REQ_TIMEOUT (5 + 1) /* sec */ +#define FJES_COMMAND_REQ_BUFF_TIMEOUT (8 * 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 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) + +/* 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, +}; + +/* 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; + +}; + +/* 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; + + 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; +}; + +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); + +#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 000000000000..0ddb54fe3d91 --- /dev/null +++ b/drivers/net/fjes/fjes_main.c @@ -0,0 +1,1383 @@ +/* + * 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" + +#define MAJ 1 +#define MIN 0 +#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); + +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 struct rtnl_link_stats64 * +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[] = { + {"PNP0C02", 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, + .owner = THIS_MODULE, + }, + .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 int fjes_acpi_add(struct acpi_device *device) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL}; + char str_buf[sizeof(FJES_ACPI_SYMBOL) + 1]; + struct platform_device *plat_dev; + union acpi_object *str; + acpi_status status; + int result; + + status = acpi_evaluate_object(device->handle, "_STR", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + 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 -ENODEV; + } + kfree(buffer.pointer); + + 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)); + 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; + int epidx; + + netif_tx_stop_all_queues(netdev); + netif_carrier_off(netdev); + + fjes_hw_raise_epstop(hw); + + napi_disable(&adapter->napi); + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + adapter->hw.ep_shm_info[epidx].tx.info->v1i.rx_status &= + ~FJES_RX_POLL_WORK; + } + + 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; + 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); + } + } + + 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]; + + fjes_hw_setup_epbuf(&buf_pair->tx, netdev->dev_addr, + netdev->mtu); + + 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; + } + } + } + + 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; + 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); + + if (result) + reset_flag = true; + + buf_pair = &hw->ep_shm_info[epidx]; + + fjes_hw_setup_epbuf(&buf_pair->tx, + netdev->dev_addr, netdev->mtu); + + 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 - + (long)(netdev->trans_start)) > 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 (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_hw_raise_interrupt(hw, epid, + REG_ICTL_MASK_RX_DATA); + } + } + + 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) { + 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[my_epid].net_stats + .tx_carrier_errors += 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[my_epid].net_stats.tx_dropped += 1; + adapter->stats64.tx_errors += 1; + hw->ep_shm_info[my_epid].net_stats.tx_errors += 1; + + ret = NETDEV_TX_OK; + } else if (vlan && + !fjes_hw_check_vlan_id( + &adapter->hw.ep_shm_info[dest_epid].rx, + vlan_id)) { + 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[my_epid].net_stats + .tx_fifo_errors += 1; + adapter->stats64.tx_errors += 1; + hw->ep_shm_info[my_epid].net_stats + .tx_errors += 1; + + ret = NETDEV_TX_OK; + } else { + netdev->trans_start = jiffies; + 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[my_epid].net_stats + .tx_packets += 1; + adapter->stats64.tx_bytes += len; + hw->ep_shm_info[my_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 struct rtnl_link_stats64 * +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)); + + return stats; +} + +static int fjes_change_mtu(struct net_device *netdev, int new_mtu) +{ + bool running = netif_running(netdev); + int ret = 0; + int idx; + + 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; + + if (running) + fjes_close(netdev); + + netdev->mtu = new_mtu; + + if (running) + ret = fjes_open(netdev); + + return ret; + } + } + + return -EINVAL; +} + +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; + + status = fjes_hw_get_partner_ep_status(hw, src_epid); + switch (status) { + case EP_PARTNER_UNSHARE: + case EP_PARTNER_COMPLETE: + default: + break; + case EP_PARTNER_WAITING: + if (src_epid < hw->my_epid) { + hw->ep_shm_info[src_epid].tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_DONE; + + 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; + } +} + +static void fjes_stop_req_irq(struct fjes_adapter *adapter, int src_epid) +{ + struct fjes_hw *hw = &adapter->hw; + enum ep_partner_status status; + + set_bit(src_epid, &hw->hw_info.buffer_unshare_reserve_bit); + + status = fjes_hw_get_partner_ep_status(hw, src_epid); + switch (status) { + case EP_PARTNER_WAITING: + hw->ep_shm_info[src_epid].tx.info->v1i.rx_status |= + FJES_RX_STOP_REQ_DONE; + 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; + } +} + +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); + + if (icr & REG_ICTL_MASK_DEV_STOP_REQ) + fjes_stop_req_irq(adapter, icr & REG_IS_MASK_EPID); + + if (icr & REG_ICTL_MASK_TXRX_STOP_REQ) + fjes_txrx_stop_req_irq(adapter, icr & REG_IS_MASK_EPID); + + 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); + + 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; + + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + + adapter->hw.ep_shm_info[epidx].tx.info->v1i.rx_status |= + FJES_RX_POLL_WORK; + } + + 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 { + memcpy(skb_put(skb, frame_len), + 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(napi); + + 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 { + for (epidx = 0; epidx < hw->max_epid; epidx++) { + if (epidx == hw->my_epid) + continue; + adapter->hw.ep_shm_info[epidx] + .tx.info->v1i.rx_status &= + ~FJES_RX_POLL_WORK; + } + + 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 = create_workqueue(DRV_NAME "/txrx"); + adapter->control_wq = create_workqueue(DRV_NAME "/control"); + + 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); + hw->hw_res.start = res->start; + hw->hw_res.size = res->end - res->start + 1; + hw->hw_res.irq = platform_get_irq(plat_dev, 0); + err = fjes_hw_init(&adapter->hw); + if (err) + goto err_free_netdev; + + /* 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); + + return 0; + +err_hw_exit: + fjes_hw_exit(&adapter->hw); +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; + + 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[0]; + netdev->flags |= IFF_BROADCAST; + netdev->features |= NETIF_F_HW_CSUM | 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; + 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 < hw->max_epid; epidx++) { + if (epidx == hw->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); + + fjes_hw_setup_epbuf(&hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + + 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 < hw->max_epid; epidx++) { + if (epidx == hw->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); + + fjes_hw_setup_epbuf( + &hw->ep_shm_info[epidx].tx, + netdev->dev_addr, netdev->mtu); + + 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)) { + hw->ep_shm_info[epidx].tx.info->v1i.rx_status &= + ~FJES_RX_STOP_REQ_DONE; + } + } + } +} + +/* fjes_init_module - Driver Registration Routine */ +static int __init fjes_init_module(void) +{ + int result; + + pr_info("%s - version %s - %s\n", + fjes_driver_string, fjes_driver_version, fjes_copyright); + + result = platform_driver_register(&fjes_driver); + if (result < 0) + 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); + 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); +} + +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 000000000000..029c924dc175 --- /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_ */ |