// 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)]; }; 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; mcdi->workqueue = alloc_ordered_workqueue("mcdi_wq", 0); if (!mcdi->workqueue) goto fail2; 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; fail2: 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); destroy_workqueue(mcdi->workqueue); kfree(cdx->mcdi); cdx->mcdi = NULL; } static bool cdx_mcdi_flushed(struct cdx_mcdi_iface *mcdi, bool ignore_cleanups) { bool flushed; mutex_lock(&mcdi->iface_lock); flushed = list_empty(&mcdi->cmd_list) && (ignore_cleanups || !mcdi->outstanding_cleanups); mutex_unlock(&mcdi->iface_lock); return flushed; } /* Wait for outstanding MCDI commands to complete. */ static void cdx_mcdi_wait_for_cleanup(struct cdx_mcdi *cdx) { struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); if (!mcdi) return; wait_event(mcdi->cmd_complete_wq, cdx_mcdi_flushed(mcdi, false)); } int cdx_mcdi_wait_for_quiescence(struct cdx_mcdi *cdx, unsigned int timeout_jiffies) { struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); DEFINE_WAIT_FUNC(wait, woken_wake_function); int rc = 0; if (!mcdi) return -EINVAL; flush_workqueue(mcdi->workqueue); add_wait_queue(&mcdi->cmd_complete_wq, &wait); while (!cdx_mcdi_flushed(mcdi, true)) { rc = wait_woken(&wait, TASK_IDLE, timeout_jiffies); if (rc) continue; break; } remove_wait_queue(&mcdi->cmd_complete_wq, &wait); if (rc > 0) rc = 0; else if (rc == 0) rc = -ETIMEDOUT; return rc; } static u8 cdx_mcdi_payload_csum(const struct cdx_dword *hdr, size_t hdr_len, const struct cdx_dword *sdu, size_t sdu_len) { u8 *p = (u8 *)hdr; u8 csum = 0; int i; for (i = 0; i < hdr_len; i++) csum += p[i]; p = (u8 *)sdu; for (i = 0; i < sdu_len; i++) csum += p[i]; return ~csum & 0xff; } static void cdx_mcdi_send_request(struct cdx_mcdi *cdx, struct cdx_mcdi_cmd *cmd) { struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); const struct cdx_dword *inbuf = cmd->inbuf; size_t inlen = cmd->inlen; struct cdx_dword hdr[2]; size_t hdr_len; bool not_epoch; u32 xflags; if (!mcdi) return; mcdi->prev_seq = cmd->seq; mcdi->seq_held_by[cmd->seq] = cmd; mcdi->db_held_by = cmd; cmd->started = jiffies; not_epoch = !mcdi->new_epoch; xflags = 0; /* MCDI v2 */ WARN_ON(inlen > MCDI_CTL_SDU_LEN_MAX_V2); CDX_POPULATE_DWORD_7(hdr[0], MCDI_HEADER_RESPONSE, 0, MCDI_HEADER_RESYNC, 1, MCDI_HEADER_CODE, MC_CMD_V2_EXTN, MCDI_HEADER_DATALEN, 0, MCDI_HEADER_SEQ, cmd->seq, MCDI_HEADER_XFLAGS, xflags, MCDI_HEADER_NOT_EPOCH, not_epoch); CDX_POPULATE_DWORD_3(hdr[1], MC_CMD_V2_EXTN_IN_EXTENDED_CMD, cmd->cmd, MC_CMD_V2_EXTN_IN_ACTUAL_LEN, inlen, MC_CMD_V2_EXTN_IN_MESSAGE_TYPE, MC_CMD_V2_EXTN_IN_MCDI_MESSAGE_TYPE_PLATFORM); hdr_len = 8; hdr[0].cdx_u32 |= (__force __le32)(cdx_mcdi_payload_csum(hdr, hdr_len, inbuf, inlen) << MCDI_HEADER_XFLAGS_LBN); print_hex_dump_debug("MCDI REQ HEADER: ", DUMP_PREFIX_NONE, 32, 4, hdr, hdr_len, false); print_hex_dump_debug("MCDI REQ PAYLOAD: ", DUMP_PREFIX_NONE, 32, 4, inbuf, inlen, false); cdx->mcdi_ops->mcdi_request(cdx, hdr, hdr_len, inbuf, inlen); mcdi->new_epoch = false; } static int cdx_mcdi_errno(struct cdx_mcdi *cdx, unsigned int mcdi_err) { switch (mcdi_err) { case 0: case MC_CMD_ERR_QUEUE_FULL: return mcdi_err; case MC_CMD_ERR_EPERM: return -EPERM; case MC_CMD_ERR_ENOENT: return -ENOENT; case MC_CMD_ERR_EINTR: return -EINTR; case MC_CMD_ERR_EAGAIN: return -EAGAIN; case MC_CMD_ERR_EACCES: return -EACCES; case MC_CMD_ERR_EBUSY: return -EBUSY; case MC_CMD_ERR_EINVAL: return -EINVAL; case MC_CMD_ERR_ERANGE: return -ERANGE; case MC_CMD_ERR_EDEADLK: return -EDEADLK; case MC_CMD_ERR_ENOSYS: return -EOPNOTSUPP; case MC_CMD_ERR_ETIME: return -ETIME; case MC_CMD_ERR_EALREADY: return -EALREADY; case MC_CMD_ERR_ENOSPC: return -ENOSPC; case MC_CMD_ERR_ENOMEM: return -ENOMEM; case MC_CMD_ERR_ENOTSUP: return -EOPNOTSUPP; case MC_CMD_ERR_ALLOC_FAIL: return -ENOBUFS; case MC_CMD_ERR_MAC_EXIST: return -EADDRINUSE; case MC_CMD_ERR_NO_EVB_PORT: return -EAGAIN; default: return -EPROTO; } } static void cdx_mcdi_process_cleanup_list(struct cdx_mcdi *cdx, struct list_head *cleanup_list) { struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); unsigned int cleanups = 0; if (!mcdi) return; while (!list_empty(cleanup_list)) { struct cdx_mcdi_cmd *cmd = list_first_entry(cleanup_list, struct cdx_mcdi_cmd, cleanup_list); cmd->completer(cdx, cmd->cookie, cmd->rc, cmd->outbuf, cmd->outlen); list_del(&cmd->cleanup_list); kref_put(&cmd->ref, cdx_mcdi_cmd_release); ++cleanups; } if (cleanups) { bool all_done; mutex_lock(&mcdi->iface_lock); CDX_WARN_ON_PARANOID(cleanups > mcdi->outstanding_cleanups); all_done = (mcdi->outstanding_cleanups -= cleanups) == 0; mutex_unlock(&mcdi->iface_lock); if (all_done) wake_up(&mcdi->cmd_complete_wq); } } static void _cdx_mcdi_cancel_cmd(struct cdx_mcdi_iface *mcdi, unsigned int handle, struct list_head *cleanup_list) { struct cdx_mcdi_cmd *cmd; list_for_each_entry(cmd, &mcdi->cmd_list, list) if (cdx_mcdi_cmd_handle(cmd) == handle) { switch (cmd->state) { case MCDI_STATE_QUEUED: case MCDI_STATE_RETRY: pr_debug("command %#x inlen %zu cancelled in queue\n", cmd->cmd, cmd->inlen); /* if not yet running, properly cancel it */ cmd->rc = -EPIPE; cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list); break; case MCDI_STATE_RUNNING: case MCDI_STATE_RUNNING_CANCELLED: case MCDI_STATE_FINISHED: default: /* invalid state? */ WARN_ON(1); } break; } } static void cdx_mcdi_cancel_cmd(struct cdx_mcdi *cdx, struct cdx_mcdi_cmd *cmd) { struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); LIST_HEAD(cleanup_list); if (!mcdi) return; mutex_lock(&mcdi->iface_lock); cdx_mcdi_timeout_cmd(mcdi, cmd, &cleanup_list); mutex_unlock(&mcdi->iface_lock); cdx_mcdi_process_cleanup_list(cdx, &cleanup_list); } struct cdx_mcdi_blocking_data { struct kref ref; bool done; wait_queue_head_t wq; int rc; struct cdx_dword *outbuf; size_t outlen; size_t outlen_actual; }; static void cdx_mcdi_blocking_data_release(struct kref *ref) { kfree(container_of(ref, struct cdx_mcdi_blocking_data, ref)); } static void cdx_mcdi_rpc_completer(struct cdx_mcdi *cdx, unsigned long cookie, int rc, struct cdx_dword *outbuf, size_t outlen_actual) { struct cdx_mcdi_blocking_data *wait_data = (struct cdx_mcdi_blocking_data *)cookie; wait_data->rc = rc; memcpy(wait_data->outbuf, outbuf, min(outlen_actual, wait_data->outlen)); wait_data->outlen_actual = outlen_actual; /* memory barrier */ smp_wmb(); wait_data->done = true; wake_up(&wait_data->wq); kref_put(&wait_data->ref, cdx_mcdi_blocking_data_release); } static int cdx_mcdi_rpc_sync(struct cdx_mcdi *cdx, unsigned int cmd, const struct cdx_dword *inbuf, size_t inlen, struct cdx_dword *outbuf, size_t outlen, size_t *outlen_actual, bool quiet) { struct cdx_mcdi_blocking_data *wait_data; struct cdx_mcdi_cmd *cmd_item; unsigned int handle; int rc; if (outlen_actual) *outlen_actual = 0; wait_data = kmalloc(sizeof(*wait_data), GFP_KERNEL); if (!wait_data) return -ENOMEM; cmd_item = kmalloc(sizeof(*cmd_item), GFP_KERNEL); if (!cmd_item) { kfree(wait_data); return -ENOMEM; } kref_init(&wait_data->ref); wait_data->done = false; init_waitqueue_head(&wait_data->wq); wait_data->outbuf = outbuf; wait_data->outlen = outlen; kref_init(&cmd_item->ref); cmd_item->quiet = quiet; cmd_item->cookie = (unsigned long)wait_data; cmd_item->completer = &cdx_mcdi_rpc_completer; cmd_item->cmd = cmd; cmd_item->inlen = inlen; cmd_item->inbuf = inbuf; /* Claim an extra reference for the completer to put. */ kref_get(&wait_data->ref); rc = cdx_mcdi_rpc_async_internal(cdx, cmd_item, &handle); if (rc) { kref_put(&wait_data->ref, cdx_mcdi_blocking_data_release); goto out; } if (!wait_event_timeout(wait_data->wq, wait_data->done, cdx_mcdi_rpc_timeout(cdx, cmd)) && !wait_data->done) { pr_err("MC command 0x%x inlen %zu timed out (sync)\n", cmd, inlen); cdx_mcdi_cancel_cmd(cdx, cmd_item); wait_data->rc = -ETIMEDOUT; wait_data->outlen_actual = 0; } if (outlen_actual) *outlen_actual = wait_data->outlen_actual; rc = wait_data->rc; out: kref_put(&wait_data->ref, cdx_mcdi_blocking_data_release); return rc; } static bool cdx_mcdi_get_seq(struct cdx_mcdi_iface *mcdi, unsigned char *seq) { *seq = mcdi->prev_seq; do { *seq = (*seq + 1) % ARRAY_SIZE(mcdi->seq_held_by); } while (mcdi->seq_held_by[*seq] && *seq != mcdi->prev_seq); return !mcdi->seq_held_by[*seq]; } static int cdx_mcdi_rpc_async_internal(struct cdx_mcdi *cdx, struct cdx_mcdi_cmd *cmd, unsigned int *handle) { struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); LIST_HEAD(cleanup_list); if (!mcdi) { kref_put(&cmd->ref, cdx_mcdi_cmd_release); return -ENETDOWN; } if (mcdi->mode == MCDI_MODE_FAIL) { kref_put(&cmd->ref, cdx_mcdi_cmd_release); return -ENETDOWN; } cmd->mcdi = mcdi; INIT_WORK(&cmd->work, cdx_mcdi_cmd_work); INIT_LIST_HEAD(&cmd->list); INIT_LIST_HEAD(&cmd->cleanup_list); cmd->rc = 0; cmd->outbuf = NULL; cmd->outlen = 0; queue_work(mcdi->workqueue, &cmd->work); return 0; } static void cdx_mcdi_cmd_start_or_queue(struct cdx_mcdi_iface *mcdi, struct cdx_mcdi_cmd *cmd) { struct cdx_mcdi *cdx = mcdi->cdx; u8 seq; if (!mcdi->db_held_by && cdx_mcdi_get_seq(mcdi, &seq)) { cmd->seq = seq; cmd->reboot_seen = false; cdx_mcdi_send_request(cdx, cmd); cmd->state = MCDI_STATE_RUNNING; } else { cmd->state = MCDI_STATE_QUEUED; } } /* try to advance other commands */ static void cdx_mcdi_start_or_queue(struct cdx_mcdi_iface *mcdi, bool allow_retry) { struct cdx_mcdi_cmd *cmd, *tmp; list_for_each_entry_safe(cmd, tmp, &mcdi->cmd_list, list) if (cmd->state == MCDI_STATE_QUEUED || (cmd->state == MCDI_STATE_RETRY && allow_retry)) cdx_mcdi_cmd_start_or_queue(mcdi, cmd); } void cdx_mcdi_process_cmd(struct cdx_mcdi *cdx, struct cdx_dword *outbuf, int len) { struct cdx_mcdi_iface *mcdi; struct cdx_mcdi_cmd *cmd; LIST_HEAD(cleanup_list); unsigned int respseq; if (!len || !outbuf) { pr_err("Got empty MC response\n"); return; } mcdi = cdx_mcdi_if(cdx); if (!mcdi) return; respseq = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_SEQ); mutex_lock(&mcdi->iface_lock); cmd = mcdi->seq_held_by[respseq]; if (cmd) { if (cmd->state == MCDI_STATE_FINISHED) { mutex_unlock(&mcdi->iface_lock); kref_put(&cmd->ref, cdx_mcdi_cmd_release); return; } cdx_mcdi_complete_cmd(mcdi, cmd, outbuf, len, &cleanup_list); } else { pr_err("MC response unexpected for seq : %0X\n", respseq); } mutex_unlock(&mcdi->iface_lock); cdx_mcdi_process_cleanup_list(mcdi->cdx, &cleanup_list); } static void cdx_mcdi_cmd_work(struct work_struct *context) { struct cdx_mcdi_cmd *cmd = container_of(context, struct cdx_mcdi_cmd, work); struct cdx_mcdi_iface *mcdi = cmd->mcdi; mutex_lock(&mcdi->iface_lock); cmd->handle = mcdi->prev_handle++; list_add_tail(&cmd->list, &mcdi->cmd_list); cdx_mcdi_cmd_start_or_queue(mcdi, cmd); mutex_unlock(&mcdi->iface_lock); } /* * Returns true if the MCDI module is finished with the command. * (examples of false would be if the command was proxied, or it was * rejected by the MC due to lack of resources and requeued). */ 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) { size_t resp_hdr_len, resp_data_len; struct cdx_mcdi *cdx = mcdi->cdx; unsigned int respcmd, error; bool completed = false; int rc; /* ensure the command can't go away before this function returns */ kref_get(&cmd->ref); respcmd = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_CODE); error = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_ERROR); if (respcmd != MC_CMD_V2_EXTN) { resp_hdr_len = 4; resp_data_len = CDX_DWORD_FIELD(outbuf[0], MCDI_HEADER_DATALEN); } else { resp_data_len = 0; resp_hdr_len = 8; if (len >= 8) resp_data_len = CDX_DWORD_FIELD(outbuf[1], MC_CMD_V2_EXTN_IN_ACTUAL_LEN); } if ((resp_hdr_len + resp_data_len) > len) { pr_warn("Incomplete MCDI response received %d. Expected %zu\n", len, (resp_hdr_len + resp_data_len)); resp_data_len = 0; } print_hex_dump_debug("MCDI RESP HEADER: ", DUMP_PREFIX_NONE, 32, 4, outbuf, resp_hdr_len, false); print_hex_dump_debug("MCDI RESP PAYLOAD: ", DUMP_PREFIX_NONE, 32, 4, outbuf + (resp_hdr_len / 4), resp_data_len, false); if (error && resp_data_len == 0) { /* MC rebooted during command */ rc = -EIO; } else { if (WARN_ON_ONCE(error && resp_data_len < 4)) resp_data_len = 4; if (error) { rc = CDX_DWORD_FIELD(outbuf[resp_hdr_len / 4], CDX_DWORD); if (!cmd->quiet) { int err_arg = 0; if (resp_data_len >= MC_CMD_ERR_ARG_OFST + 4) { int offset = (resp_hdr_len + MC_CMD_ERR_ARG_OFST) / 4; err_arg = CDX_DWORD_VAL(outbuf[offset]); } _cdx_mcdi_display_error(cdx, cmd->cmd, cmd->inlen, rc, err_arg, cdx_mcdi_errno(cdx, rc)); } rc = cdx_mcdi_errno(cdx, rc); } else { rc = 0; } } /* free doorbell */ if (mcdi->db_held_by == cmd) mcdi->db_held_by = NULL; if (cdx_cmd_cancelled(cmd)) { list_del(&cmd->list); kref_put(&cmd->ref, cdx_mcdi_cmd_release); completed = true; } else if (rc == MC_CMD_ERR_QUEUE_FULL) { cmd->state = MCDI_STATE_RETRY; } else { cmd->rc = rc; cmd->outbuf = outbuf + DIV_ROUND_UP(resp_hdr_len, 4); cmd->outlen = resp_data_len; cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list); completed = true; } /* free sequence number and buffer */ mcdi->seq_held_by[cmd->seq] = NULL; cdx_mcdi_start_or_queue(mcdi, rc != MC_CMD_ERR_QUEUE_FULL); /* wake up anyone waiting for flush */ wake_up(&mcdi->cmd_complete_wq); kref_put(&cmd->ref, cdx_mcdi_cmd_release); return completed; } static void cdx_mcdi_timeout_cmd(struct cdx_mcdi_iface *mcdi, struct cdx_mcdi_cmd *cmd, struct list_head *cleanup_list) { struct cdx_mcdi *cdx = mcdi->cdx; pr_err("MC command 0x%x inlen %zu state %d timed out after %u ms\n", cmd->cmd, cmd->inlen, cmd->state, jiffies_to_msecs(jiffies - cmd->started)); cmd->rc = -ETIMEDOUT; cdx_mcdi_remove_cmd(mcdi, cmd, cleanup_list); cdx_mcdi_mode_fail(cdx, cleanup_list); } /** * cdx_mcdi_rpc - Issue an MCDI command and wait for completion * @cdx: NIC through which to issue the command * @cmd: Command type number * @inbuf: Command parameters * @inlen: Length of command parameters, in bytes. Must be a multiple * of 4 and no greater than %MCDI_CTL_SDU_LEN_MAX_V1. * @outbuf: Response buffer. May be %NULL if @outlen is 0. * @outlen: Length of response buffer, in bytes. If the actual * response is longer than @outlen & ~3, it will be truncated * to that length. * @outlen_actual: Pointer through which to return the actual response * length. May be %NULL if this is not needed. * * This function may sleep and therefore must be called in process * context. * * Return: A negative error code, or zero if successful. The error * code may come from the MCDI response or may indicate a failure * to communicate with the MC. In the former case, the response * will still be copied to @outbuf and *@outlen_actual will be * set accordingly. In the latter case, *@outlen_actual will be * set to zero. */ int cdx_mcdi_rpc(struct cdx_mcdi *cdx, unsigned int cmd, const struct cdx_dword *inbuf, size_t inlen, struct cdx_dword *outbuf, size_t outlen, size_t *outlen_actual) { return cdx_mcdi_rpc_sync(cdx, cmd, inbuf, inlen, outbuf, outlen, outlen_actual, false); } /** * cdx_mcdi_rpc_async - Schedule an MCDI command to run asynchronously * @cdx: NIC through which to issue the command * @cmd: Command type number * @inbuf: Command parameters * @inlen: Length of command parameters, in bytes * @complete: Function to be called on completion or cancellation. * @cookie: Arbitrary value to be passed to @complete. * * This function does not sleep and therefore may be called in atomic * context. It will fail if event queues are disabled or if MCDI * event completions have been disabled due to an error. * * If it succeeds, the @complete function will be called exactly once * in process context, when one of the following occurs: * (a) the completion event is received (in process context) * (b) event queues are disabled (in the process that disables them) */ int cdx_mcdi_rpc_async(struct cdx_mcdi *cdx, unsigned int cmd, const struct cdx_dword *inbuf, size_t inlen, cdx_mcdi_async_completer *complete, unsigned long cookie) { struct cdx_mcdi_cmd *cmd_item = kmalloc(sizeof(struct cdx_mcdi_cmd) + inlen, GFP_ATOMIC); if (!cmd_item) return -ENOMEM; kref_init(&cmd_item->ref); cmd_item->quiet = true; cmd_item->cookie = cookie; cmd_item->completer = complete; cmd_item->cmd = cmd; cmd_item->inlen = inlen; /* inbuf is probably not valid after return, so take a copy */ cmd_item->inbuf = (struct cdx_dword *)(cmd_item + 1); memcpy(cmd_item + 1, inbuf, inlen); return cdx_mcdi_rpc_async_internal(cdx, cmd_item, NULL); } static void _cdx_mcdi_display_error(struct cdx_mcdi *cdx, unsigned int cmd, size_t inlen, int raw, int arg, int err_no) { pr_err("MC command 0x%x inlen %d failed err_no=%d (raw=%d) arg=%d\n", cmd, (int)inlen, err_no, raw, arg); } /* * Set MCDI mode to fail to prevent any new commands, then cancel any * outstanding commands. * Caller must hold the mcdi iface_lock. */ static void cdx_mcdi_mode_fail(struct cdx_mcdi *cdx, struct list_head *cleanup_list) { struct cdx_mcdi_iface *mcdi = cdx_mcdi_if(cdx); if (!mcdi) return; mcdi->mode = MCDI_MODE_FAIL; while (!list_empty(&mcdi->cmd_list)) { struct cdx_mcdi_cmd *cmd; cmd = list_first_entry(&mcdi->cmd_list, struct cdx_mcdi_cmd, list); _cdx_mcdi_cancel_cmd(mcdi, cdx_mcdi_cmd_handle(cmd), cleanup_list); } }