// SPDX-License-Identifier: GPL-2.0
/*
* Management-Controller-to-Driver Interface
*
* Copyright 2008-2013 Solarflare Communications Inc.
* Copyright (C) 2022-2023, Advanced Micro Devices, Inc.
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/if_vlan.h>
#include <linux/timer.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/rwsem.h>
#include <linux/vmalloc.h>
#include <net/netevent.h>
#include <linux/log2.h>
#include <linux/net_tstamp.h>
#include <linux/wait.h>
#include "bitfield.h"
#include "mcdi.h"
struct cdx_mcdi_copy_buffer {
struct cdx_dword buffer[DIV_ROUND_UP(MCDI_CTL_SDU_LEN_MAX, 4)];
};
#ifdef CONFIG_MCDI_LOGGING
#define LOG_LINE_MAX (1024 - 32)
#endif
static void cdx_mcdi_cancel_cmd(struct cdx_mcdi *cdx, struct cdx_mcdi_cmd *cmd);
static void cdx_mcdi_wait_for_cleanup(struct cdx_mcdi *cdx);
static int cdx_mcdi_rpc_async_internal(struct cdx_mcdi *cdx,
struct cdx_mcdi_cmd *cmd,
unsigned int *handle);
static void cdx_mcdi_start_or_queue(struct cdx_mcdi_iface *mcdi,
bool allow_retry);
static void cdx_mcdi_cmd_start_or_queue(struct cdx_mcdi_iface *mcdi,
struct cdx_mcdi_cmd *cmd);
static bool cdx_mcdi_complete_cmd(struct cdx_mcdi_iface *mcdi,
struct cdx_mcdi_cmd *cmd,
struct cdx_dword *outbuf,
int len,
struct list_head *cleanup_list);
static void cdx_mcdi_timeout_cmd(struct cdx_mcdi_iface *mcdi,
struct cdx_mcdi_cmd *cmd,
struct list_head *cleanup_list);
static void cdx_mcdi_cmd_work(struct work_struct *context);
static void cdx_mcdi_mode_fail(struct cdx_mcdi *cdx, struct list_head *cleanup_list);
static void _cdx_mcdi_display_error(struct cdx_mcdi *cdx, unsigned int cmd,
size_t inlen, int raw, int arg, int err_no);
static bool cdx_cmd_cancelled(struct cdx_mcdi_cmd *cmd)
{
return cmd->state == MCDI_STATE_RUNNING_CANCELLED;
}
static void cdx_mcdi_cmd_release(struct kref *ref)
{
kfree(container_of(ref, struct cdx_mcdi_cmd, ref));
}
static unsigned int cdx_mcdi_cmd_handle(struct cdx_mcdi_cmd *cmd)
{
return cmd->handle;
}
static void _cdx_mcdi_remove_cmd(struct cdx_mcdi_iface *mcdi,
struct cdx_mcdi_cmd *cmd,
struct list_head *cleanup_list)
{
/* if cancelled, the completers have already been called */
if (cdx_cmd_cancelled(cmd))
return;
if (cmd->completer) {
list_add_tail(&cmd->cleanup_list, cleanup_list);
++mcdi->outstanding_cleanups;
kref_get(&cmd->ref);
}
}
static void cdx_mcdi_remove_cmd(struct cdx_mcdi_iface *mcdi,
struct cdx_mcdi_cmd *cmd,
struct list_head *cleanup_list)
{
list_del(&cmd->list);
_cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list);
cmd->state = MCDI_STATE_FINISHED;
kref_put(&cmd->ref, cdx_mcdi_cmd_release);
if (list_empty(&mcdi->cmd_list))
wake_up(&mcdi->cmd_complete_wq);
}
static unsigned long cdx_mcdi_rpc_timeout(struct cdx_mcdi *cdx, unsigned int cmd)
{
if (!cdx->mcdi_ops->mcdi_rpc_timeout)
return MCDI_RPC_TIMEOUT;
else
return cdx->mcdi_ops->mcdi_rpc_timeout(cdx, cmd);
}
int cdx_mcdi_init(struct cdx_mcdi *cdx)
{
struct cdx_mcdi_iface *mcdi;
int rc = -ENOMEM;
cdx->mcdi = kzalloc(sizeof(*cdx->mcdi), GFP_KERNEL);
if (!cdx->mcdi)
goto fail;
mcdi = cdx_mcdi_if(cdx);
mcdi->cdx = cdx;
#ifdef CONFIG_MCDI_LOGGING
mcdi->logging_buffer = kmalloc(LOG_LINE_MAX, GFP_KERNEL);
if (!mcdi->logging_buffer)
goto fail2;
#endif
mcdi->workqueue = alloc_ordered_workqueue("mcdi_wq", 0);
if (!mcdi->workqueue)
goto fail3;
mutex_init(&mcdi->iface_lock);
mcdi->mode = MCDI_MODE_EVENTS;
INIT_LIST_HEAD(&mcdi->cmd_list);
init_waitqueue_head(&mcdi->cmd_complete_wq);
mcdi->new_epoch = true;
return 0;
fail3:
#ifdef CONFIG_MCDI_LOGGING
kfree(mcdi->logging_buffer);
fail2:
#endif
kfree(cdx->mcdi);
cdx->mcdi = NULL;
fail:
return rc;
}
void cdx_mcdi_finish(struct cdx_mcdi *cdx)
{
struct cdx_mcdi_iface *mcdi;
mcdi = cdx_mcdi_if(cdx);
if (!mcdi)
return;
cdx_mcdi_wait_for_cleanup(cdx