| /**************************************************************************** |
| * arch/risc-v/src/mpfs/mpfs_ihc_sbi.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <assert.h> |
| #include <debug.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <time.h> |
| |
| #include <nuttx/arch.h> |
| |
| #include <arch/board/board.h> |
| #include "hardware/mpfs_sysreg.h" |
| #include "hardware/mpfs_ihc.h" |
| #include "hardware/mpfs_ihc_sbi.h" |
| #include "riscv_internal.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static uint32_t g_connected_hart_ints_b; |
| static uint32_t g_connected_harts_b; |
| static uint32_t g_connected_hart_ints_c; |
| static uint32_t g_connected_harts_c; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: mpfs_modifyreg32 |
| * |
| * Description: |
| * This is a copy of modifyreg32() without spinlock. That function is a |
| * real danger here as it is likely located in eNVM, thus being a real |
| * bottleneck. |
| * |
| * Input Parameters: |
| * addr - Address to perform the operation |
| * clearbits - Bits to clear |
| * setbits - Bits to set |
| * |
| * Returned Value: |
| * Remote hart id |
| * |
| ****************************************************************************/ |
| |
| void mpfs_modifyreg32(uintptr_t addr, uint32_t clearbits, uint32_t setbits) |
| { |
| uint32_t regval; |
| |
| regval = getreg32(addr); |
| regval &= ~clearbits; |
| regval |= setbits; |
| putreg32(regval, addr); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_parse_incoming_hartid |
| * |
| * Description: |
| * This function determines the remote hart id and whether the remote is |
| * acking to a message or not. |
| * |
| * Input Parameters: |
| * mhartid - My hart id. If remote owns multiple harts, this is the |
| * mhartid base on the context, not necessarily the actual |
| * mhartid. |
| * is_ack - Boolean that is set true if an ack has been found |
| * is_mp - Boolean that is set true if a msg is present also |
| * |
| * Returned Value: |
| * Remote hart id |
| * |
| ****************************************************************************/ |
| |
| static uint32_t mpfs_ihc_sbi_parse_incoming_hartid(uint32_t mhartid, |
| bool *is_ack, |
| bool *is_mp) |
| { |
| uint32_t hart_id = 0; |
| uint32_t return_hart_id = UNDEFINED_HART_ID; |
| uint32_t msg_avail = getreg32(MPFS_IHC_MSG_AVAIL(mhartid)); |
| uint32_t connected_harts = g_connected_harts_b; |
| uint32_t connected_hart_ints = g_connected_hart_ints_b; |
| |
| if (mhartid == (CONTEXTA_HARTID + 1)) |
| { |
| connected_harts = g_connected_harts_c; |
| connected_hart_ints = g_connected_hart_ints_c; |
| } |
| |
| while (hart_id < MPFS_NUM_HARTS) |
| { |
| if (connected_harts & (1 << hart_id)) |
| { |
| uint32_t test_int = (1 << ((hart_id * 2) + 1)); |
| |
| if (msg_avail & test_int) |
| { |
| if (connected_hart_ints & test_int) |
| { |
| return_hart_id = hart_id; |
| *is_ack = true; |
| |
| /* We might also have a msg present */ |
| |
| test_int = (1 << (hart_id * 2)); |
| |
| if (msg_avail & test_int) |
| { |
| *is_mp = true; |
| } |
| |
| break; |
| } |
| } |
| |
| test_int = (1 << (hart_id * 2)); |
| |
| if (msg_avail & test_int) |
| { |
| if (((connected_hart_ints & test_int) == test_int)) |
| { |
| return_hart_id = hart_id; |
| *is_ack = false; |
| *is_mp = true; |
| break; |
| } |
| } |
| } |
| |
| hart_id++; |
| } |
| |
| return return_hart_id; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_context_to_remote_hart_id |
| * |
| * Description: |
| * This function determines the remote hart id with the provided context |
| * handle. |
| * |
| * Input Parameters: |
| * channel - Enum that describes the channel used. |
| * |
| * Returned Value: |
| * Remote hart id |
| * |
| ****************************************************************************/ |
| |
| static uint32_t mpfs_ihc_sbi_context_to_remote_hart_id(ihc_channel_t channel) |
| { |
| uint32_t harts_in_context = LIBERO_SETTING_CONTEXT_B_HART_EN; |
| uint32_t hart = UNDEFINED_HART_ID; |
| uint32_t hart_idx = 0; |
| |
| hart_idx = 0; |
| |
| while (hart_idx < MPFS_NUM_HARTS) |
| { |
| if (harts_in_context & (1 << hart_idx)) |
| { |
| if (channel != IHC_CHANNEL_TO_CONTEXTC) |
| { |
| hart = hart_idx; |
| break; |
| } |
| else |
| { |
| hart = hart_idx + 1; |
| break; |
| } |
| } |
| |
| hart_idx++; |
| } |
| |
| return hart; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_context_to_local_hart_id |
| * |
| * Description: |
| * Maps the context to a local hart id. |
| * |
| * Input Parameters: |
| * channel - Enum that describes the channel used. |
| * |
| * Returned Value: |
| * Local hart id |
| * |
| ****************************************************************************/ |
| |
| static uint32_t mpfs_ihc_sbi_context_to_local_hart_id(ihc_channel_t channel) |
| { |
| uint32_t hart = UNDEFINED_HART_ID; |
| uint32_t hart_idx = 0; |
| uint32_t harts_in_context = LIBERO_SETTING_CONTEXT_A_HART_EN; |
| uint32_t hart_next = 0; |
| |
| hart_idx = 0; |
| while (hart_idx < MPFS_NUM_HARTS) |
| { |
| if (harts_in_context & (1 << hart_idx)) |
| { |
| hart = hart_idx; |
| |
| if (channel != IHC_CHANNEL_TO_CONTEXTC) |
| { |
| break; |
| } |
| |
| /* Pick the 2nd channel for IHC_CHANNEL_TO_CONTEXTC */ |
| |
| if (hart_next == 1) |
| { |
| break; |
| } |
| |
| hart_next = 1; |
| } |
| |
| hart_idx++; |
| } |
| |
| return hart; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_message_present_handler |
| * |
| * Description: |
| * This function fills up a structure that gets into the remote end, such |
| * as Linux kernel. The structure may contain data from the |
| * MPFS_IHC_MSG_IN -register, or in case of an ack, just the irq_type. |
| * |
| * Input Parameters: |
| * message - Pointer for storing data, must not be NULL |
| * mhartid - The primary hart id of a set of hartids. Not necessarily |
| * the absolute mhartid if multiple harts are incorporated |
| * within the remote (eg. Linux kernel used on 2 harts). |
| * rhartid - Remote hart id |
| * is_ack - Boolean indicating an ack message |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_ihc_sbi_message_present_handler(uint32_t *message, |
| uint32_t mhartid, |
| uint32_t rhartid, |
| bool is_ack, |
| bool is_mp) |
| { |
| struct ihc_sbi_rx_msg_s *msg; |
| uintptr_t message_ihc = (uintptr_t)MPFS_IHC_MSG_IN(mhartid, rhartid); |
| |
| msg = (struct ihc_sbi_rx_msg_s *)message; |
| |
| if (is_ack && !is_mp) |
| { |
| msg->irq_type = ACK_IRQ; |
| |
| /* msg->ihc_msg content doesn't matter here */ |
| } |
| else if (is_mp && !is_ack) |
| { |
| msg->irq_type = MP_IRQ; |
| msg->ihc_msg = *(struct mpfs_ihc_msg_s *)message_ihc; |
| } |
| else |
| { |
| msg->irq_type = ACK_IRQ | MP_IRQ; |
| msg->ihc_msg = *(struct mpfs_ihc_msg_s *)message_ihc; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_rx_message |
| * |
| * Description: |
| * This function determines the remote hart id with the provided context |
| * handle. |
| * |
| * Input Parameters: |
| * rhartid - Remote hart id |
| * mhartid - Context hart id, not necessarily the absolute mhartid but |
| * rather, the primary hartid of the set of harts. |
| * is_ack - Boolean indicating an ack message |
| * msg - For storing data, could be NULL |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_ihc_sbi_rx_message(uint32_t rhartid, uint32_t mhartid, |
| bool is_ack, bool is_mp, uint32_t *msg) |
| { |
| /* Msg must be always present */ |
| |
| if (msg == NULL) |
| { |
| return; |
| } |
| |
| if (is_ack && !is_mp) |
| { |
| if (mhartid == CONTEXTB_HARTID) |
| { |
| return; |
| } |
| else |
| { |
| /* This path is meant for the OpenSBI vendor extension only */ |
| |
| mpfs_ihc_sbi_message_present_handler(msg, mhartid, rhartid, |
| is_ack, is_mp); |
| |
| /* Clear the ack */ |
| |
| mpfs_modifyreg32(MPFS_IHC_CTRL(mhartid, rhartid), ACK_CLR, 0); |
| } |
| } |
| else if (is_mp && !is_ack) |
| { |
| /* Check if we have a message */ |
| |
| if (mhartid == CONTEXTB_HARTID) |
| { |
| return; |
| } |
| else |
| { |
| /* This path is meant for the OpenSBI vendor extension only */ |
| |
| mpfs_ihc_sbi_message_present_handler(msg, mhartid, rhartid, |
| is_ack, is_mp); |
| } |
| |
| /* Set MP to 0. Note this generates an interrupt on the other hart |
| * if it has RMPIE bit set in the control register |
| */ |
| |
| mpfs_modifyreg32(MPFS_IHC_CTRL(mhartid, rhartid), MP_MASK, ACK_INT); |
| } |
| else if (is_ack && is_mp) |
| { |
| mpfs_ihc_sbi_message_present_handler(msg, mhartid, rhartid, |
| is_ack, is_mp); |
| |
| /* Clear the ack and mp */ |
| |
| mpfs_modifyreg32(MPFS_IHC_CTRL(mhartid, rhartid), ACK_CLR | MP_MASK, |
| ACK_INT); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_message_present_indirect_isr |
| * |
| * Description: |
| * This is used by OpenSBI. This is handled as an OpenSBI extension. |
| * The S-mode kernel uses this in its extended OpenSBI vendor call. |
| * |
| * Input Parameters: |
| * channel - Enum that describes the channel used. |
| * msg - The msg pointer from sbi_trap_regs->a1 register for data |
| * exchange. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void mpfs_ihc_sbi_message_present_indirect_isr(ihc_channel_t channel, |
| uint32_t *msg) |
| { |
| bool is_ack; |
| bool is_mp = false; |
| uint32_t mhartid = mpfs_ihc_sbi_context_to_local_hart_id(channel); |
| uint32_t origin_hart = mpfs_ihc_sbi_parse_incoming_hartid(mhartid, |
| &is_ack, |
| &is_mp); |
| |
| if ((origin_hart != UNDEFINED_HART_ID) && (mhartid < MPFS_NUM_HARTS)) |
| { |
| /* Process incoming packet */ |
| |
| mpfs_ihc_sbi_rx_message(origin_hart, mhartid, is_ack, is_mp, msg); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_local_context_init |
| * |
| * Description: |
| * This initializes the local context by zeroing the CTRL register and |
| * applying proper values for the g_connected_harts_b|c and |
| * g_connected_hart_ints_b|c globals. These globals are used to map the |
| * harts and contexts properly. |
| * |
| * Input Parameters: |
| * hart_to_configure - Hart to be configured |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_ihc_sbi_local_context_init(uint32_t hart_to_configure) |
| { |
| uint32_t rhartid = 0; |
| |
| while (rhartid < MPFS_NUM_HARTS) |
| { |
| putreg32(0, MPFS_IHC_CTRL(hart_to_configure, rhartid)); |
| #ifdef CONFIG_MPFS_IHC_TWO_RPMSG_CHANNELS |
| /* This is the Context C */ |
| |
| if ((hart_to_configure + 1) < MPFS_NUM_HARTS) |
| { |
| putreg32(0, MPFS_IHC_CTRL(hart_to_configure + 1, rhartid)); |
| } |
| |
| #endif |
| rhartid++; |
| } |
| |
| g_connected_harts_b = (HSS_HART_MASK | (1 << CONTEXTB_HARTID)); |
| g_connected_hart_ints_b = IHCIA_CONTEXTA_INTS; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_local_remote_config |
| * |
| * Description: |
| * This enables the required interrupts via two registers. |
| * |
| * Input Parameters: |
| * hart_to_configure - Hart to be configured |
| * rhartid - The associated remote hart id |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_ihc_sbi_local_remote_config(uint32_t hart_to_configure, |
| uint32_t rhartid) |
| { |
| /* Set-up enables in concentrator */ |
| |
| putreg32(IHCIA_CONTEXTA_INTS, MPFS_IHC_INT_EN(hart_to_configure)); |
| |
| /* OpenSBI extension may configure 2x consecutive harts */ |
| |
| #ifdef CONFIG_MPFS_IHC_TWO_RPMSG_CHANNELS |
| if ((hart_to_configure + 1) < MPFS_NUM_HARTS) |
| { |
| putreg32(IHCIA_CONTEXTA2_INTS, |
| MPFS_IHC_INT_EN(hart_to_configure + 1)); |
| } |
| #endif |
| |
| mpfs_modifyreg32(MPFS_IHC_CTRL(hart_to_configure, rhartid), 0, MPIE_EN | |
| ACKIE_EN); |
| |
| /* OpenSBI extension may configure 2x consecutive harts */ |
| |
| #ifdef CONFIG_MPFS_IHC_TWO_RPMSG_CHANNELS |
| if ((hart_to_configure + 1) < MPFS_NUM_HARTS) |
| { |
| mpfs_modifyreg32(MPFS_IHC_CTRL(hart_to_configure + 1, rhartid + 1), 0, |
| MPIE_EN | ACKIE_EN); |
| } |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_tx_message |
| * |
| * Description: |
| * Sends and notifies the remote hart of an incoming message. |
| * |
| * Input Parameters: |
| * channel - Enum that describes the channel used. |
| * message - Pointer to the message to be sent |
| * |
| * Returned Value: |
| * OK on success, busy in case of error |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_ihc_sbi_tx_message(ihc_channel_t channel, uint32_t *message) |
| { |
| uint32_t i; |
| uint32_t mhartid = mpfs_ihc_sbi_context_to_local_hart_id(channel); |
| uint32_t rhartid = mpfs_ihc_sbi_context_to_remote_hart_id(channel); |
| uint32_t message_size = getreg32(MPFS_IHC_MSG_SIZE(mhartid, rhartid)); |
| uint32_t ctrl_reg; |
| uint32_t retries = 10000; |
| |
| if (message_size > IHC_MAX_MESSAGE_SIZE) |
| { |
| return -EINVAL; |
| } |
| else if (rhartid == UNDEFINED_HART_ID) |
| { |
| return -EINVAL; |
| } |
| else if (mhartid >= MPFS_NUM_HARTS) |
| { |
| return -EINVAL; |
| } |
| |
| /* Check if the system is busy. All we can try is wait. */ |
| |
| do |
| { |
| ctrl_reg = getreg32(MPFS_IHC_CTRL(mhartid, rhartid)); |
| } |
| while ((ctrl_reg & (RMP_MESSAGE_PRESENT | ACK_INT)) && --retries); |
| |
| /* Return if RMP bit 1 indicating busy */ |
| |
| if (RMP_MESSAGE_PRESENT == (ctrl_reg & RMP_MASK)) |
| { |
| return -EBUSY; |
| } |
| else if (ACK_INT == (ctrl_reg & ACK_INT_MASK)) |
| { |
| return -EBUSY; |
| } |
| else |
| { |
| /* Fill the buffer */ |
| |
| for (i = 0; i < message_size; i++) |
| { |
| putreg32(message[i], MPFS_IHC_MSG_OUT(mhartid, rhartid) + i * 4); |
| } |
| |
| /* Set the MP bit. This will notify other of incoming hart message */ |
| |
| mpfs_modifyreg32(MPFS_IHC_CTRL(mhartid, rhartid), 0, |
| RMP_MESSAGE_PRESENT); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: mpfs_ihc_sbi_ecall_handler |
| * |
| * Description: |
| * This is sbi_platform_operations / vendor_ext_provider ecall handler. |
| * Related Linux ecalls end up here. |
| * |
| * Input Parameters: |
| * funcid - SBI_EXT_IHC_CTX_INIT, SBI_EXT_IHC_SEND or |
| * SBI_EXT_IHC_RECEIVE. Others are invalid. |
| * remote_channel - The remote we're communicating with |
| * message_ptr - Local storage for data exchange |
| * |
| * Returned Value: |
| * OK on success, a negated error code otherwise |
| * |
| ****************************************************************************/ |
| |
| int mpfs_ihc_sbi_ecall_handler(unsigned long funcid, uint32_t remote_channel, |
| uint32_t *message_ptr) |
| { |
| uint32_t mhartid; |
| uint32_t rhartid; |
| int result = OK; |
| |
| /* Check the channel is bound to a valid context. Context C indicates |
| * 2x rpmsg channels. |
| */ |
| |
| #ifdef CONFIG_MPFS_IHC_TWO_RPMSG_CHANNELS |
| if ((remote_channel < IHC_CHANNEL_TO_CONTEXTA) || |
| (remote_channel > IHC_CHANNEL_TO_CONTEXTC)) |
| { |
| return -EINVAL; |
| } |
| #else |
| if ((remote_channel < IHC_CHANNEL_TO_CONTEXTA) || |
| (remote_channel > IHC_CHANNEL_TO_CONTEXTB)) |
| { |
| return -EINVAL; |
| } |
| #endif |
| |
| switch (funcid) |
| { |
| case SBI_EXT_IHC_CTX_INIT: |
| |
| /* mhartid = Linux hart id, rhartid = NuttX hart id */ |
| |
| mhartid = mpfs_ihc_sbi_context_to_local_hart_id(remote_channel); |
| |
| if (mhartid >= MPFS_NUM_HARTS) |
| { |
| return -EINVAL; |
| } |
| |
| rhartid = mpfs_ihc_sbi_context_to_remote_hart_id(remote_channel); |
| |
| if (rhartid == UNDEFINED_HART_ID) |
| { |
| return -EINVAL; |
| } |
| |
| if (remote_channel == IHC_CHANNEL_TO_CONTEXTB) |
| { |
| mpfs_ihc_sbi_local_context_init(mhartid); |
| } |
| |
| /* If we have 2x rpmsg channels, the 2nd channel needs its ints */ |
| |
| #ifdef CONFIG_MPFS_IHC_TWO_RPMSG_CHANNELS |
| if (remote_channel == IHC_CHANNEL_TO_CONTEXTC) |
| { |
| g_connected_hart_ints_c = IHCIA_CONTEXTA2_INTS; |
| g_connected_harts_c = (HSS_HART_MASK | (1 << CONTEXTC_HARTID)); |
| } |
| #endif |
| |
| /* Remote means the NuttX here */ |
| |
| if (remote_channel == IHC_CHANNEL_TO_CONTEXTB) |
| { |
| mpfs_ihc_sbi_local_remote_config(mhartid, rhartid); |
| } |
| break; |
| |
| case SBI_EXT_IHC_SEND: |
| result = mpfs_ihc_sbi_tx_message(remote_channel, message_ptr); |
| break; |
| |
| case SBI_EXT_IHC_RECEIVE: |
| mpfs_ihc_sbi_message_present_indirect_isr(remote_channel, |
| message_ptr); |
| break; |
| |
| default: |
| result = -ENOTSUP; |
| } |
| |
| return result; |
| } |