STSI and ECAG
| Difficulty level |
|---|
Beginner |
On the s390x (IBM z/Architecture) architecture, hardware discovery is significantly different from architectures like x86 (which relies on `CPUID`) or ARM (which uses system registers and Device Trees / ACPI).
System identification, CPU capability, multithreading configuration, logical partition (LPAR) status, virtualization nesting, and CPU cache hierarchies are queried primarily through two instructions:
- STSI (Store System Information)
- ECAG (Extract CPU Attribute)
These two instructions are currently the primary standard mechanism for acquiring hardware, topology, and performance metrics on 64-bit z/Architecture systems.
Note on STIDP (Store CPU ID)
Historically, the ESA/390 architecture provided the STIDP (Store CPU ID) instruction, which writes an 8-byte processor ID record (containing the CPU version code, CPU identification number, machine model, and legacy address space partition).
STIDP is highly deprecated for modern system discovery. It does not provide information about cache geometry, physical or logical CPU topology, secondary capabilities, core multithreading (SMT), or virtualization levels. Modern operating systems should only use STIDP as a fallback legacy identifier and rely on STSI and ECAG for all topology and processor feature discovery.
STSI (Store System Information)
The STSI instruction retrieves detailed hardware, partition, virtualization, and topology information into a 4096-byte (4 KiB) page-aligned buffer called the System-Information Block (SYSIB).
Instruction Format & Register Arguments
STSI D2(B2)
- Operand: Address of a 4 KiB-aligned memory buffer (SYSIB).
- General Register 0 (GR0):
- Bits 32–35: Function Code (FC)
- Bits 36–55: Reserved (must be 0, otherwise a specification exception occurs).
- Bits 56–63: Selector 1 (Sel1)
- General Register 1 (GR1):
- Bits 32–47: Reserved (must be 0, otherwise a specification exception occurs).
- Bits 48–63: Selector 2 (Sel2)
Function Codes and Selectors
The configuration hierarchy on z/Architecture is defined across three distinct levels:
- Level 1 (Basic Machine): Bare-metal hardware.
- Level 2 (Logical Partition / LPAR): PR/SM hypervisor partition.
- Level 3 (Virtual Machine / VM): Hypervisors like z/VM or KVM.
| FC | Sel1 | Sel2 | Target / Information Requested | Output SYSIB Structure |
|---|---|---|---|---|
0 |
— | — | Query current configuration level number | No SYSIB written; Level (1, 2, or 3) returned in GR0[32..35]. |
1 |
1 |
1 |
Basic Machine Configuration (Model, serial, capacity) | SYSIB 1.1.1
|
1 |
2 |
1 |
Basic Machine CPU (Executing CPU information) | SYSIB 1.2.1
|
1 |
2 |
2 |
Basic Machine CPUs (All physical CPUs / SMT capability) | SYSIB 1.2.2
|
2 |
2 |
1 |
LPAR CPU (Executing logical CPU) | SYSIB 2.2.1
|
2 |
2 |
2 |
LPAR CPUs (Dedicated/shared CPUs, LPAR name, CAF) | SYSIB 2.2.2
|
3 |
2 |
2 |
VM CPUs (Virtual Machine descriptors, hypervisor name) | SYSIB 3.2.2
|
15 |
1 |
2–6 |
CPU Configuration Topology (Core nest & grouping) | SYSIB 15.1.x
|
Condition Codes
- CC 0: Information successfully stored (or configuration level returned).
- CC 3: Function code / selector combination is invalid, the requested level is higher than the current operating level, or the required facility is not installed.
Querying the Current Configuration Level (FC = 0)
When FC = 0, the operand address is ignored, and the executing CPU writes its current nesting level directly into bits 32–35 of GR0.
#include <stdint.h>
uint8_t stsi_get_current_level(void) {
register uint64_t r0 __asm__("0") = 0;
register uint64_t r1 __asm__("1") = 0;
int cc;
__asm__ volatile (
" stsi 0\n"
" ipm %[cc]\n"
" srl %[cc], 28\n"
: [cc] "=d" (cc), "+d" (r0)
: "d" (r1)
: "cc", "memory"
);
if (cc != 0) {
return 0; // STSI failed or not supported
}
// Level number is extracted from bits 32..35 of GR0
return (uint8_t)((r0 >> 28) & 0x0F);
}
Fetching a SYSIB Block
The following generic function performs a full STSI call against a 4 KiB-aligned page:
#include <stdint.h>
#include <stddef.h>
int stsi_query(void *sysib_page, uint8_t fc, uint8_t sel1, uint16_t sel2) {
uint64_t r0 = ((uint64_t)(fc & 0x0F) << 28) | (uint64_t)(sel1 & 0xFF);
uint64_t r1 = (uint64_t)sel2;
register uint64_t reg0 __asm__("0") = r0;
register uint64_t reg1 __asm__("1") = r1;
int cc;
__asm__ volatile (
" stsi %[sysib]\n"
" ipm %[cc]\n"
" srl %[cc], 28\n"
: [cc] "=d" (cc), [sysib] "=Q" (*(char (*)[4096])sysib_page)
: "d" (reg0), "d" (reg1)
: "cc", "memory"
);
return cc; // 0 = Success, 3 = Invalid/Unavailable
}
ECAG (Extract CPU Attribute)
The ECAG instruction retrieves specific processor attributes such as cache topology, cache line sizes, cache sizes, associativity, and CPU clock frequencies.
Instruction Format & Operands
ECAG R1, R3, D2(B2)
On 64-bit systems, R3 is ignored (set to 0), and the second operand address D2(B2) contains an encoded request bitfield. The result is returned in general register R1.
Request Format in Second Operand Address
The address register contains parameter bits formatted as follows (IBM bit numbering, where 0 is MSB):
- Bits 54–55: Attribute Set Indication (ASI)
0: Cache Attributes1: CPU Attributes
- Bits 56–59: Attribute Indication (AI)
- When ASI = 0 (Cache):
0: Extract Cache Topology Summary1: Extract Cache Line Size (in bytes)2: Extract Cache Size (in bytes)3: Extract Cache Associativity / Sets
- When ASI = 1 (CPU):
0: Extract CPU Speed (Nominal / Dynamic in MHz)
- When ASI = 0 (Cache):
- Bits 60–62: Level Indication (LI) (When ASI = 0)
0..7: Cache Level L1 through L8
- Bit 63: Type Indication (TI) (When ASI = 0)
0: Data Cache / Unified Cache1: Instruction Cache
Return Values & Error Handling
- On success, the requested scalar value is returned in register
R1. - If a requested attribute, cache level, or property is unavailable,
ECAGreturns~0ULL(0xFFFFFFFFFFFFFFFF).
Code Example: Querying CPU Cache & Frequency
#include <stdint.h>
#include <stdbool.h>
#define ECAG_INVALID (~0ULL)
static inline uint64_t ecag_invoke(uint8_t asi, uint8_t ai, uint8_t li, uint8_t ti) {
uint64_t param = ((uint64_t)(asi & 0x03) << 8) |
((uint64_t)(ai & 0x0F) << 4) |
((uint64_t)(li & 0x07) << 1) |
((uint64_t)(ti & 0x01));
uint64_t result;
__asm__ volatile (
" ecag %0, 0, 0(%1)\n"
: "=d" (result)
: "a" (param)
: "cc"
);
return result;
}
/* Query Cache Line Size for a specific level (0 = L1, 1 = L2, etc.) */
uint64_t get_cache_line_size(uint8_t level, bool is_instruction) {
uint64_t res = ecag_invoke(0, 1, level, is_instruction ? 1 : 0);
return (res == ECAG_INVALID) ? 0 : res;
}
/* Query Cache Size in bytes */
uint64_t get_cache_size(uint8_t level, bool is_instruction) {
uint64_t res = ecag_invoke(0, 2, level, is_instruction ? 1 : 0);
return (res == ECAG_INVALID) ? 0 : res;
}
/* Query CPU Frequency (MHz) */
void get_cpu_speed(uint32_t *dynamic_mhz, uint32_t *static_mhz) {
uint64_t res = ecag_invoke(1, 0, 0, 0);
if (res == ECAG_INVALID) {
*dynamic_mhz = 0;
*static_mhz = 0;
return;
}
*dynamic_mhz = (uint32_t)(res >> 32); /* Current / Turbo speed */
*static_mhz = (uint32_t)(res & 0xFFFFFFFF); /* Nominal base speed */
}
Implementation Notices
- SYSIB Alignment: The buffer passed to
STSImust be aligned to a 4 KiB boundary. Unaligned buffers will trigger a specification exception (`PGM_SPECIFICATION`). - Facility Checks:
STSIis a standard baseline instruction on modern 64-bit z/Architecture implementations.- Topology functions (
FC=15) require the Configuration-Topology Facility (Facility Bit 11). ECAGrequires the Extract-CPU-Attribute Facility (Facility Bit 49).- Multithreading information queries require the Multithreading Facility (Facility Bit 12).
- Virtualization Awareness: In guest virtual machines (e.g., running under z/VM or KVM on s390x),
STSIcalls may be intercepted and partially emulated by the hypervisor to present synthesized topology trees to the guest. Therefore it is maybe a better idea to use STHYI (Store Hypervisor Information).