core/mctp/mctp_interface.c (748 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include <stdint.h> #include <stdlib.h> #include <string.h> #include "mctp_base_protocol.h" #include "mctp_control_protocol.h" #include "mctp_control_protocol_commands.h" #include "mctp_interface.h" #include "mctp_logging.h" #include "cmd_interface/cerberus_protocol.h" #include "common/buffer_util.h" #include "common/common_math.h" #include "common/unused.h" #include "spdm/spdm_protocol.h" /** * Generate a list of MCTP packets that should be transmitted for an MCTP message. Except for the * last packet, all packets in the list will be the same size. * * @param mctp MCTP handler generating the MCTP packets. * @param payload Buffer that contains the MCTP message payload to packetize. * @param payload_len Length of message payload. * @param dest_eid Destination EID for the MCTP packets. * @param dest_addr SMBus address the packets will be sent to. * @param src_eid EID of the device sending the packets. * @param src_addr SMBus address of the source device. * @param msg_tag MCTP message tag to use in the packet header. * @param tag_owner MCTP tag owner to use in the packet header. * @param packets Output buffer to fill with the list of MCTP packets. * @param max_list_len Maximum length of the packet list buffer. * @param max_packet_len Output to provide the length of a full MCTP packet. * * @return Total length of the list of MCTP packets or an error code. */ static int mctp_interface_generate_packets_from_payload (const struct mctp_interface *mctp, const uint8_t *payload, size_t payload_len, uint8_t dest_eid, uint8_t dest_addr, uint8_t src_eid, uint8_t src_addr, uint8_t msg_tag, uint8_t tag_owner, uint8_t *packets, size_t max_list_len, size_t *max_packet_len) { uint8_t packet_seq = 0; size_t max_packet_payload; size_t packet_payload_len; size_t i_payload = 0; size_t i_packets = 0; bool som = true; bool eom = false; int status; max_packet_payload = device_manager_get_max_transmission_unit_by_eid (mctp->device_manager, dest_eid); while (payload_len > 0) { if (payload_len > max_packet_payload) { packet_payload_len = max_packet_payload; } else { eom = true; packet_payload_len = payload_len; } status = mctp_base_protocol_construct (&payload[i_payload], packet_payload_len, &packets[i_packets], max_list_len - i_packets, src_addr, dest_eid, src_eid, som, eom, packet_seq, msg_tag, tag_owner, dest_addr); if (ROT_IS_ERROR (status)) { return status; } if (som) { *max_packet_len = status; } i_packets += status; i_payload += packet_payload_len; payload_len -= packet_payload_len; som = false; packet_seq = (packet_seq + 1) % 4; } return i_packets; } /** * Reset MCTP message assembly. This discards previously received packets and begins looking for a * new message. * * @param mctp The MCTP layer to reset. */ static void mctp_interface_reset_message_assembly (const struct mctp_interface *mctp) { mctp->state->req_buffer.data = &mctp->state->msg_buffer[sizeof (mctp->state->msg_buffer) - MCTP_BASE_PROTOCOL_MAX_MESSAGE_BODY]; mctp->state->resp_buffer.data = mctp->state->msg_buffer; mctp->state->start_packet_len = 0; cmd_interface_msg_new_message (&mctp->state->req_buffer, 0, 0, 0, 0); } #ifdef CMD_ENABLE_ISSUE_REQUEST int mctp_interface_get_max_message_overhead (const struct msg_transport *transport, uint8_t dest_id) { const struct mctp_interface *mctp = (const struct mctp_interface*) transport; size_t max_message; size_t packet_size; size_t max_packets; if (mctp == NULL) { return MSG_TRANSPORT_INVALID_ARGUMENT; } max_message = device_manager_get_max_message_len_by_eid (mctp->device_manager, dest_id); packet_size = device_manager_get_max_transmission_unit_by_eid (mctp->device_manager, dest_id); max_packets = MCTP_BASE_PROTOCOL_PACKETS_IN_MESSAGE (max_message, packet_size); return MCTP_BASE_PROTOCOL_PACKET_OVERHEAD * max_packets; } int mctp_interface_get_max_message_payload_length (const struct msg_transport *transport, uint8_t dest_id) { const struct mctp_interface *mctp = (const struct mctp_interface*) transport; if (mctp == NULL) { return MSG_TRANSPORT_INVALID_ARGUMENT; } return device_manager_get_max_message_len_by_eid (mctp->device_manager, dest_id); } int mctp_interface_get_max_encapsulated_message_length (const struct msg_transport *transport, uint8_t dest_id) { const struct mctp_interface *mctp = (const struct mctp_interface*) transport; size_t max_message; size_t packet_size; size_t max_packets; if (mctp == NULL) { return MSG_TRANSPORT_INVALID_ARGUMENT; } max_message = device_manager_get_max_message_len_by_eid (mctp->device_manager, dest_id); packet_size = device_manager_get_max_transmission_unit_by_eid (mctp->device_manager, dest_id); max_packets = MCTP_BASE_PROTOCOL_PACKETS_IN_MESSAGE (max_message, packet_size); return MCTP_BASE_PROTOCOL_MESSAGE_LEN (max_packets, max_message); } int mctp_interface_get_buffer_overhead (const struct msg_transport *transport, uint8_t dest_id, size_t length) { const struct mctp_interface *mctp = (const struct mctp_interface*) transport; size_t packet_size; size_t full_packets; size_t total_overhead; size_t last_remaining; packet_size = device_manager_get_max_transmission_unit_by_eid (mctp->device_manager, dest_id); packet_size += MCTP_BASE_PROTOCOL_PACKET_OVERHEAD; full_packets = length / packet_size; total_overhead = full_packets * MCTP_BASE_PROTOCOL_PACKET_OVERHEAD; /* Add overhead for the last possible packet in the buffer, which may not even have enough room * for the MCTP packet header. */ last_remaining = length % packet_size; if (last_remaining < MCTP_BASE_PROTOCOL_PACKET_OVERHEAD) { total_overhead += last_remaining; } else { total_overhead += MCTP_BASE_PROTOCOL_PACKET_OVERHEAD; } return total_overhead; } /** * @deprecated Pairs with the deprecated issue_request call and will be removed. * * Handle a response message for a request issued with issue_request. * * @param mctp The MCTP handler that has received a response message. * * @return 0 if response processing was successful or an error code. */ static int mctp_interface_deprecated_handle_response_message (const struct mctp_interface *mctp) { int status; /* We know the message is one of the three supported types by this point. If it wasn't, * it would have failed earlier in packet processing. */ if (MCTP_BASE_PROTOCOL_IS_CONTROL_MSG (mctp->state->msg_type)) { if (mctp->cmd_mctp) { status = mctp->cmd_mctp->process_response (mctp->cmd_mctp, &mctp->state->req_buffer); if (status == CMD_HANDLER_ERROR_MESSAGE) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_MCTP_CONTROL_RSP_FAIL, status, mctp->state->channel_id); } } else { status = MCTP_BASE_PROTOCOL_UNSUPPORTED_OPERATION; goto exit; } } else if (MCTP_BASE_PROTOCOL_IS_VENDOR_MSG (mctp->state->msg_type)) { status = mctp->cmd_cerberus->process_response (mctp->cmd_cerberus, &mctp->state->req_buffer); } else if (MCTP_BASE_PROTOCOL_IS_SPDM_MSG (mctp->state->msg_type)) { if (mctp->cmd_spdm) { cmd_interface_msg_remove_protocol_header (&mctp->state->req_buffer, sizeof (struct spdm_protocol_mctp_header)); status = mctp->cmd_spdm->process_response (mctp->cmd_spdm, &mctp->state->req_buffer); } else { status = MCTP_BASE_PROTOCOL_UNSUPPORTED_OPERATION; goto exit; } } else { status = MCTP_BASE_PROTOCOL_UNSUPPORTED_OPERATION; } if (status == CMD_HANDLER_ERROR_MESSAGE) { mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_ERROR_DEPRECATED; status = 0; } else if (status != 0) { mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_FAIL_DEPRECATED; } else { mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_SUCCESS; } platform_semaphore_post (&mctp->state->wait_for_response); exit: mctp_interface_reset_message_assembly (mctp); platform_mutex_unlock (&mctp->state->response_lock); return status; } int mctp_interface_send_request_message (const struct msg_transport *transport, struct cmd_interface_msg *request, uint32_t timeout_ms, struct cmd_interface_msg *response) { const struct mctp_interface *mctp = (const struct mctp_interface*) transport; struct mctp_base_protocol_message_header *msg_header; struct cmd_message cmd_msg; uint8_t msg_type; int src_eid; int src_addr; int dest_eid; int dest_addr; int status; if ((mctp == NULL) || (request == NULL)) { return MSG_TRANSPORT_INVALID_ARGUMENT; } /* A response descriptor is only required if the caller wants the response message. */ if ((response == NULL) && (timeout_ms != 0)) { return MSG_TRANSPORT_INVALID_ARGUMENT; } dest_eid = request->target_eid; dest_addr = device_manager_get_device_addr_by_eid (mctp->device_manager, request->target_eid); if (ROT_IS_ERROR (dest_addr)) { return dest_addr; } if (request->payload_length > device_manager_get_max_message_len_by_eid (mctp->device_manager, request->target_eid)) { return MSG_TRANSPORT_REQUEST_TOO_LARGE; } msg_header = (struct mctp_base_protocol_message_header*) request->payload; msg_type = msg_header->msg_type; src_eid = device_manager_get_device_eid (mctp->device_manager, DEVICE_MANAGER_SELF_DEVICE_NUM); src_addr = device_manager_get_device_addr (mctp->device_manager, DEVICE_MANAGER_SELF_DEVICE_NUM); /* The transport needs to be locked before building the message since it uses the next response * tag as part of the packet construction. */ platform_mutex_lock (&mctp->state->request_lock); status = mctp_interface_generate_packets_from_payload (mctp, request->payload, request->payload_length, request->target_eid, dest_addr, src_eid, src_addr, mctp->state->next_msg_tag, MCTP_BASE_PROTOCOL_TO_REQUEST, request->data, request->max_response, &cmd_msg.pkt_size); if (ROT_IS_ERROR (status)) { goto unlock_tx; } cmd_msg.msg_size = status; cmd_msg.data = request->data; cmd_msg.dest_addr = dest_addr; /* Do not manipulate any of the response handling state if the handler is currently processing * a received response message. */ platform_mutex_lock (&mctp->state->response_lock); if (timeout_ms != 0) { status = platform_semaphore_reset (&mctp->state->wait_for_response); if (status != 0) { platform_mutex_unlock (&mctp->state->response_lock); goto unlock_tx; } mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_WAITING; } else { mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_PENDING; } mctp->state->response_msg_tag = mctp->state->next_msg_tag; mctp->state->response_msg_type = msg_type; mctp->state->response_msg = response; /* The message tag has been consumed, regardless of whether the message transaction is * successful, so increment it for the next message. */ mctp->state->next_msg_tag = (mctp->state->next_msg_tag + 1) % 8; platform_mutex_unlock (&mctp->state->response_lock); status = cmd_channel_send_message (mctp->channel, &cmd_msg); if (status != 0) { /* Reset to the idle state since request transmission failed. */ platform_mutex_lock (&mctp->state->response_lock); mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_IDLE; platform_mutex_unlock (&mctp->state->response_lock); goto unlock_tx; } if (timeout_ms == 0) { status = MSG_TRANSPORT_NO_WAIT_RESPONSE; goto unlock_tx; } status = platform_semaphore_wait (&mctp->state->wait_for_response, timeout_ms); /* Take the response handling lock again to clear up the response state, particularly to handle * error cases. */ platform_mutex_lock (&mctp->state->response_lock); if (status == 0) { if (mctp->state->rsp_state == MCTP_INTERFACE_RESPONSE_TOO_BIG) { status = MSG_TRANSPORT_RESPONSE_TOO_LARGE; } } else if (status == 1) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_RSP_TIMEOUT, (dest_eid << 8) | mctp->state->response_msg_tag, timeout_ms); status = MSG_TRANSPORT_REQUEST_TIMEOUT; } mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_IDLE; platform_mutex_unlock (&mctp->state->response_lock); unlock_tx: mctp->state->response_msg = NULL; platform_mutex_unlock (&mctp->state->request_lock); return status; } #endif /** * Check a response packet against the MCTP transport state to see if it's part of an expected * response message. * * @param mctp The MCTP handler to check the packet against. * @param msg_tag Message tag of the response packet. * @param som Flag indicating if the packet is the first in the message. * @param msg_type Message type of the response message. This is only valid for SOM packets. * * @return true if the response message is part of an expected response or false if not. */ static bool mctp_interface_is_expected_response (const struct mctp_interface *mctp, uint8_t msg_tag, bool som, uint8_t msg_type) { #ifdef CMD_ENABLE_ISSUE_REQUEST bool expected = true; platform_mutex_lock (&mctp->state->response_lock); if ((mctp->state->rsp_state != MCTP_INTERFACE_RESPONSE_WAITING) && (mctp->state->rsp_state != MCTP_INTERFACE_RESPONSE_PENDING) && (mctp->state->rsp_state != MCTP_INTERFACE_RESPONSE_WAITING_DEPRECATED)) { /* We are not waiting for any response. Just drop the message. */ debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_RSP_DROPPED, MCTP_LOGGING_RSP_DROPPED_UNEXPECTED, mctp->state->channel_id); expected = false; goto exit; } if (msg_tag != mctp->state->response_msg_tag) { /* This response message does not match the request that was sent. Drop it. */ debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_RSP_DROPPED, MCTP_LOGGING_RSP_DROPPED_WRONG_TAG, (mctp->state->response_msg_tag << 16) | (msg_tag << 8) | mctp->state->channel_id); expected = false; goto exit; } if (som && (msg_type != mctp->state->response_msg_type)) { /* MCTP message assembly should not be interrupted by packets for an unsupported message * type. The message type is only present in the SOM packet. For response messages, assume * the message type should match the request message that was sent. Drop it. */ debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_RSP_DROPPED, MCTP_LOGGING_RSP_DROPPED_WRONG_TYPE, (mctp->state->response_msg_type << 16) | (msg_type << 8) | mctp->state->channel_id); expected = false; } exit: platform_mutex_unlock (&mctp->state->response_lock); return expected; #else UNUSED (msg_tag); UNUSED (som); UNUSED (msg_type); /* Always drop response messages if issuing requests is not supported. */ debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_RSP_DROPPED, MCTP_LOGGING_RSP_DROPPED_UNEXPECTED, mctp->state->channel_id); return false; #endif } /** * Handle a received response message to pair it with any outstanding request. * * @param mctp The MCTP handler that has received a response message. * * @return 0 or the result of any deprecated response processing. */ static int mctp_interface_handle_response_message (const struct mctp_interface *mctp) { #ifdef CMD_ENABLE_ISSUE_REQUEST platform_mutex_lock (&mctp->state->response_lock); /* Handle deprecated response processing. */ if (mctp->state->rsp_state == MCTP_INTERFACE_RESPONSE_WAITING_DEPRECATED) { return mctp_interface_deprecated_handle_response_message (mctp); } if (mctp->state->rsp_state == MCTP_INTERFACE_RESPONSE_PENDING) { /* Received the expected response message. Nothing is waiting for it so drop it. */ mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_IDLE; mctp_interface_reset_message_assembly (mctp); platform_mutex_unlock (&mctp->state->response_lock); return 0; } /* A response was received for the request that was sent. Copy the response message into the * response buffer. */ if (mctp->state->req_buffer.length <= mctp->state->response_msg->max_response) { cmd_interface_msg_new_message (mctp->state->response_msg, mctp->state->req_buffer.source_eid, mctp->state->req_buffer.source_addr, mctp->state->req_buffer.target_eid, mctp->state->req_buffer.channel_id); cmd_interface_msg_add_payload_data (mctp->state->response_msg, mctp->state->req_buffer.data, mctp->state->req_buffer.length); mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_SUCCESS; } else { mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_TOO_BIG; } mctp_interface_reset_message_assembly (mctp); platform_semaphore_post (&mctp->state->wait_for_response); platform_mutex_unlock (&mctp->state->response_lock); return 0; #else UNUSED (mctp); return 0; #endif } /** * Handle a received request message to generate an appropriate response. * * @param mctp The MCTP handler that received the message. * @param rx_packet The last received packet in the message. * @param tx_message Output for a response message to send. If this is null, there is was no * response generated for the received data. If this is not null, this will point to the packetized * message data in the MCTP message buffer. * * @return 0 if the request was processed successfully or an error code. */ static int mctp_interface_handle_request_message (const struct mctp_interface *mctp, struct cmd_packet *rx_packet, struct cmd_message **tx_message) { uint8_t response_addr; int status; mctp->state->req_buffer.max_response = MCTP_BASE_PROTOCOL_MAX_MESSAGE_BODY; status = mctp->req_handler->base.process_request (&mctp->req_handler->base, &mctp->state->req_buffer); if (status != 0) { mctp_interface_reset_message_assembly (mctp); return status; } /* Check to see if the response requires the message timeout to be adjusted. */ if (rx_packet->timeout_valid && mctp->state->req_buffer.crypto_timeout) { platform_increase_timeout (MCTP_BASE_PROTOCOL_MAX_CRYPTO_TIMEOUT_MS - MCTP_BASE_PROTOCOL_MAX_RESPONSE_TIMEOUT_MS, &rx_packet->pkt_timeout); } /* If a response was generated during request processing, packetize the message for transmission * back to the requester. */ if (mctp->state->req_buffer.length > 0) { response_addr = mctp->state->req_buffer.source_addr; status = mctp_interface_generate_packets_from_payload (mctp, mctp->state->req_buffer.data, mctp->state->req_buffer.length, mctp->state->req_buffer.source_eid, response_addr, mctp->state->req_buffer.target_eid, rx_packet->dest_addr, mctp->state->msg_tag, MCTP_BASE_PROTOCOL_TO_RESPONSE, mctp->state->resp_buffer.data, sizeof (mctp->state->msg_buffer), &mctp->state->resp_buffer.pkt_size); mctp_interface_reset_message_assembly (mctp); if (ROT_IS_ERROR (status)) { return status; } mctp->state->resp_buffer.msg_size = status; mctp->state->resp_buffer.dest_addr = response_addr; *tx_message = &mctp->state->resp_buffer; } else { mctp_interface_reset_message_assembly (mctp); *tx_message = NULL; } return 0; } /** * Initialize a handler for MCTP messages. MCTP packets will use the SMBus transport binding. * * @param mctp The MCTP interface to initialize. * @param state Variable context for the MCTP message handler. This must be uninitialized. * @param req_handler The handler to call processing a received MCTP request message. This handler * will be called irrespective of the message type. * @param device_mgr The device manager linked to command interface. * @param channel The channel to use for sending request messages. This can be null if sending * requests is not necessary. * @param cmd_cerberus The command interface for legacy processing of Cerberus response messages. * This can be null if sending requests is not necessary. * @param cmd_mctp The command interface for legacy processing of MCTP response messages. This can * be null if sending requests is not necessary. * @param cmd_spdm The command interface for legacy processing of SPDM response messages. This is * optional and can be set to NULL if SPDM responses are not supported. * * @return Initialization status, 0 if success or an error code. */ int mctp_interface_init (struct mctp_interface *mctp, struct mctp_interface_state *state, const struct cmd_interface_multi_handler *req_handler, struct device_manager *device_mgr, const struct cmd_channel *channel, const struct cmd_interface *cmd_cerberus, const struct cmd_interface *cmd_mctp, const struct cmd_interface *cmd_spdm) { if (mctp == NULL) { return MCTP_BASE_PROTOCOL_INVALID_ARGUMENT; } memset (mctp, 0, sizeof (struct mctp_interface)); #ifdef CMD_ENABLE_ISSUE_REQUEST mctp->base.get_max_message_overhead = mctp_interface_get_max_message_overhead; mctp->base.get_max_message_payload_length = mctp_interface_get_max_message_payload_length; mctp->base.get_max_encapsulated_message_length = mctp_interface_get_max_encapsulated_message_length; mctp->base.get_buffer_overhead = mctp_interface_get_buffer_overhead; mctp->base.send_request_message = mctp_interface_send_request_message; mctp->channel = channel; mctp->cmd_cerberus = cmd_cerberus; mctp->cmd_mctp = cmd_mctp; mctp->cmd_spdm = cmd_spdm; #else UNUSED (channel); UNUSED (cmd_cerberus); UNUSED (cmd_mctp); UNUSED (cmd_spdm); #endif mctp->state = state; mctp->req_handler = req_handler; mctp->device_manager = device_mgr; return mctp_interface_init_state (mctp); } /** * Initialize only the variable state for an MCTP message handler. The rest of the MCTP instance is * assumed to have already been initialized. * * This would generally be used with a statically initialized instance. * * @param mctp The MCTP handler instance that contains the state to initialize. * * @return 0 if the state was successfully initialized or an error code. */ int mctp_interface_init_state (const struct mctp_interface *mctp) { #ifdef CMD_ENABLE_ISSUE_REQUEST int status; #endif if ((mctp == NULL) || (mctp->state == NULL) || (mctp->req_handler == NULL) || (mctp->device_manager == NULL)) { return MCTP_BASE_PROTOCOL_INVALID_ARGUMENT; } memset (mctp->state, 0, sizeof (struct mctp_interface_state)); #ifdef CMD_ENABLE_ISSUE_REQUEST status = platform_semaphore_init (&mctp->state->wait_for_response); if (status != 0) { return status; } status = platform_mutex_init (&mctp->state->request_lock); if (status != 0) { goto free_rx_wait; } status = platform_mutex_init (&mctp->state->response_lock); if (status != 0) { goto free_tx_lock; } #endif mctp_interface_reset_message_assembly (mctp); return 0; #ifdef CMD_ENABLE_ISSUE_REQUEST free_tx_lock: platform_mutex_free (&mctp->state->request_lock); free_rx_wait: platform_semaphore_free (&mctp->state->wait_for_response); return status; #endif } /** * Release the resources used by an SMBus MCTP handler. * * @param mctp The MCTP handler to release. */ void mctp_interface_release (const struct mctp_interface *mctp) { #ifdef CMD_ENABLE_ISSUE_REQUEST if (mctp != NULL) { platform_semaphore_free (&mctp->state->wait_for_response); platform_mutex_free (&mctp->state->request_lock); platform_mutex_free (&mctp->state->response_lock); } #else UNUSED (mctp); #endif } /** * Assign a channel identifier to the MCTP handler. * * @param mctp The MCTP interface to assign the channel ID. * @param channel_id The channel ID to associate with this interface. * * @return 0 if the channel ID was successfully assigned or an error code. */ int mctp_interface_set_channel_id (const struct mctp_interface *mctp, int channel_id) { if (mctp == NULL) { return MCTP_BASE_PROTOCOL_INVALID_ARGUMENT; } mctp->state->channel_id = channel_id; return 0; } /** * Drop an invalid or unsupported MCTP packet. * * @param mctp The MCTP transport layer that received the packet. * @param rx_packet The packet being dropped. * @param error_data Detailed information about the error cause. * @param src_eid EID for the packet sender. * @param dest_eid EID for the packet target. * @param msg_tag Message tag for the message containing the invalid packet. * @param alt_data Alternative error data for logging. * * @return Always returns 0. No error needs to be reported, since the condition has been logged * here. */ static int mctp_interface_drop_packet (const struct mctp_interface *mctp, const struct cmd_packet *rx_packet, uint32_t error_data, uint8_t src_eid, uint8_t dest_eid, uint8_t msg_tag, uint32_t alt_data) { uint32_t msg1 = 0; uint32_t msg2 = 0; uint8_t byte; /* When MCTP packets get dropped, they are silently discarded. There is no active NACK or other * error reporting in the MCTP spec. Log the packet information for debugging and telemetry * purposes. */ msg2 = rx_packet->pkt_size << 24; for (byte = 0; byte < 7; ++byte) { if (byte < 4) { msg1 |= (rx_packet->data[byte] << (byte * 8)); } else { msg2 |= (rx_packet->data[byte] << ((byte - 4) * 8)); } } debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_CHANNEL, mctp->state->channel_id, 0); debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_PKT_DROPPED, msg1, msg2); if (error_data != MCTP_BASE_PROTOCOL_PKT_TOO_SHORT) { switch (error_data) { case MCTP_BASE_PROTOCOL_BAD_CHECKSUM: debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_PROTOCOL_ERROR, (((uint32_t) CERBERUS_PROTOCOL_ERROR_INVALID_CHECKSUM << 24) | (src_eid << 16) | (dest_eid << 8) | msg_tag), alt_data); break; case MCTP_BASE_PROTOCOL_NO_SOM: debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_PROTOCOL_ERROR, (((uint32_t) CERBERUS_PROTOCOL_ERROR_OUT_OF_ORDER_MSG << 24) | (src_eid << 16) | (dest_eid << 8) | msg_tag), 0); break; case MCTP_BASE_PROTOCOL_OUT_OF_SEQUENCE: debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_PROTOCOL_ERROR, (((uint32_t) CERBERUS_PROTOCOL_ERROR_OUT_OF_SEQ_WINDOW << 24) | (src_eid << 16) | (dest_eid << 8) | msg_tag), 0); break; case MCTP_BASE_PROTOCOL_MIDDLE_PKT_LENGTH: debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_PROTOCOL_ERROR, (((uint32_t) CERBERUS_PROTOCOL_ERROR_INVALID_PACKET_LEN << 24) | (src_eid << 16) | (dest_eid << 8) | msg_tag), alt_data); break; case MCTP_BASE_PROTOCOL_MSG_TOO_LARGE: debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_PROTOCOL_ERROR, (((uint32_t) CERBERUS_PROTOCOL_ERROR_MSG_OVERFLOW << 24) | (src_eid << 16) | (dest_eid << 8) | msg_tag), alt_data); break; default: debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_PROTOCOL_ERROR, (((uint32_t) CERBERUS_PROTOCOL_ERROR_INVALID_REQ << 24) | (src_eid << 16) | (dest_eid << 8) | msg_tag), error_data); break; } } return 0; } /** * Process a received MCTP packet using the SMBus transport binding. * * If the packet completes an MCTP message, the request handler will get called to process the * message and generate an appropriate response. If the packet only represents a partial message, * the data will be saved until the rest of the message has been received. * * @param mctp The MCTP handler that will process the packet. * @param rx_packet The received packet to process. Upon return, it is always safe for the caller * to reuse this packet context for new data. * @param tx_message Output for a response message to send. If this is null, there is was no * response generated for the received data. If this is not null, it represents a packetized MCTP * message that should be transmitted. This pointer MUST NOT be freed by the caller and is only * valid until the next call to {@link mctp_interface_process_packet}. * * @return Completion status, 0 if success or an error code. */ int mctp_interface_process_packet (const struct mctp_interface *mctp, struct cmd_packet *rx_packet, struct cmd_message **tx_message) { const uint8_t *payload; uint8_t src_eid = 0; uint8_t dest_eid = 0; uint8_t msg_tag = 0; uint8_t packet_seq; uint8_t crc = 0; uint8_t response_addr = 0; uint8_t tag_owner; uint8_t msg_type; size_t payload_len; bool som; bool eom; int self_eid; int status; if ((mctp == NULL) || (rx_packet == NULL) || (tx_message == NULL)) { return MCTP_BASE_PROTOCOL_INVALID_ARGUMENT; } *tx_message = NULL; self_eid = device_manager_get_device_eid (mctp->device_manager, DEVICE_MANAGER_SELF_DEVICE_NUM); if (ROT_IS_ERROR (self_eid)) { return self_eid; } /* Parse the received packet. */ status = mctp_base_protocol_interpret (rx_packet->data, rx_packet->pkt_size, rx_packet->dest_addr, &response_addr, &som, &eom, &src_eid, &dest_eid, &payload, &payload_len, &msg_tag, &packet_seq, &crc, &msg_type, &tag_owner); if (status == 0) { /* If the packet is not destined for this device, ignore it. */ if ((dest_eid != self_eid) && (dest_eid != MCTP_BASE_PROTOCOL_NULL_EID)) { return 0; } if (tag_owner == MCTP_BASE_PROTOCOL_TO_RESPONSE) { if (!mctp_interface_is_expected_response (mctp, msg_tag, som, msg_type)) { /* Unexpected response packets must not interrupt message assembly and should be * ignored. */ return 0; } } else if (som) { /* MCTP message assembly should not be interrupted by packets for an unsupported message * type. The message type is only present in the SOM packet. For request messages, * check the request handler to see if the message type is supported. */ status = mctp->req_handler->is_message_type_supported (mctp->req_handler, msg_type); if (status == CMD_HANDLER_UNKNOWN_MESSAGE_TYPE) { status = MCTP_BASE_PROTOCOL_UNSUPPORTED_MSG; } } } if (status != 0) { /* Drop the packet if it's invalid in some way. This must not impact any active message * assembly. */ return mctp_interface_drop_packet (mctp, rx_packet, status, src_eid, dest_eid, msg_tag, crc); } /* Check message assembly state relative to the new packet that was received. */ if (som) { if (mctp->state->start_packet_len != 0) { /* A new message is being started before the previous one has completed. This is * considered an error scenario by MCTP, so it's logged. */ debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_CHANNEL, mctp->state->channel_id, 0); debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_RESTART_MESSAGE, ((mctp->state->req_buffer.source_eid << 24) | (mctp->state->msg_tag << 16) | (mctp->state->tag_owner << 8) | mctp->state->msg_type), ((src_eid << 24) | (msg_tag << 16) | (tag_owner << 8) | msg_type)); } cmd_interface_msg_new_message (&mctp->state->req_buffer, src_eid, response_addr, dest_eid, mctp->state->channel_id); mctp->state->start_packet_len = payload_len; mctp->state->packet_seq = packet_seq; mctp->state->msg_tag = msg_tag; mctp->state->msg_type = msg_type; mctp->state->tag_owner = tag_owner; } else if (mctp->state->start_packet_len == 0) { /* Drop the packet if this packet is not a SOM and there is no message currently being * assembled. */ return mctp_interface_drop_packet (mctp, rx_packet, MCTP_BASE_PROTOCOL_NO_SOM, src_eid, dest_eid, msg_tag, 0); } else if ((msg_tag != mctp->state->msg_tag) || (tag_owner != mctp->state->tag_owner)) { /* This is a special case of the no SOM handling. In this case, a packet was received for a * message terminus that is different from the message currently being assembled. * * Drop the packet and do not interrupt message assembly. */ return mctp_interface_drop_packet (mctp, rx_packet, MCTP_BASE_PROTOCOL_UNEXPECTED_PKT, src_eid, dest_eid, msg_tag, 0); } else if (src_eid != mctp->state->req_buffer.source_eid) { /* This is the same special case for no SOM handling, but in this case the message terminus * is different because of the source EID. This is handled separately only so that a * different error can be logged. * * Drop the packet and do not interrupt message assembly. */ return mctp_interface_drop_packet (mctp, rx_packet, MCTP_BASE_PROTOCOL_INVALID_EID, src_eid, dest_eid, msg_tag, 0); } else if (packet_seq != mctp->state->packet_seq) { /* The packet sequence number is wrong. Reset message assembly and drop the packet. */ mctp_interface_reset_message_assembly (mctp); return mctp_interface_drop_packet (mctp, rx_packet, MCTP_BASE_PROTOCOL_OUT_OF_SEQUENCE, src_eid, dest_eid, msg_tag, 0); } else if ((payload_len != mctp->state->start_packet_len) && !(eom && (payload_len < mctp->state->start_packet_len))) { /* Middle packets must be the same size as the first packet. Reset message assembly and * drop the packet. * * Last packets can be smaller, but not larger, than the first packet. */ mctp_interface_reset_message_assembly (mctp); return mctp_interface_drop_packet (mctp, rx_packet, MCTP_BASE_PROTOCOL_MIDDLE_PKT_LENGTH, src_eid, dest_eid, msg_tag, payload_len); } /* Add the new packet data to the message being assembled. */ if ((payload_len + mctp->state->req_buffer.length) > MCTP_BASE_PROTOCOL_MAX_MESSAGE_BODY) { /* The request has grown too large for the message buffer. Reset message assembly and * drop the packet. */ payload_len += mctp->state->req_buffer.length; mctp_interface_reset_message_assembly (mctp); return mctp_interface_drop_packet (mctp, rx_packet, MCTP_BASE_PROTOCOL_MSG_TOO_LARGE, src_eid, dest_eid, msg_tag, payload_len); } cmd_interface_msg_add_payload_data (&mctp->state->req_buffer, payload, payload_len); mctp->state->packet_seq = (mctp->state->packet_seq + 1) % 4; /* If this is the last packet in the message, process the complete message. */ if (eom) { if (tag_owner == MCTP_BASE_PROTOCOL_TO_RESPONSE) { /* The message contains response data. */ return mctp_interface_handle_response_message (mctp); } else { /* The message contains request data. */ return mctp_interface_handle_request_message (mctp, rx_packet, tx_message); } } return 0; } #ifdef CMD_ENABLE_ISSUE_REQUEST /** * @deprecated Do not use this API for new workflows. Use the msg_transport interface instead. * This is only being maintained to support existing workflows temporarily while they get migrated. * Once they get moved, this API will be removed. * * Packetize a request message and send it over a command channel. This call will block until the * full message has been transmitted and a response has been received or the operation times out. * If a timeout_ms of 0 is provided, the request is sent and the function returns immediately * without waiting for a response. * * @param mctp MCTP instance that will be processing the request message. * @param channel Command channel to use for transmitting the packets. * @param dest_addr The destination address for the request. * @param dest_eid The destination EID for the request. * @param request Buffer that contains the request body to send. * @param length Length of the request message before any packetization. * @param msg_buffer Buffer that will be used to store the packetized message. This can be * overlapping with the request buffer. If the buffers overlap, the request data will be modified * upon return. * @param max_length Maximum length of the message buffer. This buffer should be * MCTP_BASE_PROTOCOL_MAX_MESSAGE_LEN bytes to ensure any message packetized in any way can fit. * @param timeout_ms Timeout period in milliseconds to wait for response to be received. If * wait for response not needed, set to 0. * * @return 0 if the request was transmitted successfully or an error code. */ int mctp_interface_issue_request (const struct mctp_interface *mctp, const struct cmd_channel *channel, uint8_t dest_addr, uint8_t dest_eid, uint8_t *request, size_t length, uint8_t *msg_buffer, size_t max_length, uint32_t timeout_ms) { /* TODO: Delete this function in favor of using the msg_transport interface. */ struct mctp_base_protocol_message_header *msg_header; struct cmd_message cmd_msg; size_t max_transmission_unit; size_t num_packets; uint8_t msg_type; int src_eid; int src_addr; int status; if ((mctp == NULL) || (channel == NULL) || (request == NULL) || (msg_buffer == NULL) || (length == 0)) { return MCTP_BASE_PROTOCOL_INVALID_ARGUMENT; } if (length > device_manager_get_max_message_len_by_eid (mctp->device_manager, dest_eid)) { return MCTP_BASE_PROTOCOL_MSG_TOO_LARGE; } max_transmission_unit = device_manager_get_max_transmission_unit_by_eid (mctp->device_manager, dest_eid); num_packets = (MCTP_BASE_PROTOCOL_PACKETS_IN_MESSAGE (length, max_transmission_unit)); if (max_length < MCTP_BASE_PROTOCOL_MESSAGE_LEN (num_packets, length)) { return MCTP_BASE_PROTOCOL_BUF_TOO_SMALL; } src_eid = device_manager_get_device_eid (mctp->device_manager, DEVICE_MANAGER_SELF_DEVICE_NUM); if (ROT_IS_ERROR (src_eid)) { return src_eid; } src_addr = device_manager_get_device_addr (mctp->device_manager, DEVICE_MANAGER_SELF_DEVICE_NUM); if (ROT_IS_ERROR (src_addr)) { return src_addr; } if (buffer_are_overlapping (request, length, msg_buffer, max_length)) { if ((request + length) != (msg_buffer + max_length)) { memmove (msg_buffer + max_length - length, request, length); request = msg_buffer + max_length - length; } } msg_header = (struct mctp_base_protocol_message_header*) request; msg_type = msg_header->msg_type; platform_mutex_lock (&mctp->state->request_lock); status = mctp_interface_generate_packets_from_payload (mctp, request, length, dest_eid, dest_addr, src_eid, src_addr, mctp->state->next_msg_tag, MCTP_BASE_PROTOCOL_TO_REQUEST, msg_buffer, max_length, &cmd_msg.pkt_size); if (ROT_IS_ERROR (status)) { goto unlock; } cmd_msg.msg_size = status; cmd_msg.data = msg_buffer; cmd_msg.dest_addr = dest_addr; platform_mutex_lock (&mctp->state->response_lock); mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_WAITING_DEPRECATED; mctp->state->response_msg_tag = mctp->state->next_msg_tag; mctp->state->response_msg_type = msg_type; mctp->state->response_msg = NULL; mctp->state->next_msg_tag = (mctp->state->next_msg_tag + 1) % 8; status = platform_semaphore_reset (&mctp->state->wait_for_response); if (status != 0) { goto exit; } platform_mutex_unlock (&mctp->state->response_lock); status = cmd_channel_send_message (channel, &cmd_msg); if (status != 0) { platform_mutex_lock (&mctp->state->response_lock); mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_IDLE; platform_mutex_unlock (&mctp->state->response_lock); goto unlock; } if (timeout_ms == 0) { goto unlock; } status = platform_semaphore_wait (&mctp->state->wait_for_response, timeout_ms); platform_mutex_lock (&mctp->state->response_lock); if (status == 1) { debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_MCTP, MCTP_LOGGING_RSP_TIMEOUT, (dest_eid << 8) | mctp->state->response_msg_tag, timeout_ms); status = MCTP_BASE_PROTOCOL_RESPONSE_TIMEOUT; } else if (mctp->state->rsp_state == MCTP_INTERFACE_RESPONSE_ERROR_DEPRECATED) { status = MCTP_BASE_PROTOCOL_ERROR_RESPONSE; } else if (mctp->state->rsp_state == MCTP_INTERFACE_RESPONSE_FAIL_DEPRECATED) { status = MCTP_BASE_PROTOCOL_FAIL_RESPONSE; } exit: mctp->state->rsp_state = MCTP_INTERFACE_RESPONSE_IDLE; platform_mutex_unlock (&mctp->state->response_lock); unlock: platform_mutex_unlock (&mctp->state->request_lock); return status; } /** * Generate and send an MCTP control protocol Discovery Notify request to the MCTP bridge. * * @param mctp MCTP instance that will be processing the request message. * @param timeout_ms The amount of time, in milliseconds, to wait for a response from the bridge. * If this is 0, the response will be ignored and the function will return immediately after sending * the request. * @param response Output for the discovery response message, if one is required. This must be * provided if there is a non-zero timeout. Otherwise, it can be null. If provided, it must be * initialized per the same parameter in {@link msg_transport.send_request_message}. * * @return 0 if the request was transmitted successfully or an error code. */ int mctp_interface_send_discovery_notify (const struct mctp_interface *mctp, uint32_t timeout_ms, struct cmd_interface_msg *response) { uint8_t request_data[MCTP_BASE_PROTOCOL_MIN_MESSAGE_LEN]; struct cmd_interface_msg request; int bridge_eid; int status; if (mctp == NULL) { return MCTP_BASE_PROTOCOL_INVALID_ARGUMENT; } /* TODO: This should probably use the NULL EID. */ bridge_eid = device_manager_get_device_eid (mctp->device_manager, DEVICE_MANAGER_MCTP_BRIDGE_DEVICE_NUM); if (ROT_IS_ERROR (bridge_eid)) { return bridge_eid; } msg_transport_create_empty_request (&mctp->base, request_data, sizeof (request_data), bridge_eid, &request); cmd_interface_msg_set_message_payload_length (&request, mctp_control_protocol_generate_discovery_notify_request (request.payload, request.payload_length)); status = mctp_interface_send_request_message (&mctp->base, &request, timeout_ms, response); if (status == MSG_TRANSPORT_NO_WAIT_RESPONSE) { status = 0; } return status; } #endif