From af26d7d6f01068809f17cc2d49a9b3d573c640a9 Mon Sep 17 00:00:00 2001 From: Tamas Ban Date: Mon, 10 Jan 2022 17:04:03 +0100 Subject: [PATCH] feat(drivers/arm/mhu): add MHU driver The Arm Message Handling Unit (MHU) is a mailbox controller used to communicate with other processing element(s). Adding a driver to enable the communication: - Adding generic MHU driver interface, - Adding MHU_v2_x driver. Driver supports: - Discovering available MHU channels, - Sending / receiving words over MHU channels, - Signaling happens over a dedicated channel. Signed-off-by: Tamas Ban Signed-off-by: David Vincze Change-Id: I41a5b968f6b8319cdbdf7907d70bd8837839862e --- drivers/arm/mhu/mhu_v2_x.c | 379 +++++++++++++++++++++++++++++ drivers/arm/mhu/mhu_v2_x.h | 210 ++++++++++++++++ drivers/arm/mhu/mhu_wrapper_v2_x.c | 302 +++++++++++++++++++++++ include/drivers/arm/mhu.h | 79 ++++++ 4 files changed, 970 insertions(+) create mode 100644 drivers/arm/mhu/mhu_v2_x.c create mode 100644 drivers/arm/mhu/mhu_v2_x.h create mode 100644 drivers/arm/mhu/mhu_wrapper_v2_x.c create mode 100644 include/drivers/arm/mhu.h diff --git a/drivers/arm/mhu/mhu_v2_x.c b/drivers/arm/mhu/mhu_v2_x.c new file mode 100644 index 000000000..3103b9243 --- /dev/null +++ b/drivers/arm/mhu/mhu_v2_x.c @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2020-2022, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include "mhu_v2_x.h" + +#define MHU_V2_X_MAX_CHANNELS 124 +#define MHU_V2_1_MAX_CHCOMB_INT 4 +#define ENABLE 0x1 +#define DISABLE 0x0 +#define CLEAR_INTR 0x1 +#define CH_PER_CH_COMB 0x20 +#define SEND_FRAME(p_mhu) ((struct mhu_v2_x_send_frame_t *)p_mhu) +#define RECV_FRAME(p_mhu) ((struct mhu_v2_x_recv_frame_t *)p_mhu) + +#define MHU_MAJOR_REV_V2 0x1u +#define MHU_MINOR_REV_2_0 0x0u +#define MHU_MINOR_REV_2_1 0x1u + +struct mhu_v2_x_send_ch_window_t { + /* Offset: 0x00 (R/ ) Channel Status */ + volatile uint32_t ch_st; + /* Offset: 0x04 (R/ ) Reserved */ + volatile uint32_t reserved_0; + /* Offset: 0x08 (R/ ) Reserved */ + volatile uint32_t reserved_1; + /* Offset: 0x0C ( /W) Channel Set */ + volatile uint32_t ch_set; + /* Offset: 0x10 (R/ ) Channel Interrupt Status (Reserved in 2.0) */ + volatile uint32_t ch_int_st; + /* Offset: 0x14 ( /W) Channel Interrupt Clear (Reserved in 2.0) */ + volatile uint32_t ch_int_clr; + /* Offset: 0x18 (R/W) Channel Interrupt Enable (Reserved in 2.0) */ + volatile uint32_t ch_int_en; + /* Offset: 0x1C (R/ ) Reserved */ + volatile uint32_t reserved_2; +}; + +struct mhu_v2_x_send_frame_t { + /* Offset: 0x000 ( / ) Sender Channel Window 0 -123 */ + struct mhu_v2_x_send_ch_window_t send_ch_window[MHU_V2_X_MAX_CHANNELS]; + /* Offset: 0xF80 (R/ ) Message Handling Unit Configuration */ + volatile uint32_t mhu_cfg; + /* Offset: 0xF84 (R/W) Response Configuration */ + volatile uint32_t resp_cfg; + /* Offset: 0xF88 (R/W) Access Request */ + volatile uint32_t access_request; + /* Offset: 0xF8C (R/ ) Access Ready */ + volatile uint32_t access_ready; + /* Offset: 0xF90 (R/ ) Interrupt Status */ + volatile uint32_t int_st; + /* Offset: 0xF94 ( /W) Interrupt Clear */ + volatile uint32_t int_clr; + /* Offset: 0xF98 (R/W) Interrupt Enable */ + volatile uint32_t int_en; + /* Offset: 0xF9C (R/ ) Reserved */ + volatile uint32_t reserved_0; + /* Offset: 0xFA0 (R/W) Channel Combined IRQ Stat (Reserved in 2.0) */ + volatile uint32_t ch_comb_int_st[MHU_V2_1_MAX_CHCOMB_INT]; + /* Offset: 0xFC4 (R/ ) Reserved */ + volatile uint32_t reserved_1[6]; + /* Offset: 0xFC8 (R/ ) Implementer Identification Register */ + volatile uint32_t iidr; + /* Offset: 0xFCC (R/ ) Architecture Identification Register */ + volatile uint32_t aidr; + /* Offset: 0xFD0 (R/ ) */ + volatile uint32_t pid_1[4]; + /* Offset: 0xFE0 (R/ ) */ + volatile uint32_t pid_0[4]; + /* Offset: 0xFF0 (R/ ) */ + volatile uint32_t cid[4]; +}; + +struct mhu_v2_x_rec_ch_window_t { + /* Offset: 0x00 (R/ ) Channel Status */ + volatile uint32_t ch_st; + /* Offset: 0x04 (R/ ) Channel Status Masked */ + volatile uint32_t ch_st_msk; + /* Offset: 0x08 ( /W) Channel Clear */ + volatile uint32_t ch_clr; + /* Offset: 0x0C (R/ ) Reserved */ + volatile uint32_t reserved_0; + /* Offset: 0x10 (R/ ) Channel Mask Status */ + volatile uint32_t ch_msk_st; + /* Offset: 0x14 ( /W) Channel Mask Set */ + volatile uint32_t ch_msk_set; + /* Offset: 0x18 ( /W) Channel Mask Clear */ + volatile uint32_t ch_msk_clr; + /* Offset: 0x1C (R/ ) Reserved */ + volatile uint32_t reserved_1; +}; + +struct mhu_v2_x_recv_frame_t { + /* Offset: 0x000 ( / ) Receiver Channel Window 0 -123 */ + struct mhu_v2_x_rec_ch_window_t rec_ch_window[MHU_V2_X_MAX_CHANNELS]; + /* Offset: 0xF80 (R/ ) Message Handling Unit Configuration */ + volatile uint32_t mhu_cfg; + /* Offset: 0xF84 (R/ ) Reserved */ + volatile uint32_t reserved_0[3]; + /* Offset: 0xF90 (R/ ) Interrupt Status (Reserved in 2.0) */ + volatile uint32_t int_st; + /* Offset: 0xF94 (R/ ) Interrupt Clear (Reserved in 2.0) */ + volatile uint32_t int_clr; + /* Offset: 0xF98 (R/W) Interrupt Enable (Reserved in 2.0) */ + volatile uint32_t int_en; + /* Offset: 0xF9C (R/ ) Reserved */ + volatile uint32_t reserved_1; + /* Offset: 0xFA0 (R/ ) Channel Combined IRQ Stat (Reserved in 2.0) */ + volatile uint32_t ch_comb_int_st[MHU_V2_1_MAX_CHCOMB_INT]; + /* Offset: 0xFB0 (R/ ) Reserved */ + volatile uint32_t reserved_2[6]; + /* Offset: 0xFC8 (R/ ) Implementer Identification Register */ + volatile uint32_t iidr; + /* Offset: 0xFCC (R/ ) Architecture Identification Register */ + volatile uint32_t aidr; + /* Offset: 0xFD0 (R/ ) */ + volatile uint32_t pid_1[4]; + /* Offset: 0xFE0 (R/ ) */ + volatile uint32_t pid_0[4]; + /* Offset: 0xFF0 (R/ ) */ + volatile uint32_t cid[4]; +}; + +union mhu_v2_x_frame { + struct mhu_v2_x_send_frame_t send_frame; + struct mhu_v2_x_recv_frame_t recv_frame; +}; + +enum mhu_v2_x_error_t mhu_v2_x_driver_init(struct mhu_v2_x_dev_t *dev, + enum mhu_v2_x_supported_revisions rev) +{ + uint32_t AIDR = 0; + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (dev->is_initialized) { + return MHU_V_2_X_ERR_ALREADY_INIT; + } + + if (rev == MHU_REV_READ_FROM_HW) { + /* Read revision from HW */ + if (dev->frame == MHU_V2_X_RECEIVER_FRAME) { + AIDR = p_mhu->recv_frame.aidr; + } else { + AIDR = p_mhu->send_frame.aidr; + } + + /* Get bits 7:4 to read major revision */ + if (((AIDR >> 4) & 0b1111) != MHU_MAJOR_REV_V2) { + /* Unsupported MHU version */ + return MHU_V_2_X_ERR_UNSUPPORTED_VERSION; + } /* No need to save major version, driver only supports MHUv2 */ + + /* Get bits 3:0 to read minor revision */ + dev->subversion = AIDR & 0b1111; + + if (dev->subversion != MHU_MINOR_REV_2_0 && + dev->subversion != MHU_MINOR_REV_2_1) { + /* Unsupported subversion */ + return MHU_V_2_X_ERR_UNSUPPORTED_VERSION; + } + } else { + /* Revisions were provided by caller */ + if (rev == MHU_REV_2_0) { + dev->subversion = MHU_MINOR_REV_2_0; + } else if (rev == MHU_REV_2_1) { + dev->subversion = MHU_MINOR_REV_2_1; + } else { + /* Unsupported subversion */ + return MHU_V_2_X_ERR_UNSUPPORTED_VERSION; + } /* No need to save major version, driver only supports MHUv2 */ + } + + dev->is_initialized = true; + + return MHU_V_2_X_ERR_NONE; +} + +uint32_t mhu_v2_x_get_num_channel_implemented(const struct mhu_v2_x_dev_t *dev) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame == MHU_V2_X_SENDER_FRAME) { + return (SEND_FRAME(p_mhu))->mhu_cfg; + } else { + assert(dev->frame == MHU_V2_X_RECEIVER_FRAME); + return (RECV_FRAME(p_mhu))->mhu_cfg; + } +} + +enum mhu_v2_x_error_t mhu_v2_x_channel_send(const struct mhu_v2_x_dev_t *dev, + uint32_t channel, uint32_t val) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame == MHU_V2_X_SENDER_FRAME) { + (SEND_FRAME(p_mhu))->send_ch_window[channel].ch_set = val; + return MHU_V_2_X_ERR_NONE; + } else { + return MHU_V_2_X_ERR_INVALID_ARG; + } +} + +enum mhu_v2_x_error_t mhu_v2_x_channel_poll(const struct mhu_v2_x_dev_t *dev, + uint32_t channel, uint32_t *value) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame == MHU_V2_X_SENDER_FRAME) { + *value = (SEND_FRAME(p_mhu))->send_ch_window[channel].ch_st; + return MHU_V_2_X_ERR_NONE; + } else { + return MHU_V_2_X_ERR_INVALID_ARG; + } +} + +enum mhu_v2_x_error_t mhu_v2_x_channel_clear(const struct mhu_v2_x_dev_t *dev, + uint32_t channel) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame == MHU_V2_X_RECEIVER_FRAME) { + (RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_clr = UINT32_MAX; + return MHU_V_2_X_ERR_NONE; + } else { + return MHU_V_2_X_ERR_INVALID_ARG; + } +} + +enum mhu_v2_x_error_t mhu_v2_x_channel_receive( + const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t *value) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame == MHU_V2_X_RECEIVER_FRAME) { + *value = (RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_st; + return MHU_V_2_X_ERR_NONE; + } else { + return MHU_V_2_X_ERR_INVALID_ARG; + } +} + +enum mhu_v2_x_error_t mhu_v2_x_channel_mask_set( + const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame == MHU_V2_X_RECEIVER_FRAME) { + (RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_msk_set = mask; + return MHU_V_2_X_ERR_NONE; + } else { + return MHU_V_2_X_ERR_INVALID_ARG; + } +} + +enum mhu_v2_x_error_t mhu_v2_x_channel_mask_clear( + const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame == MHU_V2_X_RECEIVER_FRAME) { + (RECV_FRAME(p_mhu))->rec_ch_window[channel].ch_msk_clr = mask; + return MHU_V_2_X_ERR_NONE; + } else { + return MHU_V_2_X_ERR_INVALID_ARG; + } +} +enum mhu_v2_x_error_t mhu_v2_x_initiate_transfer( + const struct mhu_v2_x_dev_t *dev) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame != MHU_V2_X_SENDER_FRAME) { + return MHU_V_2_X_ERR_INVALID_ARG; + } + + (SEND_FRAME(p_mhu))->access_request = ENABLE; + + while (!((SEND_FRAME(p_mhu))->access_ready)) { + /* Wait in a loop for access ready signal to be high */ + ; + } + + return MHU_V_2_X_ERR_NONE; +} + +enum mhu_v2_x_error_t mhu_v2_x_close_transfer(const struct mhu_v2_x_dev_t *dev) +{ + union mhu_v2_x_frame *p_mhu; + + assert(dev != NULL); + + p_mhu = (union mhu_v2_x_frame *)dev->base; + + if (!(dev->is_initialized)) { + return MHU_V_2_X_ERR_NOT_INIT; + } + + if (dev->frame != MHU_V2_X_SENDER_FRAME) { + return MHU_V_2_X_ERR_INVALID_ARG; + } + + (SEND_FRAME(p_mhu))->access_request = DISABLE; + + return MHU_V_2_X_ERR_NONE; +} diff --git a/drivers/arm/mhu/mhu_v2_x.h b/drivers/arm/mhu/mhu_v2_x.h new file mode 100644 index 000000000..10247d24f --- /dev/null +++ b/drivers/arm/mhu/mhu_v2_x.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2020-2022, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MHU_V2_X_H +#define MHU_V2_X_H + +#include +#include + +#define MHU_2_X_INTR_NR2R_OFF (0x0u) +#define MHU_2_X_INTR_R2NR_OFF (0x1u) +#define MHU_2_1_INTR_CHCOMB_OFF (0x2u) + +#define MHU_2_X_INTR_NR2R_MASK (0x1u << MHU_2_X_INTR_NR2R_OFF) +#define MHU_2_X_INTR_R2NR_MASK (0x1u << MHU_2_X_INTR_R2NR_OFF) +#define MHU_2_1_INTR_CHCOMB_MASK (0x1u << MHU_2_1_INTR_CHCOMB_OFF) + +enum mhu_v2_x_frame_t { + MHU_V2_X_SENDER_FRAME = 0x0u, + MHU_V2_X_RECEIVER_FRAME = 0x1u, +}; + +enum mhu_v2_x_supported_revisions { + MHU_REV_READ_FROM_HW = 0, + MHU_REV_2_0, + MHU_REV_2_1, +}; + +struct mhu_v2_x_dev_t { + uintptr_t base; + enum mhu_v2_x_frame_t frame; + uint32_t subversion; /*!< Hardware subversion: v2.X */ + bool is_initialized; /*!< Indicates if the MHU driver + * is initialized and enabled + */ +}; + +/** + * MHU v2 error enumeration types. + */ +enum mhu_v2_x_error_t { + MHU_V_2_X_ERR_NONE = 0, + MHU_V_2_X_ERR_NOT_INIT = -1, + MHU_V_2_X_ERR_ALREADY_INIT = -2, + MHU_V_2_X_ERR_UNSUPPORTED_VERSION = -3, + MHU_V_2_X_ERR_INVALID_ARG = -4, + MHU_V_2_X_ERR_GENERAL = -5 +}; + +/** + * Initializes the driver. + * + * dev MHU device struct mhu_v2_x_dev_t. + * rev MHU revision (if can't be identified from HW). + * + * Reads the MHU hardware version. + * + * Returns mhu_v2_x_error_t error code. + * + * MHU revision only has to be specified when versions can't be read + * from HW (ARCH_MAJOR_REV reg reads as 0x0). + * + * This function doesn't check if dev is NULL. + */ +enum mhu_v2_x_error_t mhu_v2_x_driver_init(struct mhu_v2_x_dev_t *dev, + enum mhu_v2_x_supported_revisions rev); + +/** + * Returns the number of channels implemented. + * + * dev MHU device struct mhu_v2_x_dev_t. + * + * This function doesn't check if dev is NULL. + */ +uint32_t mhu_v2_x_get_num_channel_implemented( + const struct mhu_v2_x_dev_t *dev); + +/** + * Sends the value over a channel. + * + * dev MHU device struct mhu_v2_x_dev_t. + * channel Channel to send the value over. + * val Value to send. + * + * Sends the value over a channel. + * + * Returns mhu_v2_x_error_t error code. + * + * This function doesn't check if dev is NULL. + * This function doesn't check if channel is implemented. + */ +enum mhu_v2_x_error_t mhu_v2_x_channel_send(const struct mhu_v2_x_dev_t *dev, + uint32_t channel, uint32_t val); + +/** + * Polls sender channel status. + * + * dev MHU device struct mhu_v2_x_dev_t. + * channel Channel to poll the status of. + * value Pointer to variable that will store the value. + * + * Polls sender channel status. + * + * Returns mhu_v2_x_error_t error code. + * + * This function doesn't check if dev is NULL. + * This function doesn't check if channel is implemented. + */ +enum mhu_v2_x_error_t mhu_v2_x_channel_poll(const struct mhu_v2_x_dev_t *dev, + uint32_t channel, uint32_t *value); + +/** + * Clears the channel after the value is send over it. + * + * dev MHU device struct mhu_v2_x_dev_t. + * channel Channel to clear. + * + * Clears the channel after the value is send over it. + * + * Returns mhu_v2_x_error_t error code.. + * + * This function doesn't check if dev is NULL. + * This function doesn't check if channel is implemented. + */ +enum mhu_v2_x_error_t mhu_v2_x_channel_clear(const struct mhu_v2_x_dev_t *dev, + uint32_t channel); + +/** + * Receives the value over a channel. + * + * dev MHU device struct mhu_v2_x_dev_t. + * channel Channel to receive the value from. + * value Pointer to variable that will store the value. + * + * Receives the value over a channel. + * + * Returns mhu_v2_x_error_t error code. + * + * This function doesn't check if dev is NULL. + * This function doesn't check if channel is implemented. + */ +enum mhu_v2_x_error_t mhu_v2_x_channel_receive( + const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t *value); + +/** + * Sets bits in the Channel Mask. + * + * dev MHU device struct mhu_v2_x_dev_t. + * channel Which channel's mask to set. + * mask Mask to be set over a receiver frame. + * + * Sets bits in the Channel Mask. + * + * Returns mhu_v2_x_error_t error code.. + * + * This function doesn't check if dev is NULL. + * This function doesn't check if channel is implemented. + */ +enum mhu_v2_x_error_t mhu_v2_x_channel_mask_set( + const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask); + +/** + * Clears bits in the Channel Mask. + * + * dev MHU device struct mhu_v2_x_dev_t. + * channel Which channel's mask to clear. + * mask Mask to be clear over a receiver frame. + * + * Clears bits in the Channel Mask. + * + * Returns mhu_v2_x_error_t error code. + * + * This function doesn't check if dev is NULL. + * This function doesn't check if channel is implemented. + */ +enum mhu_v2_x_error_t mhu_v2_x_channel_mask_clear( + const struct mhu_v2_x_dev_t *dev, uint32_t channel, uint32_t mask); + +/** + * Initiates a MHU transfer with the handshake signals. + * + * dev MHU device struct mhu_v2_x_dev_t. + * + * Initiates a MHU transfer with the handshake signals in a blocking mode. + * + * Returns mhu_v2_x_error_t error code. + * + * This function doesn't check if dev is NULL. + */ +enum mhu_v2_x_error_t mhu_v2_x_initiate_transfer( + const struct mhu_v2_x_dev_t *dev); + +/** + * Closes a MHU transfer with the handshake signals. + * + * dev MHU device struct mhu_v2_x_dev_t. + * + * Closes a MHU transfer with the handshake signals in a blocking mode. + * + * Returns mhu_v2_x_error_t error code. + * + * This function doesn't check if dev is NULL. + */ +enum mhu_v2_x_error_t mhu_v2_x_close_transfer( + const struct mhu_v2_x_dev_t *dev); + +#endif /* MHU_V2_X_H */ diff --git a/drivers/arm/mhu/mhu_wrapper_v2_x.c b/drivers/arm/mhu/mhu_wrapper_v2_x.c new file mode 100644 index 000000000..d8b7cfdab --- /dev/null +++ b/drivers/arm/mhu/mhu_wrapper_v2_x.c @@ -0,0 +1,302 @@ +/* + * 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; +} diff --git a/include/drivers/arm/mhu.h b/include/drivers/arm/mhu.h new file mode 100644 index 000000000..7745bd9d8 --- /dev/null +++ b/include/drivers/arm/mhu.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MHU_H +#define MHU_H + +#include +#include + +/** + * Generic MHU error enumeration types. + */ +enum mhu_error_t { + MHU_ERR_NONE = 0, + MHU_ERR_NOT_INIT = -1, + MHU_ERR_ALREADY_INIT = -2, + MHU_ERR_UNSUPPORTED_VERSION = -3, + MHU_ERR_UNSUPPORTED = -4, + MHU_ERR_INVALID_ARG = -5, + MHU_ERR_BUFFER_TOO_SMALL = -6, + MHU_ERR_GENERAL = -7, +}; + +/** + * Initializes sender MHU. + * + * mhu_sender_base Base address of sender MHU. + * + * Returns mhu_error_t error code. + * + * This function must be called before mhu_send_data(). + */ +enum mhu_error_t mhu_init_sender(uintptr_t mhu_sender_base); + + +/** + * Initializes receiver MHU. + * + * mhu_receiver_base Base address of receiver MHU. + * + * Returns mhu_error_t error code. + * + * This function must be called before mhu_receive_data(). + */ +enum mhu_error_t mhu_init_receiver(uintptr_t mhu_receiver_base); + +/** + * Sends data over MHU. + * + * send_buffer Pointer to buffer containing the data to be transmitted. + * size Size of the data to be transmitted in bytes. + * + * Returns mhu_error_t error code. + * + * The send_buffer must be 4-byte aligned and its length must be at least + * (4 - (size % 4)) bytes bigger than the data size to prevent buffer + * over-reading. + */ +enum mhu_error_t mhu_send_data(const uint8_t *send_buffer, size_t size); + +/** + * Receives data from MHU. + * + * receive_buffer Pointer the buffer where to store the received data. + * size As input the size of the receive_buffer, as output the + * number of bytes received. As a limitation, + * the size of the buffer must be a multiple of 4. + * + * Returns mhu_error_t error code. + * + * The receive_buffer must be 4-byte aligned and its length must be a + * multiple of 4. + */ +enum mhu_error_t mhu_receive_data(uint8_t *receive_buffer, size_t *size); + +#endif /* MHU_H */