| /* |
| * SPDX-License-Identifier: BSD-3-Clause |
| * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors |
| * |
| */ |
| |
| #include "psa/service.h" |
| #include "psa_manifest/scmi_comms.h" |
| #include "scmi_comms.h" |
| #include "scmi_hal.h" |
| #include "scmi_protocol.h" |
| #include "tfm_log_unpriv.h" |
| |
| #include <assert.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #define SCMI_MESSAGE_HEADER_MESSAGE_ID_POS 0 |
| #define SCMI_MESSAGE_HEADER_MESSAGE_ID_MASK \ |
| (UINT32_C(0xFF) << SCMI_MESSAGE_HEADER_MESSAGE_ID_POS) |
| |
| #define SCMI_MESSAGE_HEADER_MESSAGE_TYPE_POS 8 |
| #define SCMI_MESSAGE_HEADER_MESSAGE_TYPE_MASK \ |
| (UINT32_C(0x3) << SCMI_MESSAGE_HEADER_MESSAGE_TYPE_POS) |
| |
| #define SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS 10 |
| #define SCMI_MESSAGE_HEADER_PROTOCOL_ID_MASK \ |
| (UINT32_C(0xFF) << SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS) |
| |
| #define SCMI_MESSAGE_HEADER_TOKEN_POS 18 |
| #define SCMI_MESSAGE_HEADER_TOKEN_MASK \ |
| (UINT32_C(0x3FF) << SCMI_MESSAGE_HEADER_TOKEN_POS) |
| |
| static uint32_t scmi_message_header(uint8_t message_id, uint8_t message_type, |
| uint8_t protocol_id, uint8_t token) |
| { |
| return (((uint32_t)message_id << SCMI_MESSAGE_HEADER_MESSAGE_ID_POS) & |
| SCMI_MESSAGE_HEADER_MESSAGE_ID_MASK) | |
| (((uint32_t)message_type << SCMI_MESSAGE_HEADER_MESSAGE_TYPE_POS) & |
| SCMI_MESSAGE_HEADER_MESSAGE_TYPE_MASK) | |
| (((uint32_t)protocol_id << SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS) & |
| SCMI_MESSAGE_HEADER_PROTOCOL_ID_MASK) | |
| (((uint32_t)token << SCMI_MESSAGE_HEADER_TOKEN_POS) & |
| SCMI_MESSAGE_HEADER_TOKEN_MASK); |
| } |
| |
| #define TRANSPORT_BUFFER_STATUS_FREE_POS 0 |
| #define TRANSPORT_BUFFER_STATUS_FREE_MASK \ |
| (UINT32_C(0x1) << TRANSPORT_BUFFER_STATUS_FREE_POS) |
| |
| #define TRANSPORT_BUFFER_STATUS_ERROR_POS 1 |
| #define TRANSPORT_BUFFER_STATUS_ERROR_MASK \ |
| (UINT32_C(0x1) << TRANSPORT_BUFFER_STATUS_ERROR_POS) |
| |
| #define TRANSPORT_BUFFER_FLAGS_INTERRUPT_POS 0 |
| #define TRANSPORT_BUFFER_FLAGS_INTERRUPT_MASK \ |
| (UINT32_C(0x1) << TRANSPORT_BUFFER_FLAGS_INTERRUPT_POS) |
| |
| #define TRANSPORT_BUFFER_MAX_LENGTH \ |
| (SCP_SHARED_MEMORY_SIZE - offsetof(struct transport_buffer_t, message_header)) |
| |
| /** |
| * \brief Shared memory area layout used for sending & receiving messages |
| */ |
| struct transport_buffer_t { |
| uint32_t reserved0; /**< Reserved, must be zero */ |
| volatile uint32_t status; /**< Channel status */ |
| uint64_t reserved1; /**< Implementation defined field */ |
| uint32_t flags; /**< Channel flags */ |
| volatile uint32_t length; /**< Length in bytes of the message header and payload */ |
| uint32_t message_header; /**< Message header */ |
| uint32_t message_payload[]; /**< Message payload */ |
| }; |
| |
| /** |
| * \brief Structure representing an SCMI message. |
| */ |
| struct scmi_message_t { |
| uint32_t header; |
| uint32_t payload[(TRANSPORT_BUFFER_MAX_LENGTH - sizeof(uint32_t)) / sizeof(uint32_t)]; |
| uint32_t payload_len; |
| }; |
| |
| static struct transport_buffer_t *const shared_memory = |
| (struct transport_buffer_t *)SCP_SHARED_MEMORY_BASE; |
| |
| /** |
| * \brief Initialize the SCMI transport layer. |
| * |
| * \return Error value as defined by scmi_comms_err_t. |
| */ |
| static scmi_comms_err_t transport_init(void) |
| { |
| scmi_comms_err_t err; |
| |
| err = scmi_hal_doorbell_init(); |
| if (err != SCMI_COMMS_SUCCESS) { |
| return err; |
| } |
| |
| err = scmi_hal_shared_memory_init(); |
| if (err != SCMI_COMMS_SUCCESS) { |
| return err; |
| } |
| |
| shared_memory->flags = 0; |
| shared_memory->length = 0; |
| shared_memory->status = TRANSPORT_BUFFER_STATUS_FREE_MASK; |
| |
| return SCMI_COMMS_SUCCESS; |
| } |
| |
| /** |
| * \brief Read a message from the shared memory to the local buffer. |
| * |
| * \param[out] msg SCMI message |
| * |
| * \return Error value as defined by scmi_comms_err_t. |
| */ |
| static scmi_comms_err_t transport_receive(struct scmi_message_t *msg) |
| { |
| scmi_comms_err_t err = scmi_hal_doorbell_clear(); |
| if (err != SCMI_COMMS_SUCCESS) { |
| return err; |
| } |
| |
| uint32_t length = shared_memory->length; |
| |
| if ((length < sizeof(shared_memory->message_header)) || |
| (length > TRANSPORT_BUFFER_MAX_LENGTH)) { |
| return SCMI_COMMS_INVALID_ARGUMENT; |
| } |
| |
| memcpy(msg, &shared_memory->message_header, length); |
| msg->payload_len = length - sizeof(msg->header); |
| |
| return SCMI_COMMS_SUCCESS; |
| } |
| |
| /** |
| * \brief Write a response from the local buffer to the shared memory and signal |
| * completion. |
| * |
| * \param[in] msg SCMI message |
| */ |
| static void transport_respond(const struct scmi_message_t *msg) |
| { |
| /* Populate shared memory area */ |
| memcpy(shared_memory->message_payload, msg->payload, msg->payload_len); |
| shared_memory->length = msg->payload_len + sizeof(msg->header); |
| |
| /* Mark channel as free */ |
| shared_memory->status |= TRANSPORT_BUFFER_STATUS_FREE_MASK; |
| |
| /* TODO: Issue completion interrupt */ |
| } |
| |
| /** |
| * \brief Write a message from the local buffer to the shared memory and wait |
| * for a response. |
| * |
| * \param[in] msg SCMI message |
| * |
| * \return Error value as defined by scmi_comms_err_t. |
| */ |
| static int32_t transport_send(const struct scmi_message_t *msg) |
| { |
| int32_t err; |
| uint32_t length = msg->payload_len + sizeof(msg->header); |
| |
| if (length > TRANSPORT_BUFFER_MAX_LENGTH) { |
| return SCMI_COMMS_INVALID_ARGUMENT; |
| } |
| |
| /* Wait for channel to be free */ |
| /* TODO: Timeout */ |
| while (!(shared_memory->status & TRANSPORT_BUFFER_STATUS_FREE_MASK)); |
| |
| /* Populate shared memory area */ |
| memcpy(&shared_memory->message_header, msg, length); |
| shared_memory->length = length; |
| |
| /* Mark channel as busy */ |
| shared_memory->status &= ~TRANSPORT_BUFFER_STATUS_FREE_MASK; |
| |
| /* Ring doorbell */ |
| err = scmi_hal_doorbell_ring(); |
| if (err != SCMI_COMMS_SUCCESS) { |
| return err; |
| } |
| |
| /* Wait until channel is free */ |
| /* TODO: Wait for completion interrupt */ |
| while (!(shared_memory->status & TRANSPORT_BUFFER_STATUS_FREE_MASK)); |
| |
| return SCMI_COMMS_SUCCESS; |
| } |
| |
| /** |
| * \brief Create an SCMI response containing only status. |
| * |
| * \param[out] msg SCMI message |
| * \param[in] status SCMI status |
| */ |
| static void scmi_response_status(struct scmi_message_t *msg, int32_t status) |
| { |
| msg->payload[0] = status; |
| msg->payload_len = sizeof(msg->payload[0]); |
| } |
| |
| /** |
| * \brief Create an SCMI system power state notify message. |
| * |
| * \param[out] msg SCMI message |
| */ |
| static void scmi_message_sys_power_state_notify(struct scmi_message_t *msg) |
| { |
| msg->header = |
| scmi_message_header(SCMI_MESSAGE_ID_SYS_POWER_STATE_NOTIFY, |
| SCMI_MESSAGE_TYPE_COMMAND, |
| SCMI_PROTOCOL_ID_SYS_POWER_STATE, |
| 0); |
| |
| assert(sizeof(struct scmi_sys_power_state_notify_t) <= sizeof(msg->payload)); |
| |
| memcpy(msg->payload, |
| &(struct scmi_sys_power_state_notify_t) { .notify_enable = 1 }, |
| sizeof(struct scmi_sys_power_state_notify_t)); |
| |
| msg->payload_len = sizeof(struct scmi_sys_power_state_notify_t); |
| } |
| |
| /** |
| * \brief Handle an SCMI system power state set message. |
| * |
| * \param[in,out] msg SCMI message |
| */ |
| static void scmi_handle_sys_power_state_set(struct scmi_message_t *msg) |
| { |
| if (msg->payload_len != sizeof(struct scmi_sys_power_state_set_t)) { |
| scmi_response_status(msg, SCMI_STATUS_PROTOCOL_ERROR); |
| return; |
| } |
| |
| struct scmi_sys_power_state_set_t *pwr_set = |
| (struct scmi_sys_power_state_set_t *)msg->payload; |
| |
| int32_t status = scmi_hal_sys_power_state(0, pwr_set->flags, pwr_set->system_state); |
| |
| scmi_response_status(msg, status); |
| } |
| |
| /** |
| * \brief Handle an SCMI system power state notification. |
| * |
| * \param[in,out] msg SCMI message |
| */ |
| static void scmi_handle_sys_power_state_notifier(struct scmi_message_t *msg) |
| { |
| if (msg->payload_len != sizeof(struct scmi_sys_power_state_notifier_t)) { |
| /* No return values for notifications */ |
| msg->payload_len = 0; |
| return; |
| } |
| |
| struct scmi_sys_power_state_notifier_t *pwr_not = |
| (struct scmi_sys_power_state_notifier_t *)msg->payload; |
| |
| scmi_hal_sys_power_state(pwr_not->agent_id, pwr_not->flags, pwr_not->system_state); |
| |
| /* No return values for notifications */ |
| msg->payload_len = 0; |
| } |
| |
| /** |
| * \brief Handle a received SCMI message. |
| * |
| * \param[in,out] msg SCMI message |
| */ |
| static void scmi_handle_message(struct scmi_message_t *msg) |
| { |
| uint8_t message_id = (msg->header & SCMI_MESSAGE_HEADER_MESSAGE_ID_MASK) |
| >> SCMI_MESSAGE_HEADER_MESSAGE_ID_POS; |
| uint8_t message_type = (msg->header & SCMI_MESSAGE_HEADER_MESSAGE_TYPE_MASK) |
| >> SCMI_MESSAGE_HEADER_MESSAGE_TYPE_POS; |
| uint8_t protocol_id = (msg->header & SCMI_MESSAGE_HEADER_PROTOCOL_ID_MASK) |
| >> SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS; |
| |
| if (protocol_id == SCMI_PROTOCOL_ID_SYS_POWER_STATE) { |
| if (message_type == SCMI_MESSAGE_TYPE_COMMAND && |
| message_id == SCMI_MESSAGE_ID_SYS_POWER_STATE_SET) { |
| scmi_handle_sys_power_state_set(msg); |
| return; |
| } else if (message_type == SCMI_MESSAGE_TYPE_NOTIFICATION && |
| message_id == SCMI_MESSAGE_ID_SYS_POWER_STATE_NOTIFIER) { |
| scmi_handle_sys_power_state_notifier(msg); |
| return; |
| } |
| } |
| |
| /* Any command that is sent with an unknown protocol_id or message_id must |
| * be responded to with a return value of NOT_SUPPORTED as the status code. |
| */ |
| scmi_response_status(msg, SCMI_STATUS_NOT_SUPPORTED); |
| } |
| |
| /** |
| * \brief Handle a received SCMI response. |
| * |
| * \param[in] msg SCMI message |
| * |
| * \return SCMI status of the response. |
| */ |
| static int32_t scmi_handle_response(const struct scmi_message_t *msg) |
| { |
| /* Only simple status responses are currently supported */ |
| if (msg->payload_len != sizeof(int32_t)) { |
| return SCMI_STATUS_PROTOCOL_ERROR; |
| } |
| return (int32_t)msg->payload[0]; |
| } |
| |
| /** |
| * \brief Subscribe to system power state notifications. |
| * |
| * \param[in,out] msg SCMI message |
| * |
| * \return Error value as defined by scmi_comms_err_t. |
| */ |
| static scmi_comms_err_t scmi_comms_notification_subscribe(struct scmi_message_t *msg) |
| { |
| scmi_comms_err_t err; |
| |
| scmi_message_sys_power_state_notify(msg); |
| |
| err = transport_send(msg); |
| if (err != SCMI_COMMS_SUCCESS) { |
| return err; |
| } |
| |
| err = transport_receive(msg); |
| if (err != SCMI_COMMS_SUCCESS) { |
| return err; |
| } |
| |
| return (scmi_handle_response(msg) == SCMI_STATUS_SUCCESS) ? |
| SCMI_COMMS_SUCCESS : SCMI_COMMS_GENERIC_ERROR; |
| } |
| |
| void scmi_comms_main(void) |
| { |
| scmi_comms_err_t err; |
| struct scmi_message_t agent_buf; |
| |
| err = transport_init(); |
| if (err != SCMI_COMMS_SUCCESS) { |
| psa_panic(); |
| } |
| |
| psa_irq_enable(SCP_DOORBELL_SIGNAL); |
| |
| /* First wait for SCP to signal that it is ready to receive commands. */ |
| (void)psa_wait(SCP_DOORBELL_SIGNAL, PSA_BLOCK); |
| |
| err = scmi_hal_doorbell_clear(); |
| if (err != SCMI_COMMS_SUCCESS) { |
| psa_panic(); |
| } |
| |
| psa_eoi(SCP_DOORBELL_SIGNAL); |
| |
| /* Subscribe to notifications. If it fails, the agent will still listen for |
| * SCMI commands. |
| */ |
| err = scmi_comms_notification_subscribe(&agent_buf); |
| if (err == SCMI_COMMS_SUCCESS) { |
| INFO_UNPRIV_RAW("SCMI Comms subscribed to power state notifications\n"); |
| } else { |
| ERROR_UNPRIV_RAW("SCMI Comms failed to subscribe to power state notifications\n"); |
| } |
| |
| while (1) { |
| (void)psa_wait(SCP_DOORBELL_SIGNAL, PSA_BLOCK); |
| |
| err = transport_receive(&agent_buf); |
| if (err == SCMI_COMMS_SUCCESS) { |
| scmi_handle_message(&agent_buf); |
| } else { |
| scmi_response_status(&agent_buf, SCMI_STATUS_PROTOCOL_ERROR); |
| } |
| transport_respond(&agent_buf); |
| |
| psa_eoi(SCP_DOORBELL_SIGNAL); |
| } |
| } |