From: Tao Cui <cui.tao@linux.dev>
To: zhaotianrui@loongson.cn, maobibo@loongson.cn
Cc: chenhuacai@kernel.org, kernel@xen0n.name, kvm@vger.kernel.org,
loongarch@lists.linux.dev, linux-kernel@vger.kernel.org,
cui.tao@linux.dev, cuitao@kylinos.cn
Subject: [PATCH v3] LoongArch: KVM: Add KVM_LOONGARCH_GET/SET_CSR for bulk CSR migration
Date: Wed, 22 Jul 2026 22:28:05 +0800 [thread overview]
Message-ID: <20260722142805.278325-1-cui.tao@linux.dev> (raw)
From: Tao Cui <cuitao@kylinos.cn>
Migrating a vCPU's CSR state takes one KVM_GET/SET_ONE_REG ioctl per
register, thousands of syscalls for a large VM.
Add a bulk pair, KVM_LOONGARCH_GET_CSR / KVM_SET_CSR. Userspace passes
an array of {index, data} entries; the kernel fills entries[].data on
GET and applies them on SET, both in a single call:
- KVM_LOONGARCH_GET_CSR does a single vcpu_load/put with
kvm_deliver_intr(), which pulls pending interrupts into ESTAT,
avoiding the per-register load/put side-effect that made ONE_REG
snapshots of ESTAT order-sensitive.
- KVM_LOONGARCH_SET_CSR writes all CSRs via _kvm_setcsr in one pass,
propagates _kvm_setcsr errors, and clears KVM_LARCH_HWCSR_USABLE up
front so a mid-loop failure still forces the next vcpu_load() to
reload from SW, matching KVM_SET_ONE_REG.
Entries are {index, data} rather than a positional array, so future CSRs
can be added without resizing the struct or changing the ioctl number.
CSRs above the core range (debug, breakpoint, PMU) stay on
KVM_GET/SET_ONE_REG; struct kvm_sregs is left empty, so the new ioctls
break no userspace, and userspace probes them via KVM_CAP_LOONGARCH_CSR.
The ioctl hands get_gcsr_flag() a user-controlled index, so switch that
helper (and the set_gcsr_sw_flag/set_gcsr_hw_flag init helpers) to an
unsigned index; the csr < CSR_MAX_NUMS bound check is then robust against
a sign-extended value regardless of the caller.
On a Loongson 3A6000, snapshotting one vCPU's core CSR range (0x0-0x183)
drops from 388 KVM_GET_ONE_REG calls (~2 ms) to a single
KVM_LOONGARCH_GET_CSR ioctl (~6 us).
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
Changes since v2:
- Bound-check the CSR index (< CSR_MAX_NUMS) and make
get_gcsr_flag()/set_gcsr_*_flag() take an unsigned index. An index
with bit 31 set was implicitly converted to a negative signed int,
bypassing the csr < CSR_MAX_NUMS check and causing an out-of-bounds
read/write into the SW CSR shadow.
- Reject non-zero pad/reserved fields with -EINVAL so they stay reserved.
- Add KVM_CAP_LOONGARCH_CSR for userspace feature detection.
arch/loongarch/include/asm/kvm_host.h | 2 +-
arch/loongarch/include/uapi/asm/kvm.h | 13 +++++
arch/loongarch/kvm/main.c | 6 +--
arch/loongarch/kvm/vcpu.c | 77 +++++++++++++++++++++++++++
arch/loongarch/kvm/vm.c | 1 +
include/uapi/linux/kvm.h | 5 ++
6 files changed, 100 insertions(+), 4 deletions(-)
diff --git a/arch/loongarch/include/asm/kvm_host.h b/arch/loongarch/include/asm/kvm_host.h
index 23cfbecebbd7..aacf5d84217f 100644
--- a/arch/loongarch/include/asm/kvm_host.h
+++ b/arch/loongarch/include/asm/kvm_host.h
@@ -362,7 +362,7 @@ extern struct kvm_world_switch *kvm_loongarch_ops;
#define HW_GCSR (1 << 1)
#define INVALID_GCSR (1 << 2)
-int get_gcsr_flag(int csr);
+int get_gcsr_flag(unsigned int csr);
void set_hw_gcsr(int csr_id, unsigned long val);
#endif /* __ASM_LOONGARCH_KVM_HOST_H__ */
diff --git a/arch/loongarch/include/uapi/asm/kvm.h b/arch/loongarch/include/uapi/asm/kvm.h
index cd0b5c11ca9c..59b3b7efa039 100644
--- a/arch/loongarch/include/uapi/asm/kvm.h
+++ b/arch/loongarch/include/uapi/asm/kvm.h
@@ -127,6 +127,19 @@ struct kvm_sync_regs {
struct kvm_sregs {
};
+/* bulk CSR entries for VM migration */
+struct kvm_loongarch_csr_entry {
+ __u32 index;
+ __u32 reserved;
+ __u64 data;
+};
+
+struct kvm_loongarch_csrs {
+ __u32 ncsrs; /* number of csr entries */
+ __u32 pad;
+ __DECLARE_FLEX_ARRAY(struct kvm_loongarch_csr_entry, entries);
+};
+
struct kvm_iocsr_entry {
__u32 addr;
__u32 pad;
diff --git a/arch/loongarch/kvm/main.c b/arch/loongarch/kvm/main.c
index 3e1005526f4b..d802d393cd44 100644
--- a/arch/loongarch/kvm/main.c
+++ b/arch/loongarch/kvm/main.c
@@ -19,7 +19,7 @@ struct kvm_world_switch *kvm_loongarch_ops;
static int gcsr_flag[CSR_MAX_NUMS];
static struct kvm_context __percpu *vmcs;
-int get_gcsr_flag(int csr)
+int get_gcsr_flag(unsigned int csr)
{
if (csr < CSR_MAX_NUMS)
return gcsr_flag[csr];
@@ -27,13 +27,13 @@ int get_gcsr_flag(int csr)
return INVALID_GCSR;
}
-static inline void set_gcsr_sw_flag(int csr)
+static inline void set_gcsr_sw_flag(unsigned int csr)
{
if (csr < CSR_MAX_NUMS)
gcsr_flag[csr] |= SW_GCSR;
}
-static inline void set_gcsr_hw_flag(int csr)
+static inline void set_gcsr_hw_flag(unsigned int csr)
{
if (csr < CSR_MAX_NUMS)
gcsr_flag[csr] |= HW_GCSR;
diff --git a/arch/loongarch/kvm/vcpu.c b/arch/loongarch/kvm/vcpu.c
index 20c207d80e31..2bb6a7e0ea87 100644
--- a/arch/loongarch/kvm/vcpu.c
+++ b/arch/loongarch/kvm/vcpu.c
@@ -1009,6 +1009,77 @@ int kvm_arch_vcpu_ioctl_set_sregs(struct kvm_vcpu *vcpu, struct kvm_sregs *sregs
return -ENOIOCTLCMD;
}
+/* max CSR entries handled per ioctl */
+#define KVM_LOONGARCH_MAX_CSR_REGS 0x400
+
+/* Bulk get/set of CSR state for VM migration. */
+static int kvm_loongarch_csr_io(struct kvm_vcpu *vcpu,
+ struct kvm_loongarch_csrs __user *ucsrs,
+ bool write)
+{
+ int i, ret = 0;
+ unsigned long size;
+ struct kvm_loongarch_csrs csrs;
+ struct kvm_loongarch_csr_entry *entries;
+ struct loongarch_csrs *csr = vcpu->arch.csr;
+
+ if (copy_from_user(&csrs, ucsrs, sizeof(csrs)))
+ return -EFAULT;
+
+ if (csrs.pad)
+ return -EINVAL;
+
+ if (csrs.ncsrs > KVM_LOONGARCH_MAX_CSR_REGS)
+ return -E2BIG;
+
+ size = array_size(csrs.ncsrs, sizeof(*entries));
+ entries = memdup_user(ucsrs->entries, size);
+ if (IS_ERR(entries))
+ return PTR_ERR(entries);
+
+ if (write) {
+ vcpu->arch.aux_inuse &= ~KVM_LARCH_HWCSR_USABLE;
+ } else {
+ preempt_disable();
+ vcpu_load(vcpu);
+ kvm_deliver_intr(vcpu);
+ vcpu->arch.aux_inuse &= ~KVM_LARCH_SWCSR_LATEST;
+ vcpu_put(vcpu);
+ preempt_enable();
+ }
+
+ for (i = 0; i < csrs.ncsrs; i++) {
+ unsigned int id = entries[i].index;
+
+ if (entries[i].reserved || id >= CSR_MAX_NUMS ||
+ (get_gcsr_flag(id) & INVALID_GCSR)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (write) {
+ ret = _kvm_setcsr(vcpu, id, entries[i].data);
+ if (ret)
+ break;
+ } else if (id == LOONGARCH_CSR_ESTAT) {
+ unsigned long estat, gintc;
+
+ /* ESTAT IP0~IP7 come from GINTC */
+ gintc = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_GINTC) & KVM_GINTC_IRQ_MASK;
+ estat = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_ESTAT) & ~KVM_ESTAT_EXTI_MASK;
+ entries[i].data = estat | (gintc << VIP_DELTA);
+ } else {
+ entries[i].data = kvm_read_sw_gcsr(csr, id);
+ }
+ }
+
+ if (!write && copy_to_user(ucsrs->entries, entries, size))
+ ret = -EFAULT;
+
+ kfree(entries);
+ return ret;
+}
+
int kvm_arch_vcpu_ioctl_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs)
{
int i;
@@ -1266,6 +1337,12 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
r = kvm_get_reg(vcpu, ®);
break;
}
+ case KVM_LOONGARCH_GET_CSR:
+ r = kvm_loongarch_csr_io(vcpu, argp, false);
+ break;
+ case KVM_LOONGARCH_SET_CSR:
+ r = kvm_loongarch_csr_io(vcpu, argp, true);
+ break;
case KVM_ENABLE_CAP: {
struct kvm_enable_cap cap;
diff --git a/arch/loongarch/kvm/vm.c b/arch/loongarch/kvm/vm.c
index 1317c718f896..785f2fa0d164 100644
--- a/arch/loongarch/kvm/vm.c
+++ b/arch/loongarch/kvm/vm.c
@@ -122,6 +122,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_IOEVENTFD:
case KVM_CAP_MP_STATE:
case KVM_CAP_SET_GUEST_DEBUG:
+ case KVM_CAP_LOONGARCH_CSR:
r = 1;
break;
case KVM_CAP_NR_VCPUS:
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 419011097fa8..45d9eb06cb8a 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -997,6 +997,7 @@ struct kvm_enable_cap {
#define KVM_CAP_S390_KEYOP 247
#define KVM_CAP_S390_VSIE_ESAMODE 248
#define KVM_CAP_S390_HPAGE_2G 249
+#define KVM_CAP_LOONGARCH_CSR 250
struct kvm_irq_routing_irqchip {
__u32 irqchip;
@@ -1670,4 +1671,8 @@ struct kvm_pre_fault_memory {
__u64 padding[5];
};
+/* LoongArch: bulk CSR get/set for migration */
+#define KVM_LOONGARCH_GET_CSR _IOWR(KVMIO, 0xd6, struct kvm_loongarch_csrs)
+#define KVM_LOONGARCH_SET_CSR _IOW(KVMIO, 0xd7, struct kvm_loongarch_csrs)
+
#endif /* __LINUX_KVM_H */
--
2.43.0
reply other threads:[~2026-07-22 14:28 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260722142805.278325-1-cui.tao@linux.dev \
--to=cui.tao@linux.dev \
--cc=chenhuacai@kernel.org \
--cc=cuitao@kylinos.cn \
--cc=kernel@xen0n.name \
--cc=kvm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=loongarch@lists.linux.dev \
--cc=maobibo@loongson.cn \
--cc=zhaotianrui@loongson.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.