From patchwork Wed Feb 1 12:53:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jean-Philippe Brucker X-Patchwork-Id: 13124403 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id A248DC05027 for ; Wed, 1 Feb 2023 14:21:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=nkQMB1QaAhd0UoXqe90HaNoOTzEZDeTsA0rjInvsdl8=; b=4iMxDjD9PBMMid Anto33D89DlRzfD4/CAhpSVA9qCg0EZJf70r6B2MQMLoWaWl/kdDx9ScuQDo5aawINthXBlI7Qmer 3DfUCACOUjMxNYkRaUY6umOkYW3MCuD7C+8AlTwFmte44c2WvJvgA9OUxx/R1Vs0EefrlNSRGOBqk xbq2G+As8ljPdlSMrZhOgSKIB3Mt51u4A82CmADVRoW2oqeU3FiYqqLcCnoKZyJXuSC6a/UzvEHjy 4LtCEa6+UkKPgvXjjR39ocFSgzeRHVdO0X5pbEUJ6gRLZE1AkQThYLrhqrTZAZDT9Rd7+Q7gHBsQF HtgwpSQg6dLNkXmcMzDQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.94.2 #2 (Red Hat Linux)) id 1pNDyU-00CIg2-3v; Wed, 01 Feb 2023 14:20:34 +0000 Received: from mail-wm1-x329.google.com ([2a00:1450:4864:20::329]) by bombadil.infradead.org with esmtps (Exim 4.94.2 #2 (Red Hat Linux)) id 1pNCiU-00BnXG-3U for linux-arm-kernel@lists.infradead.org; Wed, 01 Feb 2023 13:00:00 +0000 Received: by mail-wm1-x329.google.com with SMTP id c4-20020a1c3504000000b003d9e2f72093so1314152wma.1 for ; Wed, 01 Feb 2023 04:59:53 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=eJX+SGqG6zLegXNP2FyB5mHsuPTdUwTTVk34rvHHB9Y=; b=PX2X8jEZRjn8B8OW49X9Nl3Le/FoV4QE6thuBsRX/+nQGyRsVqDLBvVfNztzNB/jbw RLqoljwE0InPvfppftU1jxH8AjJ6YPIrVNh760Ncn4iJufwpkb9ZDy9ZXYGc2qs2IcdU 47ectI5CDPmjzt1TBluvUasl1VQFttGX+Iziaax7lP9KI6UO0kUxBesC7i7kyN5/IRwZ MVokWcuVTv6QYBqKthSeBEFFzcGhVBuYtsUydbkiSpZN5vdHeQHr9ASZt9CvkNdqX/Ku 7MT5C/UmYdvFTN2Y9xq4gImapqPicu2RyWwf9LGwsoGavKOq7GZR1/ftyYs/K1PpcHyk uzKw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=eJX+SGqG6zLegXNP2FyB5mHsuPTdUwTTVk34rvHHB9Y=; b=vmIjaq2OQ2OeFnPtkAeeOY3F3DSFMF0iGBjIfIHyVK6WcE//kNcaJ665uLfTCQNeQe x1FPIr2HevHScsqNkLZrDp6o6y8mslHbrVphn4aCIj6TKU8HpOyNpt2v5sDvMttrQQz2 9mqjRixR2Wf0Eki5e2onm3Dit/nwH5Eg0lW07NDnvSdl6j0INbQMcEFG0xJR7oVw0Njl H5GUDQU7OMNsK7CAnHeUUhe+4XHltMwtBbUG5iMGC6KnT8DbNSQgYdqyoFd5c6qqG6Tm 4Of1BC13hhdONwM/KDsY1hY23xHLYE5IhR+j1+gLf0422F6+7oOgEMNZzd7OOG0l6D9V bZ6Q== X-Gm-Message-State: AO0yUKV+J8TQOFPPTL5uKu+sEuxqRgWFgv4gGbQkYyNFoYsTZRwMAt7v s8Yf39JgO9+08+6tI4PWgflC6A== X-Google-Smtp-Source: AK7set/gosJV6CCV/9cnL0aXRK2ZgMnlEyfxjA1eQ/7IxDIjm2hJYiDK0rKFD5ZmlMlkIiJhOyQH2g== X-Received: by 2002:a05:600c:3789:b0:3dc:54e9:dfd7 with SMTP id o9-20020a05600c378900b003dc54e9dfd7mr2105226wmr.25.1675256392661; Wed, 01 Feb 2023 04:59:52 -0800 (PST) Received: from localhost.localdomain (054592b0.skybroadband.com. [5.69.146.176]) by smtp.gmail.com with ESMTPSA id m15-20020a056000024f00b002bfae16ee2fsm17972811wrz.111.2023.02.01.04.59.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Feb 2023 04:59:52 -0800 (PST) From: Jean-Philippe Brucker To: maz@kernel.org, catalin.marinas@arm.com, will@kernel.org, joro@8bytes.org Cc: robin.murphy@arm.com, james.morse@arm.com, suzuki.poulose@arm.com, oliver.upton@linux.dev, yuzenghui@huawei.com, smostafa@google.com, dbrazdil@google.com, ryan.roberts@arm.com, linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, iommu@lists.linux.dev, Jean-Philippe Brucker Subject: [RFC PATCH 42/45] KVM: arm64: pkvm: Support SCMI power domain Date: Wed, 1 Feb 2023 12:53:26 +0000 Message-Id: <20230201125328.2186498-43-jean-philippe@linaro.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201125328.2186498-1-jean-philippe@linaro.org> References: <20230201125328.2186498-1-jean-philippe@linaro.org> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20230201_045958_173204_3C57C7A1 X-CRM114-Status: GOOD ( 36.18 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org The hypervisor needs to catch power domain changes for devices it owns, such as the SMMU. Possible reasons: * Ensure that software and hardware states are consistent. The driver does not attempt to modify the state while the device is off. * Save and restore the device state. * Enforce dependency between consumers and suppliers. For example ensure that endpoints are off before turning the SMMU off, in case a powered off SMMU lets DMA through. However this is normally enforced by firmware. Add a SCMI power domain, as the standard method for device power management on Arm. Other methods can be added to kvm_power_domain later. Signed-off-by: Jean-Philippe Brucker --- arch/arm64/kvm/hyp/nvhe/Makefile | 1 + arch/arm64/include/asm/kvm_hyp.h | 1 + arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 26 ++ .../arm64/kvm/hyp/include/nvhe/trap_handler.h | 2 + include/kvm/power_domain.h | 22 ++ arch/arm64/kvm/hyp/nvhe/hyp-main.c | 4 +- arch/arm64/kvm/hyp/nvhe/power/scmi.c | 233 ++++++++++++++++++ 7 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 include/kvm/power_domain.h create mode 100644 arch/arm64/kvm/hyp/nvhe/power/scmi.c diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile index 8359909bd796..583a1f920c81 100644 --- a/arch/arm64/kvm/hyp/nvhe/Makefile +++ b/arch/arm64/kvm/hyp/nvhe/Makefile @@ -32,6 +32,7 @@ hyp-obj-$(CONFIG_KVM_IOMMU) += iommu/iommu.o hyp-obj-$(CONFIG_ARM_SMMU_V3_PKVM) += iommu/arm-smmu-v3.o hyp-obj-$(CONFIG_ARM_SMMU_V3_PKVM) += iommu/io-pgtable-arm.o \ ../../../../../drivers/iommu/io-pgtable-arm-common.o +hyp-obj-y += power/scmi.o ## ## Build rules for compiling nVHE hyp code diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h index 0226a719e28f..91b792d1c074 100644 --- a/arch/arm64/include/asm/kvm_hyp.h +++ b/arch/arm64/include/asm/kvm_hyp.h @@ -104,6 +104,7 @@ void deactivate_traps_vhe_put(struct kvm_vcpu *vcpu); u64 __guest_enter(struct kvm_vcpu *vcpu); bool kvm_host_psci_handler(struct kvm_cpu_context *host_ctxt); +bool kvm_host_scmi_handler(struct kvm_cpu_context *host_ctxt); #ifdef __KVM_NVHE_HYPERVISOR__ void __noreturn __hyp_do_panic(struct kvm_cpu_context *host_ctxt, u64 spsr, diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h index 746dc1c05a8e..1025354b4650 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h @@ -8,6 +8,7 @@ #define __ARM64_KVM_NVHE_PKVM_H__ #include +#include #include #include @@ -112,4 +113,29 @@ struct pkvm_hyp_vcpu *pkvm_mpidr_to_hyp_vcpu(struct pkvm_hyp_vm *vm, u64 mpidr); int pkvm_timer_init(void); void pkvm_udelay(unsigned long usecs); +struct kvm_power_domain_ops { + int (*power_on)(struct kvm_power_domain *pd); + int (*power_off)(struct kvm_power_domain *pd); +}; + +int pkvm_init_scmi_pd(struct kvm_power_domain *pd, + const struct kvm_power_domain_ops *ops); + +/* + * Register a power domain. When the hypervisor catches power requests from the + * host for this power domain, it calls the power ops with @pd as argument. + */ +static inline int pkvm_init_power_domain(struct kvm_power_domain *pd, + const struct kvm_power_domain_ops *ops) +{ + switch (pd->type) { + case KVM_POWER_DOMAIN_NONE: + return 0; + case KVM_POWER_DOMAIN_ARM_SCMI: + return pkvm_init_scmi_pd(pd, ops); + default: + return -EOPNOTSUPP; + } +} + #endif /* __ARM64_KVM_NVHE_PKVM_H__ */ diff --git a/arch/arm64/kvm/hyp/include/nvhe/trap_handler.h b/arch/arm64/kvm/hyp/include/nvhe/trap_handler.h index 1e6d995968a1..0e6bb92ccdb7 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/trap_handler.h +++ b/arch/arm64/kvm/hyp/include/nvhe/trap_handler.h @@ -15,4 +15,6 @@ #define DECLARE_REG(type, name, ctxt, reg) \ type name = (type)cpu_reg(ctxt, (reg)) +void __kvm_hyp_host_forward_smc(struct kvm_cpu_context *host_ctxt); + #endif /* __ARM64_KVM_NVHE_TRAP_HANDLER_H__ */ diff --git a/include/kvm/power_domain.h b/include/kvm/power_domain.h new file mode 100644 index 000000000000..3dcb40005a04 --- /dev/null +++ b/include/kvm/power_domain.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __KVM_POWER_DOMAIN_H +#define __KVM_POWER_DOMAIN_H + +enum kvm_power_domain_type { + KVM_POWER_DOMAIN_NONE, + KVM_POWER_DOMAIN_ARM_SCMI, +}; + +struct kvm_power_domain { + enum kvm_power_domain_type type; + union { + struct { + u32 smc_id; + u32 domain_id; + phys_addr_t shmem_base; + size_t shmem_size; + } arm_scmi; + }; +}; + +#endif /* __KVM_POWER_DOMAIN_H */ diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c index 34ec46b890f0..ad0877e6ea54 100644 --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c @@ -37,8 +37,6 @@ DEFINE_PER_CPU(struct kvm_nvhe_init_params, kvm_init_params); struct kvm_iommu_ops kvm_iommu_ops; -void __kvm_hyp_host_forward_smc(struct kvm_cpu_context *host_ctxt); - typedef void (*hyp_entry_exit_handler_fn)(struct pkvm_hyp_vcpu *); static void handle_pvm_entry_wfx(struct pkvm_hyp_vcpu *hyp_vcpu) @@ -1217,6 +1215,8 @@ static void handle_host_smc(struct kvm_cpu_context *host_ctxt) bool handled; handled = kvm_host_psci_handler(host_ctxt); + if (!handled) + handled = kvm_host_scmi_handler(host_ctxt); if (!handled) default_host_smc_handler(host_ctxt); diff --git a/arch/arm64/kvm/hyp/nvhe/power/scmi.c b/arch/arm64/kvm/hyp/nvhe/power/scmi.c new file mode 100644 index 000000000000..e9ac33f3583c --- /dev/null +++ b/arch/arm64/kvm/hyp/nvhe/power/scmi.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Linaro Ltd. + */ + +#include + +#include +#include +#include +#include + +/* SCMI protocol */ +#define SCMI_PROTOCOL_POWER_DOMAIN 0x11 + +/* shmem registers */ +#define SCMI_SHM_CHANNEL_STATUS 0x4 +#define SCMI_SHM_CHANNEL_FLAGS 0x10 +#define SCMI_SHM_LENGTH 0x14 +#define SCMI_SHM_MESSAGE_HEADER 0x18 +#define SCMI_SHM_MESSAGE_PAYLOAD 0x1c + +/* channel status */ +#define SCMI_CHN_FREE (1U << 0) +#define SCMI_CHN_ERROR (1U << 1) + +/* channel flags */ +#define SCMI_CHN_IRQ (1U << 0) + +/* message header */ +#define SCMI_HDR_TOKEN GENMASK(27, 18) +#define SCMI_HDR_PROTOCOL_ID GENMASK(17, 10) +#define SCMI_HDR_MESSAGE_TYPE GENMASK(9, 8) +#define SCMI_HDR_MESSAGE_ID GENMASK(7, 0) + +/* power domain */ +#define SCMI_PD_STATE_SET 0x4 +#define SCMI_PD_STATE_SET_FLAGS 0x0 +#define SCMI_PD_STATE_SET_DOMAIN_ID 0x4 +#define SCMI_PD_STATE_SET_POWER_STATE 0x8 + +#define SCMI_PD_STATE_SET_STATUS 0x0 + +#define SCMI_PD_STATE_SET_FLAGS_ASYNC (1U << 0) + +#define SCMI_PD_POWER_ON 0 +#define SCMI_PD_POWER_OFF (1U << 30) + +#define SCMI_SUCCESS 0 + + +static struct { + u32 smc_id; + phys_addr_t shmem_pfn; + size_t shmem_size; + void __iomem *shmem; +} scmi_channel; + +#define MAX_POWER_DOMAINS 16 + +struct scmi_power_domain { + struct kvm_power_domain *pd; + const struct kvm_power_domain_ops *ops; +}; + +static struct scmi_power_domain scmi_power_domains[MAX_POWER_DOMAINS]; +static int scmi_power_domain_count; + +#define SCMI_POLL_TIMEOUT_US 1000000 /* 1s! */ + +/* Forward the command to EL3, and wait for completion */ +static int scmi_run_command(struct kvm_cpu_context *host_ctxt) +{ + u32 reg; + unsigned long i = 0; + + __kvm_hyp_host_forward_smc(host_ctxt); + + do { + reg = readl_relaxed(scmi_channel.shmem + SCMI_SHM_CHANNEL_STATUS); + if (reg & SCMI_CHN_FREE) + break; + + if (WARN_ON(++i > SCMI_POLL_TIMEOUT_US)) + return -ETIMEDOUT; + + pkvm_udelay(1); + } while (!(reg & (SCMI_CHN_FREE | SCMI_CHN_ERROR))); + + if (reg & SCMI_CHN_ERROR) + return -EIO; + + reg = readl_relaxed(scmi_channel.shmem + SCMI_SHM_MESSAGE_PAYLOAD + + SCMI_PD_STATE_SET_STATUS); + if (reg != SCMI_SUCCESS) + return -EIO; + + return 0; +} + +static void __kvm_host_scmi_handler(struct kvm_cpu_context *host_ctxt) +{ + int i; + u32 reg; + struct scmi_power_domain *scmi_pd = NULL; + + /* + * FIXME: the spec does not really allow for an intermediary filtering + * messages on the channel: as soon as the host clears SCMI_CHN_FREE, + * the server may process the message. It doesn't have to wait for a + * doorbell and could just poll on the shared mem. Unlikely in practice, + * but this code is not correct without a spec change requiring the + * server to observe an SMC before processing the message. + */ + reg = readl_relaxed(scmi_channel.shmem + SCMI_SHM_CHANNEL_STATUS); + if (reg & (SCMI_CHN_FREE | SCMI_CHN_ERROR)) + return; + + reg = readl_relaxed(scmi_channel.shmem + SCMI_SHM_MESSAGE_HEADER); + if (FIELD_GET(SCMI_HDR_PROTOCOL_ID, reg) != SCMI_PROTOCOL_POWER_DOMAIN) + goto out_forward_smc; + + if (FIELD_GET(SCMI_HDR_MESSAGE_ID, reg) != SCMI_PD_STATE_SET) + goto out_forward_smc; + + reg = readl_relaxed(scmi_channel.shmem + SCMI_SHM_MESSAGE_PAYLOAD + + SCMI_PD_STATE_SET_FLAGS); + if (WARN_ON(reg & SCMI_PD_STATE_SET_FLAGS_ASYNC)) + /* We don't support async requests at the moment */ + return; + + reg = readl_relaxed(scmi_channel.shmem + SCMI_SHM_MESSAGE_PAYLOAD + + SCMI_PD_STATE_SET_DOMAIN_ID); + + for (i = 0; i < MAX_POWER_DOMAINS; i++) { + if (!scmi_power_domains[i].pd) + break; + + if (reg == scmi_power_domains[i].pd->arm_scmi.domain_id) { + scmi_pd = &scmi_power_domains[i]; + break; + } + } + if (!scmi_pd) + goto out_forward_smc; + + reg = readl_relaxed(scmi_channel.shmem + SCMI_SHM_MESSAGE_PAYLOAD + + SCMI_PD_STATE_SET_POWER_STATE); + switch (reg) { + case SCMI_PD_POWER_ON: + if (scmi_run_command(host_ctxt)) + break; + + scmi_pd->ops->power_on(scmi_pd->pd); + break; + case SCMI_PD_POWER_OFF: + scmi_pd->ops->power_off(scmi_pd->pd); + + if (scmi_run_command(host_ctxt)) + scmi_pd->ops->power_on(scmi_pd->pd); + break; + } + return; + +out_forward_smc: + __kvm_hyp_host_forward_smc(host_ctxt); +} + +bool kvm_host_scmi_handler(struct kvm_cpu_context *host_ctxt) +{ + DECLARE_REG(u64, func_id, host_ctxt, 0); + + if (!scmi_channel.shmem || func_id != scmi_channel.smc_id) + return false; /* Unhandled */ + + /* + * Prevent the host from modifying the request while it is in flight. + * One page is enough, SCMI messages are smaller than that. + * + * FIXME: the host is allowed to poll the shmem while the request is in + * flight, or read shmem when receiving the SCMI interrupt. Although + * it's unlikely with the SMC-based transport, this too requires some + * tightening in the spec. + */ + if (WARN_ON(__pkvm_host_add_remove_page(scmi_channel.shmem_pfn, true))) + return true; + + __kvm_host_scmi_handler(host_ctxt); + + WARN_ON(__pkvm_host_add_remove_page(scmi_channel.shmem_pfn, false)); + return true; /* Handled */ +} + +int pkvm_init_scmi_pd(struct kvm_power_domain *pd, + const struct kvm_power_domain_ops *ops) +{ + int ret; + + if (!IS_ALIGNED(pd->arm_scmi.shmem_base, PAGE_SIZE) || + pd->arm_scmi.shmem_size < PAGE_SIZE) { + return -EINVAL; + } + + if (!scmi_channel.shmem) { + unsigned long shmem; + + /* FIXME: Do we need to mark those pages shared in the host s2? */ + ret = __pkvm_create_private_mapping(pd->arm_scmi.shmem_base, + pd->arm_scmi.shmem_size, + PAGE_HYP_DEVICE, + &shmem); + if (ret) + return ret; + + scmi_channel.smc_id = pd->arm_scmi.smc_id; + scmi_channel.shmem_pfn = hyp_phys_to_pfn(pd->arm_scmi.shmem_base); + scmi_channel.shmem = (void *)shmem; + + } else if (scmi_channel.shmem_pfn != + hyp_phys_to_pfn(pd->arm_scmi.shmem_base) || + scmi_channel.smc_id != pd->arm_scmi.smc_id) { + /* We support a single channel at the moment */ + return -ENXIO; + } + + if (scmi_power_domain_count == MAX_POWER_DOMAINS) + return -ENOSPC; + + scmi_power_domains[scmi_power_domain_count].pd = pd; + scmi_power_domains[scmi_power_domain_count].ops = ops; + scmi_power_domain_count++; + return 0; +}