| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| // Copyright 2022 IBM Corp. |
| |
| #define pr_fmt(fmt) "PLDM: " fmt |
| |
| #include <cpu.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <timebase.h> |
| #include <ast.h> |
| #include <libpldm/utils.h> |
| #include "pldm.h" |
| |
| #define TIMEOUT_MS 8000 |
| |
| struct pldm_request { |
| struct list_node link; |
| |
| /* originating request params */ |
| struct pldm_header_info hdrinf; |
| |
| /* messages requested */ |
| struct pldm_tx_data *tx; |
| |
| /* timeout handling */ |
| struct timer timeout; |
| uint64_t timeout_ms; |
| uint64_t start_time; |
| |
| /* completion callback */ |
| void (*complete)(struct pldm_rx_data *rx, void *data); |
| void *complete_data; |
| }; |
| |
| struct pldm_response { |
| void **msg; |
| size_t *msg_size; |
| bool done; |
| int rc; |
| }; |
| |
| /* pldm requests queue */ |
| static struct lock pldm_requests_lock = LOCK_UNLOCKED; |
| static LIST_HEAD(list_pldm_requests); |
| |
| static struct pldm_request *active_request; |
| |
| static bool matches_request(const struct pldm_rx_data *rx, |
| const struct pldm_request *req) |
| { |
| if (req->hdrinf.instance != rx->hdrinf.instance) |
| return false; |
| if (req->hdrinf.pldm_type != rx->hdrinf.pldm_type) |
| return false; |
| if (req->hdrinf.command != rx->hdrinf.command) |
| return false; |
| |
| return true; |
| } |
| |
| static void send_and_wait_complete(struct pldm_rx_data *rx, void *data) |
| { |
| struct pldm_response *resp = (struct pldm_response *)data; |
| int len; |
| |
| if (rx != NULL) { |
| len = rx->msg_len; |
| *resp->msg_size = len; |
| *resp->msg = zalloc(len); |
| memcpy(*resp->msg, rx->msg, len); |
| |
| resp->rc = OPAL_SUCCESS; |
| } else { |
| *resp->msg_size = 0; |
| *resp->msg = NULL; |
| resp->rc = OPAL_TIMEOUT; |
| } |
| |
| resp->done = true; |
| } |
| |
| static void handle_response(struct pldm_rx_data *rx) |
| { |
| uint64_t now; |
| |
| if (active_request == NULL) { |
| prlog(PR_ERR, "%s: No active request\n", __func__); |
| return; |
| } |
| |
| /* unactivate the timer */ |
| if (rx != NULL) |
| cancel_timer(&active_request->timeout); |
| |
| if (active_request->complete) |
| active_request->complete(rx, active_request->complete_data); |
| |
| now = mftb(); |
| prlog(PR_TRACE, "%s: Finished after %ldms, t:%d c:%d i:%d\n", |
| __func__, |
| tb_to_msecs(now - active_request->start_time), |
| active_request->hdrinf.pldm_type, |
| active_request->hdrinf.command, |
| active_request->hdrinf.instance); |
| |
| free(active_request->tx); |
| free(active_request); |
| active_request = NULL; |
| } |
| |
| /* |
| * Timeout :( |
| */ |
| static void expiry(struct timer *t __unused, void *data, uint64_t now __unused) |
| { |
| struct pldm_request *req = (struct pldm_request *)data; |
| |
| if (active_request == NULL) { |
| prlog(PR_ERR, "request timedout! (active request NULL)\n"); |
| return; |
| } |
| |
| prlog(PR_ERR, "PLDM: request timedout! (active request: t:0x%x c:0x%x i:%d)\n", |
| active_request->hdrinf.pldm_type, |
| active_request->hdrinf.command, |
| active_request->hdrinf.instance); |
| |
| prlog(PR_ERR, "PLDM: Original request t:0x%x c:0x%x i:%d -----\n", |
| req->hdrinf.pldm_type, |
| req->hdrinf.command, req->hdrinf.instance); |
| |
| /* no data received. Finish the procedure */ |
| handle_response(NULL); |
| } |
| |
| /* |
| * Handle PLDM message received from the PLDM terminus over MCTP |
| */ |
| int pldm_requester_handle_response(struct pldm_rx_data *rx) |
| { |
| /* check the message received */ |
| if (active_request == NULL) { |
| prlog(PR_ERR, "%s: No active request. " |
| "Response received t:%d c:%d i:%d\n", |
| __func__, |
| rx->hdrinf.pldm_type, |
| rx->hdrinf.command, |
| rx->hdrinf.instance); |
| return OPAL_WRONG_STATE; |
| } |
| |
| if (!matches_request(rx, active_request)) { |
| prlog(PR_ERR, "%s: Unexpected response! t:%d c:%d i:%d want %d,%d,%d\n", |
| __func__, |
| rx->hdrinf.pldm_type, |
| rx->hdrinf.command, |
| rx->hdrinf.instance, |
| active_request->hdrinf.pldm_type, |
| active_request->hdrinf.command, |
| active_request->hdrinf.instance); |
| return OPAL_WRONG_STATE; |
| } |
| |
| /* The expected message seems correct */ |
| handle_response(rx); |
| |
| return OPAL_SUCCESS; |
| } |
| |
| /* |
| * Send the PLDM request |
| */ |
| static void requests_poller(void *data __unused) |
| { |
| int rc = OPAL_SUCCESS; |
| |
| lock(&pldm_requests_lock); |
| |
| /* wait for the end of the processing of the current request */ |
| if (active_request) { |
| unlock(&pldm_requests_lock); |
| return; |
| } |
| |
| /* no new request to handle */ |
| if (list_empty(&list_pldm_requests)) { |
| unlock(&pldm_requests_lock); |
| return; |
| } |
| |
| /* remove the first entry in a list */ |
| active_request = list_pop(&list_pldm_requests, |
| struct pldm_request, |
| link); |
| |
| unlock(&pldm_requests_lock); |
| |
| /* Start timer to control a timeout from the PLDM terminus */ |
| init_timer(&active_request->timeout, expiry, active_request); |
| schedule_timer(&active_request->timeout, |
| msecs_to_tb(active_request->timeout_ms)); |
| active_request->start_time = mftb(); |
| |
| /* Send PLDM message over MCTP */ |
| prlog(PR_TRACE, "%s: Sending request to BMC t:%d c:%d i:%d -----\n", |
| __func__, |
| active_request->hdrinf.pldm_type, |
| active_request->hdrinf.command, |
| active_request->hdrinf.instance); |
| |
| rc = pldm_mctp_message_tx(active_request->tx); |
| if (rc) |
| prlog(PR_ERR, "%s: Error %d while sending request\n", |
| __func__, rc); |
| } |
| |
| /* |
| * Add PLDM request in the queue |
| */ |
| static int queue_request(struct pldm_tx_data *tx, |
| uint64_t timeout_ms, |
| void (*complete)(struct pldm_rx_data *rx, void *data), |
| void *complete_data) |
| { |
| struct pldm_request *pending; |
| struct pldm_msg *pldm_msg; |
| size_t tx_size; |
| |
| tx_size = sizeof(struct pldm_tx_data) + tx->data_size; |
| |
| pending = zalloc(sizeof(struct pldm_request)); |
| if (!pending) { |
| prlog(PR_ERR, "%s: failed to allocate request\n", __func__); |
| return OPAL_NO_MEM; |
| } |
| |
| pending->timeout_ms = timeout_ms; |
| pending->complete = complete; |
| pending->complete_data = complete_data; |
| pending->tx = zalloc(tx_size); |
| if (!pending->tx) { |
| free(pending); |
| prlog(PR_ERR, "%s: failed to allocate pldm packet (size: 0x%lx)\n", |
| __func__, tx_size); |
| return OPAL_NO_MEM; |
| } |
| |
| memcpy(pending->tx, tx, tx_size); |
| |
| pldm_msg = (struct pldm_msg *)tx->data; |
| if (unpack_pldm_header(&pldm_msg->hdr, &pending->hdrinf)) { |
| free(pending->tx); |
| free(pending); |
| prlog(PR_ERR, "%s: error parsing pldm header\n", __func__); |
| return OPAL_PARAMETER; |
| } |
| |
| /* add an entry at the end of a linked list */ |
| prlog(PR_TRACE, "%s: Add request t:%d c:%d i:%d -----\n", |
| __func__, |
| pending->hdrinf.pldm_type, |
| pending->hdrinf.command, |
| pending->hdrinf.instance); |
| |
| lock(&pldm_requests_lock); |
| list_add_tail(&list_pldm_requests, &pending->link); |
| unlock(&pldm_requests_lock); |
| |
| return OPAL_SUCCESS; |
| } |
| |
| /* |
| * Queue a PLDM request and don't wait. |
| * When a response is received, call the associated callback. |
| */ |
| int pldm_requester_queue(struct pldm_tx_data *tx, |
| void (*complete)(struct pldm_rx_data *rx, void *data), |
| void *complete_data) |
| { |
| int rc = OPAL_SUCCESS; |
| |
| /* Queue PLDM request */ |
| rc = queue_request(tx, TIMEOUT_MS, complete, complete_data); |
| if (rc) { |
| prlog(PR_ERR, "%s: error %d while queuing request\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * Queue a PLDM request and spin until we get a response. |
| */ |
| int pldm_requester_queue_and_wait(struct pldm_tx_data *tx, |
| void **msg, size_t *msg_size) |
| { |
| struct pldm_response *resp; |
| int rc = OPAL_SUCCESS; |
| |
| resp = zalloc(sizeof(struct pldm_response)); |
| if (!resp) { |
| prlog(PR_ERR, "%s: failed to allocate response\n", __func__); |
| return OPAL_NO_MEM; |
| } |
| |
| resp->msg = msg; |
| resp->msg_size = msg_size; |
| |
| rc = pldm_requester_queue(tx, send_and_wait_complete, resp); |
| if (rc) |
| goto out; |
| |
| /* wait for a response from the BMC */ |
| for (;;) { |
| if (resp->done) |
| break; |
| |
| time_wait_ms(5); |
| } |
| rc = resp->rc; |
| |
| out: |
| free(resp); |
| return rc; |
| } |
| |
| int pldm_requester_init(void) |
| { |
| /* requests poller */ |
| opal_add_poller(requests_poller, NULL); |
| |
| return OPAL_SUCCESS; |
| } |