#include <linux/etherdevice.h>
#include <linux/iopoll.h>
#include <linux/utsname.h>
#include <linux/version.h>
#include "eea_adminq.h"
#include "eea_net.h"
#include "eea_pci.h"
#include "eea_ring.h"
#define EEA_AQ_CMD_CFG_QUERY ((0 << 8) | 0)
#define EEA_AQ_CMD_QUEUE_CREATE ((1 << 8) | 0)
#define EEA_AQ_CMD_QUEUE_DESTROY_ALL ((1 << 8) | 1)
#define EEA_AQ_CMD_HOST_INFO ((2 << 8) | 0)
#define EEA_AQ_CMD_DEV_STATUS ((3 << 8) | 0)
#define EEA_RING_DESC_F_AQ_PHASE (BIT(15) | BIT(7))
#define EEA_QUEUE_FLAGS_HW_SPLIT_HDR BIT(0)
#define EEA_QUEUE_FLAGS_SQCQ BIT(1)
#define EEA_QUEUE_FLAGS_HWTS BIT(2)
struct eea_aq_create {
__le32 flags;
__le16 qidx;
__le16 depth;
u8 hdr_buf_size;
u8 sq_desc_size;
u8 cq_desc_size;
u8 reserve0;
__le16 msix_vector;
__le16 reserve;
__le32 sq_addr_low;
__le32 sq_addr_high;
__le32 cq_addr_low;
__le32 cq_addr_high;
};
struct eea_aq_queue_drv_status {
__le16 qidx;
__le16 sq_head;
__le16 cq_head;
__le16 reserved;
};
#define EEA_OS_DISTRO 0
#define EEA_DRV_TYPE 0
#define EEA_OS_LINUX 1
#define EEA_SPEC_VER_MAJOR 1
#define EEA_SPEC_VER_MINOR 0
struct eea_aq_host_info_cfg {
__le16 os_type;
__le16 os_dist;
__le16 drv_type;
__le16 kern_ver_major;
__le16 kern_ver_minor;
__le16 kern_ver_sub_minor;
__le16 drv_ver_major;
__le16 drv_ver_minor;
__le16 drv_ver_sub_minor;
__le16 spec_ver_major;
__le16 spec_ver_minor;
__le16 pci_bdf;
__le32 pci_domain;
u8 os_ver_str[64];
u8 isa_str[64];
};
#define EEA_HINFO_MAX_REP_LEN 1024
#define EEA_HINFO_REP_BAD 2
struct eea_aq_host_info_rep {
u8 op_code;
u8 has_reply;
u8 reply_str[EEA_HINFO_MAX_REP_LEN];
};
static struct eea_ring *qid_to_ering(struct eea_net *enet, u32 qid)
{
struct eea_ring *ering;
if (qid % 2 == 0)
ering = enet->rx[qid / 2]->ering;
else
ering = enet->tx[qid / 2].ering;
return ering;
}
#define EEA_AQ_TIMEOUT_US (60 * 1000 * 1000)
static void eea_device_broken(struct eea_net *enet)
{
if (enet->adminq.broken)
return;
eea_device_reset(enet->edev);
enet->adminq.broken = true;
}
static int eea_adminq_submit(struct eea_net *enet, u16 cmd,
dma_addr_t req_addr, dma_addr_t res_addr,
u32 req_size, u32 res_size, u32 *reply_len)
{
struct eea_aq_cdesc *cdesc;
struct eea_aq_desc *desc;
int ret;
if (enet->adminq.broken)
return -EIO;
desc = eea_ering_aq_alloc_desc(enet->adminq.ring);
desc->classid = cmd >> 8;
desc->command = cmd & 0xff;
desc->data_addr = cpu_to_le64(req_addr);
desc->data_len = cpu_to_le32(req_size);
desc->reply_addr = cpu_to_le64(res_addr);
desc->reply_len = cpu_to_le32(res_size);
dma_wmb();
desc->flags = cpu_to_le16(enet->adminq.phase);
eea_ering_sq_commit_desc(enet->adminq.ring);
eea_ering_kick(enet->adminq.ring);
++enet->adminq.num;
if ((enet->adminq.num % enet->adminq.ring->num) == 0)
enet->adminq.phase ^= EEA_RING_DESC_F_AQ_PHASE;
ret = read_poll_timeout(eea_ering_cq_get_desc, cdesc, cdesc, 10,
EEA_AQ_TIMEOUT_US, false, enet->adminq.ring);
if (ret) {
netdev_err(enet->netdev,
"adminq exec timeout. cmd: %d reset device.\n",
cmd);
eea_device_broken(enet);
return ret;
}
ret = le32_to_cpu(cdesc->status);
eea_ering_cq_ack_desc(enet->adminq.ring, 1);
if (ret)
netdev_err(enet->netdev,
"adminq exec failed. cmd: %d ret %d\n", cmd, ret);
else
*reply_len = le32_to_cpu(cdesc->reply_len);
return ret;
}
static int eea_adminq_exec(struct eea_net *enet, u16 cmd,
void *req, u32 req_size,
void *res, u32 res_size,
u32 *reply)
{
dma_addr_t req_addr = 0, res_addr = 0;
struct device *dma;
u32 reply_len = 0;
int ret;
if (reply)
*reply = 0;
dma = enet->edev->dma_dev;
if (req) {
req_addr = dma_map_single(dma, req, req_size, DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(dma, req_addr)))
return -ENOMEM;
}
if (res) {
res_addr = dma_map_single(dma, res, res_size, DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(dma, res_addr))) {
ret = -ENOMEM;
goto err_unmap_req;
}
}
mutex_lock(&enet->adminq.lock);
ret = eea_adminq_submit(enet, cmd, req_addr, res_addr,
req_size, res_size, &reply_len);
mutex_unlock(&enet->adminq.lock);
if (res) {
dma_unmap_single(dma, res_addr, res_size, DMA_FROM_DEVICE);
if (ret)
memset(res, 0, res_size);
else if (res_size > reply_len)
memset(res + reply_len, 0, res_size - reply_len);
if (reply)
*reply = reply_len;
}
err_unmap_req:
if (req)
dma_unmap_single(dma, req_addr, req_size, DMA_TO_DEVICE);
return ret;
}
void eea_destroy_adminq(struct eea_net *enet)
{
struct eea_aq *aq;
aq = &enet->adminq;
if (aq->ring) {
eea_ering_free(aq->ring);
aq->ring = NULL;
aq->phase = 0;
}
kfree(aq->q_req_buf);
kfree(aq->q_res_buf);
aq->q_req_buf = NULL;
aq->q_res_buf = NULL;
}
int eea_create_adminq(struct eea_net *enet, u32 qid)
{
u32 db_size, q_size, num;
struct eea_ring *ering;
struct eea_aq *aq;
int err = -ENOMEM;
num = enet->edev->rx_num + enet->edev->tx_num;
aq = &enet->adminq;
ering = eea_ering_alloc(qid, 64, enet->edev, sizeof(struct eea_aq_desc),
sizeof(struct eea_aq_cdesc), "adminq");
if (!ering)
return -ENOMEM;
aq->ring = ering;
err = eea_pci_active_aq(ering, qid / 2 + 1);
if (err)
goto err;
aq->phase = BIT(7);
aq->num = 0;
q_size = sizeof(*aq->q_req_buf) * num;
db_size = sizeof(*aq->q_res_buf) * num;
aq->q_req_size = q_size;
aq->q_res_size = db_size;
err = -ENOMEM;
aq->q_req_buf = kzalloc(q_size, GFP_KERNEL);
if (!aq->q_req_buf)
goto err;
aq->q_res_buf = kzalloc(db_size, GFP_KERNEL);
if (!aq->q_res_buf)
goto err;
err = eea_pci_set_aq_up(enet->edev);
if (err)
goto err;
aq->broken = false;
mutex_init(&aq->lock);
return 0;
err:
eea_destroy_adminq(enet);
return err;
}
int eea_adminq_query_cfg(struct eea_net *enet, struct eea_aq_cfg *cfg)
{
return eea_adminq_exec(enet, EEA_AQ_CMD_CFG_QUERY, NULL, 0, cfg,
sizeof(*cfg), NULL);
}
static void qcfg_fill(struct eea_aq_create *qcfg, struct eea_ring *ering,
u32 flags)
{
qcfg->flags = cpu_to_le32(flags);
qcfg->qidx = cpu_to_le16(ering->index);
qcfg->depth = cpu_to_le16(ering->num);
qcfg->hdr_buf_size = flags & EEA_QUEUE_FLAGS_HW_SPLIT_HDR ? 1 : 0;
qcfg->sq_desc_size = ering->sq.desc_size;
qcfg->cq_desc_size = ering->cq.desc_size;
qcfg->msix_vector = cpu_to_le16(ering->msix_vec);
qcfg->sq_addr_low = cpu_to_le32(lower_32_bits(ering->sq.dma_addr));
qcfg->sq_addr_high = cpu_to_le32(upper_32_bits(ering->sq.dma_addr));
qcfg->cq_addr_low = cpu_to_le32(lower_32_bits(ering->cq.dma_addr));
qcfg->cq_addr_high = cpu_to_le32(upper_32_bits(ering->cq.dma_addr));
}
int eea_adminq_create_q(struct eea_net *enet, u32 num, u32 flags)
{
int i, db_size, q_size, err = -ENOMEM;
struct eea_net_cfg *cfg;
struct eea_ring *ering;
struct eea_aq *aq;
u32 reply_len;
cfg = &enet->cfg;
aq = &enet->adminq;
if (cfg->split_hdr)
flags |= EEA_QUEUE_FLAGS_HW_SPLIT_HDR;
flags |= EEA_QUEUE_FLAGS_SQCQ;
flags |= EEA_QUEUE_FLAGS_HWTS;
q_size = sizeof(*aq->q_req_buf) * num;
db_size = sizeof(*aq->q_res_buf) * num;
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
qcfg_fill(aq->q_req_buf + i, ering, flags);
}
err = eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_CREATE,
aq->q_req_buf, q_size,
aq->q_res_buf, db_size,
&reply_len);
if (err)
return err;
if (reply_len != db_size) {
eea_adminq_destroy_all_q(enet);
netdev_err(enet->netdev, "invalid reply len %u\n", reply_len);
return -EINVAL;
}
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
ering->db = eea_pci_db_addr(ering->edev,
le32_to_cpu(aq->q_res_buf[i]));
if (!ering->db) {
netdev_err(enet->netdev, "invalid db off %u\n",
le32_to_cpu(aq->q_res_buf[i]));
goto err;
}
}
return err;
err:
eea_adminq_destroy_all_q(enet);
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
ering->db = NULL;
}
return -EIO;
}
int eea_adminq_destroy_all_q(struct eea_net *enet)
{
int err;
err = eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_DESTROY_ALL, NULL, 0,
NULL, 0, NULL);
if (err) {
mutex_lock(&enet->adminq.lock);
eea_device_broken(enet);
mutex_unlock(&enet->adminq.lock);
netdev_err(enet->netdev, "QUEUE_DESTROY fail: reset device.\n");
}
return err;
}
int eea_adminq_dev_status(struct eea_net *enet,
struct eea_aq_dev_status *dstatus)
{
struct eea_aq_queue_drv_status *drv_status;
struct __eea_aq_dev_status *dev_status;
int err, i, io_num, size, q_num;
struct eea_ring *ering;
void *rep, *req;
q_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num + 1;
io_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num;
req = kcalloc(q_num, sizeof(struct eea_aq_queue_drv_status),
GFP_KERNEL);
if (!req)
return -ENOMEM;
size = struct_size(dev_status, q_status, q_num);
rep = kzalloc(size, GFP_KERNEL);
if (!rep) {
kfree(req);
return -ENOMEM;
}
drv_status = req;
for (i = 0; i < io_num; ++i, ++drv_status) {
ering = qid_to_ering(enet, i);
drv_status->qidx = cpu_to_le16(i);
drv_status->cq_head = cpu_to_le16(ering->cq.head);
drv_status->sq_head = cpu_to_le16(ering->sq.head);
}
drv_status->qidx = cpu_to_le16(i);
drv_status->cq_head = cpu_to_le16(enet->adminq.ring->cq.head);
drv_status->sq_head = cpu_to_le16(enet->adminq.ring->sq.head);
err = eea_adminq_exec(enet, EEA_AQ_CMD_DEV_STATUS, req,
q_num * sizeof(struct eea_aq_queue_drv_status),
rep, size, NULL);
kfree(req);
if (err) {
kfree(rep);
return err;
}
dstatus->num = q_num;
dstatus->status = rep;
return 0;
}
void eea_adminq_config_host_info(struct eea_net *enet)
{
struct device *dev = enet->edev->dma_dev;
struct eea_aq_host_info_cfg *cfg;
struct eea_aq_host_info_rep *rep;
int rc = -ENOMEM;
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg)
return;
rep = kzalloc(sizeof(*rep), GFP_KERNEL);
if (!rep)
goto err_free_cfg;
cfg->os_type = cpu_to_le16(EEA_OS_LINUX);
cfg->os_dist = cpu_to_le16(EEA_OS_DISTRO);
cfg->drv_type = cpu_to_le16(EEA_DRV_TYPE);
cfg->kern_ver_major = cpu_to_le16(LINUX_VERSION_MAJOR);
cfg->kern_ver_minor = cpu_to_le16(LINUX_VERSION_PATCHLEVEL);
cfg->kern_ver_sub_minor = cpu_to_le16(LINUX_VERSION_SUBLEVEL);
cfg->drv_ver_major = cpu_to_le16(EEA_VER_MAJOR);
cfg->drv_ver_minor = cpu_to_le16(EEA_VER_MINOR);
cfg->drv_ver_sub_minor = cpu_to_le16(EEA_VER_SUB_MINOR);
cfg->spec_ver_major = cpu_to_le16(EEA_SPEC_VER_MAJOR);
cfg->spec_ver_minor = cpu_to_le16(EEA_SPEC_VER_MINOR);
cfg->pci_bdf = cpu_to_le16(eea_pci_bdf(enet->edev));
cfg->pci_domain = cpu_to_le32(eea_pci_domain_nr(enet->edev));
strscpy(cfg->os_ver_str, utsname()->release, sizeof(cfg->os_ver_str));
strscpy(cfg->isa_str, utsname()->machine, sizeof(cfg->isa_str));
rc = eea_adminq_exec(enet, EEA_AQ_CMD_HOST_INFO,
cfg, sizeof(*cfg), rep, sizeof(*rep), NULL);
if (!rc) {
if (rep->op_code == EEA_HINFO_REP_BAD)
dev_warn(dev, "The hardware-driven state validation may be abnormal.\n");
if (rep->has_reply) {
char buf[EEA_HINFO_MAX_REP_LEN] = {0};
rep->reply_str[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
string_escape_str(rep->reply_str, buf, sizeof(buf),
ESCAPE_NP, NULL);
buf[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
dev_warn(dev, "Device replied: %s\n", buf);
}
}
kfree(rep);
err_free_cfg:
kfree(cfg);
}