162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Management-Controller-to-Driver Interface 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2008-2013 Solarflare Communications Inc. 662306a36Sopenharmony_ci * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <linux/delay.h> 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci#include <linux/io.h> 1162306a36Sopenharmony_ci#include <linux/spinlock.h> 1262306a36Sopenharmony_ci#include <linux/netdevice.h> 1362306a36Sopenharmony_ci#include <linux/etherdevice.h> 1462306a36Sopenharmony_ci#include <linux/ethtool.h> 1562306a36Sopenharmony_ci#include <linux/if_vlan.h> 1662306a36Sopenharmony_ci#include <linux/timer.h> 1762306a36Sopenharmony_ci#include <linux/list.h> 1862306a36Sopenharmony_ci#include <linux/pci.h> 1962306a36Sopenharmony_ci#include <linux/device.h> 2062306a36Sopenharmony_ci#include <linux/rwsem.h> 2162306a36Sopenharmony_ci#include <linux/vmalloc.h> 2262306a36Sopenharmony_ci#include <net/netevent.h> 2362306a36Sopenharmony_ci#include <linux/log2.h> 2462306a36Sopenharmony_ci#include <linux/net_tstamp.h> 2562306a36Sopenharmony_ci#include <linux/wait.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include "bitfield.h" 2862306a36Sopenharmony_ci#include "mcdi.h" 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistruct cdx_mcdi_copy_buffer { 3162306a36Sopenharmony_ci struct cdx_dword buffer[DIV_ROUND_UP(MCDI_CTL_SDU_LEN_MAX, 4)]; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic void cdx_mcdi_cancel_cmd(struct cdx_mcdi *cdx, struct cdx_mcdi_cmd *cmd); 3562306a36Sopenharmony_cistatic void cdx_mcdi_wait_for_cleanup(struct cdx_mcdi *cdx); 3662306a36Sopenharmony_cistatic int cdx_mcdi_rpc_async_internal(struct cdx_mcdi *cdx, 3762306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 3862306a36Sopenharmony_ci unsigned int *handle); 3962306a36Sopenharmony_cistatic void cdx_mcdi_start_or_queue(struct cdx_mcdi_iface *mcdi, 4062306a36Sopenharmony_ci bool allow_retry); 4162306a36Sopenharmony_cistatic void cdx_mcdi_cmd_start_or_queue(struct cdx_mcdi_iface *mcdi, 4262306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd); 4362306a36Sopenharmony_cistatic bool cdx_mcdi_complete_cmd(struct cdx_mcdi_iface *mcdi, 4462306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 4562306a36Sopenharmony_ci struct cdx_dword *outbuf, 4662306a36Sopenharmony_ci int len, 4762306a36Sopenharmony_ci struct list_head *cleanup_list); 4862306a36Sopenharmony_cistatic void cdx_mcdi_timeout_cmd(struct cdx_mcdi_iface *mcdi, 4962306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 5062306a36Sopenharmony_ci struct list_head *cleanup_list); 5162306a36Sopenharmony_cistatic void cdx_mcdi_cmd_work(struct work_struct *context); 5262306a36Sopenharmony_cistatic void cdx_mcdi_mode_fail(struct cdx_mcdi *cdx, struct list_head *cleanup_list); 5362306a36Sopenharmony_cistatic void _cdx_mcdi_display_error(struct cdx_mcdi *cdx, unsigned int cmd, 5462306a36Sopenharmony_ci size_t inlen, int raw, int arg, int err_no); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic bool cdx_cmd_cancelled(struct cdx_mcdi_cmd *cmd) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci return cmd->state == MCDI_STATE_RUNNING_CANCELLED; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic void cdx_mcdi_cmd_release(struct kref *ref) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci kfree(container_of(ref, struct cdx_mcdi_cmd, ref)); 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic unsigned int cdx_mcdi_cmd_handle(struct cdx_mcdi_cmd *cmd) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci return cmd->handle; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void _cdx_mcdi_remove_cmd(struct cdx_mcdi_iface *mcdi, 7262306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 7362306a36Sopenharmony_ci struct list_head *cleanup_list) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci /* if cancelled, the completers have already been called */ 7662306a36Sopenharmony_ci if (cdx_cmd_cancelled(cmd)) 7762306a36Sopenharmony_ci return; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (cmd->completer) { 8062306a36Sopenharmony_ci list_add_tail(&cmd->cleanup_list, cleanup_list); 8162306a36Sopenharmony_ci ++mcdi->outstanding_cleanups; 8262306a36Sopenharmony_ci kref_get(&cmd->ref); 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic void cdx_mcdi_remove_cmd(struct cdx_mcdi_iface *mcdi, 8762306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 8862306a36Sopenharmony_ci struct list_head *cleanup_list) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci list_del(&cmd->list); 9162306a36Sopenharmony_ci _cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list); 9262306a36Sopenharmony_ci cmd->state = MCDI_STATE_FINISHED; 9362306a36Sopenharmony_ci kref_put(&cmd->ref, cdx_mcdi_cmd_release); 9462306a36Sopenharmony_ci if (list_empty(&mcdi->cmd_list)) 9562306a36Sopenharmony_ci wake_up(&mcdi->cmd_complete_wq); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic unsigned long cdx_mcdi_rpc_timeout(struct cdx_mcdi *cdx, unsigned int cmd) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci if (!cdx->mcdi_ops->mcdi_rpc_timeout) 10162306a36Sopenharmony_ci return MCDI_RPC_TIMEOUT; 10262306a36Sopenharmony_ci else 10362306a36Sopenharmony_ci return cdx->mcdi_ops->mcdi_rpc_timeout(cdx, cmd); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ciint cdx_mcdi_init(struct cdx_mcdi *cdx) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi; 10962306a36Sopenharmony_ci int rc = -ENOMEM; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci cdx->mcdi = kzalloc(sizeof(*cdx->mcdi), GFP_KERNEL); 11262306a36Sopenharmony_ci if (!cdx->mcdi) 11362306a36Sopenharmony_ci goto fail; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci mcdi = cdx_mcdi_if(cdx); 11662306a36Sopenharmony_ci mcdi->cdx = cdx; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci mcdi->workqueue = alloc_ordered_workqueue("mcdi_wq", 0); 11962306a36Sopenharmony_ci if (!mcdi->workqueue) 12062306a36Sopenharmony_ci goto fail2; 12162306a36Sopenharmony_ci mutex_init(&mcdi->iface_lock); 12262306a36Sopenharmony_ci mcdi->mode = MCDI_MODE_EVENTS; 12362306a36Sopenharmony_ci INIT_LIST_HEAD(&mcdi->cmd_list); 12462306a36Sopenharmony_ci init_waitqueue_head(&mcdi->cmd_complete_wq); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci mcdi->new_epoch = true; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return 0; 12962306a36Sopenharmony_cifail2: 13062306a36Sopenharmony_ci kfree(cdx->mcdi); 13162306a36Sopenharmony_ci cdx->mcdi = NULL; 13262306a36Sopenharmony_cifail: 13362306a36Sopenharmony_ci return rc; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_civoid cdx_mcdi_finish(struct cdx_mcdi *cdx) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci mcdi = cdx_mcdi_if(cdx); 14162306a36Sopenharmony_ci if (!mcdi) 14262306a36Sopenharmony_ci return; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci cdx_mcdi_wait_for_cleanup(cdx); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci destroy_workqueue(mcdi->workqueue); 14762306a36Sopenharmony_ci kfree(cdx->mcdi); 14862306a36Sopenharmony_ci cdx->mcdi = NULL; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic bool cdx_mcdi_flushed(struct cdx_mcdi_iface *mcdi, bool ignore_cleanups) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci bool flushed; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci mutex_lock(&mcdi->iface_lock); 15662306a36Sopenharmony_ci flushed = list_empty(&mcdi->cmd_list) && 15762306a36Sopenharmony_ci (ignore_cleanups || !mcdi->outstanding_cleanups); 15862306a36Sopenharmony_ci mutex_unlock(&mcdi->iface_lock); 15962306a36Sopenharmony_ci return flushed; 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/* Wait for outstanding MCDI commands to complete. */ 16362306a36Sopenharmony_cistatic void cdx_mcdi_wait_for_cleanup(struct cdx_mcdi *cdx) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (!mcdi) 16862306a36Sopenharmony_ci return; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci wait_event(mcdi->cmd_complete_wq, 17162306a36Sopenharmony_ci cdx_mcdi_flushed(mcdi, false)); 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ciint cdx_mcdi_wait_for_quiescence(struct cdx_mcdi *cdx, 17562306a36Sopenharmony_ci unsigned int timeout_jiffies) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); 17862306a36Sopenharmony_ci DEFINE_WAIT_FUNC(wait, woken_wake_function); 17962306a36Sopenharmony_ci int rc = 0; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (!mcdi) 18262306a36Sopenharmony_ci return -EINVAL; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci flush_workqueue(mcdi->workqueue); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci add_wait_queue(&mcdi->cmd_complete_wq, &wait); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci while (!cdx_mcdi_flushed(mcdi, true)) { 18962306a36Sopenharmony_ci rc = wait_woken(&wait, TASK_IDLE, timeout_jiffies); 19062306a36Sopenharmony_ci if (rc) 19162306a36Sopenharmony_ci continue; 19262306a36Sopenharmony_ci break; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci remove_wait_queue(&mcdi->cmd_complete_wq, &wait); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (rc > 0) 19862306a36Sopenharmony_ci rc = 0; 19962306a36Sopenharmony_ci else if (rc == 0) 20062306a36Sopenharmony_ci rc = -ETIMEDOUT; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci return rc; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic u8 cdx_mcdi_payload_csum(const struct cdx_dword *hdr, size_t hdr_len, 20662306a36Sopenharmony_ci const struct cdx_dword *sdu, size_t sdu_len) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci u8 *p = (u8 *)hdr; 20962306a36Sopenharmony_ci u8 csum = 0; 21062306a36Sopenharmony_ci int i; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci for (i = 0; i < hdr_len; i++) 21362306a36Sopenharmony_ci csum += p[i]; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci p = (u8 *)sdu; 21662306a36Sopenharmony_ci for (i = 0; i < sdu_len; i++) 21762306a36Sopenharmony_ci csum += p[i]; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci return ~csum & 0xff; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic void cdx_mcdi_send_request(struct cdx_mcdi *cdx, 22362306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); 22662306a36Sopenharmony_ci const struct cdx_dword *inbuf = cmd->inbuf; 22762306a36Sopenharmony_ci size_t inlen = cmd->inlen; 22862306a36Sopenharmony_ci struct cdx_dword hdr[2]; 22962306a36Sopenharmony_ci size_t hdr_len; 23062306a36Sopenharmony_ci bool not_epoch; 23162306a36Sopenharmony_ci u32 xflags; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (!mcdi) 23462306a36Sopenharmony_ci return; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci mcdi->prev_seq = cmd->seq; 23762306a36Sopenharmony_ci mcdi->seq_held_by[cmd->seq] = cmd; 23862306a36Sopenharmony_ci mcdi->db_held_by = cmd; 23962306a36Sopenharmony_ci cmd->started = jiffies; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci not_epoch = !mcdi->new_epoch; 24262306a36Sopenharmony_ci xflags = 0; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* MCDI v2 */ 24562306a36Sopenharmony_ci WARN_ON(inlen > MCDI_CTL_SDU_LEN_MAX_V2); 24662306a36Sopenharmony_ci CDX_POPULATE_DWORD_7(hdr[0], 24762306a36Sopenharmony_ci MCDI_HEADER_RESPONSE, 0, 24862306a36Sopenharmony_ci MCDI_HEADER_RESYNC, 1, 24962306a36Sopenharmony_ci MCDI_HEADER_CODE, MC_CMD_V2_EXTN, 25062306a36Sopenharmony_ci MCDI_HEADER_DATALEN, 0, 25162306a36Sopenharmony_ci MCDI_HEADER_SEQ, cmd->seq, 25262306a36Sopenharmony_ci MCDI_HEADER_XFLAGS, xflags, 25362306a36Sopenharmony_ci MCDI_HEADER_NOT_EPOCH, not_epoch); 25462306a36Sopenharmony_ci CDX_POPULATE_DWORD_3(hdr[1], 25562306a36Sopenharmony_ci MC_CMD_V2_EXTN_IN_EXTENDED_CMD, cmd->cmd, 25662306a36Sopenharmony_ci MC_CMD_V2_EXTN_IN_ACTUAL_LEN, inlen, 25762306a36Sopenharmony_ci MC_CMD_V2_EXTN_IN_MESSAGE_TYPE, 25862306a36Sopenharmony_ci MC_CMD_V2_EXTN_IN_MCDI_MESSAGE_TYPE_PLATFORM); 25962306a36Sopenharmony_ci hdr_len = 8; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci hdr[0].cdx_u32 |= (__force __le32)(cdx_mcdi_payload_csum(hdr, hdr_len, inbuf, inlen) << 26262306a36Sopenharmony_ci MCDI_HEADER_XFLAGS_LBN); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci print_hex_dump_debug("MCDI REQ HEADER: ", DUMP_PREFIX_NONE, 32, 4, hdr, hdr_len, false); 26562306a36Sopenharmony_ci print_hex_dump_debug("MCDI REQ PAYLOAD: ", DUMP_PREFIX_NONE, 32, 4, inbuf, inlen, false); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci cdx->mcdi_ops->mcdi_request(cdx, hdr, hdr_len, inbuf, inlen); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci mcdi->new_epoch = false; 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic int cdx_mcdi_errno(struct cdx_mcdi *cdx, unsigned int mcdi_err) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci switch (mcdi_err) { 27562306a36Sopenharmony_ci case 0: 27662306a36Sopenharmony_ci case MC_CMD_ERR_QUEUE_FULL: 27762306a36Sopenharmony_ci return mcdi_err; 27862306a36Sopenharmony_ci case MC_CMD_ERR_EPERM: 27962306a36Sopenharmony_ci return -EPERM; 28062306a36Sopenharmony_ci case MC_CMD_ERR_ENOENT: 28162306a36Sopenharmony_ci return -ENOENT; 28262306a36Sopenharmony_ci case MC_CMD_ERR_EINTR: 28362306a36Sopenharmony_ci return -EINTR; 28462306a36Sopenharmony_ci case MC_CMD_ERR_EAGAIN: 28562306a36Sopenharmony_ci return -EAGAIN; 28662306a36Sopenharmony_ci case MC_CMD_ERR_EACCES: 28762306a36Sopenharmony_ci return -EACCES; 28862306a36Sopenharmony_ci case MC_CMD_ERR_EBUSY: 28962306a36Sopenharmony_ci return -EBUSY; 29062306a36Sopenharmony_ci case MC_CMD_ERR_EINVAL: 29162306a36Sopenharmony_ci return -EINVAL; 29262306a36Sopenharmony_ci case MC_CMD_ERR_ERANGE: 29362306a36Sopenharmony_ci return -ERANGE; 29462306a36Sopenharmony_ci case MC_CMD_ERR_EDEADLK: 29562306a36Sopenharmony_ci return -EDEADLK; 29662306a36Sopenharmony_ci case MC_CMD_ERR_ENOSYS: 29762306a36Sopenharmony_ci return -EOPNOTSUPP; 29862306a36Sopenharmony_ci case MC_CMD_ERR_ETIME: 29962306a36Sopenharmony_ci return -ETIME; 30062306a36Sopenharmony_ci case MC_CMD_ERR_EALREADY: 30162306a36Sopenharmony_ci return -EALREADY; 30262306a36Sopenharmony_ci case MC_CMD_ERR_ENOSPC: 30362306a36Sopenharmony_ci return -ENOSPC; 30462306a36Sopenharmony_ci case MC_CMD_ERR_ENOMEM: 30562306a36Sopenharmony_ci return -ENOMEM; 30662306a36Sopenharmony_ci case MC_CMD_ERR_ENOTSUP: 30762306a36Sopenharmony_ci return -EOPNOTSUPP; 30862306a36Sopenharmony_ci case MC_CMD_ERR_ALLOC_FAIL: 30962306a36Sopenharmony_ci return -ENOBUFS; 31062306a36Sopenharmony_ci case MC_CMD_ERR_MAC_EXIST: 31162306a36Sopenharmony_ci return -EADDRINUSE; 31262306a36Sopenharmony_ci case MC_CMD_ERR_NO_EVB_PORT: 31362306a36Sopenharmony_ci return -EAGAIN; 31462306a36Sopenharmony_ci default: 31562306a36Sopenharmony_ci return -EPROTO; 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic void cdx_mcdi_process_cleanup_list(struct cdx_mcdi *cdx, 32062306a36Sopenharmony_ci struct list_head *cleanup_list) 32162306a36Sopenharmony_ci{ 32262306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); 32362306a36Sopenharmony_ci unsigned int cleanups = 0; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (!mcdi) 32662306a36Sopenharmony_ci return; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci while (!list_empty(cleanup_list)) { 32962306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd = 33062306a36Sopenharmony_ci list_first_entry(cleanup_list, 33162306a36Sopenharmony_ci struct cdx_mcdi_cmd, cleanup_list); 33262306a36Sopenharmony_ci cmd->completer(cdx, cmd->cookie, cmd->rc, 33362306a36Sopenharmony_ci cmd->outbuf, cmd->outlen); 33462306a36Sopenharmony_ci list_del(&cmd->cleanup_list); 33562306a36Sopenharmony_ci kref_put(&cmd->ref, cdx_mcdi_cmd_release); 33662306a36Sopenharmony_ci ++cleanups; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci if (cleanups) { 34062306a36Sopenharmony_ci bool all_done; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci mutex_lock(&mcdi->iface_lock); 34362306a36Sopenharmony_ci CDX_WARN_ON_PARANOID(cleanups > mcdi->outstanding_cleanups); 34462306a36Sopenharmony_ci all_done = (mcdi->outstanding_cleanups -= cleanups) == 0; 34562306a36Sopenharmony_ci mutex_unlock(&mcdi->iface_lock); 34662306a36Sopenharmony_ci if (all_done) 34762306a36Sopenharmony_ci wake_up(&mcdi->cmd_complete_wq); 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cistatic void _cdx_mcdi_cancel_cmd(struct cdx_mcdi_iface *mcdi, 35262306a36Sopenharmony_ci unsigned int handle, 35362306a36Sopenharmony_ci struct list_head *cleanup_list) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci list_for_each_entry(cmd, &mcdi->cmd_list, list) 35862306a36Sopenharmony_ci if (cdx_mcdi_cmd_handle(cmd) == handle) { 35962306a36Sopenharmony_ci switch (cmd->state) { 36062306a36Sopenharmony_ci case MCDI_STATE_QUEUED: 36162306a36Sopenharmony_ci case MCDI_STATE_RETRY: 36262306a36Sopenharmony_ci pr_debug("command %#x inlen %zu cancelled in queue\n", 36362306a36Sopenharmony_ci cmd->cmd, cmd->inlen); 36462306a36Sopenharmony_ci /* if not yet running, properly cancel it */ 36562306a36Sopenharmony_ci cmd->rc = -EPIPE; 36662306a36Sopenharmony_ci cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list); 36762306a36Sopenharmony_ci break; 36862306a36Sopenharmony_ci case MCDI_STATE_RUNNING: 36962306a36Sopenharmony_ci case MCDI_STATE_RUNNING_CANCELLED: 37062306a36Sopenharmony_ci case MCDI_STATE_FINISHED: 37162306a36Sopenharmony_ci default: 37262306a36Sopenharmony_ci /* invalid state? */ 37362306a36Sopenharmony_ci WARN_ON(1); 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci break; 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic void cdx_mcdi_cancel_cmd(struct cdx_mcdi *cdx, struct cdx_mcdi_cmd *cmd) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); 38262306a36Sopenharmony_ci LIST_HEAD(cleanup_list); 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci if (!mcdi) 38562306a36Sopenharmony_ci return; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci mutex_lock(&mcdi->iface_lock); 38862306a36Sopenharmony_ci cdx_mcdi_timeout_cmd(mcdi, cmd, &cleanup_list); 38962306a36Sopenharmony_ci mutex_unlock(&mcdi->iface_lock); 39062306a36Sopenharmony_ci cdx_mcdi_process_cleanup_list(cdx, &cleanup_list); 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_cistruct cdx_mcdi_blocking_data { 39462306a36Sopenharmony_ci struct kref ref; 39562306a36Sopenharmony_ci bool done; 39662306a36Sopenharmony_ci wait_queue_head_t wq; 39762306a36Sopenharmony_ci int rc; 39862306a36Sopenharmony_ci struct cdx_dword *outbuf; 39962306a36Sopenharmony_ci size_t outlen; 40062306a36Sopenharmony_ci size_t outlen_actual; 40162306a36Sopenharmony_ci}; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_cistatic void cdx_mcdi_blocking_data_release(struct kref *ref) 40462306a36Sopenharmony_ci{ 40562306a36Sopenharmony_ci kfree(container_of(ref, struct cdx_mcdi_blocking_data, ref)); 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_cistatic void cdx_mcdi_rpc_completer(struct cdx_mcdi *cdx, unsigned long cookie, 40962306a36Sopenharmony_ci int rc, struct cdx_dword *outbuf, 41062306a36Sopenharmony_ci size_t outlen_actual) 41162306a36Sopenharmony_ci{ 41262306a36Sopenharmony_ci struct cdx_mcdi_blocking_data *wait_data = 41362306a36Sopenharmony_ci (struct cdx_mcdi_blocking_data *)cookie; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci wait_data->rc = rc; 41662306a36Sopenharmony_ci memcpy(wait_data->outbuf, outbuf, 41762306a36Sopenharmony_ci min(outlen_actual, wait_data->outlen)); 41862306a36Sopenharmony_ci wait_data->outlen_actual = outlen_actual; 41962306a36Sopenharmony_ci /* memory barrier */ 42062306a36Sopenharmony_ci smp_wmb(); 42162306a36Sopenharmony_ci wait_data->done = true; 42262306a36Sopenharmony_ci wake_up(&wait_data->wq); 42362306a36Sopenharmony_ci kref_put(&wait_data->ref, cdx_mcdi_blocking_data_release); 42462306a36Sopenharmony_ci} 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_cistatic int cdx_mcdi_rpc_sync(struct cdx_mcdi *cdx, unsigned int cmd, 42762306a36Sopenharmony_ci const struct cdx_dword *inbuf, size_t inlen, 42862306a36Sopenharmony_ci struct cdx_dword *outbuf, size_t outlen, 42962306a36Sopenharmony_ci size_t *outlen_actual, bool quiet) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct cdx_mcdi_blocking_data *wait_data; 43262306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd_item; 43362306a36Sopenharmony_ci unsigned int handle; 43462306a36Sopenharmony_ci int rc; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci if (outlen_actual) 43762306a36Sopenharmony_ci *outlen_actual = 0; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci wait_data = kmalloc(sizeof(*wait_data), GFP_KERNEL); 44062306a36Sopenharmony_ci if (!wait_data) 44162306a36Sopenharmony_ci return -ENOMEM; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci cmd_item = kmalloc(sizeof(*cmd_item), GFP_KERNEL); 44462306a36Sopenharmony_ci if (!cmd_item) { 44562306a36Sopenharmony_ci kfree(wait_data); 44662306a36Sopenharmony_ci return -ENOMEM; 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci kref_init(&wait_data->ref); 45062306a36Sopenharmony_ci wait_data->done = false; 45162306a36Sopenharmony_ci init_waitqueue_head(&wait_data->wq); 45262306a36Sopenharmony_ci wait_data->outbuf = outbuf; 45362306a36Sopenharmony_ci wait_data->outlen = outlen; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci kref_init(&cmd_item->ref); 45662306a36Sopenharmony_ci cmd_item->quiet = quiet; 45762306a36Sopenharmony_ci cmd_item->cookie = (unsigned long)wait_data; 45862306a36Sopenharmony_ci cmd_item->completer = &cdx_mcdi_rpc_completer; 45962306a36Sopenharmony_ci cmd_item->cmd = cmd; 46062306a36Sopenharmony_ci cmd_item->inlen = inlen; 46162306a36Sopenharmony_ci cmd_item->inbuf = inbuf; 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci /* Claim an extra reference for the completer to put. */ 46462306a36Sopenharmony_ci kref_get(&wait_data->ref); 46562306a36Sopenharmony_ci rc = cdx_mcdi_rpc_async_internal(cdx, cmd_item, &handle); 46662306a36Sopenharmony_ci if (rc) { 46762306a36Sopenharmony_ci kref_put(&wait_data->ref, cdx_mcdi_blocking_data_release); 46862306a36Sopenharmony_ci goto out; 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci if (!wait_event_timeout(wait_data->wq, wait_data->done, 47262306a36Sopenharmony_ci cdx_mcdi_rpc_timeout(cdx, cmd)) && 47362306a36Sopenharmony_ci !wait_data->done) { 47462306a36Sopenharmony_ci pr_err("MC command 0x%x inlen %zu timed out (sync)\n", 47562306a36Sopenharmony_ci cmd, inlen); 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci cdx_mcdi_cancel_cmd(cdx, cmd_item); 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci wait_data->rc = -ETIMEDOUT; 48062306a36Sopenharmony_ci wait_data->outlen_actual = 0; 48162306a36Sopenharmony_ci } 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci if (outlen_actual) 48462306a36Sopenharmony_ci *outlen_actual = wait_data->outlen_actual; 48562306a36Sopenharmony_ci rc = wait_data->rc; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ciout: 48862306a36Sopenharmony_ci kref_put(&wait_data->ref, cdx_mcdi_blocking_data_release); 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci return rc; 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_cistatic bool cdx_mcdi_get_seq(struct cdx_mcdi_iface *mcdi, unsigned char *seq) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci *seq = mcdi->prev_seq; 49662306a36Sopenharmony_ci do { 49762306a36Sopenharmony_ci *seq = (*seq + 1) % ARRAY_SIZE(mcdi->seq_held_by); 49862306a36Sopenharmony_ci } while (mcdi->seq_held_by[*seq] && *seq != mcdi->prev_seq); 49962306a36Sopenharmony_ci return !mcdi->seq_held_by[*seq]; 50062306a36Sopenharmony_ci} 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cistatic int cdx_mcdi_rpc_async_internal(struct cdx_mcdi *cdx, 50362306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 50462306a36Sopenharmony_ci unsigned int *handle) 50562306a36Sopenharmony_ci{ 50662306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); 50762306a36Sopenharmony_ci LIST_HEAD(cleanup_list); 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci if (!mcdi) { 51062306a36Sopenharmony_ci kref_put(&cmd->ref, cdx_mcdi_cmd_release); 51162306a36Sopenharmony_ci return -ENETDOWN; 51262306a36Sopenharmony_ci } 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci if (mcdi->mode == MCDI_MODE_FAIL) { 51562306a36Sopenharmony_ci kref_put(&cmd->ref, cdx_mcdi_cmd_release); 51662306a36Sopenharmony_ci return -ENETDOWN; 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci cmd->mcdi = mcdi; 52062306a36Sopenharmony_ci INIT_WORK(&cmd->work, cdx_mcdi_cmd_work); 52162306a36Sopenharmony_ci INIT_LIST_HEAD(&cmd->list); 52262306a36Sopenharmony_ci INIT_LIST_HEAD(&cmd->cleanup_list); 52362306a36Sopenharmony_ci cmd->rc = 0; 52462306a36Sopenharmony_ci cmd->outbuf = NULL; 52562306a36Sopenharmony_ci cmd->outlen = 0; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci queue_work(mcdi->workqueue, &cmd->work); 52862306a36Sopenharmony_ci return 0; 52962306a36Sopenharmony_ci} 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_cistatic void cdx_mcdi_cmd_start_or_queue(struct cdx_mcdi_iface *mcdi, 53262306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd) 53362306a36Sopenharmony_ci{ 53462306a36Sopenharmony_ci struct cdx_mcdi *cdx = mcdi->cdx; 53562306a36Sopenharmony_ci u8 seq; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci if (!mcdi->db_held_by && 53862306a36Sopenharmony_ci cdx_mcdi_get_seq(mcdi, &seq)) { 53962306a36Sopenharmony_ci cmd->seq = seq; 54062306a36Sopenharmony_ci cmd->reboot_seen = false; 54162306a36Sopenharmony_ci cdx_mcdi_send_request(cdx, cmd); 54262306a36Sopenharmony_ci cmd->state = MCDI_STATE_RUNNING; 54362306a36Sopenharmony_ci } else { 54462306a36Sopenharmony_ci cmd->state = MCDI_STATE_QUEUED; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci} 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci/* try to advance other commands */ 54962306a36Sopenharmony_cistatic void cdx_mcdi_start_or_queue(struct cdx_mcdi_iface *mcdi, 55062306a36Sopenharmony_ci bool allow_retry) 55162306a36Sopenharmony_ci{ 55262306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, *tmp; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci list_for_each_entry_safe(cmd, tmp, &mcdi->cmd_list, list) 55562306a36Sopenharmony_ci if (cmd->state == MCDI_STATE_QUEUED || 55662306a36Sopenharmony_ci (cmd->state == MCDI_STATE_RETRY && allow_retry)) 55762306a36Sopenharmony_ci cdx_mcdi_cmd_start_or_queue(mcdi, cmd); 55862306a36Sopenharmony_ci} 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_civoid cdx_mcdi_process_cmd(struct cdx_mcdi *cdx, struct cdx_dword *outbuf, int len) 56162306a36Sopenharmony_ci{ 56262306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi; 56362306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd; 56462306a36Sopenharmony_ci LIST_HEAD(cleanup_list); 56562306a36Sopenharmony_ci unsigned int respseq; 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci if (!len || !outbuf) { 56862306a36Sopenharmony_ci pr_err("Got empty MC response\n"); 56962306a36Sopenharmony_ci return; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci mcdi = cdx_mcdi_if(cdx); 57362306a36Sopenharmony_ci if (!mcdi) 57462306a36Sopenharmony_ci return; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci respseq = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_SEQ); 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci mutex_lock(&mcdi->iface_lock); 57962306a36Sopenharmony_ci cmd = mcdi->seq_held_by[respseq]; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci if (cmd) { 58262306a36Sopenharmony_ci if (cmd->state == MCDI_STATE_FINISHED) { 58362306a36Sopenharmony_ci mutex_unlock(&mcdi->iface_lock); 58462306a36Sopenharmony_ci kref_put(&cmd->ref, cdx_mcdi_cmd_release); 58562306a36Sopenharmony_ci return; 58662306a36Sopenharmony_ci } 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci cdx_mcdi_complete_cmd(mcdi, cmd, outbuf, len, &cleanup_list); 58962306a36Sopenharmony_ci } else { 59062306a36Sopenharmony_ci pr_err("MC response unexpected for seq : %0X\n", respseq); 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci mutex_unlock(&mcdi->iface_lock); 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci cdx_mcdi_process_cleanup_list(mcdi->cdx, &cleanup_list); 59662306a36Sopenharmony_ci} 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_cistatic void cdx_mcdi_cmd_work(struct work_struct *context) 59962306a36Sopenharmony_ci{ 60062306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd = 60162306a36Sopenharmony_ci container_of(context, struct cdx_mcdi_cmd, work); 60262306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cmd->mcdi; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci mutex_lock(&mcdi->iface_lock); 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci cmd->handle = mcdi->prev_handle++; 60762306a36Sopenharmony_ci list_add_tail(&cmd->list, &mcdi->cmd_list); 60862306a36Sopenharmony_ci cdx_mcdi_cmd_start_or_queue(mcdi, cmd); 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci mutex_unlock(&mcdi->iface_lock); 61162306a36Sopenharmony_ci} 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci/* 61462306a36Sopenharmony_ci * Returns true if the MCDI module is finished with the command. 61562306a36Sopenharmony_ci * (examples of false would be if the command was proxied, or it was 61662306a36Sopenharmony_ci * rejected by the MC due to lack of resources and requeued). 61762306a36Sopenharmony_ci */ 61862306a36Sopenharmony_cistatic bool cdx_mcdi_complete_cmd(struct cdx_mcdi_iface *mcdi, 61962306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 62062306a36Sopenharmony_ci struct cdx_dword *outbuf, 62162306a36Sopenharmony_ci int len, 62262306a36Sopenharmony_ci struct list_head *cleanup_list) 62362306a36Sopenharmony_ci{ 62462306a36Sopenharmony_ci size_t resp_hdr_len, resp_data_len; 62562306a36Sopenharmony_ci struct cdx_mcdi *cdx = mcdi->cdx; 62662306a36Sopenharmony_ci unsigned int respcmd, error; 62762306a36Sopenharmony_ci bool completed = false; 62862306a36Sopenharmony_ci int rc; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci /* ensure the command can't go away before this function returns */ 63162306a36Sopenharmony_ci kref_get(&cmd->ref); 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci respcmd = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_CODE); 63462306a36Sopenharmony_ci error = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_ERROR); 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci if (respcmd != MC_CMD_V2_EXTN) { 63762306a36Sopenharmony_ci resp_hdr_len = 4; 63862306a36Sopenharmony_ci resp_data_len = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_DATALEN); 63962306a36Sopenharmony_ci } else { 64062306a36Sopenharmony_ci resp_data_len = 0; 64162306a36Sopenharmony_ci resp_hdr_len = 8; 64262306a36Sopenharmony_ci if (len >= 8) 64362306a36Sopenharmony_ci resp_data_len = 64462306a36Sopenharmony_ci CDX_DWORD_FIELD(outbuf[1], MC_CMD_V2_EXTN_IN_ACTUAL_LEN); 64562306a36Sopenharmony_ci } 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci if ((resp_hdr_len + resp_data_len) > len) { 64862306a36Sopenharmony_ci pr_warn("Incomplete MCDI response received %d. Expected %zu\n", 64962306a36Sopenharmony_ci len, (resp_hdr_len + resp_data_len)); 65062306a36Sopenharmony_ci resp_data_len = 0; 65162306a36Sopenharmony_ci } 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci print_hex_dump_debug("MCDI RESP HEADER: ", DUMP_PREFIX_NONE, 32, 4, 65462306a36Sopenharmony_ci outbuf, resp_hdr_len, false); 65562306a36Sopenharmony_ci print_hex_dump_debug("MCDI RESP PAYLOAD: ", DUMP_PREFIX_NONE, 32, 4, 65662306a36Sopenharmony_ci outbuf + (resp_hdr_len / 4), resp_data_len, false); 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci if (error && resp_data_len == 0) { 65962306a36Sopenharmony_ci /* MC rebooted during command */ 66062306a36Sopenharmony_ci rc = -EIO; 66162306a36Sopenharmony_ci } else { 66262306a36Sopenharmony_ci if (WARN_ON_ONCE(error && resp_data_len < 4)) 66362306a36Sopenharmony_ci resp_data_len = 4; 66462306a36Sopenharmony_ci if (error) { 66562306a36Sopenharmony_ci rc = CDX_DWORD_FIELD(outbuf[resp_hdr_len / 4], CDX_DWORD); 66662306a36Sopenharmony_ci if (!cmd->quiet) { 66762306a36Sopenharmony_ci int err_arg = 0; 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci if (resp_data_len >= MC_CMD_ERR_ARG_OFST + 4) { 67062306a36Sopenharmony_ci int offset = (resp_hdr_len + MC_CMD_ERR_ARG_OFST) / 4; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci err_arg = CDX_DWORD_VAL(outbuf[offset]); 67362306a36Sopenharmony_ci } 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci _cdx_mcdi_display_error(cdx, cmd->cmd, 67662306a36Sopenharmony_ci cmd->inlen, rc, err_arg, 67762306a36Sopenharmony_ci cdx_mcdi_errno(cdx, rc)); 67862306a36Sopenharmony_ci } 67962306a36Sopenharmony_ci rc = cdx_mcdi_errno(cdx, rc); 68062306a36Sopenharmony_ci } else { 68162306a36Sopenharmony_ci rc = 0; 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci } 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci /* free doorbell */ 68662306a36Sopenharmony_ci if (mcdi->db_held_by == cmd) 68762306a36Sopenharmony_ci mcdi->db_held_by = NULL; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci if (cdx_cmd_cancelled(cmd)) { 69062306a36Sopenharmony_ci list_del(&cmd->list); 69162306a36Sopenharmony_ci kref_put(&cmd->ref, cdx_mcdi_cmd_release); 69262306a36Sopenharmony_ci completed = true; 69362306a36Sopenharmony_ci } else if (rc == MC_CMD_ERR_QUEUE_FULL) { 69462306a36Sopenharmony_ci cmd->state = MCDI_STATE_RETRY; 69562306a36Sopenharmony_ci } else { 69662306a36Sopenharmony_ci cmd->rc = rc; 69762306a36Sopenharmony_ci cmd->outbuf = outbuf + DIV_ROUND_UP(resp_hdr_len, 4); 69862306a36Sopenharmony_ci cmd->outlen = resp_data_len; 69962306a36Sopenharmony_ci cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list); 70062306a36Sopenharmony_ci completed = true; 70162306a36Sopenharmony_ci } 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci /* free sequence number and buffer */ 70462306a36Sopenharmony_ci mcdi->seq_held_by[cmd->seq] = NULL; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci cdx_mcdi_start_or_queue(mcdi, rc != MC_CMD_ERR_QUEUE_FULL); 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci /* wake up anyone waiting for flush */ 70962306a36Sopenharmony_ci wake_up(&mcdi->cmd_complete_wq); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci kref_put(&cmd->ref, cdx_mcdi_cmd_release); 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci return completed; 71462306a36Sopenharmony_ci} 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_cistatic void cdx_mcdi_timeout_cmd(struct cdx_mcdi_iface *mcdi, 71762306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd, 71862306a36Sopenharmony_ci struct list_head *cleanup_list) 71962306a36Sopenharmony_ci{ 72062306a36Sopenharmony_ci struct cdx_mcdi *cdx = mcdi->cdx; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci pr_err("MC command 0x%x inlen %zu state %d timed out after %u ms\n", 72362306a36Sopenharmony_ci cmd->cmd, cmd->inlen, cmd->state, 72462306a36Sopenharmony_ci jiffies_to_msecs(jiffies - cmd->started)); 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci cmd->rc = -ETIMEDOUT; 72762306a36Sopenharmony_ci cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list); 72862306a36Sopenharmony_ci 72962306a36Sopenharmony_ci cdx_mcdi_mode_fail(cdx, cleanup_list); 73062306a36Sopenharmony_ci} 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci/** 73362306a36Sopenharmony_ci * cdx_mcdi_rpc - Issue an MCDI command and wait for completion 73462306a36Sopenharmony_ci * @cdx: NIC through which to issue the command 73562306a36Sopenharmony_ci * @cmd: Command type number 73662306a36Sopenharmony_ci * @inbuf: Command parameters 73762306a36Sopenharmony_ci * @inlen: Length of command parameters, in bytes. Must be a multiple 73862306a36Sopenharmony_ci * of 4 and no greater than %MCDI_CTL_SDU_LEN_MAX_V1. 73962306a36Sopenharmony_ci * @outbuf: Response buffer. May be %NULL if @outlen is 0. 74062306a36Sopenharmony_ci * @outlen: Length of response buffer, in bytes. If the actual 74162306a36Sopenharmony_ci * response is longer than @outlen & ~3, it will be truncated 74262306a36Sopenharmony_ci * to that length. 74362306a36Sopenharmony_ci * @outlen_actual: Pointer through which to return the actual response 74462306a36Sopenharmony_ci * length. May be %NULL if this is not needed. 74562306a36Sopenharmony_ci * 74662306a36Sopenharmony_ci * This function may sleep and therefore must be called in process 74762306a36Sopenharmony_ci * context. 74862306a36Sopenharmony_ci * 74962306a36Sopenharmony_ci * Return: A negative error code, or zero if successful. The error 75062306a36Sopenharmony_ci * code may come from the MCDI response or may indicate a failure 75162306a36Sopenharmony_ci * to communicate with the MC. In the former case, the response 75262306a36Sopenharmony_ci * will still be copied to @outbuf and *@outlen_actual will be 75362306a36Sopenharmony_ci * set accordingly. In the latter case, *@outlen_actual will be 75462306a36Sopenharmony_ci * set to zero. 75562306a36Sopenharmony_ci */ 75662306a36Sopenharmony_ciint cdx_mcdi_rpc(struct cdx_mcdi *cdx, unsigned int cmd, 75762306a36Sopenharmony_ci const struct cdx_dword *inbuf, size_t inlen, 75862306a36Sopenharmony_ci struct cdx_dword *outbuf, size_t outlen, 75962306a36Sopenharmony_ci size_t *outlen_actual) 76062306a36Sopenharmony_ci{ 76162306a36Sopenharmony_ci return cdx_mcdi_rpc_sync(cdx, cmd, inbuf, inlen, outbuf, outlen, 76262306a36Sopenharmony_ci outlen_actual, false); 76362306a36Sopenharmony_ci} 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci/** 76662306a36Sopenharmony_ci * cdx_mcdi_rpc_async - Schedule an MCDI command to run asynchronously 76762306a36Sopenharmony_ci * @cdx: NIC through which to issue the command 76862306a36Sopenharmony_ci * @cmd: Command type number 76962306a36Sopenharmony_ci * @inbuf: Command parameters 77062306a36Sopenharmony_ci * @inlen: Length of command parameters, in bytes 77162306a36Sopenharmony_ci * @complete: Function to be called on completion or cancellation. 77262306a36Sopenharmony_ci * @cookie: Arbitrary value to be passed to @complete. 77362306a36Sopenharmony_ci * 77462306a36Sopenharmony_ci * This function does not sleep and therefore may be called in atomic 77562306a36Sopenharmony_ci * context. It will fail if event queues are disabled or if MCDI 77662306a36Sopenharmony_ci * event completions have been disabled due to an error. 77762306a36Sopenharmony_ci * 77862306a36Sopenharmony_ci * If it succeeds, the @complete function will be called exactly once 77962306a36Sopenharmony_ci * in process context, when one of the following occurs: 78062306a36Sopenharmony_ci * (a) the completion event is received (in process context) 78162306a36Sopenharmony_ci * (b) event queues are disabled (in the process that disables them) 78262306a36Sopenharmony_ci */ 78362306a36Sopenharmony_ciint 78462306a36Sopenharmony_cicdx_mcdi_rpc_async(struct cdx_mcdi *cdx, unsigned int cmd, 78562306a36Sopenharmony_ci const struct cdx_dword *inbuf, size_t inlen, 78662306a36Sopenharmony_ci cdx_mcdi_async_completer *complete, unsigned long cookie) 78762306a36Sopenharmony_ci{ 78862306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd_item = 78962306a36Sopenharmony_ci kmalloc(sizeof(struct cdx_mcdi_cmd) + inlen, GFP_ATOMIC); 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci if (!cmd_item) 79262306a36Sopenharmony_ci return -ENOMEM; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci kref_init(&cmd_item->ref); 79562306a36Sopenharmony_ci cmd_item->quiet = true; 79662306a36Sopenharmony_ci cmd_item->cookie = cookie; 79762306a36Sopenharmony_ci cmd_item->completer = complete; 79862306a36Sopenharmony_ci cmd_item->cmd = cmd; 79962306a36Sopenharmony_ci cmd_item->inlen = inlen; 80062306a36Sopenharmony_ci /* inbuf is probably not valid after return, so take a copy */ 80162306a36Sopenharmony_ci cmd_item->inbuf = (struct cdx_dword *)(cmd_item + 1); 80262306a36Sopenharmony_ci memcpy(cmd_item + 1, inbuf, inlen); 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci return cdx_mcdi_rpc_async_internal(cdx, cmd_item, NULL); 80562306a36Sopenharmony_ci} 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_cistatic void _cdx_mcdi_display_error(struct cdx_mcdi *cdx, unsigned int cmd, 80862306a36Sopenharmony_ci size_t inlen, int raw, int arg, int err_no) 80962306a36Sopenharmony_ci{ 81062306a36Sopenharmony_ci pr_err("MC command 0x%x inlen %d failed err_no=%d (raw=%d) arg=%d\n", 81162306a36Sopenharmony_ci cmd, (int)inlen, err_no, raw, arg); 81262306a36Sopenharmony_ci} 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci/* 81562306a36Sopenharmony_ci * Set MCDI mode to fail to prevent any new commands, then cancel any 81662306a36Sopenharmony_ci * outstanding commands. 81762306a36Sopenharmony_ci * Caller must hold the mcdi iface_lock. 81862306a36Sopenharmony_ci */ 81962306a36Sopenharmony_cistatic void cdx_mcdi_mode_fail(struct cdx_mcdi *cdx, struct list_head *cleanup_list) 82062306a36Sopenharmony_ci{ 82162306a36Sopenharmony_ci struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci if (!mcdi) 82462306a36Sopenharmony_ci return; 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci mcdi->mode = MCDI_MODE_FAIL; 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci while (!list_empty(&mcdi->cmd_list)) { 82962306a36Sopenharmony_ci struct cdx_mcdi_cmd *cmd; 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci cmd = list_first_entry(&mcdi->cmd_list, struct cdx_mcdi_cmd, 83262306a36Sopenharmony_ci list); 83362306a36Sopenharmony_ci _cdx_mcdi_cancel_cmd(mcdi, cdx_mcdi_cmd_handle(cmd), cleanup_list); 83462306a36Sopenharmony_ci } 83562306a36Sopenharmony_ci} 836