Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
edac816
models: Describe pool memory as kernel chunks plus per-node requests
congwang-mk Aug 21, 2026
e2f6b3c
parser: Read pool chunks and per-node memory requests from the baseline
congwang-mk Aug 21, 2026
b64e634
parser: Scope legacy memory checks to /resources and migrate the exam…
congwang-mk Aug 21, 2026
61144b8
extractor: Describe baseline memory as per-node allocation requests
congwang-mk Aug 21, 2026
d2c9874
pool: Compute the difference between the live pool and a requested ba…
congwang-mk Aug 21, 2026
9da9520
pool: Cover surplus-node release and size ordering in the diff
congwang-mk Aug 21, 2026
203bee0
overlay: Address fragments by target path and generate pool transactions
congwang-mk Aug 21, 2026
b1c6ab7
runtime: Share one overlay write path and read every pool chunk
congwang-mk Aug 21, 2026
f9b8d79
init: Reconcile the live pool against the requested baseline
congwang-mk Aug 21, 2026
3da9f29
init: Detect a live pool from the kernel read-back, not the host CPU …
congwang-mk Aug 21, 2026
3d938a0
show: List every pool chunk with its NUMA node
congwang-mk Aug 21, 2026
abf06cb
init: Keep pool CPUs valid when re-initializing a live pool
congwang-mk Aug 21, 2026
301ded3
pool: Validate and allocate against every pool chunk
congwang-mk Aug 21, 2026
a2d36c8
parser: Accept a live pool that holds no memory
congwang-mk Aug 21, 2026
18ddf57
init: Say why the pool did not shrink
congwang-mk Aug 21, 2026
1496e9d
resources: Take the free CPU list from the kernel
congwang-mk Aug 21, 2026
096017b
kerf: Clean up around the pool interface
congwang-mk Aug 21, 2026
de63020
pool: Never offer a busy chunk back to the host
congwang-mk Aug 21, 2026
65b61e4
init: Request per-node pool memory as SIZE@N
congwang-mk Aug 22, 2026
cfb764e
init: Pick the NUMA node for an unpinned memory request in user space
congwang-mk Aug 22, 2026
bc51ac6
tests: Satisfy the newer pylint run by CI
congwang-mk Aug 22, 2026
51b4894
ci: Build pylibfdt with a pinned swig before poetry installs
congwang-mk Aug 22, 2026
8b0a67a
init: Spell an empty resource as none and drop --teardown
congwang-mk Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/lint_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ jobs:

- name: Install dependencies
run: |
# pylibfdt builds its SWIG wrapper from source, and swig 4.5 dropped
# the PyInt_* macros that wrapper still uses. Poetry builds sdists
# with "pip --isolated", which ignores PIP_CONSTRAINT, so build
# pylibfdt ourselves with a pinned swig and let poetry find it.
poetry run python -m pip install "swig<4.5" setuptools setuptools_scm
poetry run python -m pip install --no-build-isolation "pylibfdt>=1.7.0,<2"
poetry install --no-interaction

- name: Analysing the code with pylint
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,19 @@ Baseline DTB (static)

### Command Line Interface
```bash
# Initialize resource pool with CPUs (memory parsed from /proc/iomem)
kerf init --cpus=4-7
# Initialize resource pool with CPUs and pool memory
# A plain size lands on the NUMA node of the requested CPUs
kerf init --cpus=4-7 --memory=2GB

# Initialize with CPUs and devices
kerf init --cpus=4-31 --devices=enp9s0_dev,nvme0
# Initialize with CPUs, per-node memory and devices
kerf init --cpus=4-31 --memory=8GB@0,8GB@1 --devices=enp9s0_dev,nvme0

# Re-run to reshape the live pool; "none" asks for none of a resource
kerf init --cpus=4-15 --memory=4GB
kerf init --cpus=4-15 --memory=none

# Hand everything back to the host
kerf init --cpus=none --memory=none

# Create kernel instance with resource allocation
kerf create web-server --cpus=4-7 --memory=2GB
Expand Down
5 changes: 3 additions & 2 deletions examples/bad_system.dts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
resources {
cpus = <4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31>;
memory-base = <0x80000000>;
memory-bytes = <0x0 0x380000000>; // 14GB
memory@0 {
size = <0x0 0x380000000>; // 14GB
};

devices {
eth0: ethernet@0 {
Expand Down
5 changes: 3 additions & 2 deletions examples/baseline.dts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
resources {
cpus = <4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31>;
memory-base = <0x80000000>;
memory-bytes = <0x0 0x380000000>; // 14GB
memory@0 {
size = <0x0 0x380000000>; // 14GB
};

devices {
eth0: ethernet@0 {
Expand Down
5 changes: 3 additions & 2 deletions examples/conflict_example.dts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

resources {
cpus = <2 3 4 5 6 7 8 9 10 11 12 13 14 15>;
memory-base = <0x100000000>;
memory-bytes = <0x0 0x300000000>; // 12GB
memory@0 {
size = <0x0 0x300000000>; // 12GB
};
};
};
5 changes: 3 additions & 2 deletions examples/edge_computing.dts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

resources {
cpus = <2 3 4 5 6 7 8 9 10 11 12 13 14 15>;
memory-base = <0x200000000>;
memory-bytes = <0x0 0x600000000>; // 24GB
memory@0 {
size = <0x0 0x600000000>; // 24GB
};

devices {
eth0: ethernet@0 {
Expand Down
5 changes: 3 additions & 2 deletions examples/high_performance.dts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119>;
memory-base = <0x400000000>;
memory-bytes = <0x0 0xC00000000>; // 48GB
memory@0 {
size = <0x0 0xC00000000>; // 48GB
};

devices {
eth0: ethernet@0 {
Expand Down
5 changes: 3 additions & 2 deletions examples/minimal.dts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

resources {
cpus = <2 3 4 5 6 7>;
memory-base = <0x40000000>;
memory-bytes = <0x0 0x1C0000000>; // 7GB
memory@0 {
size = <0x0 0x1C0000000>; // 7GB
};
};
};
29 changes: 23 additions & 6 deletions examples/numa_topology.dts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,31 @@
};
};

memory-base = <0x0 0x800000000>;
memory-bytes = <0x0 0x1800000000>; // 96GB
memory@0 {
size = <0x0 0x600000000>; // 24GB
numa-node-id = <0>;
};

memory@1 {
size = <0x0 0x600000000>; // 24GB
numa-node-id = <1>;
};

memory@2 {
size = <0x0 0x600000000>; // 24GB
numa-node-id = <2>;
};

memory@3 {
size = <0x0 0x600000000>; // 24GB
numa-node-id = <3>;
};

devices {
eth0: ethernet@0 {
compatible = "intel,i40e";
pci-id = "0000:01:00.0";
numa-node = <0>;
numa-node-id = <0>;
sriov-vfs = <8>;
host-reserved-vf = <0>;
available-vfs = <1 2 3 4 5 6 7>;
Expand All @@ -74,7 +91,7 @@
eth1: ethernet@1 {
compatible = "intel,i40e";
pci-id = "0000:02:00.0";
numa-node = <2>;
numa-node-id = <2>;
sriov-vfs = <8>;
host-reserved-vf = <0>;
available-vfs = <1 2 3 4 5 6 7>;
Expand All @@ -83,7 +100,7 @@
nvme0: storage@0 {
compatible = "nvme";
pci-id = "0000:03:00.0";
numa-node = <0>;
numa-node-id = <0>;
namespaces = <4>;
host-reserved-ns = <1>;
available-ns = <2 3 4>;
Expand All @@ -92,7 +109,7 @@
nvme1: storage@1 {
compatible = "nvme";
pci-id = "0000:04:00.0";
numa-node = <2>;
numa-node-id = <2>;
namespaces = <4>;
host-reserved-ns = <1>;
available-ns = <2 3 4>;
Expand Down
11 changes: 9 additions & 2 deletions examples/simple_numa.dts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@
};
};

memory-base = <0x0 0x400000000>;
memory-bytes = <0x0 0xC00000000>; // 48GB
memory@0 {
size = <0x0 0x600000000>; // 24GB
numa-node-id = <0>;
};

memory@1 {
size = <0x0 0x600000000>; // 24GB
numa-node-id = <1>;
};

devices {
eth0: ethernet@0 {
Expand Down
5 changes: 3 additions & 2 deletions examples/system.dts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
resources {
cpus = <4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31>;
memory-base = <0x80000000>;
memory-bytes = <0x0 0x380000000>; // 14GB
memory@0 {
size = <0x0 0x380000000>; // 14GB
};

devices {
eth0: ethernet@0 {
Expand Down
4 changes: 2 additions & 2 deletions src/kerf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
get_available_cpus,
get_allocated_cpus,
get_allocated_memory_regions,
find_available_memory_base,
chunk_containing,
validate_cpu_allocation,
validate_memory_allocation,
find_next_instance_id,
Expand All @@ -67,7 +67,7 @@
"get_available_cpus",
"get_allocated_cpus",
"get_allocated_memory_regions",
"find_available_memory_base",
"chunk_containing",
"validate_cpu_allocation",
"validate_memory_allocation",
"find_next_instance_id",
Expand Down
26 changes: 13 additions & 13 deletions src/kerf/create/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from ..resources import (
validate_cpu_allocation,
validate_memory_allocation,
find_available_memory_base,
get_available_cpus,
)
from ..exceptions import ValidationError, KernelInterfaceError, ResourceError, ParseError
Expand Down Expand Up @@ -302,6 +301,12 @@ def parse_memory_spec(memory_spec: str) -> int:
) from exc


def _placement(instance) -> str:
"""Name the base only when one was asked for; the kernel picks otherwise."""
base = instance.resources.memory_base
return f" at {hex(base)}" if base else ""


def parse_memory_base(base_spec: str) -> int:
"""
Parse memory base address specification.
Expand Down Expand Up @@ -432,7 +437,8 @@ def dump_overlay_for_debug(
)
@click.option(
"--memory-base",
help="Memory base address (hex: 0x80000000 or decimal, auto-assigned if not specified)",
help="Memory base address to request (hex: 0x80000000 or decimal). "
"Only checked against the pool; the kernel places instance memory itself.",
)
@click.option(
"--devices",
Expand Down Expand Up @@ -641,17 +647,11 @@ def create_instance_operation(current):
# Validate CPU allocation (against baseline and existing instances)
validate_cpu_allocation(modified, cpu_list)

# Find memory base if not specified
# The kernel places instance memory itself; a base is only ever
# a caller's request, so it is all there is to validate.
if memory_base_addr is None:
found_base = find_available_memory_base(modified, memory_bytes)
if found_base is None:
raise ResourceError(
f"No available memory region found for {memory_bytes} bytes. "
"Try specifying --memory-base or reduce memory size."
)
memory_base_addr = found_base
memory_base_addr = 0
else:
# Validate specified memory base
validate_memory_allocation(modified, memory_base_addr, memory_bytes)

# Create instance resources with topology settings
Expand Down Expand Up @@ -705,7 +705,7 @@ def create_instance_operation(current):
click.echo(
f" NUMA Nodes: {', '.join(map(str, instance.resources.numa_nodes))}"
)
click.echo(f" Memory: {memory} at {hex(instance.resources.memory_base)}")
click.echo(f" Memory: {memory}{_placement(instance)}")
if instance.resources.memory_policy:
click.echo(f" Memory Policy: {instance.resources.memory_policy}")
if instance.resources.devices:
Expand Down Expand Up @@ -745,7 +745,7 @@ def create_instance_operation(current):
if instance.resources.numa_nodes:
numa_str = ", ".join(map(str, instance.resources.numa_nodes))
click.echo(f" NUMA Nodes: {numa_str}")
click.echo(f" Memory: {memory} at {hex(instance.resources.memory_base)}")
click.echo(f" Memory: {memory}{_placement(instance)}")
if instance.resources.memory_policy:
click.echo(f" Memory Policy: {instance.resources.memory_policy}")
if instance.resources.devices:
Expand Down
84 changes: 9 additions & 75 deletions src/kerf/dtc/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import libfdt
from ..models import GlobalDeviceTree, Instance
from ..pool_diff import ANY_NODE
from .cells import pack_cpu_ids


Expand Down Expand Up @@ -125,9 +126,14 @@ def _add_cpu_properties_sw(self, fdt_sw, cpus):
fdt_sw.property("cpus", pack_cpu_ids(cpus.available))

def _add_memory_properties_sw(self, fdt_sw, memory):
"""Add memory properties directly to resources node."""
fdt_sw.property_u64("memory-base", memory.memory_pool_base)
fdt_sw.property_u64("memory-bytes", memory.memory_pool_bytes)
"""Add one memory@<idx> request node per requested size (or live chunk)."""
entries = list(memory.requested.items()) or [(r.node, r.size) for r in memory.regions]
for idx, (node, size) in enumerate(entries):
fdt_sw.begin_node(f"memory@{idx}")
fdt_sw.property_u64("size", size)
if node != ANY_NODE:
fdt_sw.property_u32("numa-node-id", node)
fdt_sw.end_node()

def _add_devices_section_sw(self, fdt_sw, devices):
"""Add devices section using FdtSw."""
Expand Down Expand Up @@ -244,78 +250,6 @@ def _add_device_references_sw(self, fdt_sw, device_references):

fdt_sw.end_node()

def _add_resources_section(self, parent_offset: int, tree: GlobalDeviceTree):
"""Add resources section to DTB."""
resources_offset = self.fdt.add_subnode(parent_offset, "resources")

# Add CPU information
self._add_cpu_section(resources_offset, tree.hardware.cpus)

# Add memory information
self._add_memory_section(resources_offset, tree.hardware.memory)

# Add device information
self._add_devices_section(resources_offset, tree.hardware.devices)

def _add_cpu_section(self, parent_offset: int, cpus):
"""Add CPU section to DTB."""
cpus_offset = self.fdt.add_subnode(parent_offset, "cpus")
self.fdt.setprop_u32(cpus_offset, "total", cpus.total)

self.fdt.setprop(cpus_offset, "host-reserved", pack_cpu_ids(cpus.host_reserved))
self.fdt.setprop(cpus_offset, "available", pack_cpu_ids(cpus.available))

def _add_memory_section(self, parent_offset: int, memory):
"""Add memory section to DTB."""
memory_offset = self.fdt.add_subnode(parent_offset, "memory")
self.fdt.setprop_u64(memory_offset, "total-bytes", memory.total_bytes)
self.fdt.setprop_u64(memory_offset, "host-reserved-bytes", memory.host_reserved_bytes)
self.fdt.setprop_u64(memory_offset, "memory-pool-base", memory.memory_pool_base)
self.fdt.setprop_u64(memory_offset, "memory-pool-bytes", memory.memory_pool_bytes)

def _add_devices_section(self, parent_offset: int, devices):
"""Add devices section to DTB."""
devices_offset = self.fdt.add_subnode(parent_offset, "devices")

for name, device_info in devices.items():
device_offset = self.fdt.add_subnode(devices_offset, name)
self.fdt.setprop_str(device_offset, "compatible", device_info.compatible)

if device_info.pci_id:
self.fdt.setprop_str(device_offset, "pci-id", device_info.pci_id)

if device_info.sriov_vfs is not None:
self.fdt.setprop_u32(device_offset, "sriov-vfs", device_info.sriov_vfs)

if device_info.host_reserved_vf is not None:
self.fdt.setprop_u32(
device_offset, "host-reserved-vf", device_info.host_reserved_vf
)

if device_info.available_vfs:
import struct

vfs_data = struct.pack(
">" + "I" * len(device_info.available_vfs), *device_info.available_vfs
)
self.fdt.setprop(device_offset, "available-vfs", vfs_data)

if device_info.namespaces is not None:
self.fdt.setprop_u32(device_offset, "namespaces", device_info.namespaces)

if device_info.host_reserved_ns is not None:
self.fdt.setprop_u32(
device_offset, "host-reserved-ns", device_info.host_reserved_ns
)

if device_info.available_ns:
import struct

ns_data = struct.pack(
">" + "I" * len(device_info.available_ns), *device_info.available_ns
)
self.fdt.setprop(device_offset, "available-ns", ns_data)

def _add_instances_section(self, parent_offset: int, tree: GlobalDeviceTree):
"""Add instances section to DTB."""
instances_offset = self.fdt.add_subnode(parent_offset, "instances")
Expand Down
Loading
Loading