[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [RFC v2 5/8] xen/arm: introduce SCMI-SMC mediator driver
This is the implementation of SCI interface, called SCMI-SMC driver, which works as the mediator between XEN Domains and Firmware (SCP, ATF etc). This allows devices from the Domains to work with clocks, resets and power-domains without access to CPG. Originally, cpg should be passed to the domain so it can work with power-domains/clocks/resets etc. Considering that cpg can't be split between the Domains, we get the limitation that the devices, which are using power-domains/clocks/resets etc, couldn't be split between the domains. The solution is to move the power-domain/clock/resets etc to the Firmware (such as SCP firmware or ATF) and provide interface for the Domains. XEN should have an entity, caled SCI-Mediator, which is responsible for messages redirection between Domains and Firmware and for permission handling. The following features are implemented: - request SCMI channels from ATF and pass channels to Domains; - set device permissions for Domains based on the Domain partial device-tree. Devices with permissions are able to work with clocks, resets and power-domains via SCMI; - redirect scmi messages from Domains to ATF. Signed-off-by: Oleksii Moisieiev <oleksii_moisieiev@xxxxxxxx> --- xen/arch/arm/Kconfig | 2 + xen/arch/arm/sci/Kconfig | 10 + xen/arch/arm/sci/scmi_smc.c | 959 ++++++++++++++++++++++++++++++++++++ 3 files changed, 971 insertions(+) create mode 100644 xen/arch/arm/sci/Kconfig create mode 100644 xen/arch/arm/sci/scmi_smc.c diff --git a/xen/arch/arm/Kconfig b/xen/arch/arm/Kconfig index ab07833582..3b0dfc57b6 100644 --- a/xen/arch/arm/Kconfig +++ b/xen/arch/arm/Kconfig @@ -123,6 +123,8 @@ config ARM_SCI support. It allows guests to control system resourcess via one of ARM_SCI mediators implemented in XEN. + source "arch/arm/sci/Kconfig" + endmenu menu "ARM errata workaround via the alternative framework" diff --git a/xen/arch/arm/sci/Kconfig b/xen/arch/arm/sci/Kconfig new file mode 100644 index 0000000000..10b634d2ed --- /dev/null +++ b/xen/arch/arm/sci/Kconfig @@ -0,0 +1,10 @@ +config SCMI_SMC + bool "Enable SCMI-SMC mediator driver" + default n + depends on ARM_SCI && HOST_DTB_EXPORT + ---help--- + + Enables mediator in XEN to pass SCMI requests from Domains to ATF. + This feature allows drivers from Domains to work with System + Controllers (such as power,resets,clock etc.). SCP is used as transport + for communication. diff --git a/xen/arch/arm/sci/scmi_smc.c b/xen/arch/arm/sci/scmi_smc.c new file mode 100644 index 0000000000..103529dfab --- /dev/null +++ b/xen/arch/arm/sci/scmi_smc.c @@ -0,0 +1,959 @@ +/* + * xen/arch/arm/sci/scmi_smc.c + * + * SCMI mediator driver, using SCP as transport. + * + * Oleksii Moisieiev <oleksii_moisieiev@xxxxxxxx> + * Copyright (C) 2021, EPAM Systems. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <asm/sci/sci.h> +#include <asm/smccc.h> +#include <asm/io.h> +#include <xen/bitops.h> +#include <xen/config.h> +#include <xen/sched.h> +#include <xen/device_tree.h> +#include <xen/iocap.h> +#include <xen/init.h> +#include <xen/err.h> +#include <xen/lib.h> +#include <xen/list.h> +#include <xen/mm.h> +#include <xen/string.h> +#include <xen/time.h> +#include <xen/vmap.h> + +#define SCMI_BASE_PROTOCOL 0x10 +#define SCMI_BASE_PROTOCOL_ATTIBUTES 0x1 +#define SCMI_BASE_SET_DEVICE_PERMISSIONS 0x9 +#define SCMI_BASE_RESET_AGENT_CONFIGURATION 0xB +#define SCMI_BASE_DISCOVER_AGENT 0x7 + +/* SCMI return codes. See section 4.1.4 of SCMI spec (DEN0056C) */ +#define SCMI_SUCCESS 0 +#define SCMI_NOT_SUPPORTED (-1) +#define SCMI_INVALID_PARAMETERS (-2) +#define SCMI_DENIED (-3) +#define SCMI_NOT_FOUND (-4) +#define SCMI_OUT_OF_RANGE (-5) +#define SCMI_BUSY (-6) +#define SCMI_COMMS_ERROR (-7) +#define SCMI_GENERIC_ERROR (-8) +#define SCMI_HARDWARE_ERROR (-9) +#define SCMI_PROTOCOL_ERROR (-10) + +#define DT_MATCH_SCMI_SMC DT_MATCH_COMPATIBLE("arm,scmi-smc") + +#define SCMI_SMC_ID "arm,smc-id" +#define SCMI_SHARED_MEMORY "arm,scmi-shmem" +#define SCMI_SHMEM "shmem" +#define SCMI_SHMEM_MAPPED_SIZE PAGE_SIZE + +#define HYP_CHANNEL 0x0 + +#define HDR_ID GENMASK(7,0) +#define HDR_TYPE GENMASK(9, 8) +#define HDR_PROTO GENMASK(17, 10) + +/* SCMI protocol, refer to section 4.2.2.2 (DEN0056C) */ +#define MSG_N_AGENTS_MASK GENMASK(15, 8) + +#define FIELD_GET(_mask, _reg)\ + ((typeof(_mask))(((_reg) & (_mask)) >> (ffs64(_mask) - 1))) +#define FIELD_PREP(_mask, _val)\ + (((typeof(_mask))(_val) << (ffs64(_mask) - 1)) & (_mask)) + +typedef struct scmi_msg_header { + uint8_t id; + uint8_t type; + uint8_t protocol; +} scmi_msg_header_t; + +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE BIT(0, UL) +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR BIT(1, UL) + +#define SCMI_ALLOW_ACCESS BIT(0, UL) + +struct scmi_shared_mem { + uint32_t reserved; + uint32_t channel_status; + uint32_t reserved1[2]; + uint32_t flags; + uint32_t length; + uint32_t msg_header; + uint8_t msg_payload[]; +}; + +struct dt_channel_addr { + u64 addr; + u64 size; + struct list_head list; +}; + +struct scmi_channel { + int chan_id; + int agent_id; + uint32_t func_id; + domid_t domain_id; + uint64_t paddr; + uint64_t len; + struct scmi_shared_mem *shmem; + spinlock_t lock; + struct list_head list; +}; + +struct scmi_data { + struct list_head channel_list; + spinlock_t channel_list_lock; + bool initialized; +}; + +static struct scmi_data scmi_data; + + +/* + * pack_scmi_header() - packs and returns 32-bit header + * + * @hdr: pointer to header containing all the information on message id, + * protocol id and type id. + * + * Return: 32-bit packed message header to be sent to the platform. + */ +static inline uint32_t pack_scmi_header(scmi_msg_header_t *hdr) +{ + return FIELD_PREP(HDR_ID, hdr->id) | + FIELD_PREP(HDR_TYPE, hdr->type) | + FIELD_PREP(HDR_PROTO, hdr->protocol); +} + +/* + * unpack_scmi_header() - unpacks and records message and protocol id + * + * @msg_hdr: 32-bit packed message header sent from the platform + * @hdr: pointer to header to fetch message and protocol id. + */ +static inline void unpack_scmi_header(uint32_t msg_hdr, scmi_msg_header_t *hdr) +{ + hdr->id = FIELD_GET(HDR_ID, msg_hdr); + hdr->type = FIELD_GET(HDR_TYPE, msg_hdr); + hdr->protocol = FIELD_GET(HDR_PROTO, msg_hdr); +} + +static inline int channel_is_free(struct scmi_channel *chan_info) +{ + return ( chan_info->shmem->channel_status + & SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE ) ? 0 : -EBUSY; +} + +/* + * Copy data from IO memory space to "real" memory space. + */ +void __memcpy_fromio(void *to, const volatile void __iomem *from, size_t count) +{ + while (count && !IS_ALIGNED((unsigned long)from, 4)) { + *(u8 *)to = __raw_readb(from); + from++; + to++; + count--; + } + + while (count >= 4) { + *(u32 *)to = __raw_readl(from); + from += 4; + to += 4; + count -= 4; + } + + while (count) { + *(u8 *)to = __raw_readb(from); + from++; + to++; + count--; + } +} + +/* + * Copy data from "real" memory space to IO memory space. + */ +void __memcpy_toio(volatile void __iomem *to, const void *from, size_t count) +{ + while (count && !IS_ALIGNED((unsigned long)to, 4)) { + __raw_writeb(*(u8 *)from, to); + from++; + to++; + count--; + } + + while (count >= 4) { + __raw_writel(*(u32 *)from, to); + from += 4; + to += 4; + count -= 4; + } + + while (count) { + __raw_writeb(*(u8 *)from, to); + from++; + to++; + count--; + } +} + +static int send_smc_message(struct scmi_channel *chan_info, + scmi_msg_header_t *hdr, void *data, int len) +{ + struct arm_smccc_res resp; + int ret; + + if ( (len + sizeof(chan_info->shmem->msg_header)) > + SCMI_SHMEM_MAPPED_SIZE ) + { + printk(XENLOG_ERR + "scmi: Wrong size of smc message. Data is invalid\n"); + return -EINVAL; + } + + printk(XENLOG_DEBUG "scmi: status =%d len=%d\n", + chan_info->shmem->channel_status, len); + printk(XENLOG_DEBUG "scmi: header id = %d type = %d, proto = %d\n", + hdr->id, hdr->type, hdr->protocol); + + ret = channel_is_free(chan_info); + if ( IS_ERR_VALUE(ret) ) + return ret; + + chan_info->shmem->channel_status = 0x0; + /* Writing 0x0 right now, but SCMI_SHMEM_FLAG_INTR_ENABLED can be set */ + chan_info->shmem->flags = 0x0; + chan_info->shmem->length = sizeof(chan_info->shmem->msg_header) + len; + chan_info->shmem->msg_header = pack_scmi_header(hdr); + + printk(XENLOG_DEBUG "scmi: Writing to shmem address %p\n", + chan_info->shmem); + if ( len > 0 && data ) + __memcpy_toio((void *)(chan_info->shmem->msg_payload), data, len); + + arm_smccc_smc(chan_info->func_id, 0, 0, 0, 0, 0, 0, chan_info->chan_id, + &resp); + + printk(XENLOG_DEBUG "scmi: scmccc_smc response %d\n", (int)(resp.a0)); + + if ( resp.a0 ) + return -EOPNOTSUPP; + + return 0; +} + +static int check_scmi_status(int scmi_status) +{ + if ( scmi_status == SCMI_SUCCESS ) + return 0; + + printk(XENLOG_DEBUG "scmi: Error received: %d\n", scmi_status); + + switch ( scmi_status ) + { + case SCMI_NOT_SUPPORTED: + return -EOPNOTSUPP; + case SCMI_INVALID_PARAMETERS: + return -EINVAL; + case SCMI_DENIED: + return -EACCES; + case SCMI_NOT_FOUND: + return -ENOENT; + case SCMI_OUT_OF_RANGE: + return -ERANGE; + case SCMI_BUSY: + return -EBUSY; + case SCMI_COMMS_ERROR: + return -ENOTCONN; + case SCMI_GENERIC_ERROR: + return -EIO; + case SCMI_HARDWARE_ERROR: + return -ENXIO; + case SCMI_PROTOCOL_ERROR: + return -EBADMSG; + default: + return -EINVAL; + } +} + +static int get_smc_response(struct scmi_channel *chan_info, + scmi_msg_header_t *hdr, void *data, int len) +{ + int recv_len; + int ret; + + printk(XENLOG_DEBUG "scmi: get smc responce msgid %d\n", hdr->id); + + if ( len >= SCMI_SHMEM_MAPPED_SIZE - sizeof(chan_info->shmem) ) + { + printk(XENLOG_ERR + "scmi: Wrong size of input smc message. Data may be invalid\n"); + return -EINVAL; + } + + ret = channel_is_free(chan_info); + if ( IS_ERR_VALUE(ret) ) + return ret; + + recv_len = chan_info->shmem->length - sizeof(chan_info->shmem->msg_header); + + if ( recv_len < 0 ) + { + printk(XENLOG_ERR + "scmi: Wrong size of smc message. Data may be invalid\n"); + return -EINVAL; + } + + if ( recv_len > len ) + { + printk(XENLOG_ERR + "scmi: Not enough buffer for message %d, expecting %d\n", + recv_len, len); + return -EINVAL; + } + + unpack_scmi_header(chan_info->shmem->msg_header, hdr); + + if ( recv_len > 0 ) + { + __memcpy_fromio(data, chan_info->shmem->msg_payload, recv_len); + } + + return 0; +} + +static int do_smc_xfer(struct scmi_channel *channel, scmi_msg_header_t *hdr, void *tx_data, int tx_size, + void *rx_data, int rx_size) +{ + int ret = 0; + + ASSERT( channel && channel->shmem); + + if ( !hdr ) + return -EINVAL; + + spin_lock(&channel->lock); + + ret = send_smc_message(channel, hdr, tx_data, tx_size); + if ( ret ) + goto clean; + + ret = get_smc_response(channel, hdr, rx_data, rx_size); +clean: + spin_unlock(&channel->lock); + + return ret; +} + +static struct scmi_channel *get_channel_by_id(uint8_t chan_id) +{ + struct scmi_channel *curr; + bool found = false; + + spin_lock(&scmi_data.channel_list_lock); + list_for_each_entry(curr, &scmi_data.channel_list, list) + { + if ( curr->chan_id == chan_id ) + { + found = true; + break; + } + } + + spin_unlock(&scmi_data.channel_list_lock); + if ( found ) + return curr; + + return NULL; +} + +static struct scmi_channel *aquire_scmi_channel(domid_t domain_id) +{ + struct scmi_channel *curr; + bool found = false; + + ASSERT(domain_id != DOMID_INVALID && domain_id >= 0); + + spin_lock(&scmi_data.channel_list_lock); + list_for_each_entry(curr, &scmi_data.channel_list, list) + { + if ( curr->domain_id == DOMID_INVALID ) + { + curr->domain_id = domain_id; + found = true; + break; + } + } + + spin_unlock(&scmi_data.channel_list_lock); + if ( found ) + return curr; + + return NULL; +} + +static void relinquish_scmi_channel(struct scmi_channel *channel) +{ + ASSERT(channel != NULL); + + spin_lock(&scmi_data.channel_list_lock); + channel->domain_id = DOMID_INVALID; + spin_unlock(&scmi_data.channel_list_lock); +} + +static int map_channel_memory(struct scmi_channel *channel) +{ + ASSERT( channel && channel->paddr ); + channel->shmem = ioremap_cache(channel->paddr, SCMI_SHMEM_MAPPED_SIZE); + if ( !channel->shmem ) + return -ENOMEM; + + channel->shmem->channel_status = SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE; + printk(XENLOG_DEBUG "scmi: Got shmem after vmap %p\n", channel->shmem); + return 0; +} + +static void unmap_channel_memory(struct scmi_channel *channel) +{ + ASSERT( channel && channel->shmem ); + iounmap(channel->shmem); + channel->shmem = NULL; +} + +static struct scmi_channel *smc_create_channel(uint8_t chan_id, + uint32_t func_id, uint64_t addr) +{ + struct scmi_channel *channel; + + channel = get_channel_by_id(chan_id); + if ( channel ) + return ERR_PTR(EEXIST); + + channel = xmalloc(struct scmi_channel); + if ( !channel ) + return ERR_PTR(ENOMEM); + + channel->chan_id = chan_id; + channel->func_id = func_id; + channel->domain_id = DOMID_INVALID; + channel->shmem = NULL; + channel->paddr = addr; + spin_lock_init(&channel->lock); + spin_lock(&scmi_data.channel_list_lock); + list_add(&channel->list, &scmi_data.channel_list); + spin_unlock(&scmi_data.channel_list_lock); + return channel; +} + +static int mem_permit_access(struct domain *d, uint64_t addr, uint64_t len) +{ + return iomem_permit_access(d, paddr_to_pfn(addr), + paddr_to_pfn(PAGE_ALIGN(addr + len -1))); +} + +static int mem_deny_access(struct domain *d, uint64_t addr, + uint64_t len) +{ + return iomem_deny_access(d, paddr_to_pfn(addr), + paddr_to_pfn(PAGE_ALIGN(addr + len -1))); +} + +static int dt_update_domain_range(uint64_t addr, uint64_t size) +{ + struct dt_device_node *shmem_node; + __be32 *hw_reg; + const struct dt_property *pp; + uint32_t len; + + shmem_node = dt_find_compatible_node(NULL, NULL, SCMI_SHARED_MEMORY); + if ( !shmem_node ) + { + printk(XENLOG_ERR "scmi: Unable to find %s node in DT\n", SCMI_SHMEM); + return -EINVAL; + } + + pp = dt_find_property(shmem_node, "reg", &len); + if ( !pp ) + { + printk(XENLOG_ERR "scmi: Unable to find regs entry in shmem node\n"); + return -ENOENT; + } + + hw_reg = pp->value; + dt_set_range(&hw_reg, shmem_node, addr, size); + + return 0; +} + +static void free_channel_list(void) +{ + struct scmi_channel *curr, *_curr; + + spin_lock(&scmi_data.channel_list_lock); + list_for_each_entry_safe (curr, _curr, &scmi_data.channel_list, list) + { + list_del(&curr->list); + xfree(curr); + } + + spin_unlock(&scmi_data.channel_list_lock); +} + +static struct dt_device_node *get_dt_node_from_property( + struct dt_device_node *node, const char * p_name) +{ + const __be32 *prop; + + ASSERT( node ); + + prop = dt_get_property(node, p_name, NULL); + if ( !prop ) + return ERR_PTR(-EINVAL); + + return dt_find_node_by_phandle(be32_to_cpup(prop)); +} + +static int get_shmem_regions(struct list_head *head, u64 hyp_addr) +{ + struct dt_device_node *node; + int ret; + struct dt_channel_addr *lchan; + u64 laddr, lsize; + + node = dt_find_compatible_node(NULL, NULL, SCMI_SHARED_MEMORY); + if ( !node ) + return -ENOENT; + + while ( node ) + { + ret = dt_device_get_address(node, 0, &laddr, &lsize); + if ( ret ) + return ret; + + if ( laddr != hyp_addr ) + { + lchan = xmalloc(struct dt_channel_addr); + if ( !lchan ) + return -ENOMEM; + lchan->addr = laddr; + lchan->size = lsize; + + list_add_tail(&lchan->list, head); + } + + node = dt_find_compatible_node(node, NULL, SCMI_SHARED_MEMORY); + } + + return 0; +} + +static int read_hyp_channel_addr(struct dt_device_node *scmi_node, + u64 *addr, u64 *size) +{ + struct dt_device_node *shmem_node; + shmem_node = get_dt_node_from_property(scmi_node, "shmem"); + if ( IS_ERR_OR_NULL(shmem_node) ) + { + printk(XENLOG_ERR + "scmi: Device tree error, can't parse reserved memory %ld\n", + PTR_ERR(shmem_node)); + return PTR_ERR(shmem_node); + } + + return dt_device_get_address(shmem_node, 0, addr, size); +} + +static void free_shmem_regions(struct list_head *addr_list) +{ + struct dt_channel_addr *curr, *_curr; + + list_for_each_entry_safe (curr, _curr, addr_list, list) + { + list_del(&curr->list); + xfree(curr); + } +} + +static __init bool scmi_probe(struct dt_device_node *scmi_node) +{ + u64 addr, size; + int ret, i; + struct scmi_channel *channel, *agent_channel; + int n_agents; + scmi_msg_header_t hdr; + struct rx_t { + int32_t status; + uint32_t attributes; + } rx; + struct dt_channel_addr *entry; + struct list_head addr_list; + + uint32_t func_id; + + ASSERT(scmi_node != NULL); + + INIT_LIST_HEAD(&scmi_data.channel_list); + spin_lock_init(&scmi_data.channel_list_lock); + + if ( !dt_property_read_u32(scmi_node, SCMI_SMC_ID, &func_id) ) + { + printk(XENLOG_ERR "scmi: Unable to read smc-id from DT\n"); + return false; + } + + ret = read_hyp_channel_addr(scmi_node, &addr, &size); + if ( IS_ERR_VALUE(ret) ) + return false; + + if ( !IS_ALIGNED(size, SCMI_SHMEM_MAPPED_SIZE) ) + { + printk(XENLOG_ERR "scmi: Reserved memory is not aligned\n"); + return false; + } + + INIT_LIST_HEAD(&addr_list); + + ret = get_shmem_regions(&addr_list, addr); + if ( IS_ERR_VALUE(ret) ) + goto out; + + channel = smc_create_channel(HYP_CHANNEL, func_id, addr); + if ( IS_ERR(channel) ) + goto out; + + ret = map_channel_memory(channel); + if ( ret ) + goto out; + + spin_lock(&scmi_data.channel_list_lock); + channel->domain_id = DOMID_XEN; + spin_unlock(&scmi_data.channel_list_lock); + + hdr.id = SCMI_BASE_PROTOCOL_ATTIBUTES; + hdr.type = 0; + hdr.protocol = SCMI_BASE_PROTOCOL; + + ret = do_smc_xfer(channel, &hdr, NULL, 0, &rx, sizeof(rx)); + if ( ret ) + goto error; + + ret = check_scmi_status(rx.status); + if ( ret ) + goto error; + + n_agents = FIELD_GET(MSG_N_AGENTS_MASK, rx.attributes); + printk(XENLOG_DEBUG "scmi: Got agent count %d\n", n_agents); + + i = 1; + list_for_each_entry(entry, &addr_list, list) + { + uint32_t tx_agent_id = 0xFFFFFFFF; + struct { + int32_t status; + uint32_t agent_id; + char name[16]; + } da_rx; + + agent_channel = smc_create_channel(i, func_id, + entry->addr); + if ( IS_ERR(agent_channel) ) + { + ret = PTR_ERR(agent_channel); + goto error; + } + + ret = map_channel_memory(agent_channel); + if ( ret ) + goto error; + + hdr.id = SCMI_BASE_DISCOVER_AGENT; + hdr.type = 0; + hdr.protocol = SCMI_BASE_PROTOCOL; + + ret = do_smc_xfer(agent_channel, &hdr, &tx_agent_id, + sizeof(tx_agent_id), &da_rx, sizeof(da_rx)); + if ( ret ) + { + unmap_channel_memory(agent_channel); + goto error; + } + + unmap_channel_memory(agent_channel); + + ret = check_scmi_status(da_rx.status); + if ( ret ) + goto error; + + printk(XENLOG_DEBUG "scmi: status=0x%x id=0x%x name=%s\n", + da_rx.status, da_rx.agent_id, da_rx.name); + + agent_channel->agent_id = da_rx.agent_id; + + if ( i == n_agents ) + break; + + i++; + } + + scmi_data.initialized = true; + goto out; + +error: + unmap_channel_memory(channel); + free_channel_list(); +out: + free_shmem_regions(&addr_list); + return ret == 0; +} + +static int scmi_domain_init(struct domain *d, + struct xen_arch_domainconfig *config) +{ + struct scmi_channel *channel; + int ret; + + if ( !scmi_data.initialized ) + return 0; + + printk(XENLOG_INFO "scmi: domain_id = %d\n", d->domain_id); + + channel = aquire_scmi_channel(d->domain_id); + if ( IS_ERR_OR_NULL(channel) ) + return -ENOENT; + +#ifdef CONFIG_ARM_32 + printk(XENLOG_INFO + "scmi: Aquire SCMI channel id = 0x%x , domain_id = %d paddr = 0x%llx\n", + channel->chan_id, channel->domain_id, channel->paddr); +#else + printk(XENLOG_INFO + "scmi: Aquire SCMI channel id = 0x%x , domain_id = %d paddr = 0x%lx\n", + channel->chan_id, channel->domain_id, channel->paddr); +#endif + + if ( is_hardware_domain(d) ) + { + ret = mem_permit_access(d, channel->paddr, PAGE_SIZE); + if ( IS_ERR_VALUE(ret) ) + goto error; + + ret = dt_update_domain_range(channel->paddr, PAGE_SIZE); + if ( IS_ERR_VALUE(ret) ) + { + int rc = mem_deny_access(d, channel->paddr, PAGE_SIZE); + if ( rc ) + printk(XENLOG_ERR "Unable to mem_deny_access\n"); + + goto error; + } + } + + d->arch.sci = channel; + if ( config ) + config->arm_sci_agent_paddr = channel->paddr; + + return 0; +error: + relinquish_scmi_channel(channel); + + return ret; +} + +static int scmi_add_device_by_devid(struct domain *d, uint32_t scmi_devid) +{ + struct scmi_channel *channel, *agent_channel; + scmi_msg_header_t hdr; + struct scmi_perms_tx { + uint32_t agent_id; + uint32_t device_id; + uint32_t flags; + } tx; + struct rx_t { + int32_t status; + uint32_t attributes; + } rx; + int ret; + + if ( !scmi_data.initialized ) + return 0; + + printk(XENLOG_DEBUG "scmi: scmi_devid = %d\n", scmi_devid); + + agent_channel = d->arch.sci; + if ( IS_ERR_OR_NULL(agent_channel) ) + return PTR_ERR(agent_channel); + + channel = get_channel_by_id(HYP_CHANNEL); + if ( IS_ERR_OR_NULL(channel) ) + return PTR_ERR(channel); + + hdr.id = SCMI_BASE_SET_DEVICE_PERMISSIONS; + hdr.type = 0; + hdr.protocol = SCMI_BASE_PROTOCOL; + + tx.agent_id = agent_channel->agent_id; + tx.device_id = scmi_devid; + tx.flags = SCMI_ALLOW_ACCESS; + + ret = do_smc_xfer(channel, &hdr, &tx, sizeof(tx), &rx, sizeof(&rx)); + if ( IS_ERR_VALUE(ret) ) + return ret; + + ret = check_scmi_status(rx.status); + if ( IS_ERR_VALUE(ret) ) + return ret; + + return 0; +} + +static int scmi_add_dt_device(struct domain *d, struct dt_device_node *dev) +{ + uint32_t scmi_devid; + + if ( (!scmi_data.initialized) || (!d->arch.sci) ) + return 0; + + if ( !dt_property_read_u32(dev, "scmi_devid", &scmi_devid) ) + return 0; + + printk(XENLOG_INFO "scmi: dt_node = %s\n", dt_node_full_name(dev)); + + return scmi_add_device_by_devid(d, scmi_devid); +} + +static int scmi_relinquish_resources(struct domain *d) +{ + int ret; + struct scmi_channel *channel, *agent_channel; + scmi_msg_header_t hdr; + struct reset_agent_tx { + uint32_t agent_id; + uint32_t flags; + } tx; + uint32_t rx; + + if ( !d->arch.sci ) + return 0; + + agent_channel = d->arch.sci; + + spin_lock(&agent_channel->lock); + tx.agent_id = agent_channel->agent_id; + spin_unlock(&agent_channel->lock); + + channel = get_channel_by_id(HYP_CHANNEL); + if ( !channel ) + { + printk(XENLOG_ERR + "scmi: Unable to get Hypervisor scmi channel for domain %d\n", + d->domain_id); + return -EINVAL; + } + + hdr.id = SCMI_BASE_RESET_AGENT_CONFIGURATION; + hdr.type = 0; + hdr.protocol = SCMI_BASE_PROTOCOL; + + tx.flags = 0; + + ret = do_smc_xfer(channel, &hdr, &tx, sizeof(tx), &rx, sizeof(rx)); + if ( ret ) + return ret; + + ret = check_scmi_status(rx); + + return ret; +} + +static void scmi_domain_destroy(struct domain *d) +{ + struct scmi_channel *channel; + + if ( !d->arch.sci ) + return; + + channel = d->arch.sci; + spin_lock(&channel->lock); + + relinquish_scmi_channel(channel); + printk(XENLOG_DEBUG "scmi: Free domain %d\n", d->domain_id); + + d->arch.sci = NULL; + + mem_deny_access(d, channel->paddr, PAGE_SIZE); + spin_unlock(&channel->lock); +} + +static bool scmi_handle_call(struct domain *d, void *args) +{ + bool res = false; + struct scmi_channel *agent_channel; + struct arm_smccc_res resp; + struct cpu_user_regs *regs = args; + + if ( !d->arch.sci ) + return false; + + agent_channel = d->arch.sci; + spin_lock(&agent_channel->lock); + + if ( agent_channel->func_id != regs->r0 ) + { + res = false; + goto unlock; + } + + arm_smccc_smc(agent_channel->func_id, 0, 0, 0, 0, 0, 0, + agent_channel->chan_id, &resp); + + set_user_reg(regs, 0, resp.a0); + set_user_reg(regs, 1, resp.a1); + set_user_reg(regs, 2, resp.a2); + set_user_reg(regs, 3, resp.a3); + res = true; +unlock: + spin_unlock(&agent_channel->lock); + + return res; +} + +static const struct dt_device_match scmi_smc_match[] __initconst = +{ + DT_MATCH_SCMI_SMC, + { /* sentinel */ }, +}; + +static const struct sci_mediator_ops scmi_ops = +{ + .probe = scmi_probe, + .domain_init = scmi_domain_init, + .domain_destroy = scmi_domain_destroy, + .add_dt_device = scmi_add_dt_device, + .relinquish_resources = scmi_relinquish_resources, + .handle_call = scmi_handle_call, +}; + +REGISTER_SCI_MEDIATOR(scmi_smc, "SCMI-SMC", XEN_DOMCTL_CONFIG_ARM_SCI_SCMI_SMC, + scmi_smc_match, &scmi_ops); + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ -- 2.27.0
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |