/* * Copyright (c) 2022, Arm Limited. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include "mhu_v2_x.h" #define MHU_NOTIFY_VALUE (1234u) /* * MHU devices for host: * HSE: Host to Secure Enclave (sender device) * SEH: Secure Enclave to Host (receiver device) */ struct mhu_v2_x_dev_t MHU1_HSE_DEV = {0, MHU_V2_X_SENDER_FRAME}; struct mhu_v2_x_dev_t MHU1_SEH_DEV = {0, MHU_V2_X_RECEIVER_FRAME}; static enum mhu_error_t error_mapping_to_mhu_error_t(enum mhu_v2_x_error_t err) { switch (err) { case MHU_V_2_X_ERR_NONE: return MHU_ERR_NONE; case MHU_V_2_X_ERR_NOT_INIT: return MHU_ERR_NOT_INIT; case MHU_V_2_X_ERR_ALREADY_INIT: return MHU_ERR_ALREADY_INIT; case MHU_V_2_X_ERR_UNSUPPORTED_VERSION: return MHU_ERR_UNSUPPORTED_VERSION; case MHU_V_2_X_ERR_INVALID_ARG: return MHU_ERR_INVALID_ARG; case MHU_V_2_X_ERR_GENERAL: return MHU_ERR_GENERAL; default: return MHU_ERR_GENERAL; } } static enum mhu_v2_x_error_t signal_and_wait_for_clear(void) { enum mhu_v2_x_error_t err; struct mhu_v2_x_dev_t *dev = &MHU1_HSE_DEV; uint32_t val = MHU_NOTIFY_VALUE; /* Using the last channel for notifications */ uint32_t channel_notify = mhu_v2_x_get_num_channel_implemented(dev) - 1; err = mhu_v2_x_channel_send(dev, channel_notify, val); if (err != MHU_V_2_X_ERR_NONE) { return err; } do { err = mhu_v2_x_channel_poll(dev, channel_notify, &val); if (err != MHU_V_2_X_ERR_NONE) { break; } } while (val != 0); return err; } static enum mhu_v2_x_error_t wait_for_signal(void) { enum mhu_v2_x_error_t err; struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV; uint32_t val = 0; /* Using the last channel for notifications */ uint32_t channel_notify = mhu_v2_x_get_num_channel_implemented(dev) - 1; do { err = mhu_v2_x_channel_receive(dev, channel_notify, &val); if (err != MHU_V_2_X_ERR_NONE) { break; } } while (val != MHU_NOTIFY_VALUE); return err; } static enum mhu_v2_x_error_t clear_and_wait_for_next_signal(void) { enum mhu_v2_x_error_t err; struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV; uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev); uint32_t i; /* Clear all channels */ for (i = 0; i < num_channels; ++i) { err = mhu_v2_x_channel_clear(dev, i); if (err != MHU_V_2_X_ERR_NONE) { return err; } } return wait_for_signal(); } enum mhu_error_t mhu_init_sender(uintptr_t mhu_sender_base) { enum mhu_v2_x_error_t err; assert(mhu_sender_base != (uintptr_t)NULL); MHU1_HSE_DEV.base = mhu_sender_base; err = mhu_v2_x_driver_init(&MHU1_HSE_DEV, MHU_REV_READ_FROM_HW); return error_mapping_to_mhu_error_t(err); } enum mhu_error_t mhu_init_receiver(uintptr_t mhu_receiver_base) { enum mhu_v2_x_error_t err; uint32_t num_channels, i; assert(mhu_receiver_base != (uintptr_t)NULL); MHU1_SEH_DEV.base = mhu_receiver_base; err = mhu_v2_x_driver_init(&MHU1_SEH_DEV, MHU_REV_READ_FROM_HW); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } num_channels = mhu_v2_x_get_num_channel_implemented(&MHU1_SEH_DEV); /* Mask all channels except the notifying channel */ for (i = 0; i < (num_channels - 1); ++i) { err = mhu_v2_x_channel_mask_set(&MHU1_SEH_DEV, i, UINT32_MAX); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } } /* The last channel is used for notifications */ err = mhu_v2_x_channel_mask_clear( &MHU1_SEH_DEV, (num_channels - 1), UINT32_MAX); return error_mapping_to_mhu_error_t(err); } /* * Public function. See mhu.h * * The basic steps of transferring a message: * 1. Initiate MHU transfer. * 2. Send over the size of the payload on Channel 1. It is the very first * 4 Bytes of the transfer. Continue with Channel 2. * 3. Send over the payload, writing the channels one after the other * (4 Bytes each). The last available channel is reserved for controlling * the transfer. * When the last channel is reached or no more data is left, STOP. * 4. Notify the receiver using the last channel and wait for acknowledge. * If there is still data to transfer, jump to step 3. Otherwise, proceed. * 5. Close MHU transfer. * */ enum mhu_error_t mhu_send_data(const uint8_t *send_buffer, size_t size) { enum mhu_v2_x_error_t err; struct mhu_v2_x_dev_t *dev = &MHU1_HSE_DEV; uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev); uint32_t chan = 0; uint32_t i; uint32_t *p; /* For simplicity, require the send_buffer to be 4-byte aligned */ if ((uintptr_t)send_buffer & 0x3U) { return MHU_ERR_INVALID_ARG; } err = mhu_v2_x_initiate_transfer(dev); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } /* First send over the size of the actual message */ err = mhu_v2_x_channel_send(dev, chan, (uint32_t)size); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } chan++; p = (uint32_t *)send_buffer; for (i = 0; i < size; i += 4) { err = mhu_v2_x_channel_send(dev, chan, *p++); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } if (++chan == (num_channels - 1)) { err = signal_and_wait_for_clear(); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } chan = 0; } } /* Signal the end of transfer. * It's not required to send a signal when the message was * perfectly-aligned (num_channels - 1 channels were used in the last * round) preventing it from signaling twice at the end of transfer. */ if (chan != 0) { err = signal_and_wait_for_clear(); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } } err = mhu_v2_x_close_transfer(dev); return error_mapping_to_mhu_error_t(err); } /* * Public function. See mhu.h * * The basic steps of receiving a message: * 1. Read the size of the payload from Channel 1. It is the very first * 4 Bytes of the transfer. Continue with Channel 2. * 2. Receive the payload, read the channels one after the other * (4 Bytes each). The last available channel is reserved for controlling * the transfer. * When the last channel is reached clear all the channels * (also sending an acknowledge on the last channel). * 3. If there is still data to receive wait for a notification on the last * channel and jump to step 2 as soon as it arrived. Otherwise, proceed. * 4. End of transfer. * */ enum mhu_error_t mhu_receive_data(uint8_t *receive_buffer, size_t *size) { enum mhu_v2_x_error_t err; struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV; uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev); uint32_t chan = 0; uint32_t message_len; uint32_t i; uint32_t *p; /* For simplicity, require: * - the receive_buffer to be 4-byte aligned, * - the buffer size to be a multiple of 4. */ if (((uintptr_t)receive_buffer & 0x3U) || (*size & 0x3U)) { return MHU_ERR_INVALID_ARG; } /* Busy wait for incoming reply */ err = wait_for_signal(); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } /* The first word is the length of the actual message */ err = mhu_v2_x_channel_receive(dev, chan, &message_len); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } chan++; if (message_len > *size) { /* Message buffer too small */ *size = message_len; return MHU_ERR_BUFFER_TOO_SMALL; } p = (uint32_t *)receive_buffer; for (i = 0; i < message_len; i += 4) { err = mhu_v2_x_channel_receive(dev, chan, p++); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } /* Only wait for next transfer if there is still missing data */ if (++chan == (num_channels - 1) && (message_len - i) > 4) { /* Busy wait for next transfer */ err = clear_and_wait_for_next_signal(); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } chan = 0; } } /* Clear all channels */ for (i = 0; i < num_channels; ++i) { err = mhu_v2_x_channel_clear(dev, i); if (err != MHU_V_2_X_ERR_NONE) { return error_mapping_to_mhu_error_t(err); } } *size = message_len; return MHU_ERR_NONE; } size_t mhu_get_max_message_size(void) { struct mhu_v2_x_dev_t *dev = &MHU1_SEH_DEV; uint32_t num_channels = mhu_v2_x_get_num_channel_implemented(dev); assert(num_channels != 0); /* * Returns only usable size of memory. As one channel is specifically * used to inform about the size of payload, discard it from avialable * memory size. */ return (num_channels - 1) * sizeof(uint32_t); }