diff --git a/MAINTAINERS b/MAINTAINERS index 076cf1e141cb..0079191d7e6b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -538,6 +538,12 @@ S: Supported F: xen/arch/arm/include/asm/tee F: xen/arch/arm/tee/ +SCI MEDIATORS +M: Oleksii Moisieiev +S: Supported +F: xen/arch/arm/sci +F: xen/include/asm-arm/sci + TOOLSTACK M: Wei Liu M: Anthony PERARD diff --git a/docs/man/xl.cfg.5.pod.in b/docs/man/xl.cfg.5.pod.in index cc2e0f626717..5c4dc244f4d1 100644 --- a/docs/man/xl.cfg.5.pod.in +++ b/docs/man/xl.cfg.5.pod.in @@ -1715,6 +1715,28 @@ Specifies OS ID. =back +=item B + +B Set ARM_SCI type for the guest. ARM_SCI is System Control Protocol +allows domain to manage various functions that are provided by HW platform. + +=over 4 + +=item B + +Don't allow guest to use ARM_SCI if present on the platform. This is the +default value. + +=item B + +Enables SCMI_SMC support for the guest. SCMI is System Control Management +Inferface - allows domain to manage various functions that are provided by HW +platform, such as clocks, resets and power-domains. Xen will mediate access to +clocks, power-domains and resets between Domains and ATF. Disabled by default. +SCP is used as transport. + +=back + =back =head2 Paravirtualised (PV) Guest Specific Options diff --git a/tools/golang/xenlight/helpers.gen.go b/tools/golang/xenlight/helpers.gen.go index 768ab0f56602..7990cac40a09 100644 --- a/tools/golang/xenlight/helpers.gen.go +++ b/tools/golang/xenlight/helpers.gen.go @@ -1127,6 +1127,8 @@ if err := x.DmRestrict.fromC(&xc.dm_restrict);err != nil { return fmt.Errorf("converting field DmRestrict: %v", err) } x.Tee = TeeType(xc.tee) +x.ArmSci = ArmSciType(xc.arm_sci) + x.Type = DomainType(xc._type) switch x.Type{ case DomainTypeHvm: @@ -1485,6 +1487,7 @@ if err := x.DmRestrict.toC(&xc.dm_restrict); err != nil { return fmt.Errorf("converting field DmRestrict: %v", err) } xc.tee = C.libxl_tee_type(x.Tee) +xc.arm_sci = C.libxl_arm_sci_type(x.ArmSci) xc._type = C.libxl_domain_type(x.Type) switch x.Type{ case DomainTypeHvm: diff --git a/tools/golang/xenlight/types.gen.go b/tools/golang/xenlight/types.gen.go index 0b712d2aa462..b90def380d60 100644 --- a/tools/golang/xenlight/types.gen.go +++ b/tools/golang/xenlight/types.gen.go @@ -513,6 +513,12 @@ SveType1920 SveType = 1920 SveType2048 SveType = 2048 ) +type ArmSciType int +const( +ArmSciTypeNone ArmSciType = 0 +ArmSciTypeScmi ArmSciType = 1 +) + type RdmReserve struct { Strategy RdmReserveStrategy Policy RdmReservePolicy @@ -584,6 +590,7 @@ NestedHvm Defbool Apic Defbool DmRestrict Defbool Tee TeeType +ArmSci ArmSciType Type DomainType TypeUnion DomainBuildInfoTypeUnion ArchArm struct { diff --git a/tools/include/libxl.h b/tools/include/libxl.h index d34d09eaaa16..3c2b211345e9 100644 --- a/tools/include/libxl.h +++ b/tools/include/libxl.h @@ -308,6 +308,11 @@ */ #define LIBXL_HAVE_BUILDINFO_ARCH_ARM_SVE_VL 1 +/* + * libxl_domain_build_info has the arch_arm.sci field. + */ +#define LIBXL_HAVE_BUILDINFO_ARCH_ARM_SCI 1 + /* * LIBXL_HAVE_SOFT_RESET indicates that libxl supports performing * 'soft reset' for domains and there is 'soft_reset' shutdown reason diff --git a/tools/include/xenctrl.h b/tools/include/xenctrl.h index 2ef8b4e05422..13933d691a2a 100644 --- a/tools/include/xenctrl.h +++ b/tools/include/xenctrl.h @@ -1230,6 +1230,9 @@ int xc_domain_getvnuma(xc_interface *xch, int xc_domain_soft_reset(xc_interface *xch, uint32_t domid); +int xc_domain_get_sci_info(xc_interface *xch, uint32_t domid, + uint64_t *paddr, uint32_t *func_id); + #if defined(__i386__) || defined(__x86_64__) /* * PC BIOS standard E820 types and structure. diff --git a/tools/libs/ctrl/xc_domain.c b/tools/libs/ctrl/xc_domain.c index f2d9d14b4d9f..09d328e0b490 100644 --- a/tools/libs/ctrl/xc_domain.c +++ b/tools/libs/ctrl/xc_domain.c @@ -2180,6 +2180,24 @@ int xc_domain_soft_reset(xc_interface *xch, domctl.domain = domid; return do_domctl(xch, &domctl); } + +int xc_domain_get_sci_info(xc_interface *xch, uint32_t domid, + uint64_t *paddr, uint32_t *func_id) +{ + struct xen_domctl domctl = {}; + + memset(&domctl, 0, sizeof(domctl)); + domctl.cmd = XEN_DOMCTL_get_sci_info; + domctl.domain = domid; + + if ( do_domctl(xch, &domctl) != 0 ) + return 1; + + *paddr = domctl.u.sci_info.paddr; + *func_id = domctl.u.sci_info.func_id; + return 0; +} + /* * Local variables: * mode: C diff --git a/tools/libs/hypfs/core.c b/tools/libs/hypfs/core.c index 52b30db8d777..d09bba7d8c86 100644 --- a/tools/libs/hypfs/core.c +++ b/tools/libs/hypfs/core.c @@ -307,8 +307,6 @@ char *xenhypfs_read(xenhypfs_handle *fshdl, const char *path) errno = EISDIR; break; case xenhypfs_type_blob: - errno = EDOM; - break; case xenhypfs_type_string: ret_buf = buf; buf = NULL; diff --git a/tools/libs/light/libxl_arm.c b/tools/libs/light/libxl_arm.c index 19dffe9dac91..c949f4acaf14 100644 --- a/tools/libs/light/libxl_arm.c +++ b/tools/libs/light/libxl_arm.c @@ -9,6 +9,7 @@ #include #include #include +#include /* * There is no clear requirements for the total size of Virtio MMIO region. @@ -351,6 +352,19 @@ int libxl__arch_domain_prepare_config(libxl__gc *gc, } else config->arch.vgsx_osid = 0; + switch (d_config->b_info.arm_sci) { + case LIBXL_ARM_SCI_TYPE_NONE: + config->arch.arm_sci_type = XEN_DOMCTL_CONFIG_ARM_SCI_NONE; + break; + case LIBXL_ARM_SCI_TYPE_SCMI_SMC: + config->arch.arm_sci_type = XEN_DOMCTL_CONFIG_ARM_SCI_SCMI_SMC; + break; + default: + LOG(ERROR, "Unknown ARM_SCI type %d", + d_config->b_info.arm_sci); + return ERROR_FAIL; + } + return 0; } @@ -778,9 +792,6 @@ static int make_optee_node(libxl__gc *gc, void *fdt) int res; LOG(DEBUG, "Creating OP-TEE node in dtb"); - res = fdt_begin_node(fdt, "firmware"); - if (res) return res; - res = fdt_begin_node(fdt, "optee"); if (res) return res; @@ -793,9 +804,6 @@ static int make_optee_node(libxl__gc *gc, void *fdt) res = fdt_end_node(fdt); if (res) return res; - res = fdt_end_node(fdt); - if (res) return res; - return 0; } @@ -1545,10 +1553,9 @@ static int copy_node(libxl__gc *gc, void *fdt, void *pfdt, return 0; } -static int copy_node_by_path(libxl__gc *gc, const char *path, - void *fdt, void *pfdt) +static int get_path_nodeoff(const char *path, void *pfdt) { - int nodeoff, r; + int nodeoff; const char *name = strrchr(path, '/'); if (!name) @@ -1568,12 +1575,277 @@ static int copy_node_by_path(libxl__gc *gc, const char *path, if (strcmp(fdt_get_name(pfdt, nodeoff, NULL), name)) return -FDT_ERR_NOTFOUND; + return nodeoff; +} + +static int copy_node_by_path(libxl__gc *gc, const char *path, + void *fdt, void *pfdt) +{ + int nodeoff, r; + + nodeoff = get_path_nodeoff(path, pfdt); + if (nodeoff < 0) + return nodeoff; + r = copy_node(gc, fdt, pfdt, nodeoff, 0); if (r) return r; return 0; } +static int map_sci_page(libxl__gc *gc, uint32_t domid, uint64_t paddr, + uint64_t guest_addr) +{ + int ret; + uint64_t _paddr_pfn = paddr >> XC_PAGE_SHIFT; + uint64_t _guest_pfn = guest_addr >> XC_PAGE_SHIFT; + + assert(paddr && guest_addr); + LOG(DEBUG, "[%d] mapping sci shmem page %"PRIx64, domid, _paddr_pfn); + + ret = xc_domain_iomem_permission(CTX->xch, domid, _paddr_pfn, 1, 1); + if (ret < 0) { + LOG(ERROR, + "failed give domain access to iomem page %"PRIx64, + _paddr_pfn); + return ret; + } + + ret = xc_domain_memory_mapping(CTX->xch, domid, + _guest_pfn, _paddr_pfn, + 1, 1); + if (ret < 0) { + LOG(ERROR, + "failed to map to domain iomem page %"PRIx64 + " to guest address %"PRIx64, + _paddr_pfn, _guest_pfn); + return ret; + } + + return 0; +} + +static int scmi_dt_make_shmem_node(libxl__gc *gc, void *fdt) +{ + int res; + char buf[64]; + + snprintf(buf, sizeof(buf), "scmi-shmem@%llx", GUEST_SCI_SHMEM_BASE); + + res = fdt_begin_node(fdt, buf); + if (res) return res; + + res = fdt_property_compat(gc, fdt, 1, "arm,scmi-shmem"); + if (res) return res; + + res = fdt_property_regs(gc, fdt, GUEST_ROOT_ADDRESS_CELLS, + GUEST_ROOT_SIZE_CELLS, 1, + GUEST_SCI_SHMEM_BASE, GUEST_SCI_SHMEM_SIZE); + if (res) return res; + + res = fdt_property_cell(fdt, "phandle", GUEST_PHANDLE_SCMI); + if (res) return res; + + res = fdt_end_node(fdt); + if (res) return res; + + return 0; +} + +static const char *name_from_path(const char *path) +{ + return strrchr(path, '/') + 1; +} + +static int dt_copy_properties(libxl__gc *gc, void* fdt, void *xen_fdt, + const char *full_name) +{ + int propoff, nameoff, r, nodeoff; + const struct fdt_property *prop; + + LOG(DEBUG, "Copy properties for node: %s", full_name); + nodeoff = get_path_nodeoff(full_name, xen_fdt); + if (nodeoff < 0) + return -FDT_ERR_NOTFOUND; + + for (propoff = fdt_first_property_offset(xen_fdt, nodeoff); + propoff >= 0; + propoff = fdt_next_property_offset(xen_fdt, propoff)) { + + if (!(prop = fdt_get_property_by_offset(xen_fdt, propoff, NULL))) + return -FDT_ERR_INTERNAL; + + nameoff = fdt32_to_cpu(prop->nameoff); + + /* Skipping phandle nodes in xen device-tree */ + if (strcmp(fdt_string(xen_fdt,nameoff), "phandle") == 0 || + strcmp(fdt_string(xen_fdt, nameoff), "linux,phandle") == 0) + continue; + + r = fdt_property(fdt, fdt_string(xen_fdt, nameoff), + prop->data, fdt32_to_cpu(prop->len)); + if (r) return r; + } + + return (propoff != -FDT_ERR_NOTFOUND)? propoff : 0; +} + +static int scmi_dt_scan_node(libxl__gc *gc, void *fdt, void *pfdt, + void *xen_fdt, int nodeoff) +{ + int rc; + int node_next; + char full_name[128]; + uint32_t phandle; + + node_next = fdt_first_subnode(pfdt, nodeoff); + while (node_next > 0) + { + LOG(DEBUG,"Processing node %s", + fdt_get_name(pfdt, node_next, NULL)); + + phandle = fdt_get_phandle(pfdt, node_next); + + rc = fdt_get_path(pfdt, node_next, full_name, sizeof(full_name)); + if (rc) return rc; + + rc = fdt_begin_node(fdt, name_from_path(full_name)); + if (rc) return rc; + + rc = dt_copy_properties(gc, fdt, xen_fdt, full_name); + if (rc) return rc; + + if (phandle) { + rc = fdt_property_cell(fdt, "phandle", phandle); + if (rc) return rc; + } + + rc = scmi_dt_scan_node(gc, fdt, pfdt, xen_fdt, node_next); + if (rc) return rc; + + rc = fdt_end_node(fdt); + if (rc) return rc; + + node_next = fdt_next_subnode(pfdt, node_next); + } + + return 0; +} + +static int scmi_hypfs_fdt_check(libxl__gc *gc, void *fdt) +{ + int r; + + if (fdt_magic(fdt) != FDT_MAGIC) { + LOG(ERROR, "FDT is not a valid Flat Device Tree"); + return ERROR_FAIL; + } + + r = fdt_check_header(fdt); + if (r) { + LOG(ERROR, "Failed to check the FDT (%d)", r); + return ERROR_FAIL; + } + + return r; +} + +static int scmi_dt_copy_subnodes(libxl__gc *gc, void *fdt, void *pfdt) +{ + struct xenhypfs_handle *hdl; + struct xenhypfs_dirent *ent; + void *xen_fdt; + int rc, nodeoff; + + hdl = xenhypfs_open(NULL, 0); + if (!hdl) + return -EINVAL; + + xen_fdt = xenhypfs_read_raw(hdl, "/devicetree", &ent); + if (!xen_fdt) { + rc = errno; + LOG(ERROR, "Unable to read hypfs entry: %d", rc); + goto out; + } + + rc = scmi_hypfs_fdt_check(gc, xen_fdt); + if (rc) { + LOG(ERROR, "Hypfs device tree is invalid"); + goto out; + } + + nodeoff = get_path_nodeoff("/firmware/scmi", pfdt); + if (nodeoff <= 0) { + rc = -ENODEV; + goto out; + } + + rc = scmi_dt_scan_node(gc, fdt, pfdt, xen_fdt, nodeoff); + +out: + xenhypfs_close(hdl); + return rc; +} + +static int scmi_dt_create_node(libxl__gc *gc, void *fdt, void *pfdt, + uint32_t func_id) +{ + int rc = 0; + + rc = fdt_begin_node(fdt, "scmi"); + if (rc) return rc; + + rc = fdt_property_compat(gc, fdt, 1, "arm,scmi-smc"); + if (rc) return rc; + + rc = fdt_property_cell(fdt, "shmem", GUEST_PHANDLE_SCMI); + if (rc) return rc; + + rc = fdt_property_cell(fdt, "#addrets-cells", 1); + if (rc) return rc; + + rc = fdt_property_cell(fdt, "#size-cells", 0); + if (rc) return rc; + + rc = fdt_property_cell(fdt, "arm,smc-id", func_id); + if (rc) return rc; + + rc = scmi_dt_copy_subnodes(gc, fdt, pfdt); + if (rc) return rc; + + rc = fdt_end_node(fdt); + if (rc) return rc; + + return rc; +} + +static int make_firmware_node(libxl__gc *gc, void *fdt, void *pfdt, int tee, + int sci, uint32_t func_id) +{ + int res; + + if ((tee == LIBXL_TEE_TYPE_NONE) && (sci == LIBXL_ARM_SCI_TYPE_NONE)) + return 0; + + res = fdt_begin_node(fdt, "firmware"); + if (res) return res; + + if (tee == LIBXL_TEE_TYPE_OPTEE) { + res = make_optee_node(gc, fdt); + if (res) return res; + } + + if (sci == LIBXL_ARM_SCI_TYPE_SCMI_SMC) { + res = scmi_dt_create_node(gc, fdt, pfdt, func_id); + if (res) return res; + } + + res = fdt_end_node(fdt); + if (res) return res; + + return 0; +} + /* * The partial device tree is not copied entirely. Only the relevant bits are * copied to the guest device tree: @@ -1745,8 +2017,11 @@ static int libxl__prepare_dtb(libxl__gc *gc, libxl_domain_config *d_config, if (info->arch_arm.vuart == LIBXL_VUART_TYPE_SBSA_UART) FDT( make_vpl011_uart_node(gc, fdt, ainfo, dom) ); - if (info->tee == LIBXL_TEE_TYPE_OPTEE) - FDT( make_optee_node(gc, fdt) ); + if (info->arm_sci == LIBXL_ARM_SCI_TYPE_SCMI_SMC) + FDT( scmi_dt_make_shmem_node(gc, fdt) ); + + FDT( make_firmware_node(gc, fdt, pfdt, info->tee, info->arm_sci, + state->arm_sci_agent_funcid) ); if (d_config->num_pcidevs) FDT( make_vpci_node(gc, fdt, ainfo, dom) ); @@ -2034,6 +2309,16 @@ int libxl__arch_build_dom_finish(libxl__gc *gc, { int rc = 0, ret; + if (info->arm_sci == LIBXL_ARM_SCI_TYPE_SCMI_SMC) { + ret = map_sci_page(gc, dom->guest_domid, state->arm_sci_agent_paddr, + GUEST_SCI_SHMEM_BASE); + if (ret < 0) { + LOG(ERROR, "map_sci_page failed\n"); + rc = ERROR_FAIL; + goto out; + } + } + if (info->arch_arm.vuart != LIBXL_VUART_TYPE_SBSA_UART) { rc = 0; goto out; diff --git a/tools/libs/light/libxl_create.c b/tools/libs/light/libxl_create.c index e7f5607cd68c..f11c7b1b842a 100644 --- a/tools/libs/light/libxl_create.c +++ b/tools/libs/light/libxl_create.c @@ -774,6 +774,18 @@ int libxl__domain_make(libxl__gc *gc, libxl_domain_config *d_config, */ assert(libxl_domid_valid_guest(*domid)); + if (d_config->b_info.arm_sci == LIBXL_ARM_SCI_TYPE_SCMI_SMC) { + ret = xc_domain_get_sci_info(ctx->xch, *domid, &state->arm_sci_agent_paddr, + &state->arm_sci_agent_funcid); + LOGD(DEBUG, *domid,"sci_agent_paddr = %lx", state->arm_sci_agent_paddr); + if (ret) { + LOGED(ERROR, *domid, "failed to get sci paddr"); + rc = ERROR_FAIL; + goto out; + } + + } + dom_path = libxl__xs_get_dompath(gc, *domid); if (!dom_path) { rc = ERROR_FAIL; diff --git a/tools/libs/light/libxl_internal.h b/tools/libs/light/libxl_internal.h index d1f086b1bd7d..07fbbdcbfba9 100644 --- a/tools/libs/light/libxl_internal.h +++ b/tools/libs/light/libxl_internal.h @@ -1409,6 +1409,9 @@ typedef struct { * applicable to the primary domain, not support domains (e.g. stub QEMU). */ bool restore; bool soft_reset; + + uint64_t arm_sci_agent_paddr; + uint32_t arm_sci_agent_funcid; } libxl__domain_build_state; _hidden void libxl__domain_build_state_init(libxl__domain_build_state *s); diff --git a/tools/libs/light/libxl_types.idl b/tools/libs/light/libxl_types.idl index 7ed9e7bf76a7..59c35059cea5 100644 --- a/tools/libs/light/libxl_types.idl +++ b/tools/libs/light/libxl_types.idl @@ -552,6 +552,11 @@ libxl_sve_type = Enumeration("sve_type", [ (2048, "2048") ], init_val = "LIBXL_SVE_TYPE_DISABLED") +libxl_arm_sci_type = Enumeration("arm_sci_type", [ + (0, "none"), + (1, "scmi_smc") + ], init_val = "LIBXL_ARM_SCI_TYPE_NONE") + libxl_rdm_reserve = Struct("rdm_reserve", [ ("strategy", libxl_rdm_reserve_strategy), ("policy", libxl_rdm_reserve_policy), @@ -655,6 +660,7 @@ libxl_domain_build_info = Struct("domain_build_info",[ ("tee", libxl_tee_type), ("tpm", libxl_defbool), ("virtio_pci_hosts", Array(libxl_virtio_pci_host, "num_virtio_pci_hosts")), + ("arm_sci", libxl_arm_sci_type), ("u", KeyedUnion(None, libxl_domain_type, "type", [("hvm", Struct(None, [("firmware", string), ("bios", libxl_bios_type), diff --git a/tools/xl/xl_parse.c b/tools/xl/xl_parse.c index 97cc865145a4..dc81dc11cf7a 100644 --- a/tools/xl/xl_parse.c +++ b/tools/xl/xl_parse.c @@ -3285,6 +3285,15 @@ void parse_config_data(const char *config_source, } } + if (!xlu_cfg_get_string (config, "arm_sci", &buf, 1)) { + e = libxl_arm_sci_type_from_string(buf, &b_info->arm_sci); + if (e) { + fprintf(stderr, + "Unknown arm_sci \"%s\" specified\n", buf); + exit(-ERROR_FAIL); + } + } + parse_vkb_list(config, d_config); parse_vgsx_list(config, d_config); parse_vcamera_list(config, d_config); diff --git a/xen/arch/arm/Kconfig b/xen/arch/arm/Kconfig index 50e9bfae1ac8..9c3fcd23a748 100644 --- a/xen/arch/arm/Kconfig +++ b/xen/arch/arm/Kconfig @@ -96,6 +96,22 @@ config DOM0LESS_BOOT Xen boot without the need of a control domain (Dom0), which could be present anyway. +config HOST_DTB_EXPORT + bool "Export host device tree to hypfs if enabled" + depends on ARM && HYPFS && !ACPI + ---help--- + + Export host device-tree to hypfs so toolstack can have an access for the + host device tree from Dom0. If you unsure say N. + +config HOST_DTB_MAX_SIZE + int "Max host dtb export size" + depends on HOST_DTB_EXPORT + default 8192 + ---help--- + + Maximum size of the host device-tree exported to hypfs. + config GICV3 bool "GICv3 driver" depends on !NEW_VGIC @@ -225,6 +241,17 @@ config STATIC_EVTCHN This option enables establishing static event channel communication between domains on a dom0less system (domU-domU as well as domU-dom0). +config ARM_SCI + bool "Enable ARM_SCI mediators support" + depends on ARM + default n + help + This option enables generic ARM_SCI (System Control Interface) mediators + 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/Makefile b/xen/arch/arm/Makefile index 33c677672fe6..3e3551047b79 100644 --- a/xen/arch/arm/Makefile +++ b/xen/arch/arm/Makefile @@ -8,6 +8,7 @@ obj-y += platforms/ endif obj-$(CONFIG_TEE) += tee/ obj-$(CONFIG_HAS_VPCI) += vpci.o +obj-$(CONFIG_ARM_SCI) += sci/ obj-$(CONFIG_HAS_ALTERNATIVE) += alternative.o obj-y += bootfdt.init.o @@ -21,6 +22,7 @@ obj-y += domain.o obj-y += domain_build.init.o obj-$(CONFIG_ARCH_MAP_DOMAIN_PAGE) += domain_page.o obj-y += domctl.o +obj-$(CONFIG_HOST_DTB_EXPORT) += host_dtb_export.o obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-y += efi/ obj-y += gic.o diff --git a/xen/arch/arm/device.c b/xen/arch/arm/device.c index f369cbe8f173..ce5aa256093d 100644 --- a/xen/arch/arm/device.c +++ b/xen/arch/arm/device.c @@ -9,7 +9,9 @@ */ #include +#include #include +#include #include #include #include @@ -356,6 +358,10 @@ int handle_device(struct domain *d, struct dt_device_node *dev, p2m_type_t p2mt, return res; } } + + res = ac_assign_dt_device(dev, d); + if ( res < 0 ) + return res; } res = map_device_irqs_to_domain(d, dev, own_device, irq_ranges); diff --git a/xen/arch/arm/dom0less-build.c b/xen/arch/arm/dom0less-build.c index 69ed96f5491e..3db85e5b0b0e 100644 --- a/xen/arch/arm/dom0less-build.c +++ b/xen/arch/arm/dom0less-build.c @@ -1,4 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -284,10 +287,6 @@ static int __init make_optee_node(struct kernel_info *kinfo) void *fdt = kinfo->fdt; int res; - res = fdt_begin_node(fdt, "firmware"); - if ( res ) - return res; - res = fdt_begin_node(fdt, "optee"); if ( res ) return res; @@ -305,11 +304,6 @@ static int __init make_optee_node(struct kernel_info *kinfo) if ( res ) return res; - /* end of "firmware" */ - res = fdt_end_node(fdt); - if ( res ) - return res; - return 0; } #endif @@ -403,6 +397,10 @@ static int __init handle_passthrough_prop(struct kernel_info *kinfo, if ( res < 0 ) return res; + res = ac_assign_dt_device(node, kinfo->d); + if ( res < 0 ) + return res; + /* If xen_force, we allow assignment of devices without IOMMU protection. */ if ( xen_force && !dt_device_is_protected(node) ) return 0; @@ -552,6 +550,33 @@ static int __init check_partial_fdt(void *pfdt, size_t size) return 0; } +#ifdef CONFIG_SCMI_SMC +int __init scan_firmware_node(struct kernel_info *kinfo, void *pfdt, + int nodeoff) +{ + int res; + int node_next = fdt_first_subnode(pfdt, nodeoff); + + while ( node_next > 0 ) + { + const char *name = fdt_get_name(pfdt, node_next, NULL); + + if ( dt_node_cmp(name, "scmi") == 0 ) + { + res = scmi_dt_scan_node(kinfo, pfdt, node_next); + if ( res ) + return res; + + break; + } + + node_next = fdt_next_subnode(pfdt, node_next); + } + + return 0; +} +#endif /* CONFIG_SCMI_SMC */ + static int __init domain_handle_dtb_bootmodule(struct domain *d, struct kernel_info *kinfo) { @@ -589,6 +614,16 @@ static int __init domain_handle_dtb_bootmodule(struct domain *d, continue; } +#ifdef CONFIG_SCMI_SMC + if ( dt_node_cmp(name, "firmware") == 0 ) + { + res = scan_firmware_node(kinfo, pfdt, node_next); + if ( res ) + goto out; + continue; + } +#endif + if ( dt_node_cmp(name, "aliases") == 0 ) { res = scan_pfdt_node(kinfo, pfdt, node_next, @@ -617,6 +652,73 @@ static int __init domain_handle_dtb_bootmodule(struct domain *d, return res; } +#ifdef CONFIG_SCMI_SMC +static int __init mem_permit_access(struct domain *d, uint64_t addr, uint64_t len) +{ + int res; + res = iomem_permit_access(d, paddr_to_pfn(addr), + paddr_to_pfn(PAGE_ALIGN(addr + len - 1))); + if ( res ) + return res; + + return map_regions_p2mt(d, gaddr_to_gfn(addr), PFN_DOWN(len), + maddr_to_mfn(addr), p2m_mmio_direct_nc); +} +#endif /* CONFIG_SCMI_SMC */ + +static int __init make_firmware_node(struct kernel_info *kinfo) +{ +#if defined(CONFIG_SCMI_SMC) || defined(CONFIG_OPTEE) + int ret; + +#ifdef CONFIG_SCMI_SMC + if ( kinfo->sci_type == XEN_DOMCTL_CONFIG_ARM_SCI_SCMI_SMC ) + { + ret = scmi_dt_make_shmem_node(kinfo); + if ( ret ) + return ret; + + if ( !is_domain_direct_mapped(kinfo->d) ) + { + printk(XENLOG_ERR "Non-direct mapped domains doesn't support SCMI\n"); + return -ENODEV; + } + + ret = mem_permit_access(kinfo->d, kinfo->d->arch.sci_channel.paddr, 0x1000); + if ( ret ) + return ret; + } +#endif + + ret = fdt_begin_node(kinfo->fdt, "/firmware"); + if ( ret ) + return ret; + +#ifdef CONFIG_SCMI_SMC + if ( kinfo->sci_type == XEN_DOMCTL_CONFIG_ARM_SCI_SCMI_SMC ) + { + ret = scmi_dt_create_node(kinfo); + if ( ret ) + return ret; + } +#endif + +#ifdef CONFIG_OPTEE + if ( kinfo->tee_type == XEN_DOMCTL_CONFIG_TEE_OPTEE) + { + ret = make_optee_node(kinfo); + if ( ret ) + return ret; + } +#endif + + ret = fdt_end_node(kinfo->fdt); + if ( ret ) + return ret; +#endif + return 0; +} + /* * The max size for DT is 2MB. However, the generated DT is small (not including * domU passthrough DT nodes whose size we account separately), 4KB are enough @@ -631,6 +733,7 @@ static int __init prepare_dtb_domU(struct domain *d, struct kernel_info *kinfo) kinfo->phandle_gic = GUEST_PHANDLE_GIC; kinfo->gnttab_start = GUEST_GNTTAB_BASE; kinfo->gnttab_size = GUEST_GNTTAB_SIZE; + kinfo->phandle_sci_shmem = GUEST_PHANDLE_SCMI; addrcells = GUEST_ROOT_ADDRESS_CELLS; sizecells = GUEST_ROOT_SIZE_CELLS; @@ -687,15 +790,6 @@ static int __init prepare_dtb_domU(struct domain *d, struct kernel_info *kinfo) if ( ret ) goto err; -#ifdef CONFIG_OPTEE - if ( kinfo->tee_type == XEN_DOMCTL_CONFIG_TEE_OPTEE) - { - ret = make_optee_node(kinfo); - if ( ret ) - goto err; - } -#endif - /* * domain_handle_dtb_bootmodule has to be called before the rest of * the device tree is generated because it depends on the value of @@ -708,6 +802,9 @@ static int __init prepare_dtb_domU(struct domain *d, struct kernel_info *kinfo) goto err; } + ret = make_firmware_node(kinfo); + if ( ret ) + goto err; ret = make_gic_domU_node(kinfo); if ( ret ) goto err; @@ -791,6 +888,9 @@ static int __init construct_domU(struct domain *d, const char *dom0less_enhanced; #ifdef CONFIG_TEE const char *tee; +#endif +#ifdef CONFIG_ARM_SCI + const char *arm_sci; #endif int rc; u64 mem; @@ -835,6 +935,26 @@ static int __init construct_domU(struct domain *d, else if ( rc == 0 && !strcmp(dom0less_enhanced, "no-xenstore") ) kinfo.dom0less_feature = DOM0LESS_ENHANCED_NO_XS; +#ifdef CONFIG_ARM_SCI + rc = dt_property_read_string(node, "xen,arm_sci", &arm_sci); + if ( rc == -EILSEQ || + rc == -ENODATA || + (rc == 0 && !strcmp(arm_sci, "none")) ) + { + if ( !hardware_domain ) + kinfo.sci_type = XEN_DOMCTL_CONFIG_ARM_SCI_NONE; + } + else if ( rc == 0 && !strcmp(arm_sci, "scmi_smc") ) + kinfo.sci_type = XEN_DOMCTL_CONFIG_ARM_SCI_SCMI_SMC; +#endif + + if (kinfo.sci_type != XEN_DOMCTL_CONFIG_ARM_SCI_NONE) + { + rc = sci_domain_init(d, kinfo.sci_type, NULL); + if ( rc < 0 ) + return rc; + } + #ifdef CONFIG_TEE rc = dt_property_read_string(node, "xen,tee", &tee); if ( rc == -EILSEQ || diff --git a/xen/arch/arm/domain.c b/xen/arch/arm/domain.c index 26dfabeecb49..8d1cebc1496e 100644 --- a/xen/arch/arm/domain.c +++ b/xen/arch/arm/domain.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -687,6 +688,13 @@ int arch_sanitise_domain_config(struct xen_domctl_createdomain *config) return -EINVAL; } + if ( config->arch.arm_sci_type != XEN_DOMCTL_CONFIG_ARM_SCI_NONE && + config->arch.arm_sci_type != sci_get_type() ) + { + dprintk(XENLOG_INFO, "Unsupported ARM_SCI type\n"); + return -EINVAL; + } + return 0; } @@ -767,6 +775,12 @@ int arch_domain_create(struct domain *d, /* At this stage vgic_reserve_virq should never fail */ if ( !vgic_reserve_virq(d, GUEST_EVTCHN_PPI) ) BUG(); + if ( config->arch.arm_sci_type != XEN_DOMCTL_CONFIG_ARM_SCI_NONE ) + { + if ( (rc = sci_domain_init(d, config->arch.arm_sci_type, + &config->arch)) != 0) + goto fail; + } } /* @@ -846,6 +860,7 @@ void arch_domain_destroy(struct domain *d) domain_vgic_free(d); domain_vuart_free(d); free_xenheap_page(d->shared_info); + sci_domain_destroy(d); #ifdef CONFIG_ACPI free_xenheap_pages(d->arch.efi_acpi_table, get_order_from_bytes(d->arch.efi_acpi_len)); @@ -1039,6 +1054,7 @@ enum { PROG_p2m_root, PROG_p2m, PROG_p2m_pool, + PROG_sci, PROG_done, }; @@ -1098,6 +1114,10 @@ int domain_relinquish_resources(struct domain *d) ret = relinquish_p2m_mapping(d); if ( ret ) return ret; + PROGRESS(sci): + ret = sci_relinquish_resources(d); + if ( ret ) + return ret; PROGRESS(p2m_root): /* diff --git a/xen/arch/arm/domain_build.c b/xen/arch/arm/domain_build.c index 46161848dcaf..229e043a88f0 100644 --- a/xen/arch/arm/domain_build.c +++ b/xen/arch/arm/domain_build.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include static unsigned int __initdata opt_dom0_max_vcpus; @@ -1589,6 +1591,29 @@ int __init make_chosen_node(const struct kernel_info *kinfo) return res; } +#ifdef CONFIG_SCMI_SMC +static int __init mem_permit_access(struct domain *d, uint64_t addr, uint64_t len) +{ + int res, rc; + res = iomem_permit_access(d, paddr_to_pfn(addr), + paddr_to_pfn(PAGE_ALIGN(addr + len - 1))); + if ( res ) + return res; + + res = map_regions_p2mt(d, gaddr_to_gfn(addr), PFN_DOWN(len), + maddr_to_mfn(addr), p2m_mmio_direct_nc); + if ( res ) + { + rc = iomem_deny_access(d, paddr_to_pfn(addr), + paddr_to_pfn(PAGE_ALIGN(addr + len -1))); + if ( rc ) + printk(XENLOG_ERR "Unable to deny iomem access , err = %d\n", rc); + } + + return res; +} +#endif /* CONFIG_SCMI_SMC */ + static int __init handle_node(struct domain *d, struct kernel_info *kinfo, struct dt_device_node *node, p2m_type_t p2mt) @@ -1612,6 +1637,8 @@ static int __init handle_node(struct domain *d, struct kernel_info *kinfo, DT_MATCH_TYPE("memory"), /* The memory mapped timer is not supported by Xen. */ DT_MATCH_COMPATIBLE("arm,armv7-timer-mem"), + /* SCMI shared memory is handled by Xen */ + DT_MATCH_COMPATIBLE("arm,scmi-shmem"), { /* sentinel */ }, }; static const struct dt_device_match timer_matches[] __initconst = @@ -1626,6 +1653,13 @@ static int __init handle_node(struct domain *d, struct kernel_info *kinfo, DT_MATCH_PATH("/hypervisor"), { /* sentinel */ }, }; +#ifdef CONFIG_SCMI_SMC + static const struct dt_device_match scmi_matches[] __initconst = + { + DT_MATCH_PATH("/firmware/scmi"), + { /* sentinel */ }, + }; +#endif /* CONFIG_SCMI_SMC */ struct dt_device_node *child; int res, i, nirq, irq_id; const char *name; @@ -1738,6 +1772,16 @@ static int __init handle_node(struct domain *d, struct kernel_info *kinfo, */ evtchn_allocate(d); +#ifdef CONFIG_SCMI_SMC + res = scmi_dt_make_shmem_node(kinfo); + if ( res ) + return res; + + res = mem_permit_access(kinfo->d, kinfo->d->arch.sci_channel.paddr, + PAGE_SIZE); + if ( res ) + return res; +#endif /* * The hypervisor node should always be created after all nodes * from the host DT have been parsed. @@ -1776,6 +1820,15 @@ static int __init handle_node(struct domain *d, struct kernel_info *kinfo, return res; } +#ifdef CONFIG_SCMI_SMC + if ( dt_match_node(scmi_matches, node) ) + { + res = scmi_dt_set_phandle(kinfo, dt_node_full_name(node)); + if ( res ) + return res; + } +#endif + res = fdt_end_node(kinfo->fdt); return res; @@ -1791,6 +1844,8 @@ static int __init prepare_dtb_hwdom(struct domain *d, struct kernel_info *kinfo) ASSERT(dt_host && (dt_host->sibling == NULL)); kinfo->phandle_gic = dt_interrupt_controller->phandle; + kinfo->phandle_sci_shmem = GUEST_PHANDLE_SCMI; + fdt = device_tree_flattened; new_size = fdt_totalsize(fdt) + DOM0_FDT_EXTRA_SIZE; @@ -2089,6 +2144,12 @@ static int __init construct_dom0(struct domain *d) if ( rc < 0 ) return rc; +#if CONFIG_ARM_SCI + rc = sci_domain_init(d, sci_get_type(), NULL); + if ( rc < 0 ) + return rc; +#endif + if ( acpi_disabled ) { rc = prepare_dtb_hwdom(d, &kinfo); @@ -2131,6 +2192,8 @@ void __init create_dom0(void) dom0_cfg.arch.tee_type = tee_get_type(); dom0_cfg.max_vcpus = dom0_max_vcpus(); + dom0_cfg.arch.arm_sci_type = sci_get_type(); + if ( iommu_enabled ) dom0_cfg.flags |= XEN_DOMCTL_CDF_iommu; diff --git a/xen/arch/arm/domctl.c b/xen/arch/arm/domctl.c index ad56efb0f577..72efe66b9fa8 100644 --- a/xen/arch/arm/domctl.c +++ b/xen/arch/arm/domctl.c @@ -5,6 +5,7 @@ * Copyright (c) 2012, Citrix Systems */ +#include #include #include #include @@ -48,6 +49,17 @@ static int handle_vuart_init(struct domain *d, return rc; } +static int get_sci_info(struct domain *d, struct xen_domctl_sci_info *sci_info) +{ +#ifdef CONFIG_ARM_SCI + sci_info->paddr = d->arch.sci_channel.paddr; + sci_info->func_id = d->arch.sci_channel.guest_func_id; + return 0; +#else + return -ENODEV; +#endif +} + long arch_do_domctl(struct xen_domctl *domctl, struct domain *d, XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl) { @@ -176,6 +188,15 @@ long arch_do_domctl(struct xen_domctl *domctl, struct domain *d, return rc; } + case XEN_DOMCTL_get_sci_info: + { + int rc = get_sci_info(d, &domctl->u.sci_info); + + if ( !rc ) + rc = copy_to_guest(u_domctl, domctl, 1); + + return rc; + } default: return subarch_do_domctl(domctl, d, u_domctl); } diff --git a/xen/arch/arm/host_dtb_export.c b/xen/arch/arm/host_dtb_export.c new file mode 100644 index 000000000000..c9beb2803883 --- /dev/null +++ b/xen/arch/arm/host_dtb_export.c @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Export host FDT to the hypfs + * + * Copyright (C) 2024 EPAM Systems + */ + +#include +#include +#include +#include + +static HYPFS_VARSIZE_INIT(dt_prop, XEN_HYPFS_TYPE_BLOB, + "devicetree", CONFIG_HOST_DTB_MAX_SIZE, + &hypfs_leaf_ro_funcs); + +static int __init host_dtb_export_init(void) +{ + ASSERT(dt_host && (dt_host->sibling == NULL)); + + dt_prop.u.content = device_tree_flattened; + dt_prop.e.size = fdt_totalsize(device_tree_flattened); + hypfs_add_leaf(&hypfs_root, &dt_prop, true); + + return 0; +} + +__initcall(host_dtb_export_init); diff --git a/xen/arch/arm/include/asm/domain.h b/xen/arch/arm/include/asm/domain.h index fe49550af4c9..b82c6b3c6bd6 100644 --- a/xen/arch/arm/include/asm/domain.h +++ b/xen/arch/arm/include/asm/domain.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,10 @@ struct arch_domain #ifdef CONFIG_TEE void *tee; #endif +#ifdef CONFIG_ARM_SCI + struct sci_channel sci_channel; + void *sci; +#endif /* OSID used by virtual GSX device */ uint8_t vgsx_osid; diff --git a/xen/arch/arm/include/asm/kernel.h b/xen/arch/arm/include/asm/kernel.h index 7e7b3f4d56ca..7caea5455b15 100644 --- a/xen/arch/arm/include/asm/kernel.h +++ b/xen/arch/arm/include/asm/kernel.h @@ -60,12 +60,18 @@ struct kernel_info { /* TEE type */ uint16_t tee_type; + /* SCMI type */ + uint16_t sci_type; + /* Enable/Disable PV drivers interfaces */ uint16_t dom0less_feature; /* GIC phandle */ uint32_t phandle_gic; + /* SCI SHMEM phandle */ + uint32_t phandle_sci_shmem; + /* loader to use for this kernel */ void (*load)(struct kernel_info *info); /* loader specific state */ diff --git a/xen/arch/arm/include/asm/sci/sci.h b/xen/arch/arm/include/asm/sci/sci.h new file mode 100644 index 000000000000..672b7e7c5700 --- /dev/null +++ b/xen/arch/arm/include/asm/sci/sci.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Generic part of the SCI (System Control Interface) subsystem. + * + * Oleksii Moisieiev + * Copyright (c) 2024 EPAM Systems + */ + +#ifndef __ASM_ARM_SCI_H +#define __ASM_ARM_SCI_H + +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARM_SCI + +struct sci_channel +{ + uint32_t guest_func_id; + uint64_t paddr; +}; + +struct sci_mediator_ops { + + /* + * Probe for SCI. Should return true if SCI found and + * mediator is initialized. + */ + bool (*probe)(struct dt_device_node *scmi_node); + + /* + * Called during domain construction if toolstack requests to enable + * SCI support so mediator can inform SCP-firmware about new + * guest and create own structures for the new domain. + */ + int (*domain_init)(struct domain *d, struct xen_arch_domainconfig *config); + + /* + * Called during domain destruction, releases all resources, that + * were allocated by the mediator. + */ + void (*domain_destroy)(struct domain *d); + + /* + * Called during parsing partial device-sci for the domain. + * Passing device_node so mediator could process the device and + * mark the device as related to the domain if needed. + */ + int (*add_dt_device)(struct domain *d, struct dt_device_node *dev); + + /* + * Called during domain destruction to relinquish resources used + * by mediator itself. This function can return -ERESTART to indicate + * that it does not finished work and should be called again. + */ + int (*relinquish_resources)(struct domain *d); + + /* Handle call for current domain */ + bool (*handle_call)(struct domain *d, void *regs); +}; + +struct sci_mediator_desc { + /* Printable name of the SCI. */ + const char *name; + + /* Mediator callbacks as described above. */ + const struct sci_mediator_ops *ops; + + /* + * ID of SCI. Corresponds to xen_arch_domainconfig.sci_type. + * Should be one of XEN_DOMCTL_CONFIG_ARM_SCI_xxx + */ + uint16_t sci_type; + + /* Match structure to init mediator */ + const struct dt_device_match *dt_match; + +}; + +/* + * Initialize sci domain. + * + * Initialization routine to prepare SCI mediator for the domain. + */ +int sci_domain_init(struct domain *d, uint16_t sci_type, + struct xen_arch_domainconfig *config); +/* + * Destroy sci_domain instance. + */ +void sci_domain_destroy(struct domain *d); + +/* + * Add device-tree node to the domain. + * + * SCI driver will do the register routine and set the device + * permissions for the given domain. + */ +int sci_add_dt_device(struct domain *d, struct dt_device_node *dev); + +/* + * Free resources assigned to the certain domain. + */ +int sci_relinquish_resources(struct domain *d); + +/* + * Handle sci call from the domain. + * + * SCI-Mediator acts as SMC server for the registered domains and + * does redirection of the domain calls to the SPI server, + * such as ARM-TF or similar. + */ +bool sci_handle_call(struct domain *d, void *args); + +/* + * Get current sci type. + */ +uint16_t sci_get_type(void); +int sci_do_domctl( + struct xen_domctl *domctl, struct domain *d, + XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl); + +#define REGISTER_SCI_MEDIATOR(_name, _namestr, _type, _match, _ops) \ +static const struct sci_mediator_desc __sci_desc_##_name __used \ +__section(".scimediator.info") = { \ + .name = _namestr, \ + .ops = _ops, \ + .sci_type = _type, \ + .dt_match = _match \ +} + +#else + +static inline int sci_domain_init(struct domain *d, uint16_t sci_type, + struct xen_arch_domainconfig *config) +{ + if ( likely(sci_type == XEN_DOMCTL_CONFIG_ARM_SCI_NONE) ) + return 0; + + return -ENODEV; +} + +static inline void sci_domain_destroy(struct domain *d) +{ +} + +static inline int sci_add_dt_device(struct domain *d, + struct dt_device_node *dev) +{ + return 0; +} + +static inline int sci_relinquish_resources(struct domain *d) +{ + return 0; +} + +static inline bool sci_handle_call(struct domain *d, void *args) +{ + return false; +} + +static inline uint16_t sci_get_type(void) +{ + return XEN_DOMCTL_CONFIG_ARM_SCI_NONE; +} + +#endif /* CONFIG_ARM_SCI */ + +#endif /* __ASM_ARM_SCI_H */ diff --git a/xen/arch/arm/sci/Kconfig b/xen/arch/arm/sci/Kconfig new file mode 100644 index 000000000000..359e0beacd2f --- /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 + 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/Makefile b/xen/arch/arm/sci/Makefile new file mode 100644 index 000000000000..67f261187298 --- /dev/null +++ b/xen/arch/arm/sci/Makefile @@ -0,0 +1,2 @@ +obj-y += sci.o +obj-$(CONFIG_SCMI_SMC) += scmi_smc.o diff --git a/xen/arch/arm/sci/sci.c b/xen/arch/arm/sci/sci.c new file mode 100644 index 000000000000..21a9a612bdd8 --- /dev/null +++ b/xen/arch/arm/sci/sci.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Generic part of the SCI (System Control Interface) subsystem. + * + * Oleksii Moisieiev + * Copyright (c) 2024 EPAM Systems + */ + +#include +#include +#include +#include +#include + +#include + +extern const struct sci_mediator_desc _sscimediator[], _escimediator[]; +static const struct sci_mediator_desc __read_mostly *cur_mediator; + +bool sci_handle_call(struct domain *d, void *args) +{ + if ( unlikely(!cur_mediator) ) + return false; + + return cur_mediator->ops->handle_call(d, args); +} + +int sci_domain_init(struct domain *d, uint16_t sci_type, + struct xen_arch_domainconfig *config) +{ + if ( sci_type == XEN_DOMCTL_CONFIG_ARM_SCI_NONE ) + return 0; + + if ( !cur_mediator ) + return -ENODEV; + + if ( cur_mediator->sci_type != sci_type ) + return -EINVAL; + + return cur_mediator->ops->domain_init(d, config); +} + +void sci_domain_destroy(struct domain *d) +{ + if ( !cur_mediator ) + return; + + cur_mediator->ops->domain_destroy(d); +} + +int sci_relinquish_resources(struct domain *d) +{ + if ( !cur_mediator ) + return 0; + + return cur_mediator->ops->relinquish_resources(d); +} + +uint16_t sci_get_type(void) +{ + if ( !cur_mediator ) + return XEN_DOMCTL_CONFIG_ARM_SCI_NONE; + + return cur_mediator->sci_type; +} + +static int __init sci_init(void) +{ + const struct sci_mediator_desc *desc; + struct dt_device_node *dt = NULL; + + + for ( desc = _sscimediator; desc != _escimediator; desc++ ) + { + if ( acpi_disabled ) + { + dt = dt_find_matching_node(dt_host, desc->dt_match); + if ( !dt ) + continue; + } + + if ( desc->ops->probe(dt) ) + { + printk(XENLOG_INFO "Using SCI mediator for %s\n", desc->name); + cur_mediator = desc; + return 0; + } + } + + return 0; +} + +__initcall(sci_init); diff --git a/xen/arch/arm/sci/scmi_smc.c b/xen/arch/arm/sci/scmi_smc.c new file mode 100644 index 000000000000..144902576b19 --- /dev/null +++ b/xen/arch/arm/sci/scmi_smc.c @@ -0,0 +1,840 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SCMI mediator driver, using SCP as transport. + * + * Oleksii Moisieiev + * Copyright (c) 2024 EPAM Systems + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_SECONDARY_AGENTS "epam,secondary-agents" +#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; + uint32_t status; +} 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 scmi_channel { + 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; + uint32_t func_id; + bool initialized; +}; + +struct scmi_attributes_p2a { + uint32_t attributes; +}; + +struct scmi_discover_agent_p2a { + uint32_t agent_id; + char name[16]; +}; + +struct scmi_device_permissions_a2p { + uint32_t agent_id; + uint32_t device_id; + uint32_t flags; +}; + +struct scmi_reset_agent_config_a2p { + uint32_t agent_id; + uint32_t flags; +}; + +static struct scmi_data scmi_data; + +static int scmi_add_device_by_devid(struct domain *d, uint32_t scmi_devid); + +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); +} + +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 "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->agent_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; + int pad = sizeof(hdr->status); + + printk(XENLOG_DEBUG "scmi: get smc response 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; + } + + unpack_scmi_header(chan_info->shmem->msg_header, hdr); + + hdr->status = __le32_to_cpup((const __u32 *)chan_info->shmem->msg_payload); + recv_len = recv_len > pad ? recv_len - pad : 0; + + ret = check_scmi_status(hdr->status); + if ( ret ) + return ret; + + if ( recv_len > len ) + { + printk(XENLOG_ERR + "scmi: Not enough buffer for message %d, expecting %d\n", + recv_len, len); + return -EINVAL; + } + + if ( recv_len > 0 ) + { + __memcpy_fromio(data, chan_info->shmem->msg_payload + pad, 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 agent_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->agent_id == agent_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; + struct scmi_channel *ret = NULL; + + 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; + ret = curr; + break; + } + } + + spin_unlock(&scmi_data.channel_list_lock); + + return ret; +} + +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_nocache(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 %lx after vmap %p\n", channel->paddr, + 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 agent_id, + uint32_t func_id, uint64_t addr) +{ + struct scmi_channel *channel; + + channel = get_channel_by_id(agent_id); + if ( channel ) + return ERR_PTR(EEXIST); + + channel = xmalloc(struct scmi_channel); + if ( !channel ) + return ERR_PTR(ENOMEM); + + spin_lock_init(&channel->lock); + channel->agent_id = agent_id; + channel->func_id = func_id; + channel->domain_id = DOMID_INVALID; + channel->shmem = NULL; + channel->paddr = addr; + list_add_tail(&channel->list, &scmi_data.channel_list); + return channel; +} + +static void free_channel_list(void) +{ + struct scmi_channel *curr, *_curr; + + list_for_each_entry_safe (curr, _curr, &scmi_data.channel_list, list) + { + list_del(&curr->list); + xfree(curr); + } +} + +static int read_hyp_channel_addr(struct dt_device_node *scmi_node, + u64 *addr, u64 *size) +{ + struct dt_device_node *shmem_node; + const __be32 *prop; + + prop = dt_get_property(scmi_node, "shmem", NULL); + if ( !prop ) + return -EINVAL; + + shmem_node = dt_find_node_by_phandle(be32_to_cpup(prop)); + 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 __init int collect_agents(struct dt_device_node *scmi_node) +{ + const __be32 *prop; + u32 len, i; + + prop = dt_get_property(scmi_node, SCMI_SECONDARY_AGENTS, &len); + if ( !prop ) + { + printk(XENLOG_WARNING "scmi: No %s property found\n", + SCMI_SECONDARY_AGENTS); + return -ENODEV; + } + + if ( len % (3 * sizeof(u32)) ) + { + printk(XENLOG_ERR "scmi: Invalid length of %s property: %d\n", + SCMI_SECONDARY_AGENTS, len); + return -EINVAL; + } + + for ( i = 0 ; i < len / (3 * sizeof(u32)) ; i++ ) + { + u32 agent_id = be32_to_cpu(*prop++); + u32 smc_id = be32_to_cpu(*prop++); + u32 shmem_phandle = be32_to_cpu(*prop++); + struct dt_device_node *node = dt_find_node_by_phandle(shmem_phandle); + u64 addr, size; + int ret; + + if ( !node ) + { + printk(XENLOG_ERR"scmi: Could not find shmem node for agent %d\n", + agent_id); + return -EINVAL; + } + + ret = dt_device_get_address(node, 0, &addr, &size); + if ( ret ) + { + printk(XENLOG_ERR + "scmi: Could not read shmem address for agent %d: %d", + agent_id, ret); + return ret; + } + + ret = PTR_RET(smc_create_channel(agent_id, smc_id, addr)); + if ( ret ) + { + printk(XENLOG_ERR "scmi: Could not create channel for agent %d: %d", + agent_id, ret); + return ret; + } + + printk(XENLOG_DEBUG "scmi: Agent %d SMC %X addr %lx\n", agent_id, + smc_id, addr); + } + + return 0; +} + +static int scmi_assign_device(struct dt_device_node *dev, + struct dt_phandle_args *ac_spec, + struct domain *d) +{ + uint32_t dev_id = ac_spec->args[0]; + + return scmi_add_device_by_devid(d, dev_id); +} + +static int scmi_deassign_device(struct dt_device_node *dev, + struct dt_phandle_args *ac_spec, + struct domain *d) +{ + return 0; +} + +static struct ac_ops scmi_ac_ops = +{ + .assign_device = scmi_assign_device, + .deassign_device = scmi_deassign_device, +}; + +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 scmi_attributes_p2a rx; + + 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, "arm,smc-id", &scmi_data.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; + } + + channel = smc_create_channel(HYP_CHANNEL, scmi_data.func_id, addr); + if ( IS_ERR(channel) ) + goto out; + + ret = map_channel_memory(channel); + if ( ret ) + goto out; + + channel->domain_id = DOMID_XEN; + + 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; + + n_agents = FIELD_GET(MSG_N_AGENTS_MASK, rx.attributes); + printk(XENLOG_DEBUG "scmi: Got agent count %d\n", n_agents); + + ret = collect_agents(scmi_node); + if ( ret ) + goto error; + + i = 1; + + list_for_each_entry(agent_channel, &scmi_data.channel_list, list) + { + uint32_t tx_agent_id = 0xFFFFFFFF; + struct scmi_discover_agent_p2a da_rx; + + ret = map_channel_memory(agent_channel); + if ( ret ) + goto error; + + hdr.id = SCMI_BASE_DISCOVER_AGENT; + hdr.type = 0; + hdr.protocol = SCMI_BASE_PROTOCOL; + + tx_agent_id = agent_channel->agent_id; + + ret = do_smc_xfer(agent_channel, &hdr, &tx_agent_id, + sizeof(tx_agent_id), &da_rx, sizeof(da_rx)); + if ( agent_channel->domain_id != DOMID_XEN ) + unmap_channel_memory(agent_channel); + if ( ret ) + goto error; + + printk(XENLOG_DEBUG "id=0x%x name=%s\n", + da_rx.agent_id, da_rx.name); + + agent_channel->agent_id = da_rx.agent_id; + + if ( i > n_agents ) + break; + + i++; + } + + ret = ac_register(scmi_node, &scmi_ac_ops); + if ( ret ) + goto error; + + scmi_data.initialized = true; + goto out; + +error: + unmap_channel_memory(channel); + free_channel_list(); +out: + return ret == 0; +} + +static int scmi_domain_init(struct domain *d, + struct xen_arch_domainconfig *config) +{ + struct scmi_channel *channel; + + if ( !scmi_data.initialized ) + return 0; + + channel = aquire_scmi_channel(d->domain_id); + if ( IS_ERR_OR_NULL(channel) ) + return -ENOENT; + + printk(XENLOG_INFO + "scmi: Aquire SCMI channel id = 0x%x , domain_id = %d paddr = 0x%lx\n", + channel->agent_id, channel->domain_id, channel->paddr); + + d->arch.sci = channel; + d->arch.sci_channel.paddr = channel->paddr; + d->arch.sci_channel.guest_func_id = scmi_data.func_id; + + return 0; +} + +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_device_permissions_a2p tx; + struct scmi_attributes_p2a rx; + int ret; + + if ( !scmi_data.initialized ) + return 0; + + 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; + + return 0; +} + +static int scmi_relinquish_resources(struct domain *d) +{ + int ret; + struct scmi_channel *channel, *agent_channel; + scmi_msg_header_t hdr; + struct scmi_reset_agent_config_a2p tx; + + 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), NULL, 0); + if ( ret ) + return ret; + + 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; + + 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; + u32 agent_func_id; + + if ( !d->arch.sci ) + return false; + + agent_channel = d->arch.sci; + spin_lock(&agent_channel->lock); + + agent_func_id = regs->r0 + agent_channel->agent_id; + + if ( agent_channel->func_id != agent_func_id ) + { + res = false; + goto unlock; + } + + arm_smccc_smc(agent_func_id, 0, 0, 0, 0, 0, 0, 0, &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, + .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); diff --git a/xen/arch/arm/setup.c b/xen/arch/arm/setup.c index fb57c853d7ae..cc90e03f9219 100644 --- a/xen/arch/arm/setup.c +++ b/xen/arch/arm/setup.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/xen/arch/arm/vsmc.c b/xen/arch/arm/vsmc.c index 7f2f5eb9ce3d..5ff7822b0e2e 100644 --- a/xen/arch/arm/vsmc.c +++ b/xen/arch/arm/vsmc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -288,7 +289,9 @@ static bool vsmccc_handle_call(struct cpu_user_regs *regs) handled = handle_sssc(regs); break; case ARM_SMCCC_OWNER_SIP: - handled = platform_smc(regs); + handled = sci_handle_call(current->domain, regs); + if ( !handled ) + handled = platform_smc(regs); break; case ARM_SMCCC_OWNER_TRUSTED_APP ... ARM_SMCCC_OWNER_TRUSTED_APP_END: case ARM_SMCCC_OWNER_TRUSTED_OS ... ARM_SMCCC_OWNER_TRUSTED_OS_END: diff --git a/xen/arch/arm/xen.lds.S b/xen/arch/arm/xen.lds.S index 470c8f22084f..5d485c5c2c62 100644 --- a/xen/arch/arm/xen.lds.S +++ b/xen/arch/arm/xen.lds.S @@ -152,6 +152,13 @@ SECTIONS _eteemediator = .; } :text + . = ALIGN(8); + .scimediator.info : { + _sscimediator = .; + *(.scimediator.info) + _escimediator = .; + } :text + . = ALIGN(PAGE_SIZE); /* Init code and data */ __init_begin = .; .init.text : { diff --git a/xen/drivers/passthrough/Kconfig b/xen/drivers/passthrough/Kconfig index 864fcf3b0cef..655ae018b4a6 100644 --- a/xen/drivers/passthrough/Kconfig +++ b/xen/drivers/passthrough/Kconfig @@ -61,6 +61,21 @@ config INTEL_IOMMU This is required if your system has more than 254 CPUs. If in doubt, say Y. +config ACCESS_CONTROLLER + bool "Access Controllers support" + depends on HAS_DEVICE_TREE + default y + help + Enables support for Generic Access controllers. + + Access controllers are in charge of stating which of the + hardware blocks under their responsibility (their domain) can be + accesssed by which compartment. A compartment can be a cluster of + CPUs (or coprocessors), a range of addresses or a group of hardware + blocks. An access controller's domain is the set of resources covered + by the access controller. + + config IOMMU_FORCE_PT_SHARE bool diff --git a/xen/drivers/passthrough/Makefile b/xen/drivers/passthrough/Makefile index a1621540b78d..53e0799be201 100644 --- a/xen/drivers/passthrough/Makefile +++ b/xen/drivers/passthrough/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_HAS_PCI) += pci.o obj-$(CONFIG_HAS_DEVICE_TREE) += device_tree.o obj-$(CONFIG_HAS_PCI) += ats.o obj-$(CONFIG_HAS_PCI_MSI) += msi.o +obj-$(CONFIG_ACCESS_CONTROLLER) += access-controller.o diff --git a/xen/drivers/passthrough/access-controller.c b/xen/drivers/passthrough/access-controller.c new file mode 100644 index 000000000000..5a4b1bd202be --- /dev/null +++ b/xen/drivers/passthrough/access-controller.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic access-controller framework via the device tree + * + * Copyright (c) 2024 EPAM Systems + */ + +#include +#include +#include +#include + +struct access_controller { + struct dt_device_node *np; + struct ac_ops *ops; + struct list_head next; +}; + +LIST_HEAD(ac_list); + +int ac_register(struct dt_device_node *np, struct ac_ops *ops) +{ + struct access_controller *ac; + + ac = xzalloc(struct access_controller); + + if ( !ac ) + return -ENOMEM; + + ac->np = np; + ac->ops = ops; + list_add(&ac->next, &ac_list); + + return 0; +} + +static struct access_controller *ac_find(struct dt_device_node *np) +{ + struct access_controller *ac; + + list_for_each_entry(ac, &ac_list, next) + { + if ( ac->np == np ) + return ac; + } + + return NULL; +} + +int ac_assign_dt_device(struct dt_device_node *dev, struct domain *d) +{ + struct dt_phandle_args ac_spec; + int index = 0; + int ret; + + printk(XENLOG_DEBUG"ac assign device %s to %pd\n", dt_node_name(dev), d); + + while ( !dt_parse_phandle_with_args(dev, "access-controllers", + "#access-controller-cells", + index, &ac_spec) ) + { + struct access_controller *ac = ac_find(ac_spec.np); + + if ( !ac ) + { + printk(XENLOG_INFO + "ac: [%d] Could not find access-controller ops for %s\n", + d->domain_id, dt_node_name(dev)); + continue; + } + + ret = ac->ops->assign_device(dev, &ac_spec, d); + /* TODO: Remove added devices */ + if ( ret ) + return ret; + + index++; + } + + return 0; +} diff --git a/xen/drivers/passthrough/arm/Makefile b/xen/drivers/passthrough/arm/Makefile index ed6565ec9850..7cd9ddf9add1 100644 --- a/xen/drivers/passthrough/arm/Makefile +++ b/xen/drivers/passthrough/arm/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_ARM_SMMU) += smmu.o obj-$(CONFIG_IPMMU_VMSA) += ipmmu-vmsa.o obj-$(CONFIG_IPMMU_VMSA) += ipmmu-vmsa-plat.o obj-$(CONFIG_ARM_SMMU_V3) += smmu-v3.o +obj-$(CONFIG_SCMI_SMC) += scmi_dt_maker.o \ No newline at end of file diff --git a/xen/drivers/passthrough/arm/scmi_dt_maker.c b/xen/drivers/passthrough/arm/scmi_dt_maker.c new file mode 100644 index 000000000000..33587cab3466 --- /dev/null +++ b/xen/drivers/passthrough/arm/scmi_dt_maker.c @@ -0,0 +1,326 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SCMI device-tree node generator. + * + * Oleksii Moisieiev + * Copyright (c) 2024 EPAM Systems + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define SCMI_NODE_PATH_MAX_LEN 128 + +struct scmi_phandle { + struct list_head list; + uint32_t phandle; + char full_name[SCMI_NODE_PATH_MAX_LEN]; +}; + +LIST_HEAD(scmi_ph_list); + +int __init scmi_dt_make_shmem_node(struct kernel_info *kinfo) +{ + int res; + void *fdt = kinfo->fdt; + char buf[64]; + __be32 reg[GUEST_ROOT_ADDRESS_CELLS + GUEST_ROOT_SIZE_CELLS]; + __be32 *cells; + struct domain *d = kinfo->d; + + snprintf(buf, sizeof(buf), "scmi-shmem@%lx", + d->arch.sci_channel.paddr); + + res = fdt_begin_node(fdt, buf); + if ( res ) + return res; + + res = fdt_property_string(fdt, "compatible", "arm,scmi-shmem"); + if ( res ) + return res; + + cells = ®[0]; + dt_child_set_range(&cells, GUEST_ROOT_ADDRESS_CELLS, + GUEST_ROOT_SIZE_CELLS, d->arch.sci_channel.paddr, + GUEST_SCI_SHMEM_SIZE); + + res = fdt_property(fdt, "reg", reg, sizeof(reg)); + if ( res ) + return res; + + res = fdt_property_cell(fdt, "phandle", kinfo->phandle_sci_shmem); + if ( res ) + return res; + + res = fdt_end_node(fdt); + if ( res ) + return res; + + return 0; +} + +/* + * The following methods are needed to get node name for the node full_path. + * This was done because some calls, such as dt_node_name return name + * before "@". So for node "protocol@19" it will return "protocol". + */ +static const char *dt_node_name_from_path(const struct dt_device_node *node) +{ + return strrchr(dt_node_full_name(node), '/') + 1; +} + +static const char *name_from_path(const char *path) +{ + return strrchr(path, '/') + 1; +} + +static int __init copy_properties(const struct dt_device_node *node, void* fdt) +{ + int rc; + const struct dt_property *pp; + + printk(XENLOG_DEBUG "scmi_dt_maker: copy properties for node: %s\n", + dt_node_name_from_path(node)); + + dt_for_each_property_node(node, pp) + { + /* Skipping phandle nodes in xen device-tree */ + if ( dt_property_name_is_equal(pp, "phandle") || + dt_property_name_is_equal(pp, "linux,phandle") ) + continue; + + rc = fdt_property(fdt, pp->name, pp->value, pp->length); + if ( rc ) + return rc; + } + + return 0; +} + +static struct scmi_phandle * __init get_handle_by_name(const char *name) +{ + struct scmi_phandle *handle; + list_for_each_entry(handle, &scmi_ph_list, list) + { + if ( strcmp( name_from_path(handle->full_name), name) == 0 ) + return handle; + } + + return NULL; +} + +static bool __init guest_has_child(const char *name) +{ + struct scmi_phandle *handle; + + list_for_each_entry(handle, &scmi_ph_list, list) + { + if ( strstr(handle->full_name, name) ) + return true; + } + + return false; +} + +static int __init copy_subnodes(const struct dt_device_node *node, void *fdt, + bool guest_subnode) +{ + int rc; + struct dt_device_node *child; + struct scmi_phandle *handle; + + printk(XENLOG_DEBUG "scmi_dt_maker: copy subnodes for %s\n", + dt_node_name_from_path(node)); + + dt_for_each_child_node(node,child) + { + handle = get_handle_by_name(dt_node_name_from_path(child)); + + if ( !guest_has_child(dt_node_name_from_path(child)) && !handle && + !guest_subnode ) + continue; + + rc = fdt_begin_node(fdt, dt_node_name_from_path(child)); + if ( rc ) + return rc; + + rc = copy_properties(child, fdt); + if ( rc ) + return rc; + + if ( handle ) + { + printk(XENLOG_DEBUG "scmi_dt_maker: set phandle %x\n", + handle->phandle); + + rc = fdt_property_cell(fdt, "phandle", handle->phandle); + if ( rc ) + return rc; + } + + /* + * Devices in partial device-tree can be linked to the node + * with child nodes as it happens for scmi-pinctrl nodes. + * For example: + * scmi_pinctrl { + * device_mux: mux { + * pins_clk { + * }; + * pins_bin { + * }; + * }; + * }; + * + * &device { + * pinctrl-0 = <&device_mux>; + * }; + * + * In this case phandle will be generated only for device_mux + * but subnodes should be copied to domain device-tree as well. + */ + rc = copy_subnodes(child, fdt, (guest_subnode || handle)); + if ( rc ) + return rc; + + rc = fdt_end_node(fdt); + if ( rc ) + return rc; + } + + return 0; + } + +static void __init clean_handles(void) +{ + struct scmi_phandle *curr, *_curr; + + if ( list_empty(&scmi_ph_list) ) + return; + + list_for_each_entry_safe (curr, _curr, &scmi_ph_list, list) + { + list_del(&curr->list); + xfree(curr); + } +} + +int __init scmi_dt_create_node(struct kernel_info *kinfo) +{ + int rc = 0; + struct dt_device_node *scmi = dt_find_node_by_path("/firmware/scmi"); + + if ( scmi == NULL ) + { + rc = -ENODEV; + printk(XENLOG_ERR "scmi_dt_maker: no SCMI in XEN device-tree\n"); + goto err; + } + + rc = fdt_begin_node(kinfo->fdt, "scmi"); + if ( rc ) + goto err; + + rc = fdt_property_string(kinfo->fdt, "compatible", "arm,scmi-smc"); + if ( rc ) + goto err; + + rc = fdt_property_cell(kinfo->fdt, "shmem", kinfo->phandle_sci_shmem); + if ( rc ) + goto err; + + rc = fdt_property_cell(kinfo->fdt, "#addrets-cells", 1); + if ( rc ) + goto err; + + rc = fdt_property_cell(kinfo->fdt, "#size-cells", 0); + if ( rc ) + goto err; + + rc = fdt_property_cell(kinfo->fdt, "arm,smc-id", kinfo->d->arch.sci_channel.guest_func_id); + if ( rc ) + goto err; + + rc = copy_subnodes(scmi, kinfo->fdt, false); + if ( rc ) + goto err; + + rc = fdt_end_node(kinfo->fdt); + if ( rc ) + goto err; + +err: + /* Clean handle list after nodes generation */ + clean_handles(); + + return rc; +} + +int __init scmi_dt_scan_node(struct kernel_info *kinfo, void *pfdt, + int nodeoff) +{ + int rc; + int node_next; + struct scmi_phandle *handle; + uint32_t phandle; + + node_next = fdt_first_subnode(pfdt, nodeoff); + while ( node_next > 0 ) + { + printk(XENLOG_DEBUG "scmi_dt_maker: processing node %s\n", + fdt_get_name(pfdt, node_next, NULL)); + + phandle = fdt_get_phandle(pfdt, node_next); + + if ( phandle ) + { + printk(XENLOG_DEBUG "scmi_dt_maker: phandle %x\n", phandle); + + handle = xmalloc(struct scmi_phandle); + if ( !handle ) + { + rc = -ENOMEM; + goto err; + } + + handle->phandle = phandle; + rc = fdt_get_path(pfdt, node_next, handle->full_name, 128); + if ( rc ) + { + xfree(handle); + goto err; + } + + list_add_tail(&handle->list, &scmi_ph_list); + } + + rc = scmi_dt_scan_node(kinfo, pfdt, node_next); + if ( rc ) + goto err; + + node_next = fdt_next_subnode(pfdt, node_next); + } + + return 0; +err: + clean_handles(); + return rc; +} + +int __init scmi_dt_set_phandle(struct kernel_info *kinfo, + const char *name) +{ + int offset = fdt_path_offset(kinfo->fdt, name); + __be32 val = cpu_to_be32(kinfo->phandle_sci_shmem); + + if ( !offset ) + return -ENODEV; + + return fdt_setprop_inplace(kinfo->fdt, offset, "shmem", + &val,sizeof(val)); +} diff --git a/xen/drivers/passthrough/device_tree.c b/xen/drivers/passthrough/device_tree.c index 075fb25a3706..ace66b86296b 100644 --- a/xen/drivers/passthrough/device_tree.c +++ b/xen/drivers/passthrough/device_tree.c @@ -15,6 +15,7 @@ * GNU General Public License for more details. */ +#include #include #include #include @@ -318,6 +319,10 @@ int iommu_do_dt_domctl(struct xen_domctl *domctl, struct domain *d, break; } + ret = ac_assign_dt_device(dev, d); + if ( ret < 0 ) + return ret; + ret = iommu_assign_dt_device(d, dev); if ( ret ) diff --git a/xen/include/public/arch-arm.h b/xen/include/public/arch-arm.h index 71bc8f11d143..24d730d4b39a 100644 --- a/xen/include/public/arch-arm.h +++ b/xen/include/public/arch-arm.h @@ -327,6 +327,9 @@ DEFINE_XEN_GUEST_HANDLE(vcpu_guest_context_t); #define XEN_DOMCTL_CONFIG_TEE_OPTEE 1 #define XEN_DOMCTL_CONFIG_TEE_FFA 2 +#define XEN_DOMCTL_CONFIG_ARM_SCI_NONE 0 +#define XEN_DOMCTL_CONFIG_ARM_SCI_SCMI_SMC 1 + struct xen_arch_domainconfig { /* IN/OUT */ uint8_t gic_version; @@ -335,6 +338,8 @@ struct xen_arch_domainconfig { /* IN */ uint16_t tee_type; /* IN */ + uint16_t arm_sci_type; + /* IN */ uint32_t nr_spis; /* * IN @@ -466,6 +471,10 @@ typedef uint64_t xen_callback_t; #define GUEST_PL011_BASE xen_mk_ullong(0x22000000) #define GUEST_PL011_SIZE xen_mk_ullong(0x00001000) +/* SCI mediator */ +#define GUEST_SCI_SHMEM_BASE xen_mk_ullong(0x22001000) +#define GUEST_SCI_SHMEM_SIZE xen_mk_ullong(0x01000) + /* Guest PCI-PCIe memory space where config space and BAR will be available.*/ #define GUEST_VPCI_ADDR_TYPE_MEM xen_mk_ullong(0x02000000) #define GUEST_VPCI_MEM_ADDR xen_mk_ullong(0x23000000) diff --git a/xen/include/public/device_tree_defs.h b/xen/include/public/device_tree_defs.h index 9e80d0499dc3..a961d9cbf0f2 100644 --- a/xen/include/public/device_tree_defs.h +++ b/xen/include/public/device_tree_defs.h @@ -14,6 +14,7 @@ */ #define GUEST_PHANDLE_GIC (65000) #define GUEST_PHANDLE_IOMMU (GUEST_PHANDLE_GIC + 1) +#define GUEST_PHANDLE_SCMI (GUEST_PHANDLE_GIC + 2) #define GUEST_ROOT_ADDRESS_CELLS 2 #define GUEST_ROOT_SIZE_CELLS 2 diff --git a/xen/include/public/domctl.h b/xen/include/public/domctl.h index a33f9ec32b08..571767606491 100644 --- a/xen/include/public/domctl.h +++ b/xen/include/public/domctl.h @@ -1187,6 +1187,13 @@ struct xen_domctl_vmtrace_op { #define XEN_DOMCTL_vmtrace_get_option 5 #define XEN_DOMCTL_vmtrace_set_option 6 }; + +/* XEN_DOMCTL_get_sci_info */ +struct xen_domctl_sci_info { + uint64_t paddr; + uint32_t func_id; +}; + typedef struct xen_domctl_vmtrace_op xen_domctl_vmtrace_op_t; DEFINE_XEN_GUEST_HANDLE(xen_domctl_vmtrace_op_t); @@ -1277,6 +1284,7 @@ struct xen_domctl { #define XEN_DOMCTL_vmtrace_op 84 #define XEN_DOMCTL_get_paging_mempool_size 85 #define XEN_DOMCTL_set_paging_mempool_size 86 +#define XEN_DOMCTL_get_sci_info 87 #define XEN_DOMCTL_gdbsx_guestmemio 1000 #define XEN_DOMCTL_gdbsx_pausevcpu 1001 #define XEN_DOMCTL_gdbsx_unpausevcpu 1002 @@ -1339,6 +1347,7 @@ struct xen_domctl { struct xen_domctl_vuart_op vuart_op; struct xen_domctl_vmtrace_op vmtrace_op; struct xen_domctl_paging_mempool paging_mempool; + struct xen_domctl_sci_info sci_info; uint8_t pad[128]; } u; }; diff --git a/xen/include/xen/access_controller.h b/xen/include/xen/access_controller.h new file mode 100644 index 000000000000..048f0506d7aa --- /dev/null +++ b/xen/include/xen/access_controller.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Generic access-controller framework via the device tree + * + * Copyright (c) 2024 EPAM Systems + */ + +#ifndef _ACCESSC_CONTROLLER_H_ +#define _ACCESSC_CONTROLLER_H_ + +#include +#include + +/* Access-controller driver ops */ +struct ac_ops { + /* Set device to act as an access-controller */ + int (*assign_device)(struct dt_device_node *dev, + struct dt_phandle_args *ac_spec, + struct domain *d); + /* Remove previously added access-controller device */ + int (*deassign_device)(struct dt_device_node *dev, + struct dt_phandle_args *ac_spec, + struct domain *d); +}; + +/* + * Register access-controller device. + * Access-controller device is responsible to handle + * hardware access to the different domains. + */ +int ac_register(struct dt_device_node *dev, struct ac_ops *ops); + +/* + * Unregister access controller device. + * Remove device from access-controller list. + */ +int ac_assign_dt_device(struct dt_device_node *dev, struct domain *d); + +#endif + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/include/xen/device_tree.h b/xen/include/xen/device_tree.h index 94a836cb4e38..ee8bd4cd8bd3 100644 --- a/xen/include/xen/device_tree.h +++ b/xen/include/xen/device_tree.h @@ -943,6 +943,12 @@ int dt_count_phandle_with_args(const struct dt_device_node *np, */ int dt_get_pci_domain_nr(struct dt_device_node *node); +/** + * dt_find_node_by_phandle - Find a node given a phandle + * @handle: phandle of the node to find + * + * Returns a node pointer. + */ struct dt_device_node *dt_find_node_by_phandle(dt_phandle handle); #ifdef CONFIG_DEVICE_TREE_DEBUG diff --git a/xen/include/xen/scmi_dt_maker.h b/xen/include/xen/scmi_dt_maker.h new file mode 100644 index 000000000000..f9f60a990cd1 --- /dev/null +++ b/xen/include/xen/scmi_dt_maker.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SCMI device-tree node generator header. + * + * Oleksii Moisieiev + * Copyright (c) 2024 EPAM Systems + */ + +#ifndef XEN_ARCH_ARM_SCMI_DT_MAKER_H_ +#define XEN_ARCH_ARM_SCMI_DT_MAKER_H_ + +#ifdef CONFIG_SCMI_SMC +#include + +int __init scmi_dt_make_shmem_node(struct kernel_info *kinfo); +int __init scmi_dt_create_node(struct kernel_info *kinfo); +int __init scmi_dt_scan_node(struct kernel_info *kinfo, void *pfdt, + int nodeoff); +int __init scmi_dt_set_phandle(struct kernel_info *kinfo, + const char *name); +#else +#define scmi_dt_make_shmem_node(kinfo) (0) +#define scmi_dt_create_node(kinfo) (0) +#define scmi_dt_scan_node(kinfo, pfdt, nodeoff) (0) +#define scmi_dt_set_phandle(kinfo, name) (0) + +#endif /* CONFIG_SCMI_SMC */ + +#endif /* XEN_ARCH_ARM_SCMI_DT_MAKER_H_ */