| /* |
| * SPDX-License-Identifier: BSD-2-Clause |
| * |
| * Copyright (c) 2024 Ventana Micro Systems Inc. |
| * |
| * Authors: |
| * Rahul Pathak <rpathak@ventanamicro.com> |
| */ |
| |
| #include <sbi/riscv_asm.h> |
| #include <sbi/sbi_domain.h> |
| #include <sbi/sbi_error.h> |
| #include <sbi/sbi_hart.h> |
| #include <sbi/sbi_hart_protection.h> |
| #include <sbi/sbi_heap.h> |
| #include <sbi/sbi_platform.h> |
| #include <sbi/sbi_mpxy.h> |
| #include <sbi/sbi_scratch.h> |
| #include <sbi/sbi_string.h> |
| #include <sbi/sbi_bitops.h> |
| #include <sbi/sbi_console.h> |
| #include <sbi/sbi_byteorder.h> |
| |
| /** Shared memory size across all harts */ |
| static unsigned long mpxy_shmem_size = PAGE_SIZE; |
| |
| /** List of MPXY proxy channels */ |
| static SBI_LIST_HEAD(mpxy_channel_list); |
| |
| /** Invalid Physical Address(all bits 1) */ |
| #define INVALID_ADDR (-1UL) |
| |
| /** MPXY Attribute size in bytes */ |
| #define ATTR_SIZE (4) |
| |
| /** Channel Capability - MSI */ |
| #define CAP_MSI_POS 0 |
| #define CAP_MSI_MASK (1U << CAP_MSI_POS) |
| |
| /** Channel Capability - SSE */ |
| #define CAP_SSE_POS 1 |
| #define CAP_SSE_MASK (1U << CAP_SSE_POS) |
| |
| /** Channel Capability - Events State */ |
| #define CAP_EVENTSSTATE_POS 2 |
| #define CAP_EVENTSSTATE_MASK (1U << CAP_EVENTSSTATE_POS) |
| |
| /** Channel Capability - Send Message With Response function support */ |
| #define CAP_SEND_MSG_WITH_RESP_POS 3 |
| #define CAP_SEND_MSG_WITH_RESP_MASK (1U << CAP_SEND_MSG_WITH_RESP_POS) |
| |
| /** Channel Capability - Send Message Without Response function support */ |
| #define CAP_SEND_MSG_WITHOUT_RESP_POS 4 |
| #define CAP_SEND_MSG_WITHOUT_RESP_MASK (1U << CAP_SEND_MSG_WITHOUT_RESP_POS) |
| |
| /** Channel Capability - Get Notification function support */ |
| #define CAP_GET_NOTIFICATIONS_POS 5 |
| #define CAP_GET_NOTIFICATIONS_MASK (1U << CAP_GET_NOTIFICATIONS_POS) |
| |
| /** Helpers to enable/disable channel capability bits |
| * _c: capability variable |
| * _m: capability mask |
| */ |
| #define CAP_ENABLE(_c, _m) INSERT_FIELD(_c, _m, 1) |
| #define CAP_DISABLE(_c, _m) INSERT_FIELD(_c, _m, 0) |
| #define CAP_GET(_c, _m) EXTRACT_FIELD(_c, _m) |
| |
| #define SHMEM_PHYS_ADDR(_hi, _lo) (_lo) |
| |
| /** Per hart shared memory */ |
| struct mpxy_shmem { |
| unsigned long shmem_addr_lo; |
| unsigned long shmem_addr_hi; |
| }; |
| |
| struct mpxy_state { |
| /* MSI support in MPXY */ |
| bool msi_avail; |
| /* SSE support in MPXY */ |
| bool sse_avail; |
| /* MPXY Shared memory details */ |
| struct mpxy_shmem shmem; |
| }; |
| |
| static struct mpxy_state *sbi_domain_get_mpxy_state(struct sbi_domain *dom, |
| u32 hartindex); |
| |
| /** Macro to obtain the current hart's MPXY state pointer in current domain */ |
| #define sbi_domain_mpxy_state_thishart_ptr() \ |
| sbi_domain_get_mpxy_state(sbi_domain_thishart_ptr(), \ |
| current_hartindex()) |
| |
| /** Disable hart shared memory */ |
| static inline void sbi_mpxy_shmem_disable(struct mpxy_state *ms) |
| { |
| ms->shmem.shmem_addr_lo = INVALID_ADDR; |
| ms->shmem.shmem_addr_hi = INVALID_ADDR; |
| } |
| |
| /** Check if shared memory is already setup on hart */ |
| static inline bool mpxy_shmem_enabled(struct mpxy_state *ms) |
| { |
| return (ms->shmem.shmem_addr_lo == INVALID_ADDR |
| && ms->shmem.shmem_addr_hi == INVALID_ADDR) ? |
| false : true; |
| } |
| |
| /** Get hart shared memory base address */ |
| static inline void *hart_shmem_base(struct mpxy_state *ms) |
| { |
| return (void *)(unsigned long)SHMEM_PHYS_ADDR(ms->shmem.shmem_addr_hi, |
| ms->shmem.shmem_addr_lo); |
| } |
| |
| /** Make sure all attributes are packed for direct memcpy in ATTR_READ */ |
| #define assert_field_offset(field, attr_offset) \ |
| _Static_assert( \ |
| ((offsetof(struct sbi_mpxy_channel_attrs, field)) / \ |
| sizeof(u32)) == attr_offset, \ |
| "field " #field \ |
| " from struct sbi_mpxy_channel_attrs invalid offset, expected " #attr_offset) |
| |
| assert_field_offset(msg_proto_id, SBI_MPXY_ATTR_MSG_PROT_ID); |
| assert_field_offset(msg_proto_version, SBI_MPXY_ATTR_MSG_PROT_VER); |
| assert_field_offset(msg_data_maxlen, SBI_MPXY_ATTR_MSG_MAX_LEN); |
| assert_field_offset(msg_send_timeout, SBI_MPXY_ATTR_MSG_SEND_TIMEOUT); |
| assert_field_offset(msg_completion_timeout, SBI_MPXY_ATTR_MSG_COMPLETION_TIMEOUT); |
| assert_field_offset(capability, SBI_MPXY_ATTR_CHANNEL_CAPABILITY); |
| assert_field_offset(sse_event_id, SBI_MPXY_ATTR_SSE_EVENT_ID); |
| assert_field_offset(msi_control, SBI_MPXY_ATTR_MSI_CONTROL); |
| assert_field_offset(msi_info.msi_addr_lo, SBI_MPXY_ATTR_MSI_ADDR_LO); |
| assert_field_offset(msi_info.msi_addr_hi, SBI_MPXY_ATTR_MSI_ADDR_HI); |
| assert_field_offset(msi_info.msi_data, SBI_MPXY_ATTR_MSI_DATA); |
| assert_field_offset(eventsstate_ctrl, SBI_MPXY_ATTR_EVENTS_STATE_CONTROL); |
| |
| /** |
| * Check if the attribute is a standard attribute or |
| * a message protocol specific attribute |
| * attr_id[31] = 0 for standard |
| * attr_id[31] = 1 for message protocol specific |
| */ |
| static inline bool mpxy_is_std_attr(u32 attr_id) |
| { |
| return (attr_id >> 31) ? false : true; |
| } |
| |
| /** Find channel_id in registered channels list */ |
| static struct sbi_mpxy_channel *mpxy_find_channel(u32 channel_id) |
| { |
| struct sbi_mpxy_channel *channel; |
| |
| sbi_list_for_each_entry(channel, &mpxy_channel_list, head) |
| if (channel->channel_id == channel_id) |
| return channel; |
| |
| return NULL; |
| } |
| |
| /** Copy attributes word size */ |
| static void mpxy_copy_std_attrs(u32 *outmem, u32 *inmem, u32 count) |
| { |
| int idx; |
| for (idx = 0; idx < count; idx++) |
| outmem[idx] = cpu_to_le32(inmem[idx]); |
| } |
| |
| /** Check if any channel is registered with mpxy framework */ |
| bool sbi_mpxy_channel_available(void) |
| { |
| return sbi_list_empty(&mpxy_channel_list) ? false : true; |
| } |
| |
| static void mpxy_std_attrs_init(struct sbi_mpxy_channel *channel) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| u32 capability = 0; |
| |
| /* Reset values */ |
| channel->attrs.msi_control = 0; |
| channel->attrs.msi_info.msi_data = 0; |
| channel->attrs.msi_info.msi_addr_lo = 0; |
| channel->attrs.msi_info.msi_addr_hi = 0; |
| channel->attrs.capability = 0; |
| channel->attrs.eventsstate_ctrl = 0; |
| |
| if (channel->send_message_with_response) |
| capability = CAP_ENABLE(capability, CAP_SEND_MSG_WITH_RESP_MASK); |
| |
| if (channel->send_message_without_response) |
| capability = CAP_ENABLE(capability, CAP_SEND_MSG_WITHOUT_RESP_MASK); |
| |
| if (channel->get_notification_events) { |
| capability = CAP_ENABLE(capability, CAP_GET_NOTIFICATIONS_MASK); |
| /** |
| * Check if MSI or SSE available for notification interrrupt. |
| * Priority given to MSI if both MSI and SSE are avaialble. |
| */ |
| if (ms->msi_avail) |
| capability = CAP_ENABLE(capability, CAP_MSI_MASK); |
| else if (ms->sse_avail) { |
| capability = CAP_ENABLE(capability, CAP_SSE_MASK); |
| /* TODO: Assign SSE EVENT_ID for the channel */ |
| } |
| |
| /** |
| * switch_eventstate callback support means support for events |
| * state reporting supoprt. Enable events state reporting in |
| * channel capability. |
| */ |
| if (channel->switch_eventsstate) |
| capability = CAP_ENABLE(capability, CAP_EVENTSSTATE_MASK); |
| } |
| |
| channel->attrs.capability = capability; |
| } |
| |
| /** |
| * Register a channel with MPXY framework. |
| * Called by message protocol drivers |
| */ |
| int sbi_mpxy_register_channel(struct sbi_mpxy_channel *channel) |
| { |
| if (!channel) |
| return SBI_EINVAL; |
| |
| if (mpxy_find_channel(channel->channel_id)) |
| return SBI_EALREADY; |
| |
| /* Initialize channel specific attributes */ |
| mpxy_std_attrs_init(channel); |
| |
| /* Update shared memory size if required */ |
| if (mpxy_shmem_size < channel->attrs.msg_data_maxlen) { |
| mpxy_shmem_size = channel->attrs.msg_data_maxlen; |
| mpxy_shmem_size = (mpxy_shmem_size + (PAGE_SIZE - 1)) / PAGE_SIZE; |
| } |
| |
| sbi_list_add_tail(&channel->head, &mpxy_channel_list); |
| |
| return SBI_OK; |
| } |
| |
| /** Setup per domain MPXY state data */ |
| static int domain_mpxy_state_data_setup(struct sbi_domain *dom, |
| struct sbi_domain_data *data, |
| void *data_ptr) |
| { |
| struct mpxy_state **dom_hartindex_to_mpxy_state_table = data_ptr; |
| struct mpxy_state *ms; |
| u32 i; |
| |
| sbi_hartmask_for_each_hartindex(i, dom->possible_harts) { |
| ms = sbi_zalloc(sizeof(*ms)); |
| if (!ms) |
| return SBI_ENOMEM; |
| |
| /* |
| * TODO: Proper support for checking msi support from |
| * platform. Currently disable msi and sse and use |
| * polling |
| */ |
| ms->msi_avail = false; |
| ms->sse_avail = false; |
| |
| sbi_mpxy_shmem_disable(ms); |
| |
| dom_hartindex_to_mpxy_state_table[i] = ms; |
| } |
| |
| return 0; |
| } |
| |
| /** Cleanup per domain MPXY state data */ |
| static void domain_mpxy_state_data_cleanup(struct sbi_domain *dom, |
| struct sbi_domain_data *data, |
| void *data_ptr) |
| { |
| struct mpxy_state **dom_hartindex_to_mpxy_state_table = data_ptr; |
| u32 i; |
| |
| sbi_hartmask_for_each_hartindex(i, dom->possible_harts) |
| sbi_free(dom_hartindex_to_mpxy_state_table[i]); |
| } |
| |
| static struct sbi_domain_data dmspriv = { |
| .data_setup = domain_mpxy_state_data_setup, |
| .data_cleanup = domain_mpxy_state_data_cleanup, |
| }; |
| |
| /** |
| * Get per-domain MPXY state pointer for a given domain and HART index |
| * @param dom pointer to domain |
| * @param hartindex the HART index |
| * |
| * @return per-domain MPXY state pointer for given HART index |
| */ |
| static struct mpxy_state *sbi_domain_get_mpxy_state(struct sbi_domain *dom, |
| u32 hartindex) |
| { |
| struct mpxy_state **dom_hartindex_to_mpxy_state_table; |
| |
| dom_hartindex_to_mpxy_state_table = sbi_domain_data_ptr(dom, &dmspriv); |
| if (!dom_hartindex_to_mpxy_state_table || |
| !sbi_hartindex_valid(hartindex)) |
| return NULL; |
| |
| return dom_hartindex_to_mpxy_state_table[hartindex]; |
| } |
| |
| int sbi_mpxy_init(struct sbi_scratch *scratch) |
| { |
| int ret; |
| |
| /** |
| * Allocate per-domain and per-hart MPXY state data. |
| * The data type is "struct mpxy_state **" whose memory space will be |
| * dynamically allocated by domain_setup_data_one() and |
| * domain_mpxy_state_data_setup(). Calculate needed size of memory space |
| * here. |
| */ |
| dmspriv.data_size = sizeof(struct mpxy_state *) * sbi_hart_count(); |
| ret = sbi_domain_register_data(&dmspriv); |
| if (ret) |
| return ret; |
| |
| return sbi_platform_mpxy_init(sbi_platform_ptr(scratch)); |
| } |
| |
| unsigned long sbi_mpxy_get_shmem_size(void) |
| { |
| return mpxy_shmem_size; |
| } |
| |
| int sbi_mpxy_set_shmem(unsigned long shmem_phys_lo, |
| unsigned long shmem_phys_hi, |
| unsigned long flags) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| unsigned long *ret_buf; |
| |
| /** Disable shared memory if both hi and lo have all bit 1s */ |
| if (shmem_phys_lo == INVALID_ADDR && |
| shmem_phys_hi == INVALID_ADDR) { |
| sbi_mpxy_shmem_disable(ms); |
| return SBI_SUCCESS; |
| } |
| |
| if (flags >= SBI_EXT_MPXY_SHMEM_FLAG_MAX_IDX) |
| return SBI_ERR_INVALID_PARAM; |
| |
| /** Check shared memory size and address aligned to 4K Page */ |
| if (shmem_phys_lo & ~PAGE_MASK) |
| return SBI_ERR_INVALID_PARAM; |
| |
| /* |
| * On RV32, the M-mode can only access the first 4GB of |
| * the physical address space because M-mode does not have |
| * MMU to access full 34-bit physical address space. |
| * So fail if the upper 32 bits of the physical address |
| * is non-zero on RV32. |
| * |
| * On RV64, kernel sets upper 64bit address part to zero. |
| * So fail if the upper 64bit of the physical address |
| * is non-zero on RV64. |
| */ |
| if (shmem_phys_hi) |
| return SBI_ERR_INVALID_ADDRESS; |
| |
| if (!sbi_domain_check_addr_range(sbi_domain_thishart_ptr(), |
| SHMEM_PHYS_ADDR(shmem_phys_hi, shmem_phys_lo), |
| mpxy_shmem_size, PRV_S, |
| SBI_DOMAIN_READ | SBI_DOMAIN_WRITE)) |
| return SBI_ERR_INVALID_ADDRESS; |
| |
| /** Save the current shmem details in new shmem region */ |
| if (flags == SBI_EXT_MPXY_SHMEM_FLAG_OVERWRITE_RETURN) { |
| ret_buf = (unsigned long *)(ulong)SHMEM_PHYS_ADDR(shmem_phys_hi, |
| shmem_phys_lo); |
| sbi_hart_protection_map_range((unsigned long)ret_buf, mpxy_shmem_size); |
| ret_buf[0] = cpu_to_lle(ms->shmem.shmem_addr_lo); |
| ret_buf[1] = cpu_to_lle(ms->shmem.shmem_addr_hi); |
| sbi_hart_protection_unmap_range((unsigned long)ret_buf, mpxy_shmem_size); |
| } |
| |
| /** Setup the new shared memory */ |
| ms->shmem.shmem_addr_lo = shmem_phys_lo; |
| ms->shmem.shmem_addr_hi = shmem_phys_hi; |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_mpxy_get_channel_ids(u32 start_index) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| u32 remaining, returned, max_channelids; |
| u32 node_index = 0, node_ret = 0; |
| struct sbi_mpxy_channel *channel; |
| u32 channels_count = 0; |
| u32 *shmem_base; |
| |
| if (!mpxy_shmem_enabled(ms)) |
| return SBI_ERR_NO_SHMEM; |
| |
| sbi_list_for_each_entry(channel, &mpxy_channel_list, head) |
| channels_count += 1; |
| |
| if (start_index > channels_count) |
| return SBI_ERR_INVALID_PARAM; |
| |
| shmem_base = hart_shmem_base(ms); |
| sbi_hart_protection_map_range((unsigned long)hart_shmem_base(ms), mpxy_shmem_size); |
| |
| /** number of channel ids which can be stored in shmem adjusting |
| * for remaining and returned fields */ |
| max_channelids = (mpxy_shmem_size / sizeof(u32)) - 2; |
| /* total remaining from the start index */ |
| remaining = channels_count - start_index; |
| /* how many can be returned */ |
| returned = (remaining > max_channelids)? max_channelids : remaining; |
| |
| // Iterate over the list of channels to get the channel ids. |
| sbi_list_for_each_entry(channel, &mpxy_channel_list, head) { |
| if (node_index >= start_index && |
| node_index < (start_index + returned)) { |
| shmem_base[2 + node_ret] = cpu_to_le32(channel->channel_id); |
| node_ret += 1; |
| } |
| |
| node_index += 1; |
| } |
| |
| /* final remaininig channel ids */ |
| remaining = channels_count - (start_index + returned); |
| |
| shmem_base[0] = cpu_to_le32(remaining); |
| shmem_base[1] = cpu_to_le32(returned); |
| |
| sbi_hart_protection_unmap_range((unsigned long)hart_shmem_base(ms), mpxy_shmem_size); |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_mpxy_read_attrs(u32 channel_id, u32 base_attr_id, u32 attr_count) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| int ret = SBI_SUCCESS; |
| u32 *attr_ptr, end_id; |
| void *shmem_base; |
| |
| if (!mpxy_shmem_enabled(ms)) |
| return SBI_ERR_NO_SHMEM; |
| |
| struct sbi_mpxy_channel *channel = mpxy_find_channel(channel_id); |
| if (!channel) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| /* base attribute id is not a defined std attribute or reserved */ |
| if (base_attr_id >= SBI_MPXY_ATTR_STD_ATTR_MAX_IDX && |
| base_attr_id < SBI_MPXY_ATTR_MSGPROTO_ATTR_START) |
| return SBI_ERR_INVALID_PARAM; |
| |
| /* Sanity check for base_attr_id and attr_count */ |
| if (!attr_count || (attr_count > (mpxy_shmem_size / ATTR_SIZE))) |
| return SBI_ERR_INVALID_PARAM; |
| |
| shmem_base = hart_shmem_base(ms); |
| end_id = base_attr_id + attr_count - 1; |
| |
| sbi_hart_protection_map_range((unsigned long)hart_shmem_base(ms), mpxy_shmem_size); |
| |
| /* Standard attributes range check */ |
| if (mpxy_is_std_attr(base_attr_id)) { |
| if (end_id >= SBI_MPXY_ATTR_STD_ATTR_MAX_IDX) { |
| ret = SBI_EBAD_RANGE; |
| goto out; |
| } |
| |
| attr_ptr = (u32 *)&channel->attrs; |
| mpxy_copy_std_attrs((u32 *)shmem_base, &attr_ptr[base_attr_id], |
| attr_count); |
| } else { |
| /** |
| * Even if the message protocol driver does not provide |
| * read attribute callback, return bad range error instead |
| * of not supported to let client distinguish it from channel |
| * id not supported. |
| * Check the complate range supported for message protocol |
| * attributes. Actual supported attributes will be checked |
| * by the message protocol driver. |
| */ |
| if (!channel->read_attributes || |
| end_id > SBI_MPXY_ATTR_MSGPROTO_ATTR_END) { |
| ret = SBI_ERR_BAD_RANGE; |
| goto out; |
| } |
| |
| /** |
| * Function expected to return the SBI supported errors |
| * At this point both base attribute id and only the mpxy |
| * supported range been verified. Platform callback must |
| * check if the range requested is supported by message |
| * protocol driver */ |
| ret = channel->read_attributes(channel, |
| (u32 *)shmem_base, |
| base_attr_id, attr_count); |
| } |
| out: |
| sbi_hart_protection_unmap_range((unsigned long)hart_shmem_base(ms), mpxy_shmem_size); |
| return ret; |
| } |
| |
| /** |
| * Verify the channel standard attribute wrt to write permission |
| * and the value to be set if valid or not. |
| * Only attributes needs to be checked which are defined Read/Write |
| * permission. Other with Readonly permission will result in error. |
| * |
| * Attributes values to be written must also be checked because |
| * before writing a range of attributes, we need to make sure that |
| * either complete range of attributes is written successfully or not |
| * at all. |
| */ |
| static int mpxy_check_write_std_attr(struct sbi_mpxy_channel *channel, |
| u32 attr_id, u32 attr_val) |
| { |
| struct sbi_mpxy_channel_attrs *attrs = &channel->attrs; |
| int ret = SBI_SUCCESS; |
| |
| switch(attr_id) { |
| case SBI_MPXY_ATTR_MSI_CONTROL: |
| if (attr_val > 1) |
| ret = SBI_ERR_INVALID_PARAM; |
| if (attr_val == 1 && |
| (attrs->msi_info.msi_addr_lo == INVALID_ADDR) && |
| (attrs->msi_info.msi_addr_hi == INVALID_ADDR)) |
| ret = SBI_ERR_DENIED; |
| break; |
| case SBI_MPXY_ATTR_MSI_ADDR_LO: |
| case SBI_MPXY_ATTR_MSI_ADDR_HI: |
| case SBI_MPXY_ATTR_MSI_DATA: |
| ret = SBI_SUCCESS; |
| break; |
| case SBI_MPXY_ATTR_EVENTS_STATE_CONTROL: |
| if (attr_val > 1) |
| ret = SBI_ERR_INVALID_PARAM; |
| break; |
| default: |
| /** All RO access attributes falls under default */ |
| ret = SBI_ERR_BAD_RANGE; |
| }; |
| |
| return ret; |
| } |
| |
| /** |
| * Write the attribute value |
| */ |
| static void mpxy_write_std_attr(struct sbi_mpxy_channel *channel, u32 attr_id, |
| u32 attr_val) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| struct sbi_mpxy_channel_attrs *attrs = &channel->attrs; |
| |
| switch(attr_id) { |
| case SBI_MPXY_ATTR_MSI_CONTROL: |
| if (ms->msi_avail && attr_val <= 1) |
| attrs->msi_control = attr_val; |
| break; |
| case SBI_MPXY_ATTR_MSI_ADDR_LO: |
| if (ms->msi_avail) |
| attrs->msi_info.msi_addr_lo = attr_val; |
| break; |
| case SBI_MPXY_ATTR_MSI_ADDR_HI: |
| if (ms->msi_avail) |
| attrs->msi_info.msi_addr_hi = attr_val; |
| break; |
| case SBI_MPXY_ATTR_MSI_DATA: |
| if (ms->msi_avail) |
| attrs->msi_info.msi_data = attr_val; |
| break; |
| case SBI_MPXY_ATTR_EVENTS_STATE_CONTROL: |
| if (channel->switch_eventsstate && attr_val <= 1) { |
| attrs->eventsstate_ctrl = attr_val; |
| /* message protocol callback to enable/disable |
| * events state reporting. */ |
| channel->switch_eventsstate(attr_val); |
| } |
| |
| break; |
| }; |
| } |
| |
| int sbi_mpxy_write_attrs(u32 channel_id, u32 base_attr_id, u32 attr_count) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| u32 *mem_ptr, attr_id, end_id, attr_val; |
| struct sbi_mpxy_channel *channel; |
| int ret, mem_idx; |
| void *shmem_base; |
| |
| if (!mpxy_shmem_enabled(ms)) |
| return SBI_ERR_NO_SHMEM; |
| |
| channel = mpxy_find_channel(channel_id); |
| if (!channel) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| /* base attribute id is not a defined std attribute or reserved */ |
| if (base_attr_id >= SBI_MPXY_ATTR_STD_ATTR_MAX_IDX && |
| base_attr_id < SBI_MPXY_ATTR_MSGPROTO_ATTR_START) |
| return SBI_ERR_INVALID_PARAM; |
| |
| /* Sanity check for base_attr_id and attr_count */ |
| if (!attr_count || (attr_count > (mpxy_shmem_size / ATTR_SIZE))) |
| return SBI_ERR_INVALID_PARAM; |
| |
| shmem_base = hart_shmem_base(ms); |
| end_id = base_attr_id + attr_count - 1; |
| |
| sbi_hart_protection_map_range((unsigned long)shmem_base, mpxy_shmem_size); |
| |
| mem_ptr = (u32 *)shmem_base; |
| |
| if (mpxy_is_std_attr(base_attr_id)) { |
| if (end_id >= SBI_MPXY_ATTR_STD_ATTR_MAX_IDX) { |
| ret = SBI_ERR_BAD_RANGE; |
| goto out; |
| } |
| |
| /** Verify the attribute ids range and values */ |
| mem_idx = 0; |
| for (attr_id = base_attr_id; attr_id <= end_id; attr_id++) { |
| attr_val = le32_to_cpu(mem_ptr[mem_idx++]); |
| ret = mpxy_check_write_std_attr(channel, |
| attr_id, attr_val); |
| if (ret) |
| goto out; |
| } |
| |
| /* Write the attribute ids values */ |
| mem_idx = 0; |
| for (attr_id = base_attr_id; attr_id <= end_id; attr_id++) { |
| attr_val = le32_to_cpu(mem_ptr[mem_idx++]); |
| mpxy_write_std_attr(channel, attr_id, attr_val); |
| } |
| } else {/** |
| * Message protocol specific attributes: |
| * If attributes belong to message protocol, they |
| * are simply passed to the message protocol driver |
| * callback after checking the valid range. |
| * Attributes contiguous range & permission & other checks |
| * are done by the mpxy and message protocol glue layer. |
| */ |
| /** |
| * Even if the message protocol driver does not provide |
| * write attribute callback, return bad range error instead |
| * of not supported to let client distinguish it from channel |
| * id not supported. |
| */ |
| if (!channel->write_attributes || |
| end_id > SBI_MPXY_ATTR_MSGPROTO_ATTR_END) { |
| ret = SBI_ERR_BAD_RANGE; |
| goto out; |
| } |
| |
| /** |
| * Function expected to return the SBI supported errors |
| * At this point both base attribute id and only the mpxy |
| * supported range been verified. Platform callback must |
| * check if the range requested is supported by message |
| * protocol driver */ |
| ret = channel->write_attributes(channel, |
| (u32 *)shmem_base, |
| base_attr_id, attr_count); |
| } |
| out: |
| sbi_hart_protection_unmap_range((unsigned long)shmem_base, mpxy_shmem_size); |
| return ret; |
| } |
| |
| int sbi_mpxy_send_message(u32 channel_id, u8 msg_id, |
| unsigned long msg_data_len, |
| unsigned long *resp_data_len) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| struct sbi_mpxy_channel *channel; |
| void *shmem_base, *resp_buf; |
| u32 resp_bufsize; |
| int ret; |
| |
| if (!mpxy_shmem_enabled(ms)) |
| return SBI_ERR_NO_SHMEM; |
| |
| channel = mpxy_find_channel(channel_id); |
| if (!channel) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| if (resp_data_len && !channel->send_message_with_response) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| if (!resp_data_len && !channel->send_message_without_response) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| if (msg_data_len > mpxy_shmem_size || |
| msg_data_len > channel->attrs.msg_data_maxlen) |
| return SBI_ERR_INVALID_PARAM; |
| |
| shmem_base = hart_shmem_base(ms); |
| sbi_hart_protection_map_range((unsigned long)shmem_base, mpxy_shmem_size); |
| |
| if (resp_data_len) { |
| resp_buf = shmem_base; |
| resp_bufsize = mpxy_shmem_size; |
| ret = channel->send_message_with_response(channel, msg_id, |
| shmem_base, |
| msg_data_len, |
| resp_buf, |
| resp_bufsize, |
| resp_data_len); |
| } else { |
| ret = channel->send_message_without_response(channel, msg_id, |
| shmem_base, |
| msg_data_len); |
| } |
| |
| sbi_hart_protection_unmap_range((unsigned long)shmem_base, mpxy_shmem_size); |
| |
| if (ret == SBI_ERR_TIMEOUT || ret == SBI_ERR_IO) |
| return ret; |
| else if (ret) |
| return SBI_ERR_FAILED; |
| |
| if (resp_data_len && |
| (*resp_data_len > mpxy_shmem_size || |
| *resp_data_len > channel->attrs.msg_data_maxlen)) |
| return SBI_ERR_FAILED; |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_mpxy_get_notification_events(u32 channel_id, unsigned long *events_len) |
| { |
| struct mpxy_state *ms = sbi_domain_mpxy_state_thishart_ptr(); |
| struct sbi_mpxy_channel *channel; |
| void *eventsbuf, *shmem_base; |
| int ret; |
| |
| if (!mpxy_shmem_enabled(ms)) |
| return SBI_ERR_NO_SHMEM; |
| |
| channel = mpxy_find_channel(channel_id); |
| if (!channel || !channel->get_notification_events) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| shmem_base = hart_shmem_base(ms); |
| sbi_hart_protection_map_range((unsigned long)shmem_base, mpxy_shmem_size); |
| eventsbuf = shmem_base; |
| ret = channel->get_notification_events(channel, eventsbuf, |
| mpxy_shmem_size, |
| events_len); |
| sbi_hart_protection_unmap_range((unsigned long)shmem_base, mpxy_shmem_size); |
| |
| if (ret) |
| return ret; |
| |
| if (*events_len > (mpxy_shmem_size - 16)) |
| return SBI_ERR_FAILED; |
| |
| return SBI_SUCCESS; |
| } |