From 807fa4bbffa6e81abfde5302406795c23545f1f9 Mon Sep 17 00:00:00 2001 From: Yiwei Yang Date: Thu, 13 Aug 2026 01:05:37 +0000 Subject: [PATCH] multikernel: manage LLC way isolation through device tree --- Documentation/multikernel/usage.rst | 42 ++++++ arch/x86/include/asm/multikernel.h | 3 + arch/x86/kernel/asm-offsets.c | 3 + arch/x86/kernel/cpu/resctrl/core.c | 56 +++++-- arch/x86/multikernel/direct_boot.S | 28 ++++ arch/x86/multikernel/spawn.c | 10 ++ fs/resctrl/rdtgroup.c | 219 +++++++++++++++++++++++++++- include/linux/multikernel.h | 37 ++++- include/linux/resctrl.h | 28 ++++ kernel/multikernel/baseline.c | 50 +++++++ kernel/multikernel/core.c | 122 ++++++++++++++++ kernel/multikernel/dts.c | 118 +++++++++++++++ kernel/multikernel/instance_dt.c | 5 + kernel/multikernel/internal.h | 1 + kernel/multikernel/kernfs.c | 1 + 15 files changed, 711 insertions(+), 12 deletions(-) diff --git a/Documentation/multikernel/usage.rst b/Documentation/multikernel/usage.rst index a2ec8d56ca1d06..4680fa511ff384 100644 --- a/Documentation/multikernel/usage.rst +++ b/Documentation/multikernel/usage.rst @@ -47,6 +47,7 @@ Phase 1: Instance Creation (Automatic from DTB) resources { cpus = <1>; memory-bytes = <0x20000000>; // 512MB + llc-way-mask = <0x0000ffff>; // Lower half of LLC ways }; }; @@ -142,6 +143,7 @@ The multikernel device tree uses the ``/instances`` structure with ``multikernel resources { cpus = <1>; // CPU ID 1 memory-bytes = <0x20000000>; // 512MB + llc-way-mask = <0x0000ffff>; // resctrl L3 CBM }; }; @@ -178,6 +180,7 @@ When viewing an instance's ``device_tree_source``, it appears in per-instance fo resources { cpus = <1>; memory-bytes = <0x20000000>; // 512 MB + llc-way-mask = <0x0000ffff>; }; }; @@ -186,12 +189,51 @@ Resource Properties - **cpus**: Array of CPU IDs to assign to this instance - **memory-bytes**: Memory size in bytes (must be page-aligned) +- **llc-way-mask**: Optional 32-bit resctrl L3 capacity bitmask. The baseline + value defines the LLC ways available to the multikernel pool; every instance + value must be a non-zero, non-overlapping subset of that pool. - **id**: Unique instance identifier used for kexec operations +LLC Way Isolation +----------------- + +``llc-way-mask`` uses the same bit numbering as the L3 CBM in a resctrl +``schemata`` file. Mount resctrl before applying a baseline that contains an +LLC mask, and keep it mounted while any LLC-partitioned instance exists. For +example:: + + mkdir -p /dev/resctrl + mount -t resctrl none /dev/resctrl + +The baseline mask is the pool reserved for spawn kernels. Multikernel assigns +the complementary supported mask to the host's default resctrl group. For a +16-way LLC, this reserves the upper half for multikernel and leaves the lower +half to the host:: + + /dts-v1/; + / { + compatible = "multikernel-v1"; + resources { + cpus = <0x0 0x1>; + memory-base = /bits/ 64 <0x100000000>; + memory-bytes = /bits/ 64 <0x40000000>; + llc-way-mask = <0xff00>; + }; + }; + +An instance then requests a non-overlapping subset of ``0xff00``. The host +allocates a resctrl CLOSID, programs the mask on every L3 domain, and records +the CLOSID in the generated instance DTB. ``linux,resctrl-closid`` is internal +host-generated metadata and must not be supplied in an input DTB. A spawn +kernel inherits that CLOSID and does not reset the package-wide CAT registers. +Mounting resctrl inside a spawn kernel is rejected because those registers are +shared with its parent. + The system validates that: - CPU IDs are valid and available - Memory requests don't exceed available multikernel pool +- LLC way masks stay within the baseline pool and don't overlap another instance - Instance IDs are unique - All values are properly aligned diff --git a/arch/x86/include/asm/multikernel.h b/arch/x86/include/asm/multikernel.h index f85b2a831aa205..0a903d4e8dfae4 100644 --- a/arch/x86/include/asm/multikernel.h +++ b/arch/x86/include/asm/multikernel.h @@ -90,6 +90,9 @@ struct mk_spawn_context { u32 target_apic_id; /* Target CPU's APIC ID */ u32 flags; /* MK_SPAWN_F_* flags */ u32 ready; /* Signal flag */ + u32 resctrl_closid; /* CLOSID to install before dispatch */ + u32 resctrl_l3_mask; /* L3 CBM to install before dispatch */ + u32 resctrl_l3_cdp; /* L3 CDP uses two CBM registers */ u32 reserved; /* Padding for alignment */ /* Variable-size struct last - size depends on kernel config */ struct boot_params bp; /* Standard x86 boot params */ diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c index 9132e06eb297cd..477fe6e700456f 100644 --- a/arch/x86/kernel/asm-offsets.c +++ b/arch/x86/kernel/asm-offsets.c @@ -64,6 +64,9 @@ static void __used common(void) OFFSET(MK_CTX_target_apic_id, mk_spawn_context, target_apic_id); OFFSET(MK_CTX_flags, mk_spawn_context, flags); OFFSET(MK_CTX_ready, mk_spawn_context, ready); + OFFSET(MK_CTX_resctrl_closid, mk_spawn_context, resctrl_closid); + OFFSET(MK_CTX_resctrl_l3_mask, mk_spawn_context, resctrl_l3_mask); + OFFSET(MK_CTX_resctrl_l3_cdp, mk_spawn_context, resctrl_l3_cdp); OFFSET(MK_CTX_bp, mk_spawn_context, bp); #endif diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c index 7667cf7c4e9454..4ec6df78e8bf9a 100644 --- a/arch/x86/kernel/cpu/resctrl/core.c +++ b/arch/x86/kernel/cpu/resctrl/core.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -409,13 +410,48 @@ static int domain_setup_ctrlval(struct rdt_resource *r, struct rdt_ctrl_domain * return -ENOMEM; hw_dom->ctrl_val = dc; - setup_default_ctrlval(r, dc); + if (multikernel_is_spawn_kernel()) { + u32 closid = multikernel_resctrl_closid(); + u32 mask = multikernel_resctrl_l3_mask(); + int i; + + /* Import the package-wide controls programmed by the parent. */ + for (i = 0; i < hw_res->num_closid; i++) + rdmsrq(hw_res->msr_base + i, dc[i]); + + /* + * A parent may have no online CPU in this LLC domain after + * donating the whole domain. Program our own slot locally as + * the first CPU in the domain comes online. + */ + if (r->rid == RDT_RESOURCE_L3 && mask) { + m.res = r; + m.dom = d; + if (resctrl_arch_get_cdp_enabled(RDT_RESOURCE_L3)) { + m.low = resctrl_get_config_index(closid, CDP_DATA); + m.high = m.low + 2; + if (m.high > hw_res->num_closid) + return -ERANGE; + dc[m.low] = mask; + dc[m.low + 1] = mask; + } else { + if (closid >= hw_res->num_closid) + return -ERANGE; + m.low = closid; + m.high = closid + 1; + dc[closid] = mask; + } + hw_res->msr_update(&m); + } + } else { + setup_default_ctrlval(r, dc); - m.res = r; - m.dom = d; - m.low = 0; - m.high = hw_res->num_closid; - hw_res->msr_update(&m); + m.res = r; + m.dom = d; + m.low = 0; + m.high = hw_res->num_closid; + hw_res->msr_update(&m); + } return 0; } @@ -724,13 +760,13 @@ static void domain_remove_cpu(int cpu, struct rdt_resource *r) static void clear_closid_rmid(int cpu) { struct resctrl_pqr_state *state = this_cpu_ptr(&pqr_state); + u32 closid = multikernel_resctrl_closid(); - state->default_closid = RESCTRL_RESERVED_CLOSID; + state->default_closid = closid; state->default_rmid = RESCTRL_RESERVED_RMID; - state->cur_closid = RESCTRL_RESERVED_CLOSID; + state->cur_closid = closid; state->cur_rmid = RESCTRL_RESERVED_RMID; - wrmsr(MSR_IA32_PQR_ASSOC, RESCTRL_RESERVED_RMID, - RESCTRL_RESERVED_CLOSID); + wrmsr(MSR_IA32_PQR_ASSOC, RESCTRL_RESERVED_RMID, closid); } static int resctrl_arch_online_cpu(unsigned int cpu) diff --git a/arch/x86/multikernel/direct_boot.S b/arch/x86/multikernel/direct_boot.S index f326124a2a1e7e..54eafb58700e9f 100644 --- a/arch/x86/multikernel/direct_boot.S +++ b/arch/x86/multikernel/direct_boot.S @@ -351,6 +351,34 @@ SYM_CODE_START_LOCAL_NOALIGN(mk_park_identity) cmpl %r13d, %eax jne .Lpark_relax + /* Program this LLC domain and select it before kernel code executes. */ + movl MK_CTX_resctrl_closid(%r12), %r10d + testl %r10d, %r10d + jz .Lpark_closid_done + movl MK_CTX_resctrl_l3_mask(%r12), %eax + testl %eax, %eax + jz .Lpark_select_closid + movl %r10d, %ecx + cmpl $0, MK_CTX_resctrl_l3_cdp(%r12) + je .Lpark_write_l3 + shll $1, %ecx + addl $MSR_IA32_L3_CBM_BASE, %ecx + xorl %edx, %edx + wrmsr + incl %ecx + wrmsr + jmp .Lpark_select_closid +.Lpark_write_l3: + addl $MSR_IA32_L3_CBM_BASE, %ecx + xorl %edx, %edx + wrmsr +.Lpark_select_closid: + movl $MSR_IA32_PQR_ASSOC, %ecx + rdmsr + movl %r10d, %edx + wrmsr +.Lpark_closid_done: + /* * Copy the publication into registers BEFORE claiming: once the * claim clears ready, the publisher may reuse the slot for the diff --git a/arch/x86/multikernel/spawn.c b/arch/x86/multikernel/spawn.c index 924a3fa5137d7d..e4f5ecd86d6f65 100644 --- a/arch/x86/multikernel/spawn.c +++ b/arch/x86/multikernel/spawn.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -328,6 +329,9 @@ int mk_spawn_cpu(struct mk_instance *instance, int cpu, slot->kernel_entry = ctx->kernel_entry; slot->trampoline_phys = ctx->trampoline_phys; slot->self_phys = virt_to_phys(ctx); + slot->resctrl_closid = ctx->resctrl_closid; + slot->resctrl_l3_mask = ctx->resctrl_l3_mask; + slot->resctrl_l3_cdp = ctx->resctrl_l3_cdp; slot->flags = 0; ret = mk_slot_wake(slot, apic_id, "first spawn boot cpu"); if (!ret) { @@ -447,6 +451,12 @@ int mk_arch_spawn_instance(struct kimage *image, struct mk_instance *instance, (unsigned long)instance->trampoline_va, virt_to_phys(instance->trampoline_va), virt_to_phys(instance->park_va)); + instance->spawn_ctx->resctrl_closid = instance->resctrl_closid_valid ? + instance->resctrl_closid : 0; + instance->spawn_ctx->resctrl_l3_mask = instance->llc_way_mask_valid ? + instance->llc_way_mask : 0; + instance->spawn_ctx->resctrl_l3_cdp = + resctrl_multikernel_l3_cdp_enabled(); return mk_spawn_cpu(instance, cpu, instance->spawn_ctx); } diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c index 5da305bd36c964..85aa8e06bc3ef9 100644 --- a/fs/resctrl/rdtgroup.c +++ b/fs/resctrl/rdtgroup.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -2788,6 +2789,10 @@ static int rdt_get_tree(struct fs_context *fc) struct rdt_resource *r; int ret; + /* A child kernel shares package-wide control MSRs with its parent. */ + if (multikernel_is_spawn_kernel()) + return -EBUSY; + DO_ONCE_SLEEPABLE(resctrl_arch_pre_mount); cpus_read_lock(); @@ -4094,6 +4099,218 @@ static int rdtgroup_rmdir(struct kernfs_node *kn) return ret; } +/* + * Multikernel uses resctrl as a kernel-side allocator. Keeping this here, + * next to the normal mkdir/schemata/rmdir paths, lets both users share the + * same CLOSID allocator and locking instead of having multikernel program + * architecture MSRs behind resctrl's back. + */ +static bool multikernel_cbm_valid(u32 cbm, struct rdt_resource *r) +{ + unsigned int len = r->cache.cbm_len; + unsigned long first, zero, value = cbm; + u32 supported; + + if (!len || len > 32) + return false; + + supported = len == 32 ? U32_MAX : BIT(len) - 1; + if (!cbm || (cbm & ~supported)) + return false; + + first = find_first_bit(&value, len); + zero = find_next_zero_bit(&value, len, first); + if (!r->cache.arch_has_sparse_bitmasks && + find_next_bit(&value, len, zero) < len) + return false; + + return bitmap_weight(&value, len) >= r->cache.min_cbm_bits; +} + +static int multikernel_set_l3_mask(struct rdtgroup *rdtgrp, u32 mask) +{ + struct resctrl_staged_config *cfg; + struct rdt_ctrl_domain *d; + struct resctrl_schema *s; + struct rdt_resource *r; + bool found = false; + int ret = 0; + + lockdep_assert_cpus_held(); + lockdep_assert_held(&rdtgroup_mutex); + rdt_staged_configs_clear(); + + list_for_each_entry(s, &resctrl_schema_all, list) { + r = s->res; + if (r->rid != RDT_RESOURCE_L3) + continue; + found = true; + + if (rdtgrp->closid >= s->num_closid || + !multikernel_cbm_valid(mask, r)) { + ret = -EINVAL; + goto out; + } + + list_for_each_entry(d, &r->ctrl_domains, hdr.list) { + if (rdtgroup_cbm_overlaps(s, d, mask, rdtgrp->closid, + true)) { + ret = -EBUSY; + goto out; + } + + cfg = &d->staged_config[s->conf_type]; + cfg->new_ctrl = mask; + cfg->have_new_ctrl = true; + } + + ret = resctrl_arch_update_domains(r, rdtgrp->closid); + if (ret) + goto out; + } + + if (!found) + ret = -EOPNOTSUPP; +out: + rdt_staged_configs_clear(); + return ret; +} + +int resctrl_multikernel_set_pool_mask(u32 pool_mask) +{ + struct rdt_resource *l3; + struct rdtgroup *rdtgrp; + u32 supported, host_mask; + int ret; + + if (!resctrl_mounted) + return -ENODEV; + + l3 = resctrl_arch_get_resource(RDT_RESOURCE_L3); + if (!l3 || !l3->alloc_capable || !l3->cache.cbm_len || + l3->cache.cbm_len > 32) + return -EOPNOTSUPP; + + supported = l3->cache.cbm_len == 32 ? U32_MAX : + BIT(l3->cache.cbm_len) - 1; + if (!pool_mask || (pool_mask & ~supported)) + return -EINVAL; + + host_mask = supported & ~pool_mask; + if (!multikernel_cbm_valid(pool_mask, l3) || + !multikernel_cbm_valid(host_mask, l3)) + return -EINVAL; + + rdtgrp = rdtgroup_kn_lock_live(rdtgroup_default.kn); + if (!rdtgrp) + return -ENODEV; + ret = multikernel_set_l3_mask(rdtgrp, host_mask); + rdtgroup_kn_unlock(rdtgroup_default.kn); + + return ret; +} + +int resctrl_multikernel_create_group(const char *name, u32 l3_mask, + u32 *closid) +{ + struct rdtgroup *rdtgrp = NULL; + struct kernfs_node *kn; + bool found = false; + int ret; + + if (!name || !closid || !name[0] || strchr(name, '/') || + strchr(name, '\n')) + return -EINVAL; + if (!resctrl_mounted) + return -ENODEV; + + ret = rdtgroup_mkdir_ctrl_mon(rdtgroup_default.kn, name, 0755); + if (ret) + return ret; + + /* The mkdir path inserted the group while holding rdtgroup_mutex. */ + rdtgrp = rdtgroup_kn_lock_live(rdtgroup_default.kn); + if (!rdtgrp) { + ret = -ENODEV; + goto out_find; + } + list_for_each_entry(rdtgrp, &rdt_all_groups, rdtgroup_list) { + if (rdtgrp != &rdtgroup_default && + !strcmp(rdt_kn_name(rdtgrp->kn), name)) { + found = true; + break; + } + } + if (!found) { + rdtgroup_kn_unlock(rdtgroup_default.kn); + ret = -ENOENT; + goto out_find; + } + kn = rdtgrp->kn; + kernfs_get(kn); + rdtgroup_kn_unlock(rdtgroup_default.kn); + + rdtgrp = rdtgroup_kn_lock_live(kn); + if (!rdtgrp) { + ret = -ENOENT; + goto out_put; + } + ret = multikernel_set_l3_mask(rdtgrp, l3_mask); + if (!ret && rdtgroup_mode_test_exclusive(rdtgrp)) + rdtgrp->mode = RDT_MODE_EXCLUSIVE; + else if (!ret) + ret = -EBUSY; + if (!ret) + *closid = rdtgrp->closid; + rdtgroup_kn_unlock(kn); + if (ret) + rdtgroup_rmdir(kn); +out_put: + kernfs_put(kn); + return ret; + +out_find: + kn = kernfs_find_and_get(rdtgroup_default.kn, name); + if (kn) { + rdtgroup_rmdir(kn); + kernfs_put(kn); + } + return ret; +} + +int resctrl_multikernel_remove_group(u32 closid) +{ + struct rdtgroup *rdtgrp; + struct kernfs_node *kn = NULL; + int ret; + + if (!resctrl_mounted) + return -ENODEV; + + rdtgrp = rdtgroup_kn_lock_live(rdtgroup_default.kn); + if (!rdtgrp) + return -ENODEV; + list_for_each_entry(rdtgrp, &rdt_all_groups, rdtgroup_list) { + if (rdtgrp != &rdtgroup_default && rdtgrp->closid == closid) { + kn = rdtgrp->kn; + kernfs_get(kn); + break; + } + } + rdtgroup_kn_unlock(rdtgroup_default.kn); + if (!kn) + return -ENOENT; + + ret = rdtgroup_rmdir(kn); + kernfs_put(kn); + return ret; +} + +bool resctrl_multikernel_l3_cdp_enabled(void) +{ + return resctrl_arch_get_cdp_enabled(RDT_RESOURCE_L3); +} + /** * mongrp_reparent() - replace parent CTRL_MON group of a MON group * @rdtgrp: the MON group whose parent should be replaced @@ -4274,7 +4491,7 @@ static void rdtgroup_setup_default(void) { mutex_lock(&rdtgroup_mutex); - rdtgroup_default.closid = RESCTRL_RESERVED_CLOSID; + rdtgroup_default.closid = multikernel_resctrl_closid(); rdtgroup_default.mon.rmid = RESCTRL_RESERVED_RMID; rdtgroup_default.type = RDTCTRL_GROUP; INIT_LIST_HEAD(&rdtgroup_default.mon.crdtgrp_list); diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index 2e03447286285b..5b4752a6f8b5bc 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -449,6 +449,12 @@ struct mk_dt_config { /* CPU resources */ struct mk_cpu_set *cpus; /* Set of physical CPU IDs */ + /* Last-level cache allocation (resctrl CAT capacity bitmask) */ + u32 llc_way_mask; + bool llc_way_mask_valid; + u32 resctrl_closid; /* Host-assigned resctrl CLOSID */ + bool resctrl_closid_valid; + /* PCI device resources */ struct list_head pci_devices; /* List of struct mk_pci_device */ int pci_device_count; /* Number of PCI devices */ @@ -460,7 +466,7 @@ struct mk_dt_config { bool platform_devices_valid; /* Whether platform device list is valid */ /* Extensibility: Reserved fields for future use */ - u32 reserved[7]; /* Reduced due to added fields */ + u32 reserved[5]; /* Raw device tree data */ void *dtb_data; @@ -488,6 +494,12 @@ struct mk_instance { /* CPU resources */ struct mk_cpu_set *cpus; /* Set of assigned physical CPU IDs */ + /* Last-level cache allocation (resctrl CAT capacity bitmask) */ + u32 llc_way_mask; + bool llc_way_mask_valid; + u32 resctrl_closid; /* Host-assigned resctrl CLOSID */ + bool resctrl_closid_valid; + /* PCI device resources */ struct list_head pci_devices; /* List of struct mk_pci_device */ int pci_device_count; /* Number of PCI devices */ @@ -748,6 +760,9 @@ struct pci_bus; #ifdef CONFIG_MULTIKERNEL bool multikernel_allow_emergency_restart(void); +bool multikernel_is_spawn_kernel(void); +u32 multikernel_resctrl_closid(void); +u32 multikernel_resctrl_l3_mask(void); int multikernel_halt_by_id(int mk_id); int multikernel_force_halt_by_id(int mk_id); bool cpu_is_multikernel_pool(unsigned int cpu); @@ -781,6 +796,22 @@ static inline bool multikernel_allow_emergency_restart(void) { return true; } + +static inline bool multikernel_is_spawn_kernel(void) +{ + return false; +} + +static inline u32 multikernel_resctrl_closid(void) +{ + return 0; +} + +static inline u32 multikernel_resctrl_l3_mask(void) +{ + return 0; +} + static inline int multikernel_halt_by_id(int mk_id) { return -ENODEV; @@ -844,11 +875,15 @@ static inline void mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len) */ #define MK_DT_RESOURCE_MEMORY "memory-bytes" #define MK_DT_RESOURCE_CPUS "cpus" +#define MK_DT_RESOURCE_LLC_WAYS "llc-way-mask" +#define MK_DT_RESOURCE_CLOSID "linux,resctrl-closid" #define MK_DT_RESOURCE_DEVICES "devices" static const char * const mk_resource_properties[] = { MK_DT_RESOURCE_MEMORY, MK_DT_RESOURCE_CPUS, + MK_DT_RESOURCE_LLC_WAYS, + MK_DT_RESOURCE_CLOSID, MK_DT_RESOURCE_DEVICES, NULL /* Sentinel */ }; diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h index 006e57fd7ca589..9a17a5dc51db26 100644 --- a/include/linux/resctrl.h +++ b/include/linux/resctrl.h @@ -18,6 +18,34 @@ #define RESCTRL_PICK_ANY_CPU -1 +#ifdef CONFIG_RESCTRL_FS +int resctrl_multikernel_set_pool_mask(u32 pool_mask); +int resctrl_multikernel_create_group(const char *name, u32 l3_mask, + u32 *closid); +int resctrl_multikernel_remove_group(u32 closid); +bool resctrl_multikernel_l3_cdp_enabled(void); +#else +static inline int resctrl_multikernel_set_pool_mask(u32 pool_mask) +{ + return -EOPNOTSUPP; +} + +static inline int resctrl_multikernel_create_group(const char *name, u32 l3_mask, u32 *closid) +{ + return -EOPNOTSUPP; +} + +static inline int resctrl_multikernel_remove_group(u32 closid) +{ + return -EOPNOTSUPP; +} + +static inline bool resctrl_multikernel_l3_cdp_enabled(void) +{ + return false; +} +#endif + #ifdef CONFIG_PROC_CPU_RESCTRL int proc_resctrl_show(struct seq_file *m, diff --git a/kernel/multikernel/baseline.c b/kernel/multikernel/baseline.c index d4e2dd9be3a54c..57758a2112f731 100644 --- a/kernel/multikernel/baseline.c +++ b/kernel/multikernel/baseline.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "internal.h" @@ -29,6 +30,38 @@ */ struct mk_cpu_set *mk_cpu_pool; +static int mk_baseline_parse_llc_ways(const void *fdt, int resources_node, + struct mk_instance *instance) +{ + const fdt32_t *prop; + u32 mask; + int len; + + prop = fdt_getprop(fdt, resources_node, MK_DT_RESOURCE_LLC_WAYS, + &len); + if (!prop) + return 0; + + if (len != sizeof(*prop)) { + pr_err("Invalid '%s' length: %d (must be one 32-bit cell)\n", + MK_DT_RESOURCE_LLC_WAYS, len); + return -EINVAL; + } + + mask = fdt32_to_cpu(*prop); + if (!mask) { + pr_err("Invalid zero '%s' in baseline\n", + MK_DT_RESOURCE_LLC_WAYS); + return -EINVAL; + } + + instance->llc_way_mask = mask; + instance->llc_way_mask_valid = true; + pr_info("Baseline LLC way pool: 0x%08x\n", mask); + + return 0; +} + static int mk_baseline_parse_cpus(const void *fdt, int resources_node) { const fdt64_t *prop; @@ -190,6 +223,8 @@ static void mk_baseline_clear_resources(struct mk_instance *instance) } instance->platform_device_count = 0; instance->platform_devices_valid = false; + instance->llc_way_mask = 0; + instance->llc_way_mask_valid = false; } static int mk_baseline_validate_cpus(void) @@ -605,6 +640,12 @@ int mk_baseline_validate_and_initialize(const void *fdt, size_t fdt_size) return ret; } + ret = mk_baseline_parse_llc_ways(fdt, resources_node, root_instance); + if (ret) { + pr_err("Failed to parse baseline LLC ways: %d\n", ret); + return ret; + } + ret = mk_baseline_parse_devices(fdt, resources_node, root_instance); if (ret) { pr_err("Failed to parse baseline devices: %d\n", ret); @@ -635,6 +676,15 @@ int mk_baseline_validate_and_initialize(const void *fdt, size_t fdt_size) return ret; } + if (root_instance->llc_way_mask_valid) { + ret = resctrl_multikernel_set_pool_mask(root_instance->llc_way_mask); + if (ret) { + pr_err("Failed to reserve baseline LLC way pool 0x%08x through resctrl: %d\n", + root_instance->llc_way_mask, ret); + return ret; + } + } + ret = mk_baseline_initialize_cpus(); if (ret) { pr_err("Baseline CPU initialization failed: %d\n", ret); diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index d92d4fe702bc1b..7eff39784c5da4 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -12,9 +12,36 @@ #include #include #include +#include #include #include "internal.h" +bool multikernel_is_spawn_kernel(void) +{ + return root_instance && root_instance->id != 0; +} +EXPORT_SYMBOL_GPL(multikernel_is_spawn_kernel); + +u32 multikernel_resctrl_closid(void) +{ + if (!multikernel_is_spawn_kernel() || + !root_instance->resctrl_closid_valid) + return RESCTRL_RESERVED_CLOSID; + + return root_instance->resctrl_closid; +} +EXPORT_SYMBOL_GPL(multikernel_resctrl_closid); + +u32 multikernel_resctrl_l3_mask(void) +{ + if (!multikernel_is_spawn_kernel() || + !root_instance->llc_way_mask_valid) + return 0; + + return root_instance->llc_way_mask; +} +EXPORT_SYMBOL_GPL(multikernel_resctrl_l3_mask); + static void mk_instance_return_all_cpus(struct mk_instance *instance) { if (!instance || mk_cpu_set_empty(instance->cpus)) @@ -26,6 +53,94 @@ static void mk_instance_return_all_cpus(struct mk_instance *instance) mk_instance_return_cpus(instance, instance->cpus); } +static int mk_instance_reserve_llc_ways(struct mk_instance *instance, + const struct mk_dt_config *config) +{ + struct mk_instance *other; + char group_name[32]; + u32 outside_pool; + int ret; + + instance->llc_way_mask = 0; + instance->llc_way_mask_valid = false; + instance->resctrl_closid = 0; + instance->resctrl_closid_valid = false; + + if (!config->llc_way_mask_valid) + return 0; + + if (!config->llc_way_mask) + return -EINVAL; + + if (!root_instance || !root_instance->llc_way_mask_valid) { + pr_err("Instance %d (%s): LLC mask requires a baseline llc-way-mask pool\n", + instance->id, instance->name); + return -EINVAL; + } + + if (root_instance->llc_way_mask_valid) { + outside_pool = config->llc_way_mask & + ~root_instance->llc_way_mask; + if (outside_pool) { + pr_err("Instance %d (%s): LLC mask 0x%08x uses ways outside baseline pool 0x%08x\n", + instance->id, instance->name, + config->llc_way_mask, + root_instance->llc_way_mask); + return -ERANGE; + } + } + + list_for_each_entry(other, &mk_instance_list, list) { + if (other == instance || other == root_instance || + !other->llc_way_mask_valid) + continue; + + if (config->llc_way_mask & other->llc_way_mask) { + pr_err("Instance %d (%s): LLC mask 0x%08x overlaps instance %d (%s) mask 0x%08x\n", + instance->id, instance->name, + config->llc_way_mask, other->id, other->name, + other->llc_way_mask); + return -EBUSY; + } + } + + instance->llc_way_mask = config->llc_way_mask; + instance->llc_way_mask_valid = true; + snprintf(group_name, sizeof(group_name), "multikernel-%d", instance->id); + ret = resctrl_multikernel_create_group(group_name, + instance->llc_way_mask, + &instance->resctrl_closid); + if (ret) { + pr_err("Instance %d (%s): failed to create resctrl group: %d\n", + instance->id, instance->name, ret); + instance->llc_way_mask = 0; + instance->llc_way_mask_valid = false; + return ret; + } + instance->resctrl_closid_valid = true; + + return 0; +} + +void mk_instance_release_llc_ways(struct mk_instance *instance) +{ + int ret; + + if (!instance || !instance->resctrl_closid_valid) + return; + + ret = resctrl_multikernel_remove_group(instance->resctrl_closid); + if (ret && ret != -ENOENT && ret != -ENODEV) + pr_warn("Instance %d (%s): failed to remove resctrl CLOSID %u: %d\n", + instance->id, instance->name, instance->resctrl_closid, + ret); + + instance->resctrl_closid = 0; + instance->resctrl_closid_valid = false; + instance->llc_way_mask = 0; + instance->llc_way_mask_valid = false; +} + static void mk_instance_return_pci_devices(struct mk_instance *instance) { struct mk_pci_device *pci_dev, *pci_tmp; @@ -141,6 +256,7 @@ static void mk_instance_release(struct kref *kref) instance->id, instance->name); mk_instance_return_all_cpus(instance); + mk_instance_release_llc_ways(instance); mk_instance_return_pci_devices(instance); mk_instance_return_platform_devices(instance); mk_instance_free_memory(instance); @@ -970,6 +1086,12 @@ int mk_instance_reserve_resources(struct mk_instance *instance, return ret; } + ret = mk_instance_reserve_llc_ways(instance, config); + if (ret) { + mk_instance_free_memory(instance); + return ret; + } + /* Reserve CPU resources */ ret = mk_instance_reserve_cpus(instance, config); if (ret) { diff --git a/kernel/multikernel/dts.c b/kernel/multikernel/dts.c index 51950eae735129..0be2673f713403 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "internal.h" @@ -104,6 +105,10 @@ static int mk_dt_parse_memory(const void *fdt, int chosen_node, struct mk_dt_config *config); static int mk_dt_parse_cpus(const void *fdt, int chosen_node, struct mk_dt_config *config); +static int mk_dt_parse_llc_ways(const void *fdt, int resources_node, + struct mk_dt_config *config); +static int mk_dt_parse_closid(const void *fdt, int resources_node, + struct mk_dt_config *config); static int mk_dt_parse_devices(const void *fdt, int chosen_node, struct mk_dt_config *config); static int mk_dt_validate_memory(const struct mk_dt_config *config); @@ -202,6 +207,70 @@ static int mk_dt_parse_cpus(const void *fdt, int chosen_node, return 0; } +/** + * mk_dt_parse_llc_ways() - Parse an optional resctrl LLC capacity mask + * + * The mask uses the same least-significant-bit-first representation as the + * L3 CBM in resctrl's schemata file. A baseline mask describes the ways + * available to the multikernel pool; an instance mask must be a non-overlapping + * subset of that pool mask. + */ +static int mk_dt_parse_llc_ways(const void *fdt, int resources_node, + struct mk_dt_config *config) +{ + const fdt32_t *prop; + int len; + u32 mask; + + prop = fdt_getprop(fdt, resources_node, MK_DT_RESOURCE_LLC_WAYS, + &len); + if (!prop) + return 0; + + if (len != sizeof(*prop)) { + pr_err("Invalid %s property length: %d (must be one 32-bit cell)\n", + MK_DT_RESOURCE_LLC_WAYS, len); + return -EINVAL; + } + + mask = fdt32_to_cpu(*prop); + if (!mask) { + pr_err("Invalid zero %s\n", MK_DT_RESOURCE_LLC_WAYS); + return -EINVAL; + } + + config->llc_way_mask = mask; + config->llc_way_mask_valid = true; + pr_info("Successfully parsed LLC way mask: 0x%08x\n", mask); + + return 0; +} + +/* Host-generated metadata consumed only from a per-instance manifest DTB. */ +static int mk_dt_parse_closid(const void *fdt, int resources_node, + struct mk_dt_config *config) +{ + const fdt32_t *prop; + int len; + + prop = fdt_getprop(fdt, resources_node, MK_DT_RESOURCE_CLOSID, &len); + if (!prop) + return 0; + if (len != sizeof(*prop)) { + pr_err("Invalid %s property length: %d\n", + MK_DT_RESOURCE_CLOSID, len); + return -EINVAL; + } + + config->resctrl_closid = fdt32_to_cpu(*prop); + if (config->resctrl_closid == RESCTRL_RESERVED_CLOSID) { + pr_err("Invalid reserved CLOSID in %s\n", MK_DT_RESOURCE_CLOSID); + return -EINVAL; + } + config->resctrl_closid_valid = true; + return 0; +} + static int mk_dt_parse_single_pci_device(const void *source_fdt, int dev_node, struct mk_dt_config *config, const char *device_name) @@ -557,6 +626,26 @@ int mk_dt_parse(const void *dtb_data, size_t dtb_size, return ret; } + ret = mk_dt_parse_llc_ways(fdt, resources_node, config); + if (ret) { + pr_err("Failed to parse LLC way resources: %d\n", ret); + mk_dt_config_free(config); + return ret; + } + + ret = mk_dt_parse_closid(fdt, resources_node, config); + if (ret) { + pr_err("Failed to parse resctrl CLOSID: %d\n", ret); + mk_dt_config_free(config); + return ret; + } + if (config->resctrl_closid_valid && !config->llc_way_mask_valid) { + pr_err("%s requires %s\n", MK_DT_RESOURCE_CLOSID, + MK_DT_RESOURCE_LLC_WAYS); + mk_dt_config_free(config); + return -EINVAL; + } + ret = mk_dt_parse_devices(fdt, resources_node, config); if (ret) { pr_err("Failed to parse device resources: %d\n", ret); @@ -607,6 +696,14 @@ int mk_dt_parse_resources(const void *fdt, int resources_node, return ret; } + ret = mk_dt_parse_llc_ways(fdt, resources_node, config); + if (ret) { + pr_err("Failed to parse LLC way resources for '%s': %d\n", + instance_name, ret); + mk_dt_config_free(config); + return ret; + } + ret = mk_dt_parse_devices(fdt, resources_node, config); if (ret) { pr_err("Failed to parse device resources for '%s': %d\n", instance_name, ret); @@ -846,6 +943,13 @@ void mk_dt_print_config(const struct mk_dt_config *config) pr_info(" CPU assignment: unavailable (allocation failed)\n"); } + if (config->llc_way_mask_valid) + pr_info(" LLC way mask: 0x%08x\n", config->llc_way_mask); + else + pr_info(" LLC way mask: none specified\n"); + if (config->resctrl_closid_valid) + pr_info(" resctrl CLOSID: %u\n", config->resctrl_closid); + if (config->pci_devices_valid) { if (config->pci_device_count == 0) { pr_info(" PCI devices: none specified\n"); @@ -983,6 +1087,20 @@ int mk_dt_generate_instance_dtb(struct mk_instance *instance, if (ret) goto err_free; } + if (instance->llc_way_mask_valid) { + ret = fdt_property_u32(fdt, MK_DT_RESOURCE_LLC_WAYS, + instance->llc_way_mask); + if (ret) + goto err_free; + } + + if (instance->resctrl_closid_valid) { + ret = fdt_property_u32(fdt, MK_DT_RESOURCE_CLOSID, + instance->resctrl_closid); + if (ret) + goto err_free; + } + if ((instance->pci_devices_valid && instance->pci_device_count > 0) || (instance->platform_devices_valid && instance->platform_device_count > 0)) { ret = fdt_begin_node(fdt, "devices"); diff --git a/kernel/multikernel/instance_dt.c b/kernel/multikernel/instance_dt.c index f21cc947149f10..4202bda146435b 100644 --- a/kernel/multikernel/instance_dt.c +++ b/kernel/multikernel/instance_dt.c @@ -755,6 +755,11 @@ int __init mk_instance_restore_from_manifest(void) goto cleanup_instance_name; } + instance->llc_way_mask = config.llc_way_mask; + instance->llc_way_mask_valid = config.llc_way_mask_valid; + instance->resctrl_closid = config.resctrl_closid; + instance->resctrl_closid_valid = config.resctrl_closid_valid; + instance->dtb_data = kmalloc(dtb_len, GFP_KERNEL); if (!instance->dtb_data) { pr_err("Failed to allocate memory for DTB restoration\n"); diff --git a/kernel/multikernel/internal.h b/kernel/multikernel/internal.h index 75e4b8d25eb939..29f12f33a69637 100644 --- a/kernel/multikernel/internal.h +++ b/kernel/multikernel/internal.h @@ -12,6 +12,7 @@ int mk_create_instance_from_dtb(const char *name, int id, const void *fdt, int resources_node, size_t dtb_size); struct mk_instance *mk_instance_find_by_name(const char *name); int mk_instance_destroy(struct mk_instance *instance); +void mk_instance_release_llc_ways(struct mk_instance *instance); /* dts.c */ int mk_dt_parse_resources(const void *fdt, int resources_node, diff --git a/kernel/multikernel/kernfs.c b/kernel/multikernel/kernfs.c index a0573fc23a1c99..50d1238e04f771 100644 --- a/kernel/multikernel/kernfs.c +++ b/kernel/multikernel/kernfs.c @@ -333,6 +333,7 @@ int mk_create_instance_from_dtb(const char *name, int id, const void *fdt, kfree(instance->dtb_data); instance->dtb_data = NULL; err_free_resources: + mk_instance_release_llc_ways(instance); mk_instance_free_memory(instance); err_free_idr: idr_remove(&mk_instance_idr, instance->id);